37

I have a Question class:

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

  validates_uniqueness_of :created_on, :scope => :user_id
end

A given user can only create a single question per day, so I want to force uniqueness in the database via a unique index and the Question class via validates_uniqueness_of.

The trouble I'm running into is that I only want that constraint for non-admin users. So admins can create as many questions per day as they want. Any ideas for how to achieve that elegantly?

3 Answers 3

94

You can make a validation conditional by passing either a simple string of Ruby to be executed, a Proc, or a method name as a symbol as a value to either :if or :unless in the options for your validation. Here are some examples:

Prior to Rails version 5.2 you could pass a string:

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

From 5.2 onwards, strings are no longer supported, leaving you the following options:

# 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

In your case, you could do something like this:

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

Have a look at the conditional validation section on the rails guides for more details: http://edgeguides.rubyonrails.org/active_record_validations.html#conditional-validation

Sign up to request clarification or add additional context in comments.

4 Comments

Passing a string to if condition is not supported anymore.
Thanks for the reminder - will update the answer with details.
Could also be validates :email, uniqueness: true, if: :name?
Using symbols to call a method are already mentioned in my answer.
5

The only way I know of to guarantee uniqueness is through the database (e.g. a unique index). All Rails-only based approaches involve race conditions. Given your constraints, I would think the easiest thing would be to establish a separate, uniquely indexed column containing a combination of the day and user id which you'd leave null for admins.

As for validates_uniqueness_of, you can restrict validation to non-admins through use of an if or unless option, as discussed in http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of

Comments

2

Just add a condition to the validates_uniqueness_of call.

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

But even better, just create a custom validation:

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.