Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails validate uniqueness only if conditional

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?

like image 291
kid_drew Avatar asked Jan 03 '14 23:01

kid_drew


People also ask

What is the difference between validate and validates in Rails?

So remember folks, validates is for Rails validators (and custom validator classes ending with Validator if that's what you're into), and validate is for your custom validator methods.

How does validation work in Rails?

Rails built-in Validation MethodsValidates whether associated objects are all valid themselves. Work with any kind of association. It validates whether a user has entered matching information like password or email in second entry field. Validates each attribute against a block.

How do I bypass validation?

A very common way to skip validation is by keeping the value for immediate attribute as 'true' for the UIComponents. Immediate attribute allow processing of components to move up to the Apply Request Values phase of the lifecycle. scenario: While canceling a specific action, system should not perform the validation.


2 Answers

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

like image 185
Jon Avatar answered Sep 18 '22 12:09

Jon


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

like image 41
Peter Alfvin Avatar answered Sep 18 '22 12:09

Peter Alfvin