railstutorial.jp 9章メモ

  • テーマ
    • ユーザーの更新・表示・削除
    • 認証と認可
    • フレンドリーフォワーディング
    • ページング機能

webブラウザからPATHリクエストができないので、POSTのhiddenパラメータ_methodpatchが入っている

<form accept-charset="UTF-8" action="/users/2" class="edit_user" id="edit_user_2" method="post">
  <input name="_method" type="hidden" value="patch" />

Active Recordのnew_record?メソッド
Railsは、form_for(@user)を使用してフォームを構成すると、
@user.new_record?がtrueのときにはPOSTを、falseのときにはPATCHを使用します。

spec/support/utilities.rb
sign_inメソッド
no_capyabara: trueオプション

変更を保存した後に@user.reloadメソッドを使ってDBから再読込した値とチェック

specify { expect(user.reload.name).to  eq new_name }
specify { expect(user.reload.email).to eq new_email }

utilities.rbにsign_inメソッド足してからテストが通らなくなった。謎。

Failure/Error: sign_in user
     NoMethodError:

user_pages_spec.rbとauthentication_pages_spec.rbに以下を追記したらテストが通った。謎。

include 'spec/support/utilities.rb'

Capybaraではeditのアクションはテストできても、updateのアクションはテストできない。
なので、Capybaraでpatchリクエスト(更新)を直接実行しテストする。(get, post, deleteなども同様にメソッドがある。)
responseオブジェクトが返ってくる。

describe "submitting to the update action" do
  before { patch user_path(user) }
  specify { expect(response).to redirect_to(signin_path) }
end

before_action

before_action :signed_in_user, only: [:edit, :update]

redirect_toの引数にハッシュを渡すとflashの設定になる。
flashは:notice、:success、:errorの3種類が設定できる。

redirect_to signin_url, notice: "Please sign in." unless signed_in?
# 上と同じ
unless signed_in?
  flash[:notice] = "Please sign in."
  redirect_to signin_url
end

FactoryGirlで生成したユーザ情報を一部更新するには第2引数にハッシュ

let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") }

フレンドリーフォワーディング
ログイン前に見ようとしていたページへログイン後に遷移させる機能
requestオブジェクト

def redirect_back_or(default)
  redirect_to(session[:return_to] || default)
  # セッションから削除
  session.delete(:return_to)
end

def store_location
  # リクエストのURLをrequestオブジェクトから取得してセッションへ保存
  session[:return_to] = request.url
end

Railsで予約されているオブジェクト名がいままでいろいろでてきた

  • params
  • request
  • response
  • flash
  • cookies
  • session

gravatar_forでエラーが出る。。。

1) User pages index
   Failure/Error: visit users_path
   ActionView::Template::Error:
     wrong number of arguments (2 for 1)
   # ./app/helpers/users_helper.rb:4:in `gravatar_for'

UsersHelper::gravatar_forの引数にデフォルトの値を与えるように修正して動いた。

module UsersHelper
 # 与えられたユーザーのGravatar (http://gravatar.com/) を返す。
  # def gravatar_for(user)
    # gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    # gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
    # image_tag(gravatar_url, alt: user.name, class: "gravatar")
  # end
  def gravatar_for(user, options={size:50})
    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

Faker gemでダミーのユーザデータをrakeタスクで生成する

gem 'faker'

lib/tasks/sample_data.rake
以下コマンドでもgenerateできた

$ rails g task sample_data

db:populateタスク(:dbはただの名前空間)
task populate: :environment doはRakeタスクがUserモデルなどのローカルのRails環境にアクセスできるようにする
User.create!は失敗したときにfalseではなく例外を発生させる
99.times do |n| end Faker::Name.nameでダミーデータを作成する

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

タスク実行に成功したが、データは古いままだった。
いったんrails sをおとしてからもう一度ログインし直したら、データが新しくなった。謎。

ページネーションの実装

gem 'will_paginate'
gem 'bootstrap-will_paginate'

FactoryGirlのsequenceメソッドで連続データを作る

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

timesのブロック引数にFactoryGirl.createが与えられると、sequesceでインクリメントされる

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

before(:each) ブロックのテストに対して1回づつ?
before(:all) ブロックにあるすべてのテストの前に一括実行
after(:all) ブロックにある全てのテストの後に一括実行

will_paginate
usersビューのコードの中から@usersオブジェクトを自動的に見つけ出し、 それから他のページにアクセスするためのページネーションリンクを作成

<%= will_paginate %>

rails consoleでpaginateメソッドをやってみると、デフォルト30件ずつ取得することがわかる。
nilの場合は最初の1ページ目

> User.paginate(page: 1)
  User Load (2.1ms)  SELECT  "users".* FROM "users" LIMIT 30 OFFSET 0

> User.paginate(page: 2)
  User Load (0.5ms)  SELECT  "users".* FROM "users" LIMIT 30 OFFSET 30

> User.paginate(page: nil)
  User Load (0.4ms)  SELECT  "users".* FROM "users" LIMIT 30 OFFSET 0
def index
  @users = User.paginate(page: params[:page])
end

パーシャルにUserクラスのuser変数を渡すと、_user.html.erbを探しに行く
見ているのは変数名ではなく、変数のクラス名。

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

パーシャルにオブジェクトのコレクションを渡すと、自動でコレクションの要素分、クラス名に対応するパーシャル_user.html.erbレンダリングする
each文を省略できる

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

admin権限のテスト

toggle! 引数のadmin属性を反転させてDBに保存

@user.toggle!(:admin)

be_admin admin?メソッドを実行し、trueが返ること

it { should be_admin }
$ rails generate migration add_admin_to_users admin:boolean

default: falseでデフォルト値を与えている

def change
  add_column :users, :admin, :boolean, default: false
end

user番号17を管理者へ変更するPATCHリクエスト
Strong Parametersによってパラメータ名adminを許可していないので防ぐことができる

patch /users/17?admin=1

:userのadminだけ上書きした:adminを定義

  # FactoryGirl.create(:admin)が使える
  factory :admin do
      admin true
  end

match: :first

expect do
   # Capybaraが最初に見つけたdeleteのリンクをクリック
  click_link('delete', match: :first)
end.to change(User, :count).by(-1)
User.find(params[:id]).destroy

herokuの本番データのリセット

$ git push heroku
$ heroku pg:reset DATABASE
$ heroku run rake db:migrate
$ heroku run rake db:populate