Rails Validation based on presence of some attribute - ruby-on-rails

Trying to create a validation on a model. The model has two attributes private_key and public_key. If the user provides either one I want the validation to make sure that the other is provided. So if they provide the public_key they must provide the private_key and vice versa. Right now I have the following:
validates_presence_of :public_key if :private_key?
validates_presence_of :private_key if :public_public?
For some reason if I don't provide either I am getting an error.
Thanks in advance :)

use if like attribute, not like condition:
validates_presence_of :public_key, if: :private_key?
validates_presence_of :private_key, if: :public_public?

This might work:
validates_presence_of :public_key, if: 'private_key?'
validates_presence_of :private_key, if: 'public_public?'

You could also use validates shortcut method, which allows you to tuck in multiple validations later on the same attribute if needed:
validates :public_key, presence: true, if: Proc.new { |r| r.private_key.present? }
validates :private_key, presence: true, if: Proc.new { |r| r.public_key.present? }
I would prefer to test the presence of an attribute explicitly, for example using public_key.present? rather than public_key? as it is more readable and AFAIK public_key? method is only available for boolean fields, unless you defined.

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

in Ruby on Rails is it possible to call a method in the middle of several validations?

I'm trying to find out of it's possible to perform a method call that alters the information going into the database for some attributes, during validations. The desired workflow is: user submits a url, I validate it, if it matches the regex, then embedly is called. The embedly function gets the information for the title and image_url. I would like to perform validations on the title and image_url as well, but these don't exist until I've called the embedly method.
Is there a way to:
1. validate the link_url
2. call the embedly method
3. validate the resulting title and image_url attributes?
Any help is appreciated:
class ListLink < ActiveRecord::Base
belongs_to :list
default_scope -> {order('created_at DESC')}
#the REGEX urls are matched against
VALID_URL_REGEX = /\A(http:\/\/|https:\/\/|www|)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\z/i
validates :link_url, presence: true,
format:{with: VALID_URL_REGEX, message: "Please enter a valid url."}
validates :list_id, presence: true
#if is a valid url, ping embedly for more information on it
before_save :embedly
#is it possible to call just these 2 validations after the :embedly method?
validates :title, presence: true, length:{minimum: 4, maximum: 200}
validates :image_url, presence: true
private
def embedly
embedly_api = Embedly::API.new :key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
:user_agent => 'Mozilla/5.0 (compatible; mytestapp/1.0; my#email.com)'
#duplicate the url for use in the embedly API
url = link_url.dup
obj = embedly_api.extract :url => url
#extract and save a title and image element to the database
self.title = obj[0].title
self.image_url = obj[0]["images"][0]["url"]
end
end
You could write a before_validation callback which checks your link_url and if it's valid (i.e. if it matches the URL), performs your embedly stuff. Then, during the regular validation, you can still add the error message to an invalid link_url. This could look something like this:
class ListLink < ActiveRecord::Base
before_validation :embedly_if_valid
# the REGEX urls are matched against
VALID_URL_REGEX = /\A(http:\/\/|https:\/\/|www|)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\z/i
validates :link_url, presence: true,
format:{with: VALID_URL_REGEX, message: "Please enter a valid url."}
validates :list_id, presence: true
validates :title, presence: true, length:{minimum: 4, maximum: 200}
validates :image_url, presence: true
private
def embedly_if_valid
embedly if self.link_url =~ VALID_URL_REGEX
end
def embedly
embedly_api = Embedly::API.new :key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
:user_agent => 'Mozilla/5.0 (compatible; mytestapp/1.0; my#email.com)'
#duplicate the url for use in the embedly API
url = link_url.dup
obj = embedly_api.extract :url => url
#extract and save a title and image element to the database
self.title = obj[0].title
self.image_url = obj[0]["images"][0]["url"]
end
end
Note that you will probably get validation errors for the title and image_url fields if the link_url is not valid. You could probably code around this if it is not desired by setting the link_url to nil in a before_validation hook unless it is valid.
Instead of using the before_save callback, I suggest doing this in a custom setter for link_url, something along the lines of...
def link_url=(url)
if url =~ VALID_URL_REGEX
super
embedly
end
end
or, if you're not calling link_url = ... do this in a before_validation callback.
You won't be able to do it the way you mention here. You seem to have different validations depending on the state of the object.
Instead you could run the embedly before save and then validate everything. If you need some validation in order to use the embedly method, you could use a form object (example #3) to separate the different steps of your flow.
You could also use before_validation to run embedly, however it makes it that you need to duplicate the validation on the fields required by embedly (you need to test it in your method before validation, and then on the model in case you need to re-run the method later). You could get passed it with a custom validation method called explicitely, but I'm not a big fan of this.
I would re-think the design a little. Calling an external service like Embedly is best done as a background job as you never know how long it's going to take, and it could block other requests for your application.
You would have to allow the ListLink to be created without a title and image_url, though. Your background worker would then set these attributes when it runs.
You would still want to validate them at this point, of course, but this can be done using conditional validations. Something like this:
validates :title, presence: true, length:{minimum: 4, maximum: 200}, unless: :new_record?

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.

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

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

Resources