追記:inverse_ofを使いましょう。
http://319ring.net/blog/archives/2724
以下、原文。
Railsやってていつもbelongs_toの項目の検証をどうするのかで迷っていたのですが、自分はこれが最適解なのかなーと思ったのがあったので書いておきます。
belongs_toのデータを必須にしたいのですが、:presence => trueだと必須というだけでちゃんとリレーション先に存在するidじゃない場合があるので信頼性にかけるので、どうするべきか?と悩んでいました。:inclusionでやるのがいいのかな?と思っていたのですが、どうにも重たそうだし、ほかに方法があるんじゃないのかなーと。
Web+DB Pressの70号で紹介されているのですが、Rails3.2以降ならば、modelでpluckというメソッドが使えるようになっています。これは、指定したカラムの配列を返すメソッドです。モデルのオブジェクトが生成されることがないため、高速で動作します。
1 | User.pluck( :id ) #=> [1,2,3,4,5] みたいな感じ |
これを使って、belongs_toで関連している項目の検証を行ってみました。
01 02 03 04 05 06 07 08 09 10 | # coding: utf-8 class User < ActiveRecord::Base attr_accessble :name , :role , :role_id belongs_to :role , :readonly => true validates :role_id , :inclusion => { :in => Role.pluck( :id )} validates :name , :presence => true end |
これを検証してみるために、RSpecを書いてみます(FactoryGirlでfixture作ってます)。
01 02 03 04 05 06 07 08 09 10 | # coding: utf-8 require 'spec_helper' describe User do let( :role ) {FactoryGirl.create( :role )} describe "正しいデータを入れた場合" do subject {FactoryGirl.build( :user , :role => role)} it( "エラーがないこと" ) {should be_valid} end end |
一見、通りそうなのですが、エラーになりました…。
原因がわからなかったのでググってみたところ、:inclusion => {:in => Role.pluck(:id)}の、Role.pluck(:id)が評価されるタイミングがずれるため、エラーになっているようでした。ここを以下のようにprocで評価するように書き換えたら、ちゃんとテストが通りました。
01 02 03 04 05 06 07 08 09 10 | # coding: utf-8 class User < ActiveRecord::Base attr_accessble :name , :role , :role_id belongs_to :role , :readonly => true validates :role_id , :inclusion => { :in => proc{Role.pluck( :id )}} validates :name , :presence => true end |
ここまで書いて、普通にRole.exists?(role_id)で評価すればよくね?と気づいた…。
なんでinclusionで比較しようとしていたんだ、俺は…。
最終的にこう直した。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | # coding: utf-8 class User < ActiveRecord::Base attr_accessble :name , :role , :role_id belongs_to :role , :readonly => true validates :name , :presence => true validate :role_exists ? private def role_exists? unless Role.exists?(role_id) errors.add( :role_id , I18n( 'activerecord.errors.messages.inclusion' )) end end end |
たぶんこれが最適解なんだろうなー…。もっといい方法があるよ!という方がいらっしゃいましたら教えてください。
これくらいであれば、presenceだけでもできますよ.
class User < ActiveRecord::Base
belongs_to :role
validates :role, presence: true
end
コメントありがとうございます。
これ書いた時はだいぶ前なんですが、inverse_ofを使うのが最適解でした。
http://319ring.net/blog/archives/2724