railsチュートリアル　第十章

##はじめに
railsチュートリアルで理解しにくいところや、詰まったところを書いていく記事になります。
なので、手順を示す記事とはなっていません。

のはずが、気づけばほぼ手順を示すようやくした記事になっていました＼＼\\٩( 'ω' )و //／／

##ユーザーの更新

ユーザー情報を編集する工程は新規ユーザー更新と似ているところが多い。

ビューファイルは ```newアクション``` のような ```editアクション``` を作成し、POSTリクエストに応答する ```createアクション``` のようにPATCHリクエストに応答する ```updateアクション``` を作成する。

違いとしては**更新の場合はそのユーザー自身に限られる**というところになる。


###編集フォーム
```editアクション```  を定義する。

ユーザーのid情報を取り出す@userを定義しておきたいから以下のようになる。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
  def edit
      @user = User.find(params[:id])
  end
end
```

ビューファイル(edit.html.erb)はほとんどcreateアクションのもの(new.html.erb)と同様。


```app/views/users/edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails" target="_blank">change</a>
    </div>
  </div>
</div>
```
名前やアドレスは ```@user``` の変数の属性情報から引き出される。

```form_for(@user)``` のコードは ```new.html.erbファイル``` と同じだが、**railsがユーザーが新規なのか、それともデータベースに存在する既存のユーザーであるかを、Active Recordのnew_record?論理値メソッドを使って区別する**から問題ない。

ここまで完了すると、あとはheaderにリンクを追加すればいい。
```<%= link_to "Settings", edit_user_path(current_user) %>```

##updateアクション
次に、```updateアクション``` を定義するが、これは**createアクションと非常に似通ったもの**になる。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
#省略
def create
    @user = User.new(user_params)
    if @user.save
      log_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
end

 def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
end
```

編集の場合はパスワードが空でも編集できるように、**例外処理**を加える必要がある。
編集の例外処理↓

```app/models/user.rb
# allow_nil: trueを追加
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
```

このバリーデーションを追加しても、```has_secure_password``` によりオブジェクト生成時に**存在性を検証**するようになっているため、空のパスワード (nil) が新規ユーザー登録時に有効になることはない。

##認可 (authentication)
**認証 (authentication)** はサイトのユーザーを識別することであり、**認可 (authorization)** はそのユーザーが実行可能な操作を管理すること。

このままだと、どのユーザーでもあらゆるアクションにアクセスできるため、誰でも (ログインしていないユーザーでも) ユーザー情報を編集できてしまう。

よって、**ユーザーにログインを要求し、かつ自分以外のユーザー情報を変更できないように制御する**必要がある。

###ユーザーにログインを要求する
ユーザーにログインを要求するための ```logged_in_userメソッド``` を定義し、```before_actionメソッド``` を利用してedit,updateアクションの処理が行われる前にログインを要求する。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
#省略
private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end

    # beforeアクション

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
end
```


###正しいユーザーを要求
ログインの要求は完了したから、次にユーザーが自分の情報だけを編集できるようにする。

そのためには ```current_userメソッド``` を作成し、beforeフィルターからこのメソッドを呼び出すようにする。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  before_action :correct_user,   only: [:edit, :update]
#省略
private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end

    # beforeアクション

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless @user == current_user
    end
end
```

上記のように、```correct_userメソッド``` で ```@user``` を定義しているから、edit,updateアクションでは定義する必要はなくなる。

また、フィルタリングとして、一般的には
```current_user?```
という論理値を返すメソッドが作られることが多い。

```app/helpers/sessions_helper.rb
 # 渡されたユーザーがログイン済みユーザーであればtrueを返す
  def current_user?(user)
    user == current_user
  end
```
よって、```correct_userメソッド``` をコンパクトにすると以下のようになる。

```
# 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end
```

##フレンドリーフォワーディング
認可機能はこれで大体揃った。
しかし、保護されたページにアクセスしようとすると、問答無用で自分のプロフィールページに移動させられてしまう。

**ユーザーがログインした後にはその編集ページにリダイレクトされるようにするのが望ましい**から、フレンドリーフォワーディングをここで実装する。


ユーザーを希望のページに転送するには、**リクエスト時点のページをどこかに保存しておき、その場所にリダイレクトさせる必要**がある。

この動作を ```store_location``` と ```redirect_back_or``` の2つのメソッドを使って実装することができる。

これらのメソッドは ```Sessionsヘルパー``` で定義する。


```app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  # 記憶したURL (もしくはデフォルト値) にリダイレクト
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end

  # アクセスしようとしたURLを覚えておく
  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end
end
```

```store_locationメソッド``` では、**GETリクエストが送られたURL**を ```session変数``` の ```:forwarding_urlキー``` に格納している。

定義した ```store_locationメソッド``` を使って、早速 ```logged_in_user``` を修正。

```app/controllers/users_controller.rb
# ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        #追加
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
```

フォワーディング自体を実装するには、```redirect_back_orメソッド``` を使う。

**リクエストされたURLが存在する場合はそこにリダイレクトし、ない場合は何らかのデフォルトのURLにリダイレクトするような処理**にすればいい。

```session[:forwarding_url] || default```

というredirect_back_orメソッド内の一文は、

*「値がnilでなければsession[:forwarding_url]を代入し、そうでなければデフォルトのURLを使う。」*

という意味となる。

また、次回ログインしたときに保護されたページに転送されてしまい、ブラウザを閉じるまでこれが繰り返されてしまう、という問題を解決するために、

``` session.delete(:forwarding_url) ```

という行も追加しておく。

ちなみに明示的にreturn文やメソッド内の最終行が呼び出されない限り、リダイレクトは発生しない。
よって**redirect文の後にあるコードでも、そのコードは実行される**こととなる。

```redirect_back_orメソッド``` を使い、```createアクション``` を修正していく。

```app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  .
  .
  .
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      #追加
      redirect_back_or user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end
  .
  .
  .
end
```

##ユーザーの一覧
今回のセキュリティモデルでは、ユーザーの ```showページ``` については、今後も (ログインしているかどうかに関わらず) サイトを訪れたすべてのユーザーから見えるようにしておき、ユーザーの ```indexページ``` はログインしたユーザーにしか見せないようにし、未登録のユーザーがデフォルトで表示できるページを制限する。

まず、beforeフィルターのlogged_in_userに ```indexアクション``` を追加して、このアクションを保護する。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
  before_action :correct_user,   only: [:edit, :update]

  def index
  #全ユーザーを取得する
  @users = User.all
  end
```

```app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>
```

次に**users_path**を用いてユーザー一覧用のリンク(users)を使い追加する。

```
 <% if logged_in? %>
          <li><%= link_to "Users", users_path %></li>
```

##ページネーション
ユーザーは表示できたが、このままではユーザーが増えると１つのページに大量のユーザーの表示されてしまう。

これを解決するために**ページネーション (pagination)**というものを利用する。

今回は ```will_paginateメソッド``` を使用。
以下のように ```Gemfile``` に追加する。

``` Gemfile
gem 'will_paginate',           '3.1.6'
gem 'bootstrap-will_paginate', '1.0.0'
```

```
$ bundle install
```

ページネーションが動作するには**対応するアクションとビューに、コードを追加**しなければならい。

```app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
#追加
<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>
#追加
<%= will_paginate %>
```
一覧の上下にページネーションを表示。

この ```will_paginateメソッド``` は、usersビューのコードの中から ```@usersオブジェクト``` を自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成する。

ただこのままでは@users変数にはUser.allが定義されていて、**will_paginate**では ```paginateメソッド``` を使った結果が必要となる。

よって、アクションの方を修正する必要が出てくる。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
  .
  .
  .
  def index
    #修正（@user = User.allから）
    @users = User.paginate(page: params[:page])
  end
  .
  .
  .
end
```

これにより、ページネーションが正常に動作するようになる。


##ユーザーの削除

**削除を実行できるのは管理者だけ**として、まず管理ユーザー定義する。

###管理ユーザー
特権を持つ管理ユーザーを識別するために、論理値をとる ```admin属性``` をUserモデルに追加する。
こうすると自動的に ```admin?メソッド``` (論理値を返す) も使える。

```
$ rails generate migration add_admin_to_users admin:boolean
```
```db/migrate/[timestamp]_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end
```

```default: false``` という引数をadd_columnに追加する。
これは、デフォルトでは管理者になれないということを示す。

マイグレーションを実行する。

```
$ rails db:migrate
```

これでadmin属性が追加されたので、今回は最初のユーザーを管理者にすることとする。

```db/seeds.rb
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             #追加
             admin: true)

99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
               email: email,
               password:              password,
               password_confirmation: password)
end
```
###destroyアクション
destroyアクションでは、該当するユーザーを見つけて**Active Record**の ```destroyメソッド``` 
 を使って削除し、最後にユーザーindexに移動する。

ユーザーを削除するためにはログインが必要になるから、:destroyアクションもlogged_in_userフィルターに追加しておく。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user,   only: [:edit, :update]
  .
  .
  .
  def destroy
    #２つのメソッドを連結
    User.find(params[:id]).destroy
    flash[:success] = "User deleted"
    redirect_to users_url
  end

  private
  .
  .
  .
end
```

また、**beforeフィルターを使ってdestroyアクションへのアクセスを制御する。**

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy
  .
  .
  .
  private
    .
    .
    .
    # 管理者かどうか確認
    def admin_user
      redirect_to(root_url) unless current_user.admin?
    end
end
```


仕上げにユーザー一覧に管理者だけに表示する削除リンクを追加する。


```app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
  #追加
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
  <% end %>
</li>
```

##さいごに
第１０章では、ユーザーに関する機能（編集、一覧、削除など）とそれを実装するのに必要なアクションやメソッド(before、fixture)を見てきた。
比較的理解しやすく、安心。

次↓
https://qiita.com/jonnyjonnyj1397/items/108d088ea4274b05d8c6