Ruby on Rails Tutorial (15)

継続は 9.2.3 ってことで良いのかどうか。

9.2.3 Friendly forwarding

むむ、以下な試験が追加とのこと。

  describe "authorization" do

    describe "for non-signed-in users" do
      let(:user) { FactoryGirl.create(:user) }

      describe "when attempting to visit a protected page" do
        before do
          visit edit_user_path(user)
          fill_in "Email",    with: user.email
          fill_in "Password", with: user.password
          click_button "Sign in"
        end

        describe "after signing in" do

          it "should render the desired protected page" do
            page.should have_selector('title', text: 'Edit user')
          end
        end
      end

signin したら edit なナニに遷移、なのか。
遷移云々なヘルパメソドを app/helpers/sessions_helper.rb に定義とのこと。

  def redirect_back_or(default)
    redirect_to(session[:return_to] || default)
    session.delete(:return_to)
  end

  def store_location
    session[:return_to] = request.url
  end

で、この store_location を users_controller.rb で云々。

class UsersController < ApplicationController
  before_filter :signed_in_user, only: [:edit, :update]
  before_filter :correct_user,   only: [:edit, :update]

before_filter 追加してその手続きも定義。

  private

    def signed_in_user
      unless signed_in?
        store_location
        redirect_to signin_url, notice: "Please sign in."
      end
    end

    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_path) unless current_user?(@user)
    end

edit アクションも空にするのかどうか。edit は既に空ですね。あ、追加ではなくて修正でした。とほほ。修正は signed_in_user メソドでした。あとは sessions_controller.rb の create アクションに redirect_back_or を仕込むのか。

  def create
    user = User.find_by_email(params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      sign_in user
      redirect_back_or user
    else
      flash.now[:error] = 'Invalid email/password combination'
      render 'new'
    end
  end

redirect_to を redirect_back_or に置き換え。試験 green 確認。

9.3 Showing all users

index なアクション追加とのこと。なかなかに先は長い。のでどんどんスルーで進めてしまうかどうか。

9.3.1 User index

とりあえず signin してないユーザが Users controller にアクセスしたら sign in な画面に遷移してね、な試験が追加となっています。

    describe "for non-signed-in users" do
      .
      .
      .
      describe "in the Users controller" do
        .
        .
        .
        describe "visiting the user index" do
          before { visit users_path }
          it { should have_selector('title', text: 'Sign in') }
        end
      end

で、Users controller に index なアクション追加か。ちなみに空になってます。で、user_pages_spec.rb に index アクションな試験追加。

require 'spec_helper'

describe "User pages" do

  subject { page }

  describe "index" do
    before do
      sign_in FactoryGirl.create(:user)
      FactoryGirl.create(:user, name: "Bob", email: "bob@example.com")
      FactoryGirl.create(:user, name: "Ben", email: "ben@example.com")
      visit users_path
    end

    it { should have_selector('title', text: 'All users') }
    it { should have_selector('h1',    text: 'All users') }

    it "should list each user" do
      User.all.each do |user|
        page.should have_selector('li', text: user.name)
      end
    end
  end

ユーザ一覧がリスト表示、な確認になってますね。Users controller の index は User.all なソレを、な形に修正。before_filter も修正か。

class UsersController < ApplicationController
  before_filter :signed_in_user, only: [:index, :edit, :update]

index アクションが以下。

  def index
    @users = User.all
  end

view も用意。
app/views/users/index.html.erb

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

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 52 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

新規作成でした。あと custom.css.scss も情報追加。で、authentication_pages_spec.rb の with valid information な試験について

    describe "with valid information" do
      let(:user) { FactoryGirl.create(:user) }

      before { sign_in user }

      it { should have_selector('title', text: user.name) }

      i { should have_link('Users', href: users_path ) }
      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) }
  • before の中身を修正
  • Users なリンクの確認追加

してます。で、ヘッダも修正して試験実行なのか。

<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", users_path %></li>

Uses なリンク修正ですね。つうか試験 red だorz

  • rspec ./spec/requests/authentication_pages_spec.rb:69 # Authentication authorization for non-signed-in users in the Users controller visiting the user index
  • rspec ./spec/requests/user_pages_spec.rb:15 # User pages index
  • rspec ./spec/requests/user_pages_spec.rb:18 # User pages index should list each user
  • rspec ./spec/requests/user_pages_spec.rb:16 # User pages index

諸々確認。つうかおかしいな。最後の三つはパスしないといけないはずなんですが。
と思ったら gravatar_for の引数が云々と言われてますね。以下にナニがありますね。

そのまま盛り込み。これで user_pages_spec.rb な試験は green です。もう一つの authentication_pages.rb 方面ですが users_controller.rb に盛り込み漏れあり。

  before_filter :signed_in_user, only: [:index, :edit, :update]

を忘れておりました。これで試験 green かな。ボリューム多くてうっかりな漏れがありますね。とほほ杉。

9.3.2 Sample users

Faker という Gem を追加とのこと。なんか凄そなナニですが。

source 'https://rubygems.org'
                     
gem 'rails', '3.2.11'
gem 'bootstrap-sass', '2.1'
gem 'bcrypt-ruby', '3.0.1'
gem 'faker', '1.0.1'

盛り込んで bundle install しておきます。次に lib/tasks というディレクトリに sample user を云々するソレを投入する模様。
lib/tasks/sample_data.rake

namespace :db do
  desc "Fill database with sample data"
  task populate: :environment do
    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
  end
end

で、以下の操作で 100 個の sample user が、とあります。

$ bundle exec rake db:reset
$ bundle exec rake db:populate
$ bundle exec rake db:test:prepare

ヤッてみます。test なソレでないと、なのかどうか。

$ RAILS_ENV=test ./script/rails c
Rack::File headers parameter replaces cache_control after Rack 1.5.
Loading test environment (Rails 3.2.11)
irb(main):001:0> User.all.count
  User Load (0.1ms)  SELECT "users".* FROM "users" 
=> 0

あら?

$ ls db -l
total 60
-rw-r--r-- 1 rms rms 34816 Jan 27 11:55 development.sqlite3
drwxrwxr-x 2 rms rms  4096 Jan 25 16:40 migrate
-rw-r--r-- 1 rms rms     0 Jan 21 15:40 production.sqlite3
-rw-rw-r-- 1 rms rms  1224 Jan 26 08:24 schema.rb
-rw-rw-r-- 1 rms rms   343 Jan 16 08:16 seeds.rb
-rw-r--r-- 1 rms rms  7168 Jan 27 11:56 test.sqlite3

development なのか。

$ RAILS_ENV=development ./script/rails c
Loading development environment (Rails 3.2.11)
irb(main):001:0> User.all.count
  User Load (0.9ms)  SELECT "users".* FROM "users" 
=> 100

成程。

9.3.3 Pagination

Gemfile に以下を追加とのこと。

source 'https://rubygems.org'

gem 'rails', '3.2.11'
gem 'bootstrap-sass', '2.1'
gem 'bcrypt-ruby', '3.0.1'
gem 'faker', '1.0.1'
gem 'will_paginate', '3.0.3'
gem 'bootstrap-will_paginate', '0.0.6'

で、bundle install しておきます。どんどん盛り込みます。まず spec/factories.rb が以下とのこと。

FactoryGirl.define do
  factory :user do
    sequence(:name)  { |n| "Person #{n}" }
    sequence(:email) { |n| "person_#{n}@example.com"}
    password "foobar"
    password_confirmation "foobar"
  end
end

ちなみに修正前は以下。

FactoryGirl.define do
  factory :user do |u|
    u.name     { "Michael Hartl" }
    u.email    { "michael@example.com" }
    u.password { "foobar" }
    u.password_confirmation { "foobar" }
  end
end

確認は別途、ということにて。次に spec/requests/user_pages_spec.rb が以下とのこと。

require 'spec_helper'

describe "User pages" do

  subject { page }

  describe "index" do

    let(:user) { FactoryGirl.create(:user) }

    before(:each) do
      sign_in user
      visit users_path
    end

    it { should have_selector('title', text: 'All users') }
    it { should have_selector('h1',    text: 'All users') }

    describe "pagination" do

      before(:all) { 30.times { FactoryGirl.create(:user) } }
      after(:all)  { User.delete_all }

      it { should have_selector('div.pagination') }

      it "should list each user" do
        User.paginate(page: 1).each do |user|
          page.should have_selector('li', text: user.name)
        end
      end
    end
  end

相当ざくっと修正ですね。つうことは 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: 52 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>

ざくっと置換。controller も修正。app/controllers/users_controller.rb を以下に云々。index アクションですね。

  def index
    @users = User.paginate(page: params[:page])
  end

paginate というメソド呼び出しか。これで試験実行して green 確認しております。

9.3.4 Partial refactoring

refactor とは言え、作法を知らんとアレですよね。とりあえず順に盛り込んでみます。
まず li なソレを云々とあります。app/views/users/index.html.erb をとのこと。

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

<%= will_paginate %>

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

<%= will_paginate %>

修正対象は以下なあたりなのか。

    <li>
      <%= gravatar_for user, size: 52 %>
      <%= link_to user.name, user %>
    </li>

あ、partial にするんだ。app/views/users/_user.html.erb 追加とのこと。

<li>
  <%= gravatar_for user, size: 52 %>
  <%= link_to user.name, user %>
</li>

しかもこんな書き方ができるってアレ。

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

<%= will_paginate %>

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

<%= will_paginate %>

盛り込んで動作確認してみます。これはすばらなナニですね。

9.4 Deleting users

REST な action として未実装なのが destroy とのこと。とりあえず権限持ってるユーザのみがこの操作を云々できること、らしい。当り前ですね。

9.4.1 Administrative users

admin? なナニが云々なのかどうか。とりあえず spec/models/user_spec.rb を以下にとのこと。

  it { should respond_to(:admin) }
  it { should respond_to(:authenticate) }

  it { should be_valid }
  it { should_not be_admin }

  describe "with admin attribute set to 'true'" do
    before do
      @user.save!
      @user.toggle!(:admin)
    end

    it { should be_admin }
  end

あら、なんとなく直感的に微妙なんですが気のせい?
とりあえず現状の実装を見るに

  • admin というカラムが追加なのか
  • be_admin て何だろ
    • デフォでは admin ではない、って意図かな
  • おそらく admin な role は新規なので上記の試験が追加されたと類推

とりあえず盛り込み完了。あと migration もナニですね。

$ rails generate migration add_admin_to_users admin:boolean
      invoke  active_record
      create    db/migrate/20130127103529_add_admin_to_users.rb

で、生成されたソレを以下に。

class AddAdminToUsers < ActiveRecord::Migration
  def change
    add_column :users, :admin, :boolean, default: false
  end
end

で、rake db:migrate 実行。

$ bundle exec rake db:migrate
$ bundle exec rake db:test:prepare

なんか試験 green のためのナニが云々とあるんですがとりあえず実行したんですがパスしたな。微妙だけどスルーしよう。あと以下を云々とのこと。
lib/tasks/sample_data.rake

namespace :db do
  desc "Fill database with sample data"
  task populate: :environment do
    admin = User.create!(name: "Example User",
                         email: "example@railstutorial.org",
                         password: "foobar",
                         password_confirmation: "foobar")
    admin.toggle!(:admin)

で、以下を実行。

$ bundle exec rake db:reset
$ bundle exec rake db:populate
$ bundle exec rake db:test:prepare

とりあえず toggle!(:admin) はスルーで次に。

9.4.2 The destroy action

final step とか書いてあってアレ。とりあえず spec/factories.rb を以下にとのこと。

FactoryGirl.define do
  factory :user do
    sequence(:name)  { |n| "Person #{n}" }
    sequence(:email) { |n| "person_#{n}@example.com"}
    password "foobar"
    password_confirmation "foobar"

    factory :admin do
      admin true
    end
  end
end

で、delete なソレを云々する試験を追加なのか。
spec/requests/user_pages_spec.rb

require 'spec_helper'

describe "User pages" do

  subject { page }

  describe "index" do

    let(:user) { FactoryGirl.create(:user) }

    before do
      sign_in user
      visit users_path
    end

    it { should have_selector('title', text: 'All users') }
    it { should have_selector('h1',    text: 'All users') }
 ||<
あ、ここは同じですね。before の部分が違いましたね。
あるいは以下?
>||
    describe "delete links" do

      it { should_not have_link('delete') }

      describe "as an admin user" do
        let(:admin) { FactoryGirl.create(:admin) }
        before do
          sign_in admin
          visit users_path
        end

        it { should have_link('delete', href: user_path(User.first)) }
        it "should be able to delete another user" do
          expect { click_link('delete') }.to change(User, :count).by(-1)
        end
        it { should_not have_link('delete', href: user_path(admin)) }
      end
    end
  end

ええと、app/views/users/_user.html.erb を以下に修正なのか。

<li>
  <%= gravatar_for user, size: 52 %>
  <%= link_to user.name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
  <% end %>
</li>

条件分岐が追加なのか。admin なら削除なリンクが云々。あとは controller 側で

  before_filter :signed_in_user, only: [:index, :edit, :update]

  before_filter :signed_in_user, only: [:index, :edit, :update, :destroy]

にしとくのか。あ、あと destroy なアクションにも手を入れるのかな。手を入れるというか追加でした。

  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User destroyed."
    redirect_to users_url
  end

User.find().destroy なナニが云々とか。あと試験に admin ではないソレが云々とのこと。
spec/requests/authentication_pages_spec.rb

describe "Authentication" do
  .
  .
  .
  describe "authorization" do
    .
    .
    .
    describe "as non-admin user" do
      let(:user) { FactoryGirl.create(:user) }
      let(:non_admin) { FactoryGirl.create(:user) }

      before { sign_in non_admin }

      describe "submitting a DELETE request to the Users#destroy action" do
        before { delete user_path(user) }
        specify { response.should redirect_to(root_path) }
      end
    end
  end
end

う、最後のソレが分かりづらい。スルーしすぎ感満点。
Users controller に以下を、とのこと。

class UsersController < ApplicationController
  before_filter :signed_in_user, only: [:index, :edit, :update, :destroy]
  before_filter :correct_user,   only: [:edit, :update]
  before_filter :admin_user,     only: :destroy

admin_user 手続きが以下。

    def admin_user
      redirect_to(root_path) unless current_user.admin?
    end

うーん、きちんと読めてない感満点なカンジですね。とりあえず試験実行して green なのは確認できました。

と思ったら

以下なナニ。

  1) User pages delete links as an admin user 
     Failure/Error: it { should have_link('delete', href: user_path(User.first)) }
       expected link "delete" to return something
     # ./spec/requests/user_pages_spec.rb:140:in `block (4 levels) in <top (required)>'

  2) User pages delete links as an admin user should be able to delete another user
     Failure/Error: expect { click_link('delete') }.to change(User, :count).by(-1)
     Capybara::ElementNotFound:
       no link with title, id or text 'delete' found
     # (eval):2:in `click_link'
     # ./spec/requests/user_pages_spec.rb:142:in `block (5 levels) in <top (required)>'
     # ./spec/requests/user_pages_spec.rb:142:in `block (4 levels) in <top (required)>'

ちょっとアタマを冷やす方向にて。