railstutorial.jp 10章メモ

  • テーマ
    • アソシエーション
    • パーシャル

micropostsテーブルの追加

$ rails generate model Micropost content:string user_id:integer
$ rake db:migrate
$ rake test:prepare

やはりテストがこける

Failure/Error: let(:user) { FactoryGirl.create(:user) }
ActiveRecord::RecordInvalid:
  Validation failed: Email has already been taken

いったんtest.sqlite3を削除するとうまくいった

$ rm db/test.sqlite3
$ rake db:create db:migrate RAILS_ENV=test

$ rspec spec/models/micropost_spec.rb

User has_many Micropost

class User < ActiveRecord::Base
  # 複数形
  # 自分が削除されたら関連先も削除する
  has_many :microposts, dependent: :destroy
end

Micropost belongs_to User

class Micropost < ActiveRecord::Base
  # 単数形
  belongs_to :user
end
メソッド 用途
micropost.user マイクロポストに関連付けられたユーザーオブジェクトを返す。
user.microposts ユーザーのマイクロポストの配列を返す。
user.microposts.create(arg) マイクロポストを作成する (user_id = user.id)。
user.microposts.create!(arg) マイクロポストを作成する (失敗した場合は例外を発生する)。
user.microposts.build(arg) 新しいMicropostオブジェクトを返す (user_id = user.id)。

FactoryGirlでの関連づけ
:micropostの定義内にuserを含めるだけ。userはこのコードの上の方でfactory :user doと定義しているので使えると思われる

factory :micropost do
  content "Lorem ipsum"
  # userと関連づけ
  user
end

letはその変数が参照されたときに初めてつくられる(遅延)
let!は即座に変数を作るので順番が重要なときに便利

before { @user.save }
let!(:older_micropost) do
  FactoryGirl.create(:micropost, user: @user, created_at: 1.day.ago)
end

let!(:newer_micropost) do
  FactoryGirl.create(:micropost, user: @user, created_at: 1.hour.ago)
end

# has_many関連づけの正しさをmicropostの順序でチェック
it "should have the right microposts in the right order" do
  expect(@user.microposts.to_a).to eq [newer_micropost, older_micropost]
end

デフォルトスコープ
ラムダ-> { }を受け取り、遅延評価される

 default_scope -> { order('created_at DESC') }

Procの生成を確かめる

$ rails c

> -> { puts "foo" }
=> #<Proc:0x007fe63cf604d0@(irb):2 (lambda)>
> -> { puts "foo" }.call
foo
=> nil

to_aメソッドはオブジェクトのコピーを返している

microposts = @user.microposts.to_a
@user.destroy
# ユーザにひも尽くmicropostsは削除されていないこと
expect(microposts).not_to be_empty

whereメソッドはない場合には空オブジェクトを返す

expect(Micropost.where(id: micropost.id)).to be_empty

findメソッドは内場合には例外をスローする

expect do
  Micropost.find(micropost)
end.to raise_error(ActiveRecord::RecordNotFound)
def show
  @user = User.find(params[:id])
  @microposts = @user.microposts.paginate(page: params[:page])
end

usersコントローラにおいて@userがあると推測される場合は引数無しでもOKだが、そうでない場合には手動でpaginateされたコレクションを渡す

<%= will_paginate %>
<%= will_paginate @microposts %>

カウント数

<h3>Microposts (<%= @user.microposts.count %>)</h3>

app/views/users/show.html.erbで使っていたとしてもapp/views/microposts/_micropost.html.erb(単数形!)がパーシャルとなる

<%= render @microposts %>

Fakerを使ってダミーテキストの生成

content = Faker::Lorem.sentence(5)

エラーになった。。。

$ rake db:reset
$ rake db:populate
rake aborted!
ArgumentError: wrong number of arguments (1 for 0)

allの引数、limitの指定が間違ってた。

# users = User.all(limit: 6)
users = User.all.limit(6)
$ rake db:reset
$ rake db:populate
$ rake test:prepare

$ rm db/test.sqlite3
$ rake db:create db:migrate RAILS_ENV=test

rails sし直して、micropostsが表示された。

resources :microposts, only: [:create, :destroy]

onlyを明示的に指定しなければ全アクションがbefore_actionの対象になる

before_action :signed_in_user
# before_action :signed_in_user, only: [:create, :destroy]

micropostsページの結合テストをgenerate

$ rails generate integration_test micropost_pages

error_messagesパーシャルに明示的にオブジェクトを渡す方法
object: f.object@micropostを渡している

また、 object: f.objecterror_messagesパーシャルの中でobjectという変数を使うように指定している

<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  ...
<% end %>
  def home
    @micropost = current_user.microposts.build if signed_in?
  end

テストが落ちる -> micropost_pages_spec.rbにrequire 'support/utilities.rb'を追加すると直る

$ rspec spec/requests/micropost_pages_spec.rb
FFF

Failures:
  1) Micropost pages micropost creation with invalid information should not create a micropost
     Failure/Error: before { sign_in user }
     NoMethodError:
       undefined method `sign_in' for #<RSpec::ExampleGroups::MicropostPages::MicropostCreation::WithInvalidInformation:0x007fc98bab2ff0>
     # ./spec/requests/micropost_pages_spec.rb:19:in `block (2 levels) in <top (required)>'

Rspecのinclude?メソッド
include?は本来、配列が指定の要素を含んでいるかどうか返すメソッド

its(:feed) { should include(newer_micropost) }
its(:feed) { should include(older_micropost) }
its(:feed) { should_not include(unfollowed_post) }

プレイスホルダ

Micropost.where("user_id = ?", id)

li##{item.id}の最初の# は CSS idを示す Capybara独自の文法で、2番目の#は Rubyの式展開#{}の先頭部分

expect(page).to have_selector("li##{item.id}", text: item.content)

renderはコレクションに与えられたパーシャルfeed_item変数を使う

<%= render partial: 'shared/feed_item', collection: @feed_items %>

関連づけて検索
find_byはみつからないと、nilを返す

def correct_user
  @micropost = current_user.microposts.find_by(id: params[:id])
  redirect_to root_url if @micropost.nil?
end

findはみつからないと、例外をスローする

def correct_user
  @micropost = current_user.microposts.find(params[:id])
rescue
  redirect_to root_url
end