ぞえの技術めも

Ruby on Rails勉強中

【109日目】【1日20分のRailsチュートリアル】【第9章】ユーザーインデックスのテストを追加する

Ruby on Railsチュートリアル(第3版)

今日は「9.3.4 ユーザーインデックスのテスト」から。

9.3.4 ユーザーインデックスのテスト

今回のテストでは、ログイン、indexページにアクセス、最初のページにユーザーがいることを確認、ページネーションのリンクがあることを確認、といった順でテストしていきます。

ページネーションに関するテストを追加する。

リスト9.20で2人目のユーザーをfixtureに追加しましたが、今回はもっと多くのユーザーを作成する必要があります。

fixtureでも埋め込みRubyとやらが使えるらしいのでコード書いてテストユーザーを追加する。
コピペでできなくはないにしろ、もし31人以上のテストユーザーを手作業で追加しろとか言われたら拷問だよね……。(単純作業きらい)

test/fixtures/users.yml

lana:
  name: Lana Kane
  email: hands@example.gov
  password_digest: <%= User.digest('password') %>

mallory:
  name: Mallory Archer
  email: boss@example.gov
  password_digest: <%= User.digest('password') %>

<% 30.times do |n| %>
user_<%= n %>:
  name:  <%= "User #{n}" %>
  email: <%= "user-#{n}@example.com" %>
  password_digest: <%= User.digest('password') %>
<% end %>

今後必要になるらしいので名前付きユーザー(“lana"と"mallory”)も追加。

リスト9.43のfixtureファイルができたので、indexページに対するテストを書いてみます。まずは、いつものように統合テストを生成します。

統合テストを生成。

$ rails generate integration_test users_index
      invoke  test_unit
      create    test/integration/users_index_test.rb

今回のテストでは、paginationクラスを持ったdivタグをチェックして、最初のページにユーザーがいることを確認します。

1ページ目にユーザーがいる(リンクとはユーザー名が表示されている)ことをテストする。

うーん、何のテストかは何となく分かるけど一からは書けない。。。

test/integration/users_index_test.rb

require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end
end

テストが通ることを確認。

$ bundle exec rake test
37 tests, 117 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【19分】

次は「9.3.5 パーシャルのリファクタリング」から。

【108日目】【1日20分のRailsチュートリアル】【第9章】indexページでページネーションを動作させる

Ruby on Railsチュートリアル(第3版)

今日は「9.3.3 ページネーション」から。

9.3.3 ページネーション

これで、最初のユーザーにも仲間ができました。しかし今度は逆に、1つのページに大量のユーザーが表示されてしまっています。
(中略)
これを解決するのがページネーション (pagination) というもので、この場合は、たとえば1つのページに一度に30人だけユーザーを表示するというものです。

数千になってしまうとユーザー情報をデータベースから取得するのに時間かかるしもページを表示するのにも時間かかるし良くないね。

Railsには豊富なページネーションメソッドがあります。今回はその中で最もシンプルかつ堅牢なwill_paginateメソッドを使用してみましょう。

へー、いくつもメソッドあるんだ。

will_paginateメソッドを使うためにwill_paginate gem とbootstrap-will_paginate gemをGemfileにインクルードする。

Gemfile

gem 'will_paginate',           '3.0.7'
gem 'bootstrap-will_paginate', '0.0.10'

bundle installを実行。

$ bundle install
Installing will_paginate 3.0.7
  :
Installing bootstrap-will_paginate 0.0.10
  :

will_paginateメソッドを使う準備ができました。

ページネーションが動作するには、ユーザーのページネーションを行うようにRailsに指示するコードをindexビューに追加する必要があります。
また、indexアクションにあるUser.allを、ページネーションを理解できるオブジェクトに置き換える必要もあります。

まずindexページの上下でwill_paginateメソッドを呼ぶ。

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: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>

このwill_paginateメソッドは少々不思議なことに、usersビューのコードの中から@usersオブジェクトを自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成しています。

へー、メソッドに@usersオブジェクトを指定しなくていいのか。

というのも、現在の@users変数にはUser.allの結果が含まれていますが (リスト9.33)、will_paginateではpaginateメソッドを使った結果が必要だからです。

allではなくpaginateメソッドを使うらしい。

$ rails console
>> User.paginate(page: 1)
  User Load (4.7ms)  SELECT  "users".* FROM "users" LIMIT 30 OFFSET 0
   (0.5ms)  SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1, name: "Example User", ...

デフォルトでは30個分のUserを取得する。標準?でこんなメソッドがあるのかー

paginateを使用することで、サンプルアプリケーションのユーザーのページネーションを行えるようになります。具体的には、indexアクション内のallをpaginateメソッドに置き換えます 。

indexアクションにてUser.paginateメソッドを使うように修正。

app/controllers/users_controller.rb

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

ページネーションの動作見てみよう。

サーバーを起動して

$ rails server -b $IP -p $PORT

<ローカルアドレス>/usersにアクセス。

f:id:kt_zoe:20170208123526p:plain

ページの上下にページネーションが表示されてます。

f:id:kt_zoe:20170208123537p:plain

3ページ目も表示できるよ。実装簡単だな~

今日の学習時間は【22分】

次は「9.3.4 ユーザーインデックスのテスト」から。

【107日目】【1日20分のRailsチュートリアル】【第9章】サンプルユーザーを一気に追加する

Ruby on Railsチュートリアル(第3版)

今日は「9.3.2 サンプルのユーザー」から。

9.3.2 サンプルのユーザー

この節では、一人ぼっちのユーザーに仲間を加えてあげることにします。
(中略)
せっかくなのでRubyとRakeを使用してユーザーを一気に作成しましょう。

一気に作成できるなら一気に作成しましょう。

まず、GemfileにFaker gemを追加します (リスト9.38)。これは、実際にありそうなユーザー名とメールアドレスを持つサンプルユーザーを自動的に作成するものです。

へー。いちいちテスト用ユーザーを考えなくていいからいいね。

Gemfile

  :
gem 'faker',                '1.4.2'
  :

Gemfileを更新したら以下コマンドを実行。

$ bundle install
Installing faker 1.4.2

インストールできた。

では、サンプルユーザーを生成するRakeタスクを追加してみましょう。

サンプルユーザーを計100人(1人 + 99人)分生成するタスクを追加する。

db/seeds.rb

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

Fakerを使うのはユーザー名だけなのかー。

それでは、データベースをリセットして、リスト9.39のRakeタスクを実行 (db:seed) してみましょう。

$ bundle exec rake db:migrate:reset
$ bundle exec rake db:seed

ちょっと時間かかった。

サンプルユーザーの追加ができたので実際の表示を確認してみる。

サーバーを起動して

$ rails server -b $IP -p $PORT

<ローカルアドレス>/usersにアクセス。
したらログインを要求されるので"Example User"のアカウントでログイン。

f:id:kt_zoe:20170206123956p:plain

ユーザーがたくさん表示されてます。さみしくない!

最初のいくつかのメールアドレスについては、デフォルトのGravatar画像以外の写真を関連付けてみました。

なるほど、メールアドレスをexample-*@railstutorial.orgにしたのはこのためか。。
自動生成にするとGravatar画像見れないもんね。

今日の学習時間は【20分】

次は「9.3.3 ページネーション」から。

【106日目】【1日20分のRailsチュートリアル】【第9章】すべてのユーザーを表示するビューを作成する

Ruby on Railsチュートリアル(第3版)

今日は「9.3.1 ユーザーインデックス」のindexビューを追加するところから。

9.3.1 ユーザーインデックス

今度はすべてのユーザーを表示するために、全ユーザーが格納された変数を作成し、順々に表示するindexビューを実装します。

Userコントローラーにindexアクションを追加。

app/controllers/users_controller.rb

  def index
    @users = User.all
  end

(すべてのユーザーを一気に読み出すとデータ量が多い場合に問題が生じるのではないかと思われた方、そのとおりです。このキズは9.3.3で修正します。)

一気に読み出すと時間かかりそう。

実際のインデックスページを作成するには、ユーザーを列挙してユーザーごとにliタグで囲むビューを作成する必要があります。

ファイルを作成して

$ touch app/views/users/index.html.erb

下記内容でファイル更新。

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: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

訳注: 7.7の1つ目の演習 (リスト7.31) でgravatarメソッドを拡張していない場合、以下のリストがうまく動きません。まだの方は当該リストのコードを先に反映させておいてください。

演習向けブランチでgravatarメソッドを拡張しているのでmasterブランチには反映してない。

リスト7.31の内容を反映しておく。

app/helpers/users_helper.rb

module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user, options = { size: 80 })
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    size = options[:size]
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end

CSS (正確にはSCSSですが) にもちょっぴり手を加えておきましょう (リスト9.35)。

ファイルの一番下に下記を追加。

app/assets/stylesheets/custom.css.scss

/* Users index */

.users {
  list-style: none;
  margin: 0;
  li {
    overflow: auto;
    padding: 10px 0;
    border-bottom: 1px solid $gray-lighter;
  }
}

最後に、サイト内移動用のヘッダーにユーザー一覧表示用のリンクを追加します。
これにはusers_pathを使用し、表7.1に残っている最後の名前付きルートを割り当てます。変更の結果をリスト9.36に示します。

ナビゲーションに追加してたUsersのリンク先をusers_pathに変更。

app/views/layouts/_header.html.erb

  :
          <li><%= link_to "Users", users_path %></li>
  :

これでユーザーのインデックスは完全に動くようになり、テストも全てパスするようになります。

テストが通ることを確認。

$ bundle exec rake test
36 tests, 85 assertions, 0 failures, 0 errors, 0 skips

表示も見てみよう。

サーバーを起動して

$ rails server -b $IP -p $PORT

<ローカルアドレス>/usersにアクセス。(ヘッダーの「Users」をクリック)

f:id:kt_zoe:20170203123445p:plain

…1人しかいない。さみしい。

今日の学習時間は【20分】

次は「9.3.2 サンプルのユーザー」から。

【105日目】【1日20分のRailsチュートリアル】【第9章】すべてのユーザーを表示する

Ruby on Railsチュートリアル(第3版)

今日は「9.3 すべてのユーザーを表示する」から。

9.3 すべてのユーザーを表示する

この節では、いよいよ最後から2番目のユーザーアクションであるindexアクションを追加しましょう。このアクションは、すべてのユーザーを一覧表示します。

すべてのユーザーはどのアカウントからも見れるアプリケーションなんだっけ。Twitterみたいなやつ作るんだよね、確か。

9.3.1 ユーザーインデックス

ユーザーのshowページについては、今後も (ログインしているかどうかに関わらず) サイトを訪れたすべてのユーザーから見えるようにしておきますが、
ユーザーのindexページはログインしたユーザーにしか見せないようにし、未登録のユーザーがデフォルトで表示できるページを制限します。

ほぅほぅ。

indexページを不正なアクセスから守るために、まずはindexアクションが正しくリダイレクトするか検証するテストを書いてみます (リスト9.31)。

indexアクションに関するテストを追加。

test/controllers/users_controller_test.rb

  test "should redirect index when not logged in" do
    get :index
    assert_redirected_to login_url
  end

次に、beforeフィルターのlogged_in_userにindexアクションを追加して、このアクションを保護します (リスト9.32)。

Userコントローラーにindexアクションを追加して、brforeフィルターにもindexアクションを追加。

app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
  :
  def index
  end
  :

ここでテスト実行すると通りました。
idenxアクションのリダイレクト処理書いてないのにー、と一瞬思ったけど、
logged_in_userでリダイレクト処理入れてましたね…。そういうことか。

$ bundle exec rake test
36 tests, 85 assertions, 0 failures, 0 errors, 0 skips

途中だけど今日はここまで。

今日の学習時間は【16分】

次は「9.3.1 ユーザーインデックス」のindexビューを追加するところから。

【104日目】【1日20分のRailsチュートリアル】【第9章】フレンドリーフォワーディング機能を実装する

Ruby on Railsチュートリアル(第3版)

今日は「9.2.3 フレンドリーフォワーディング」の実装するところから。

9.2.3 フレンドリーフォワーディング

失敗するテストが書けたので、ようやくフレンドリーフォワーディングを実装する準備ができました。
ユーザーを希望のページに転送するには、リクエスト時点のページをどこかに保存しておき、その場所にリダイレクトさせる必要があります。

Sessionsヘルパーにリクエスト時点のページを保存するメソッドと保存したページにリダイレクトさせるメソッドを追加。

app/helpers/sessions_helper.rb

module SessionsHelper
  :
  # 記憶したURL (もしくはデフォルト値) にリダイレクト
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end

  # アクセスしようとしたURLを覚えておく
  def store_location
    session[:forwarding_url] = request.url if request.get?
  end
end

ただし、GETリクエストが送られたときだけ格納するようにしておきます。

稀なケースに対応するために必要らしい。

こういったケースに対処しておかないと、POSTや PATCH、DELETEリクエストを期待しているURLに対して (リダイレクトを通して) GETリクエストが送られてしまい、場合によってはエラーが発生します。

不整合が発生するので対応しておく感じなのかな。。。なんかよく分からないけど今はいいや。

先ほど定義したstore_locationメソッドを使って、早速beforeフィルターのlogged_in_userを修正してみます (リスト9.28)。

logged_in_userメソッドにstore_locationを追加。

app/controllers/users_controller.rb

    :
    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
    :

フォワーディング自体を実装するには、redirect_back_orメソッドを使用します。
(中略)
デフォルトのURLは、Sessionコントローラのcreateアクションに追加し、サインイン成功後にリダイレクトします (リスト9.29)。

Sessionコントローラのcreateアクションにてredirect_back_orメソッドを使うように変更。

app/controllers/sessions_controller.rb

  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      log_in @user
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_back_or @user
    else
      :
    end
  end

サンプルソースuserだけど私のコード@user使ってる。。。
なんかどっかで直した気がするのでとりあえず@userのままで。問題はないはず。

session.delete(:forwarding_url) という式を通して転送用のURLを削除している点に注意してください。
これをやっておかないと、次回ログインしたときに保護されたページに転送されてしまい、ブラウザを閉じるまでこれが繰り返されてしまいます。

redirect_back_orメソッドの中身の話。
確かにこれうっかり忘れそうだな。使ったら初期化しておく。

実装が終わったのでテスト実行して問題ないことを確認。
これで基本ユーザー認証機能とページ保護機能の実装は完了。

$ bundle exec rake test
35 tests, 84 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【26分】

次は「9.3 すべてのユーザーを表示する」から。

【103日目】【1日20分のRailsチュートリアル】【第9章】ユーザーフレンドリーな機能のテストを追加する

Ruby on Railsチュートリアル(第3版)

今日は「9.2.2 正しいユーザーを要求する」のリファクタリングから。

9.2.2 正しいユーザーを要求する

最後に、リファクタリングではありますが、一般的な慣習に倣ってcurrent_user?という論理値を返すメソッドを実装します。

あるユーザーが今ログインしているユーザーかどうかチェックするメソッドを実装する。

app/helpers/sessions_helper.rb

  # 与えられたユーザーがログイン済みユーザーであればtrueを返す
  def current_user?(user)
    user == current_user
  end

@user == current_usercurrent_user?で置き換えることでちょっと分かりやすいコードに。

app/controllers/users_controller.rb

    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end

9.2.3 フレンドリーフォワーディング

(省略)後1つ小さなキズがあります。保護されたページにアクセスしようとすると、問答無用で自分のプロファイルページに移動させられてしまいます。

アクセスしようとしてたページに移動して欲しいよね。

実際のテストはまず編集ページにアクセスし、ログインした後に、(デフォルトのプロフィールページではなく) 編集ページにリダイレクトされているかどうかをチェックするといったテストです。

編集ページへのアクセスについてテストを下記のように修正。

test/integration/users_edit_test.rb

  :
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_path(@user)
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), 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

(なお、リダイレクトによってedit用のテンプレートが描画されなくなったので、リスト9.26では該当するテストを削除しています)

テスト内のこのコードのことかな。

    assert_template 'users/edit'

まだアクセスしようとしてたページにリダイレクトする処理は実装してないのでこの段階ではテストは失敗します。

$ bundle exec rake test
35 tests, 79 assertions, 1 failures, 0 errors, 0 skips

項の途中だけど今日はここまで。

今日の学習時間は【21分】

次は「9.2.3 フレンドリーフォワーディング」の処理を実装するところから。