preventing rails validation based on previous validation - ruby-on-rails

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

Related

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 validate :if with checkboxes

I am working with a form having 2 checkboxes: option_one and option_two.
I don't want to allow submission of the form if option_two is checked and option_one is not.
In other words if somebody checks option_two, they must check option_one as well.
So in my MyModel I wrote :
validates :option_one, :presence => true, :if => option_two_active?, :message => "Dummy message."
Then in the MyController, I added :
def option_two_active?
params[:option_two] == "1"
end
But it keeps giving me the following error :
NoMethodError in MyController#index
Is my approach correct ? How can I achieve this ? Thanks in advance.
You have to specify the conditional method with a symbol:
validates :option_one, :presence => true, :if => :option_two_active?, :message => "Dummy message."
Also, you since you can't use params from a model, you should assign that value to the model from the controller, either with create, update_attributes, or manually. If you want to persist the option_two field, then it should be a database column, else you can just create an attribute accessor:
attribute_accessor :option_two
The way you reference the method, it will be called directly as the class is first loaded. However, the :if parameter is expected to be used with either a proc which is then called during validation or with a symbol representing a method name. In your case, you should thus setup your validation like this:
validates :option_one, :presence => true, :if => :option_two?, :message => "Dummy message."
Notice the colon before the method name. Furthermor, the validation method needs to be defined on the model, not the controller. Fortunately, ActiveRecord already defines the proper methods for Boolean fields, as used here.

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.

Controlling the order of rails validations

I have a rails model which has 7 numeric attributes filled in by the user via a form.
I need to validate the presence of each of these attributes which is obviously easy using
validates :attribute1, :presence => true
validates :attribute2, :presence => true
# and so on through the attributes
However I also need to run a custom validator which takes a number of the attributes and does some calculations with them. If the result of these calculations is not within a certain range then the model should be declared invalid.
On it's own, this too is easy
validate :calculations_ok?
def calculations_ok?
errors[:base] << "Not within required range" unless within_required_range?
end
def within_required_range?
# check the calculations and return true or false here
end
However the problem is that the method "validate" always gets run before the method "validates". This means that if the user leaves one of the required fields blank, rails throws an error when it tries to do a calculation with a blank attribute.
So how can I check the presence of all the required attributes first?
I'm not sure it's guaranteed what order these validations get run in, as it might depend on how the attributes hash itself ends up ordered. You may be better off making your validate method more resilient and simply not run if some of the required data is missing. For example:
def within_required_range?
return if ([ a, b, c, d ].any?(&:blank?))
# ...
end
This will bail out if any of the variables a through d are blank, which includes nil, empty arrays or strings, and so forth.
An alternative for slightly more complex situations would be to create a helper method which runs the validations for the dependent attributes first. Then you can make your :calculations_ok? validation run conditionally.
validates :attribute1, :presence => true
validates :attribute2, :presence => true
...
validates :attribute7, :presence => true
validate :calculations_ok?, :unless => Proc.new { |a| a.dependent_attributes_valid? }
def dependent_attributes_valid?
[:attribute1, ..., :attribute7].each do |field|
self.class.validators_on(field).each { |v| v.validate(self) }
return false if self.errors.messages[field].present?
end
return true
end
I had to create something like this for a project because the validations on the dependent attributes were quite complex. My equivalent of :calculations_ok? would throw an exception if the dependent attributes didn't validate properly.
Advantages:
relatively DRY, especially if your validations are complex
ensures that your errors array reports the right failed validation instead of the macro-validation
automatically includes any additional validations on the dependent attributes you add later
Caveats:
potentially runs all validations twice
you may not want all validations to run on the dependent attributes
Check out http://railscasts.com/episodes/211-validations-in-rails-3
After implementing a custom validator, you'll simply do
validates :attribute1, :calculations_ok => true
That should solve your problem.
The James H solution makes the most sense to me. One extra thing to consider however, is that if you have conditions on the dependent validations, they need to be checked also in order for the dependent_attributes_valid? call to work.
ie.
validates :attribute1, presence: true
validates :attribute1, uniqueness: true, if: :attribute1?
validates :attribute1, numericality: true, unless: Proc.new {|r| r.attribute1.index("#") }
validates :attribute2, presence: true
...
validates :attribute7, presence: true
validate :calculations_ok?, unless: Proc.new { |a| a.dependent_attributes_valid? }
def dependent_attributes_valid?
[:attribute1, ..., :attribute7].each do |field|
self.class.validators_on(field).each do |v|
# Surely there is a better way with rails?
existing_error = v.attributes.select{|a| self.errors[a].present? }.present?
if_condition = v.options[:if]
validation_if_condition_passes = if_condition.blank?
validation_if_condition_passes ||= if_condition.class == Proc ? if_condition.call(self) : !!self.send(if_condition)
unless_condition = v.options[:unless]
validation_unless_condition_passes = unless_condition.blank?
validation_unless_condition_passes ||= unless_condition.class == Proc ? unless_condition.call(self) : !!self.send(unless_condition)
if !existing_error and validation_if_condition_passes and validation_unless_condition_passes
v.validate(self)
end
end
return false if self.errors.messages[field].present?
end
return true
end
I recall running into this issue quite some time ago, still unclear if validations order can be set and execution chain halted if a validation returns error.
I don't think Rails offers this option. It makes sense; we want to show all of the errors on the record (including those that come after a failing, due to invalid input, validation).
One possible approach is to validate only if the input to validate is present:
def within_required_range?
return unless [attribute1, attribute2, ..].all?(&:present?)
# check the calculations and return true or false here
end
Make it pretty & better structured (single responsibility) with Rails idiomatic validation options:
validates :attribute1, :presence => true
validates :attribute2, :presence => true
# and so on through the attributes
validate :calculations_ok?, if: :attributes_present?
private
def attributes_present?
[attribute1, attribute2, ..].all?(&:present?)
end
def calculations_ok?
errors[:base] << "Not within required range" unless within_required_range?
end
def within_required_range?
# check the calculations and return true or false here
end

ActiveRecord bug? Or am I getting it wrong? (validates_presence_of if)

Ok:
User
attr_accessible :name, :email, :email_confirmation
validates_presence_of :email_confirmation if :email_changed?
What happens in the following situation:
u = User.find 1
u.name = 'Fonzi'
u.name_changed? # => true
u.email_changed? # => false
u.valid? # => false : email_confirmation is required
Basically, if I change if to unless the validates works as expected, won't validate if the email has not changed, will validate if the email changed. I thought the IF indicates "run this validation if the following function returns true. Seems to work backwards!? Am I just getting it wrong?
You've got the syntax a little mixed up for conditional validations. Instead of using a regular post-fix conditional like that, you pass the validation method an option called "if" whose value is a method, a proc, or a string. So it should look more like this:
validates_presence_of :email_confirmation, :if => :email_changed?
Check out the documentation for the full scoop.

Resources