Rails:belongs_toの項目の検証方法

追記: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というメソッドが使えるようになっています。これは、指定したカラムの配列を返すメソッドです。モデルのオブジェクトが生成されることがないため、高速で動作します。

User.pluck(:id) #=> [1,2,3,4,5] みたいな感じ

これを使って、belongs_toで関連している項目の検証を行ってみました。

# 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作ってます)。

# 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で評価するように書き換えたら、ちゃんとテストが通りました。

# 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で比較しようとしていたんだ、俺は…。

最終的にこう直した。

# 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

たぶんこれが最適解なんだろうなー…。もっといい方法があるよ!という方がいらっしゃいましたら教えてください。


タグ Ruby, Ruby on Rails | パーマリンク.

コメント・トラックバック一覧

  1. satoruk says:

    これくらいであれば、presenceだけでもできますよ.

    class User < ActiveRecord::Base
    belongs_to :role
    validates :role, presence: true
    end

  2. コメントありがとうございます。
    これ書いた時はだいぶ前なんですが、inverse_ofを使うのが最適解でした。
    http://319ring.net/blog/archives/2724

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です