Rails-tutorialのまとめ(第10章 ユーザーの更新 主に演習)

その9から続く

10.1.1 編集フォーム

ユーザーのeditアクションapp/controllers/users_controller.rb
class UsersController < ApplicationController
 #追加
  def edit
   @user = User.find(params[:id])  end    
  end

beforeフィルター (before filter) を使ってこのアクセス制御を実現できます。

1: ユーザーのeditビュー
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>

gravatarへのリンクでtarget=”_blank”が使われていますが、
これを使うとリンク先を新しいタブ(またはウィンドウ)で開くようになるので、
別のWebサイトへリンクするときなどに便利です。

Railsはどうやって新規のPostと既存のPatchを区別している?
Railsは、form_for(@user)を使ってフォームを構成すると、@user.new_record?がtrueのときにはPOSTを、falseのときにはPATCHを使います。

レイアウトの “Settings” リンクを更新するapp/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home", root_path %></li>
        <li><%= link_to "Help", help_path %></li>
        <% if logged_in? %>
          <li><%= link_to "Users", '#' %></li>
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              Account <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
              <li><%= link_to "Profile", current_user %></li>
               <li>
                <%= link_to "Settings", edit_user_path(current_user) %></li>
              <li class="divider"></li>
              <li>
                <%= link_to "Log out", logout_path, method: :delete %>
              </li>
            </ul>
          </li>
        <% else %>
          <li><%= link_to "Log in", login_path %></li>
        <% end %>
      </ul>
    </nav>
  </div>
</header>

演習

1:先ほど触れたように、target=”_blank”で新しいページを開くときには、セキュリティ上の小さな問題があります。
それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング (Phising) サイトのような、
悪意のあるコンテンツを導入させられてしまう可能性があります。
Gravatarのようなサイトではこのような事態は起こらないと思いますが、
念のため、このセキュリティ上のリスクも排除しておきましょう。
対処方法は、リンク用のaタグのrel (relationship) 属性に、
“noopener”と設定するだけです。
早速、Gravatarの編集ページへのリンクにこの設定をしてみましょう。

rel= “noopener”を追加するだけ

2:リスト 10.5のパーシャルを使って、new.html.erbビュー (リスト 10.6) とedit.html.erbビュー (リスト 10.7) をリファクタリングしてみましょう。
リスト10.5,10.6,10.7参照。

new.html.erbの以下の”,url: signup_path”がedit.html.erbと異なるため、provideで指定。

<%= form_for(@user,url: signup_path) do |f| %>
_form.html.erb
<%= form_for(@user, url: yield(:url)) do |f| %> ←注目
  <%= render 'shared/error_messages', object: @user %>
  <%= 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 %>
  <%= f.password_field :password_confirmation, class: 'form-control' %>
  <%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
new.html.erb

<% provide(:title, 'Sign up') %>
<% provide(:url, signup_path) %>   注目
<% provide(:button_text, 'Create my account') %>
<h1>Sign up</h1>
<div class="row">
  <div class="col-md-6 col-md-offset-3">
      <%= render 'form' %>
  </div>
</div>
edit.html.erb

<% provide(:title, "Edit user") %>
<% provide(:url, user_path) %>   ←注目
<% provide(:button_text, 'Save changes') %>
<h1>Update your profile</h1>
<div class="row">
  <div class="col-md-6 col-md-offset-3">
      <%= render 'form' %>
    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails" target="_blank" rel="noopener">change</a>
    </div>
  </div>
</div>

10.1.2 編集の失敗

updateアクションの作成から進めますが、これはにあるように、update_attributesを使って送信されたparamsハッシュに基いてユーザーを更新します。

ユーザーのupdateアクションの初期実装app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  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 edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
if @user.update_attributes(user_params)      # 更新に成功した場合を扱う。
    else
render 'edit'    end
  end

  private

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

update_attributesへの呼び出しでuser_paramsを使っていることに
注目してください。ここではStrong Parametersを使って,
マスアサインメントの脆弱性を防止しています。

演習

1:編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。

編集フォームで試すだけ。

10.1.3 編集失敗時のテスト

rails generate integration_test users_edit
編集の失敗に対するテスト green
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'
  end
end
rails test

演習

1:リスト 10.9のテストに1行追加し、正しい数のエラーメッセージが表示されているかテストしてみましょう。

ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。

users_edit_test.rb
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
   (中略)
  test "unsuccessful edit" do
   (中略)
    assert_template 'users/edit'
    assert_select "div.alert", "The form contains 4 errors."   <---注目
  end
end

10.1.4 TDDで編集を成功させる

編集の成功に対するテスト red
test/integration/users_edit_test.rb
require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  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
ユーザーのupdateアクション red
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  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
  .
  .
  .
end

パスワードのバリデーションに対して、空だったときの例外処理を加える必要があります。allow_nil: trueというオプションをvalidatesに追加します。

パスワードが空のままでも更新できるようにする green
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では (追加したバリデーションとは別に)
オブジェクト生成時に存在性を検証するようになっているため、
空のパスワード (nil)が新規ユーザー登録時に有効になることはありません。
(空のパスワードを入力すると存在性のバリデーションとhas_secure_passwordによるバリデーションがそれぞれ実行され、2つの同じエラーメッセージが表示されるというバグがありましたが,これで解決できました。)

このコードを追加したことにより、ユーザー編集ページが動くようになります。

rails test

演習

1:実際に編集が成功するかどうか、有効な情報を送信して確かめてみましょう。

編集フォームで試してみる

2:もしGravatarと紐付いていない適当なメールアドレス (foobar@example.comなど) に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみましょう。

動作確認するだけ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です