railstutorial.jp 11章メモ
ユーザ has_many リレーションシップ
リレーションシップ has_many ユーザ
リレーションシップをリンクテーブルとしてユーザとユーザが多対多の関係にある
$ rails generate model Relationship follower_id:integer followed_id:integer
ユニークインデックスを張る
add_index :relationships, [:follower_id, :followed_id], unique: true
リレーションの関連づけ
Railsはデフォルト、クラス名_id
という名前のカラムは外部キーとして認識する
そうでない場合には外部キーforeign_key
を明示的に指定する
class User < ActiveRecord::Base # relationshipsにあるusersへの参照は、user_idではなくfollower_id。 has_many :relationships, foreign_key: "follower_id", dependent: :destroy
関連 | モデル | Relationshipで持つ参照 |
---|---|---|
belongs_to :follower | Follower | follower_id |
belongs_to :followed | Followed | followed_id |
を推測するが、FollowerもFollowedモデルも存在しない。ここではUserモデルへの参照を持つ必要がある。
明示的にクラス名class_name
を明示的に指定する。
class Relationship < ActiveRecord::Base belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" end
has_many through関連付け
User と Userで多対多
user.followed_usersで取得できる用に設定
sourceパラメータで、followed_users
はfollowed
のidの集合であることを明示的に指定
class User < ActiveRecord::Base has_many :followed_users, through: :relationships, source: :followed
つまり、こんなじょうたいの表現
User.id
<- has_many -> Relationship.follower_id
Relationship.followed_id
<- has_many -> User.id
# 逆リレーションシップを使ってuser.followersを実装する # クラスを明示的に指定してReverseRelationshipクラスを探しに行かないようにする has_many :reverse_relationships, foreign_key: "followed_id", class_name: "Relationship", dependent: :destroy has_many :followers, through: :reverse_relationships, source: :follower
?をつけたメソッドは、慣習的に論理値を返す
!をつけたメソッドは、慣習的に失敗した場合には例外を発生する(create!とかsave!)
def following?(other_user) relationships.find_by(followed_id: other_user.id) end def follow!(other_user) relationships.create!(followed_id: other_user.id) end
最初のユーザに3から51までをフォローさせ、
4から14のユーザは最初のユーザをフォローさせる
def make_relationships users = User.all user = users.first followed_users = users[2..50] followers = users[3..40] followed_users.each { |followed| user.follow!(followed) } followers.each { |follower| follower.follow!(user) } end
ルーティング
memberメソッドのルーティングはidを含むURLに対応するURLを生成する
collectionメソッドのルーティングはidを指定せずにURLに対応するURLを生成する
resources :users do member do get :following, :followers end collection do get :tigers end end
$ rake routes > Prefix Verb URI Pattern Controller#Action > following_user GET /users/:id/following(.:format) users#following > followers_user GET /users/:id/followers(.:format) users#followers > tigers_users GET /users/tigers(.:format) users#tigers > . > . > .
カレントユーザと特定ユーザIDの両立
<% @user ||= current_user %>
have_xpath
メソッドを使ってFollowしたときにUnfollowボタンに切り替わったことをテスト
describe "toggling the button" do before { click_button "Follow" } it { should have_xpath("//input[@value='Unfollow']") } end
フォームでAjaxを使用する方法
form_for
をform_for ..., remote:true
にする。
内部的にはformタグにdata-remote="true"
が加えられる
<%= form_for(current_user.relationships.build(followed_id: @user.id), remote: true) do |f| %>
xhr
メソッドでAjaxのテスト
xhr, HTTPリクエスト, アクション, paramsの内容を指定する。
it "should increment the Relationship count" do expect do xhr :post, :create, relationship: { followed_id: other_user.id } end.to change(Relationship, :count).by(1) end it "should respond with success" do xhr :post, :create, relationship: { followed_id: other_user.id } expect(response).to be_success end
controller側でAjaxレスポンスをする
実際下記の例だと、リクエストの種類に応じて、続く行の中から1つだけが実行される
# respond_toはRspecのメソッドとは別物なので注意 respond_to do |format| format.html { redirect_to @user } # htmlが要求されていたらこっち format.js # jsが要求されていたらこっち end
format.jsの場合、js.erbファイルが使われる
app/views/relationships/create.js.erb
app/views/relationships/destroy.js.erb
js.erbファイルのなかではjQueryJavaScriptヘルパーが使える
escape_javascript
関数でHTMLをエスケープできる
列挙可能(enumerable)オブジェクト(配列やハッシュなど)
アンパサンド(&)とメソッド名に対応するシンボルで短縮表記
$ rails console > [1,2,3,4].map { |i| i.to_s } => ["1", "2", "3", "4"] > [1,2,3,4].map(&:to_s) => ["1", "2", "3", "4"] > [1,2,3,4].map(&:to_s).join(',') => "1,2,3,4"
これを利用して、フォローしているユーザのID配列を取得する
> User.first.followed_users.map(&:id) > User.first.followed_user_ids => 上と同じ結果!
ActiveRecordでは、:followed_users
アソシエーションから、followed_user_ids
メソッドが使えるようになっている
さらにSQL文字列に挿入するときには?プレースホルダに配列を突っ込むと.join(',')
を自動で行って文字列にしてくれる
relationships_controller_spec.rbがこける
Failures: 1) RelationshipsController creating a relationship with Ajax should increment the Relationship count Failure/Error: before { sign_in user, no_capybara: true } NameError: undefined local variable or method `cookies' for #<RSpec::ExampleGroups::RelationshipsController::CreatingARelationshipWithAjax:0x007f9901433e28> # ./spec/support/utilities.rb:30:in `sign_in' # ./spec/controllers/relationships_controller_spec.rb:8:in `block (2 levels) in <top (required)>'
requireを追記したらうごいた
require 'rails_helper' require 'support/utilities.rb' require 'spec_helper' describe RelationshipsController do
名前付きプレースホルダ
where("user_id IN (:followed_user_ids) OR user_id = :user_id", followed_user_ids: followed_user_ids, user_id: user)