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を使います。
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
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で編集を成功させる
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
アクション redapp/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に追加します。
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など) に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみましょう。
動作確認するだけ