web-dev-qa-db-ja.com

Rails条件付きの場合のみ一意性を検証

Questionクラスがあります:

class Question < ActiveRecord::Base
  attr_accessible :user_id, :created_on

  validates_uniqueness_of :created_on, :scope => :user_id
end

特定のユーザーは1日に1つの質問しか作成できないため、一意のインデックスを介してデータベースの一意性を、validates_uniqueness_ofを介してQuestionクラスを強制する必要があります。

私が直面している問題は、管理者以外のユーザーにのみその制約が必要なことです。そのため、管理者は1日に必要なだけ質問を作成できます。それをエレガントに達成するためのアイデアはありますか?

31
kid_drew

実行するRubyの単純な文字列、Proc、またはメソッド名をシンボルとして値として:ifまたは:unlessのいずれかに渡すことにより、検証を条件付きにすることができます検証:次に例を示します。

Railsバージョン5.2の前に、文字列を渡すことができます。

# using a string:
validates :name, uniqueness: true, if: 'name.present?'

5.2以降、文字列はサポートされなくなり、次のオプションが残されています。

# using a Proc:
validates :email, presence: true, if: Proc.new { |user| user.approved? }

# using a Lambda (a type of proc ... and a good replacement for deprecated strings):
validates :email, presence: true, if: -> { name.present? }

# using a symbol to call a method:
validates :address, presence: true, if: :some_complex_condition

def some_complex_condition
  true # do your checking and return true or false
end

あなたの場合、次のようなことができます:

class Question < ActiveRecord::Base
  attr_accessible :user_id, :created_on

  validates_uniqueness_of :created_on, :scope => :user_id, unless: Proc.new { |question| question.user.is_admin? }
end

詳細については、Railsガイドの条件検証セクションをご覧ください。 http://edgeguides.rubyonrails.org/active_record_validations.html#conditional-validation

77
Jon

一意性を保証する唯一の方法は、データベース(一意のインデックスなど)を使用することです。 Railsのみに基づくすべてのアプローチには、競合状態が含まれます。あなたの制約を考えると、管理者用にnullを残す日とユーザーIDの組み合わせを含む、個別に一意にインデックス付けされた列を確立することが最も簡単だと思います。

はどうかと言うと validates_uniqueness_ofhttp://apidock.com/Rails/ActiveRecord/Validationsで説明されているように、ifまたはunlessオプションを使用して、検証を非管理者に制限できます。/ClassMethods/validates_uniqueness_of

4
Peter Alfvin

validates_uniqueness_of呼び出しに条件を追加するだけです。

validates_uniqueness_of :created_on, scope: :user_id, unless: :has_posted?
def has_posted
  exists.where(user_id: user_id).where("created_at >= ?", Time.zone.now.beginning_of_day)
end

しかし、さらに良いのは、カスタム検証を作成するだけです:

validate :has_not_posted
def has_not_posted
  posted = exists.where(user: user).where("DATE(created_at) = DATE(?)", Time.now)
  errors.add(:base, "Error message") if posted
end
1
Mohamad