Ruby on Rails Tutorial (14)
Chapter 9 Updating, showing, and deleting users 開始。user 云々はここで一旦終わりになるはず。Users resource の REST なアクションを云々とのこと。管理ユーザが云々みたいな話も出てくる模様。
とりあえず branch をナニ。
$ git checkout -b updating-users
9.1 Updating users
REST なので更新は PUT を使う模様。許可された (権限を持った) ユーザが、というあたりも含め、で実装していく模様。
9.1.1 Edit form
どんどん進めます。まず spec/requests/user_pages_spec.rb に以下を追加。
describe "edit" do let(:user) { FactoryGirl.create(:user) } before { visit edit_user_path(user) } describe "page" do it { should have_selector('h1', text: "Update your profile") } it { should have_selector('title', text: "Edit user") } it { should have_link('change', href: 'http://gravatar.com/emails') } end describe "with invalid information" do before { click_button "Save changes" } it { should have_content('error') } end end
controller 方面 (app/controllers/users_controller.rb) にも手を入れます。
def edit @user = User.find(params[:id]) end
あるいは app/views/users/edit.html.erb を以下の内容で作成。
<% provide(:title, "Edit user") %> <h1>Update your profile</h1> <div class="row"> <div class="span6 offset3"> <%= form_for(@user) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.text_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirm Password" %> <%= f.password_field :password_confirmation %> <%= f.submit "Save changes", class: "btn btn-large btn-primary" %> <% end %> <%= gravatar_for @user %> <a href="http://gravatar.com/emails">change</a> </div> </div>
試験実行してみれ、とありますね。
$ bundle exec rspec spec/requests/user_pages_spec.rb -e "edit page"
一応 green 終了を確認。とりあえず上の edit.html.erb は色々微妙なカンジなので WEBrick 起動してみてブラウザ出力なソレのソースを見てみました。
<div class="row"> <div class="span6 offset3"> <form accept-charset="UTF-8" action="/users/1" class="edit_user" id="edit_user_1" method="post"> <div style="margin:0;padding:0;display:inline"> <input name="utf8" type="hidden" value="✓" /> <input name="_method" type="hidden" value="put" /> <input name="authenticity_token" type="hidden" value="CRfHVnuowm8BNu6ZlOoSmzhM2XOhEAJo9Wf9wqTWlWA=" /> </div> <label for="user_name">Name</label> <input id="user_name" name="user[name]" size="30" type="text" value="Rails Tutorial" />
hidden なナニが多いですね。つうか _method という名前が付いたソレは一体何かというと、ブラウザは PUT なリクエストが送れないので誤魔化すためのナニらしい。
これってどうやって判断してるのか、というと以下がその根拠らしい。
$ rails console >> User.new.new_record? => true >> User.first.new_record? => false
なーるほど。form_for を使って form を作ったら Rails は @user.new_record? が真なら POST を使うし偽であれば PUT を使う、とのこと。むむむむ。
さらに試験を追加とのこと。
spec/requests/authentication_pages_spec.rb
describe "with valid information" do let(:user) { FactoryGirl.create(:user) } before { sign_in user } it { should have_selector('title', text: user.name) } it { should have_link('Profile', href: user_path(user)) } it { should have_link('Settings', href: edit_user_path(user)) } it { should have_link('Sign out', href: signout_path) } it { should_not have_link('Sign in', href: signin_path) }
Settings の行が追加ですね。あと test helper に sign_in というソレを追加。
def sign_in(user) visit signin_path fill_in "Email", with: user.email fill_in "Password", with: user.password click_button "Sign in" # Sign in when not using Capybara as well. cookies[:remember_token] = user.remember_token end
あるいは app/views/layouts/_header.html.erb を以下に、とのこと。
<header class="navbar navbar-fixed-top navbar-inverse"> <div class="navbar-inner"> <div class="container"> <%= link_to "sample app", root_path, id: "logo" %> <nav> <ul class="nav pull-right"> <li><%= link_to "Home", root_path %></li> <li><%= link_to "Help", help_path %></li> <% if signed_in? %> <li><%= link_to "Users", '#' %></li> <li id="fat-menu" 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 "Sign out", signout_path, method: "delete" %> </li> </ul> </li> <% else %> <li><%= link_to "Sign in", signin_path %></li> <% end %> </ul> </nav> </div> </div> </header>
おそらくは Settings なリンクのソレが云々なはず。WEBrick 再起動して動作確認しました。
9.1.2 Unsuccessful edits
詳細略で users_controller.rb に update なアクション追加。
def update @user = User.find(params[:id]) if @user.update_attributes(params[:user]) # Handle a successful update. else render 'edit' end end
で、試験実行しとけ、とのこと。ええと試験 green 終了なのか。
9.1.3 Successful edits
を、成功時になりましたね。試験も valid なソレが追加とのこと。
spec/requests/user_pages_spec.rb
describe "with valid information" do let(:new_name) { "New Name" } let(:new_email) { "new@example.com" } before do fill_in "Name", with: new_name fill_in "Email", with: new_email fill_in "Password", with: user.password fill_in "Confirm Password", with: user.password click_button "Save changes" end it { should have_selector('title', text: new_name) } it { should have_selector('div.alert.alert-success') } it { should have_link('Sign out', href: signout_path) } specify { user.reload.name.should == new_name } specify { user.reload.email.should == new_email } end
試験見ると確かに update してるっぽいですね。で、controller も以下に修正せよ、とのこと。
app/controllers/users_controller.rb
def update @user = User.find(params[:id]) if @user.update_attributes(params[:user]) flash[:success] = "Profile updated" sign_in @user redirect_to @user
で、試験実行なのか。なんとなく最近 red 確認して、ってソレが略されてる気がしますがスルーします。試験 green 終了確認。
9.2 Authorization
承認とか認証とかアレ。
9.2.1 Requiring signed-in users
とりあえず spec/requests/authentication_pages_spec.rb に試験を追加。
describe "authorization" do describe "for non-signed-in users" do let(:user) { FactoryGirl.create(:user) } describe "in the Users controller" do describe "visiting the edit page" do before { visit edit_user_path(user) } it { should have_selector('title', text: 'Sign in') } end describe "submitting to the update action" do before { put user_path(user) } specify { response.should redirect_to(signin_path) } end end end end
signin していないユーザはリダイレクトされてますね。つうかいきなり put しちゃう試験もあったりなんかしてますね。
controller 側では before_filter というソレを使って対応します。アクションメソド実行前に呼び出される、はず。
class UsersController < ApplicationController before_filter :signed_in_user, only: [:edit, :update] . . . private def signed_in_user redirect_to signin_url, notice: "Please sign in." unless signed_in? end end
edit および update の場合、signed_in? が偽ならリダイレクトしてますね。ちなみに signed_in_user 手続きは
- flash[:notide] = "Please sign in."
- redirect_to signin_url
と同じことみたい。あと、spec/requests/user_pages_spec.rb を先んじて以下に修正しておきます。
describe "edit" do let(:user) { FactoryGirl.create(:user) } before do sign_in user visit edit_user_path(user) end
これで試験実行ってなってますがどうなるんだろ。At this point, test suite should be green. とある通り、green 終了しました。むむむ。
9.2.2 Requiring the right user
とりあえず as wrong user な試験から着手なのかな。他のユーザを云々な試験が、なのかどうか。以下な試験が app/requests/authentication_pages_spec.rb 方面に追加とあります。
describe "as wrong user" do let(:user) { FactoryGirl.create(:user) } let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") } before { sign_in user } describe "visiting Users#edit page" do before { visit edit_user_path(wrong_user) } it { should_not have_selector('title', text: full_title('Edit user')) } end describe "submitting a PUT request to the Users#update action" do before { put user_path(wrong_user) } specify { response.should redirect_to(root_path) } end end
成程。試験の可読性が良いってのはアレですね。で、app/controllers/users_controller.rb 方面にいくつか盛り込みが必要らしい。
- before_filter に correct_user 追加
- edit アクションは空に
- update アクションも find 略で
- private な correct_user メソド追加
before_filter で find してるので、ってことみたいですね。面白い。Listing 9.15 を以下に引用しておきます。
app/controllers/users_controller.rb
class UsersController < ApplicationController before_filter :signed_in_user, only: [:edit, :update] before_filter :correct_user, only: [:edit, :update] . . . def edit end def update if @user.update_attributes(params[:user]) flash[:success] = "Profile updated" sign_in @user redirect_to @user else render 'edit' end end . . . private def signed_in_user redirect_to signin_url, notice: "Please sign in." unless signed_in? end def correct_user @user = User.find(params[:id]) redirect_to(root_path) unless current_user?(@user) end end
盛り込んだ。あと、app/helpers/sessions_helper.rb に以下な current_user? を盛り込み。
def current_user?(user) user == current_user end
で試験実行してみるに green 終了を確認。今日はちょっとここで終わります。週末対応で全部は終わりそうにないですね。