Railsチュートリアル 第１０章

#ユーザーを更新する
ユーザーの編集ビュー

```html: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" rel="noopener">change</a>
    </div>
  </div>
</div>
```

上記のHTMLソースを見てみると

```html:
<form accept-charset="UTF-8" action="/users/1" class="edit_user"
      id="edit_user_1" method="post">
  <input name="_method" type="hidden" value="patch" />
  .
  .
  .
</form>
```

WebブラウザはネイティブではPATCHリクエストを送信できないので、RailsではPOSTリクエストと隠しinputフィールドを利用してPATCHリクエストを「偽造」している。
また、ユーザー編集(edit)のHTMLはユーザー新規登録(new)のコードと完全に一緒です。Rails では、ユーザーが新規なのか、それとも既存のユーザーであるのかをActive Recordのnew_record?メソッドを使って区別しています。

```
$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false
```

form_for(@user)を使ってフォームを構成すると、@user.new_record?がtrueのときにはPOST、falseのときはPATCHを使っています。

###noopener
target="_blank"の脆弱性対策として、一緒にrel="noopener"もつける。


#編集時のテスト
編集の成功と失敗時のテスト

```ruby:test/integration/users_edit_test.rb
require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  
  test "unsuccessful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { name: "",
                                email: "foo@invalid",
                                password: "foo",
                                password_confirmation: "bar" } }
    assert_template 'users/edit'
    assert_select "div.alert", "The form contains 4 errors."
  end
  
  test "successful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    name = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name: name,
                            email: email,
                            password: "",
                            password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name, @user.name
    assert_equal email, @user.email
  end
end
```

また、編集時にパスワードを空にしておくと、パスワードのバリデーションに引っかかってしまうので、
allow_nil: trueというオプションをvalidatesに追加する。

```ruby:app/models/user.rb
class User < ApplicationRecord
  attr_accessor :remember_token
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
  .
  .
  .
end
```

上記のようにすると、新規ユーザー登録時に空のパスワードが有効になってしまうかと思われるかもしれないが、has_secure_passwordでは（追加したバリデーションとは別に）オブジェクト生成時に存在性を検証するようになっているため、空のパスワードが新規ユーザー登録時に有効なることはない。

##フレンドリーフォワーディング
フレンドリーフォワーディングとは、ユーザーがログインした後、ログイン直前に閲覧していたページへリダイレクトさせる機能。

フレンドリーフォワーディングのテスト

```ruby:test/integration/users_edit_test.rb
require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)　　　　　　　　　　　　# ユーザー編集ページにアクセス
    log_in_as(@user)　　　　　　　　　　　　　　　　　# ログイン 
    assert_redirected_to edit_user_url(@user)    # ログイン直前にアクセスしたページにリダイレクトされているか確認
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
end
```

フレンドリーフォワーディングの実装

```ruby: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メソッド
リクエストが送られたURLをsession変数の:forwarding_urlキーに格納する。
ただし、GETリクエストの場合のみ。

###redirect_back_orメソッド
session変数に格納されているURLがnilでなければ、session[:forwarding_url]を使い、そうでなければデフォルトのURLを使う。
この場合、redirect文を実行しても、次のセッション削除もされます。明示的にreturn文やメソッド内の最終行が呼び出されない限り、リダイレクトは発生しない。

```ruby:app/controllers/uses_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  before_action :correct_user,   only: [:edit, :update]
  .
  .
  .
  def edit
  end
  .
  .
  .
  private

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

    # beforeアクション

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        store_location　　　　　　　　　　　⇦追加　GETリクエストが送られたURLを格納
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

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

```ruby: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
```

#サンプルユーザー作成
Faker gemを使い、テストユーザーを一気に作成する。

```ruby:Gemfile
source 'https://rubygems.org'

gem 'rails',          '5.1.6'
gem 'bcrypt',         '3.1.12'
gem 'faker',          '1.7.3'
.
.
.
```

```
$ bundle install
```

サンプルユーザーを生成するRubyスクリプトを追加する。
Rails ではdb/seed.rbというファイルを標準として使う。

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

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
```

create!は基本的にはcreateメソッドと同じですが、ユーザーが無効な場合に　falseを返すのではなく、
例外を発生させます。こうしておくことで、見過ごしやすいエラーを回避できます。

Railsタスクを実行（db：seed）

```
$ rails db:migrate:reset
$ rails db:seed
```

#ページネーション
Gemfileにwill_paginate gemとbootstrap-will_paginate gemを追加する。

```
source 'https://rubygems.org'

gem 'rails',                   '5.1.6'
gem 'bcrypt',                  '3.1.12'
gem 'faker',                   '1.7.3'
gem 'will_paginate',           '3.1.6'
gem 'bootstrap-will_paginate', '1.0.0'
.
.
```

```
$ bundle install
```

ビューにwill_paginateメソッドを追加する。

```html: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オブジェクトを自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成します。

paginateを使うことで、ユーザー一覧のページネーションを行えるようになります。
paginateメソッドの引数にpageパラメータにはparams[:page]が使われているが、これはwill_paginateによって自動的に生成されている。

```ruby:app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
  .
  .
  def index
    @users = User.paginate(page: params[:page])
  end
  .
  .
end
```

#ユーザー一覧のテスト
ページネーションを確認するのに多くのユーザーが必要なので、fixtureにユーザーを追加する。

```
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>

archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>

lana:
  name: Lana Kane
  email: hands@example.gov
  password_digest: <%= User.digest('password') %>

malory:
  name: Malory Archer
  email: boss@example.gov
  password_digest: <%= User.digest('password') %>

<% 30.times do |n| %>
user_<%= n %>:
  name:  <%= "User #{n}" %>
  email: <%= "user-#{n}@example.com" %>
  password_digest: <%= User.digest('password') %>
<% end %>
```

ページネーションを含めた一覧ページのテスト

```ruby:test/integration/users_index_test.rb
require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination', count: 2
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end
end
```

##一覧ページのリファクタリング
元のテンプレート

```html: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 %>
```

ユーザーのliをrender呼び出しに置き換える

```html
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <%= render user %>
  <% end %>
</ul>

<%= will_paginate %>
```

ここでは、renderをパーシャル（ファイル名の文字列）に対してではなく、Userクラスのuser変数に対して実行しています。
この場合、Railsは自動的に_user.html.erbという名前のパーシャルを探しに行くので、下記のパーシャルを作成する必要があります。

```html:app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>
```

さらに、renderを@users変数に対して直接実行します。

```
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <%= render @users %>
</ul>

<%= will_paginate %>
```

Railsは@usersをUserオブジェクトのリストであると推測します。
さらに、ユーザーのコレクションを与えて呼び出すと、Railsは自動的にユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力します。