10.1 ユーザーを更新する
ユーザーの情報を編集するためのedit
アクションを実装する。
10.1.1 編集フォーム
ここではUses
コントローラにedit
アクションを追加し、対応するedit
ビュー(ユーザーの編集フォーム)を作成する。これはユーザーの新規作成と似ている。
editアクションの追加
ユーザーのidはparams[:id]
で取り出すことができるので、次を追加する。
controllers/users_controller.rb
def edit @user = User.find(params[:id]) end
対応するビューを作成
views/users/edit.html.erb
<% provide(:title, "Ebit 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.text_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_contfirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: "form-control" %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> <div class="grabatar_edit"> <%= gravatar_for @user %> <a fref="http://gravatar.com/emails" target="_blank">編集</a>←② </div> </div> </div>
①:error_messages
パーシャルを再利用している。
②:Gravatarへのリンクでtarget="_blank"
が使われているが、これによりリンク先を新しいタブ(またはウインドウ)で開くようになる。
ここで、NameやEmailの部分を見ると、名前やメールアドレスのフィールドに値が自動的に入力されていることが分かる。
これらの値は、@user
から取り出しているからである。
editフォームのhtml
<form class="edit_user" id="edit_user_1" action="/users/1" accept-charset="UTF-8" method="post"> <input type="hidden" name="_method" value="patch" />
webブラウザはネイティブではPATCHリクエストを送信できない。
よって、POSTリクエストの隠しinputフィールドを利用してPATCHリクエストを「偽造」!うーん、すごい!
ナビゲーションバー
ナビゲーションバーにあるユーザー設定へのリンクを更新。edit_user_path
という名前つきルートと、current_user
というヘルパーメソッドを使うと便利
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>
10.1.2 編集の失敗
まずはupdate
アクションの作成から。
update_attributes
(複数形)を使って、edit
フォームから送信されたparams
ハッシュを受け取り、更新したい。
更新に成功時にはtrue、失敗時にはfalseを返すので、次のようになる。
def update @user =User.find(params[:id]) if @user.update_attributes(user_params) ←① #更新に成功した場合 redirect_to @user else #更新に失敗した場合 render 'edit' end end
update_attributes
への呼び出し①で、user_params
を使っている点に注目。
第7章で扱ったStrong Parametersを使ってマスアサインメントの脆弱性を防止している。
10.1.3 編集失敗時のテスト
統合テストをかく。まずは生成。
$ rails g integration_test users_edit
まずは、編集失敗時の簡単なテストを追加。
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
①:編集ページにアクセス
②:assert_template(expected, message = nil):そのアクションで指定されたテンプレートが描写されているかをバリデーション
すなわと、edit
ビューが描画できてたらtrueを返す。
③:無効なユーザー情報を送信
④:edit
ビューが再描画できてたらtrueを返す。
無効なユーザー情報のため、edit画面にレンダ―されるので、テストは成功する。
$ rails test
10.1.4 TDDで編集の成功
今度は編集フォームが動作するようにする。
プロフィール画像の編集
プロフィール画像の編集は画像のアップロードをGravatarに任せてあるので、既に動作するようになっている。
changeボタンをクリックすれば、Gravatarを編集できる。
それ以外の機能の実装
より快適にテストするには、アプリケーション用のコードを「実装する前に」統合テストを書いた方が便利。
編集の成功に対するテスト
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
①: michaelのuserIDを取得
②:edit
ビュー(入力フォーム)が描画できたらtrue
③:user情報を送信。ここで、パスワードとパスワード確認が空であることに注目。
ユーザー名やメールアドレスを編集する時に毎回パスワードを入力するのは不便なので、
わざとパスワードを入力せずに更新している。
④:flash
メッセージが空でなければ(何らかのエラーメッセージが出ていれば)true
⑤:michaelのプロフィールページにリダイレクトされればtrue
⑥:DBから最新のユーザー情報を読み込み直して、正しく更新されたかチェック
※こうした正しい振る舞いをというのは一般に忘れがちですが、受け入れテストでは先にテストを書くので、効果的なユーザー体験について考えるようになる。
update
アクション
users_controller.rb
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
ただし、テストは失敗する。なぜなら、パスワードの長さに対するバリデーションがあるので、
パスワードやパスワード確認の欄を空にしておくとこれに引っかかるから。
allow_nil
オプション
パスワードのバリデーションに対して、空だったときの例外処理を加える必要がある、それがallow_nil: true
class User < ApplicationRecord attr_accessor :remember_token before_save { 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 ←追加