Best rails way to implement custom validations? - ruby-on-rails

I am looking for the best way to implement custom validations. I am aware of this :
validates :email, :uniqueness => {:scope => :user_id}
It works perfect. But, I want to do something like this (fictive case but it illustrates well) :
validates :email, :uniqueness => {:scope => 'user.name'}
I am thinking of using customs validations like explained here on rails cast but it seems a little overkill to use a module for this.
Anyone ?

Use a validation method.
class Model
validate :validate_email_with_scope
private
def validate_email_with_scope
if Model.where(...).any?
errors.add(:email, 'is not unique')
end
end
end
Replace Model.where(...).any? with your query.

Related

validation using real named scopes rails

I have an invoice model with approver_note, po_number and state_type.
I need validations to check:
scope :approver, where(state_type: 3)
scope :po_no, where(state_type: 2)
validates :approver_note, :presence => true, uniqueness: { scope: [:ac_id, :approver]}, if: :state_three?
validates :po_number, :presence => true, uniqueness: { scope: [:ac_id, :po_no]}, if: :state_two?
def state_three?
self.state_type==3
end
def state_two?
self.state_type==2
end
How can I make sure that the uniqueness in approver_note validator is run on selected scope of records. It should validate using records having state_type=3.
I need something in the similar lines of this bug...
https://rails.lighthouseapp.com/projects/8994/tickets/4325-real-scope-support-for-activerecords-uniqueness-validation
Is this available in rails now? or can we achieve this using custom validation?
The scope option of uniquness checks if the combination of 2 column values is uniq in the table, frankly I really I don't see how it would be clever enough to apply a dynamic scope. Too much magic even for rails !
However a custom validator is quite straightforward :
validate :approver_note_scoped_uniqueness, if: :state_three?
def approver_note_scoped_uniqueness
if self.class.approver.where(ac_id: ac_id).count > 0
errors.add(:ac_id, "My custom error message")
end
end
ADDITIONAL INFO:
Adding to that, I see that the conditions option is available in validate_uniqueness_of from Rails 4. We can use that and construct two validations one for presence and one for uniqueness. Just in case if some one is looking for an answer in Rails 4.
In case of Rails 4,
validates_presence_of :approver_note, if: :state_three?
validates_presence_of :po_number, if: :state_two?
validates_uniqueness_of :approver_note, scope: [:ac_id], conditions: -> { where(state_type: 3)}, if: :state_three?
validates_uniqueness_of :po_number, scope: [:ac_id], conditions: -> { where(state_type: 2)}, if: :state_two?
def state_three?
self.state_type==3
end
def state_two?
self.state_type==2
end

Rails validate multiple attributes lambda only if all of them present

I need to validate few attributes in my model only when they are present in params while updating or creating the object.
validates :attribute_a,format: {with: /some_regex/}, :if => lambda{ |object| object.attribute_a.present? }
Like attribute_a there are multiple attributes which may not be present while being created or updated.Instead of writing the validates statement for each of them,is there a way in which I can check the presence of multiple attributes and then validate every one with a common validation such as inclusion_inor format:{with:/some_regex/ }.
I wanted something like the following code which obviously is wrong.
validates :attribute_a,attribute_b,attribute_c,attribute_d,:format => {:with =>
/some_regex/}, :if => lambda{ |object| object.attribute_name.present? }
You can use validates_format_of:
validates_format_of :attr_a, :attr_b,
with: /someregexp/,
allow_blank: true
The allow blank option means that the regexp doesn't have to match if the attribute is not present.

Rails validation context

I need help with my ActiveRecord model. I have context based validations (mis)using the build-in context options for validations:
validates :foo, :on => :bar, :presence => true
model = Model.new
model.foo = nil
model.valid? # => true
model.save # works as expected
model.valid?(:bar) # => false
model.save(:context => :bar) # fails and returns false
But using my model in a accepts_nested_attributes_for :model and calling parent.save fails (the validation gets called and returns false), any suggestions or solutions?
Still no answer? To explain more about my problem: I have a model called Form which has many Fields. Users should see validation errors on submit, but the form should be saved anyway (with and without errors). There are different types of Fields, each with global validations (to ensure database consistency) and its own specific user-defined validations (to validate user-entered data). So my Fields look someway like that:
# Global validations, to ensure database consistency
# If this validations fail, the record should not be saved!
validates_associated :form, :on => :global
...
# Specific user-defined validations
# If this validations fail, the record should be saved but marked as invalid. (Which is done by a before_save filter btw.)
def validate
validations.each do |validation| # Array of `ActiveModel::Validations`, defined by the user and stored in a hash in the database
validation.new(:on => :specific).validate(self)
end
end
In my controller:
# def create
# ...
form.attributes = params[:form]
form.save!(:global)
form.save(:specific)
Is something similar possible in Rails using the built-in functionality? Btw this not my actual code, which is quite complicated. But I hope, you guys will get the idea.
Try conditional validations
class Customer
attr_accessor :managing
validates_presence_of :first_name
validates_presence_of :last_name
with_options :unless => :managing do |o|
o.validates_inclusion_of :city, :in=> ["San Diego","Rochester"]
o.validates_length_of :biography, :minimum => 100
end
end
#customer.managing = true
#customer.attributes = params[:customer]
#customer.save
"Ability to specify multiple contexts when defining a validation" was introduced in Rails 4.1 - check validate method, :on options description
Only for Rails 5+:
You are looking for
with_options on: :custom_event do
validates :foo, presence: true
validates :baz, inclusion: { in: ['b', 'c'] }
end
To validate or save use
model = YourModel.new
# Either
model.valid?(:custom_event)
# Or
model.save(context: :custom_event)
Change has_nested_attributes_for :model to accepts_nested_attributes_for :models.
Hope this helps.
Good luck.

preventing rails validation based on previous validation

I have a model with 2 validations on the 'name' attribute. It goes something like this:
validates :name, :uniqueness => true
validate do
errors.add(:name, "is dumb") if name_is_dumb?
end
I don't want the 2nd validation to run if the first validation fails (the name is not unique).
What's the best and cleanest way to do this?
According to the documentation:
Callbacks are generally run in the
order they are defined, with the
exception of callbacks defined as
methods on the model, which are called
last.
So the following snippet should work:
validates :name, :uniqueness => true
validate do
errors.add(:name, "is dumb") unless errors[:name].nil?
end

Rails - How to build a custom validation

I have a Rails 3 form, with data-remote => true.
The form field is handle, like a twitter handle. I want to add validation to ensure it's handle friendly, meaning, no spaces, or non-url friendly characters.
Where to start, do I build a customer validation for this?
Thanks
Looks like you want to use validates_format_of
class Product < ActiveRecord::Base
validates_format_of :handle, :with => /\A[a-zA-Z]+\z/, :message => "Only letters allowed"
end
Change the regular expression pattern to match your needs. See the Rails Guides on validation for more information.
Look into validates_each
validates_each :handle do |record,attribute,value|
# your validation code here, and you can record.errors.add().
end
In rails 3 the best practice will be using
validates :handle, :format => {:with => /\A[a-zA-Z]+\z/, :message => "Only letters allowed"}

Resources