Validate presence of one of multiple attributes in rails - ruby-on-rails

In an multilingual application, a user can input their Chinese and English names. The user can input either or both, but must input at least one name.
class Person < ActiveRecord::Base
validates :zh_name, :presence => true
validates :en_name, :presence => true
validates :fr_name, :presence => true
end
Since the built-in :validates_presence_of method can only validate both attributes at once, is there a way to validate the presence of at least one of many attributes in rails?
Like a magical, validates_one_of :zh_name, :en_name, :fr_name
Thank you in advance,

validate :at_least_one_name
def at_least_one_name
if [self.zh_name, self.en_name, self.fr_name].reject(&:blank?).size == 0
errors[:base] << ("Please choose at least one name - any language will do.")
end
end

Taking #micapam's answer a step futher, may I suggest:
validate :has_a_name
def has_a_name
unless [zh_name?, en_name?, fr_name?].include?(true)
errors.add :base, 'You need at least one name in some language!'
end
end

just a quick shot out, you can pass a "if" or "unless" to the validator, maybe you can get it working this way. i have something like this in mind
validates :zh_name, :presence => { :if => (fr_name.blank? && en_name.blank?) }

validate :has_a_name
def has_a_name
unless [zh_name, en_name, fr_name].any?{|val| val.present? }
errors.add :base, 'You need at least one name in some language!'
end
end
Max Williams' answer is fine, but I didn't see the need to count hits when any? returns a boolean.

Related

Avoid duplication in Rails validators

I have two validations:
validates :email, format: { with: /\A(.+)#(aol|gmail|office365|outlook|verizon|yahoo)\.com\Z/i }, if: Proc.new { |user| user.imap_server.blank? }
validates :email, presence: true
validates :imap_server, presence: true, if: Proc.new { |user| user.email.present? && user.email_invalid? }
def email_invalid?
self.email =~ /\A(.+)#(aol|gmail|office365|outlook|verizon|yahoo)\.com\Z/i
end
I show a user a form. It displays an email field but not imap_server field. If the value in email field does not match the specific regex, then I want to show them form again with the imap_server field present as well. If they enter a value for the imap_server field, then I no longer want to validate the regex of email field (although it must still be present).
The problem it feels like I am duplicating a validation. Both email_invalid? and the validates :email, format: ... do the same thing. How can I clean this up?
You could replace the validates :email, format: ... with
validate :email_format
def email_format
errors.add(:email, 'format invalid') if imap_server.blank? && email_invalid?
end
which is slightly more lines but lets you define the format validation in one place.
I suspect that the issue is you're trying to check the result of a validation (email_invalid?) while you're * still doing * the validations... you don't know what order the validations are going to be run (the order on the page is not something I'd trust)... so the best way to solve it is to just write all these things into a single validates method eg a quick-and-dirty way:
validates :email_or_imap_server
def email_or_imap_server
email_valid = false # for scoping
if email.present?
# note: email validation via regex is harder than you think...
# google it...
email_valid = email.match(/#{VALID_EMAIL_FORMATS}/)
if email_invalid
errors.add(:email, "email invalid format should be...")
errors.add(:imap_server, "email or imap-server must be present") unless imap_server.present?
end
else
errors.add(:imap_server, "either email or imap-server must be present") unless imap_server.present?
end
end
etc.
Note: the code above is almost certainly full of bugs and typos... don't copy/paste it almost certainly won't work and the logic doesn't exactly match that of your validations... but do something like this.

Rails: how to validate an object field's value before save?

I'm writing a Redmine plugin that should check if some fields of an Issue are filled depending on values in other fields.
I've written a plugin that implements validate callback, but I don't know how to check field values which are going to be saved.
This is what I have so far:
module IssuePatch
def self.included(receiver)
receiver.class_eval do
unloadable
validate :require_comment_when_risk
protected
def require_comment_when_risk
risk_reduction = self.custom_value_for(3)
if risk_reduction.nil? || risk_reduction.value == 0
return true
end
comment2 = self.custom_value_for(4)
if comment2.nil? || comment2.value.empty?
errors.add(:comment2, "Comment2 is empty")
end
end
end
end
end
The problem here is that self.custom_value_for() returns the value already written to the DB, but not the one that is going to be written, so validation doesn't work. How do I check for the value that was passed from the web-form?
Any help will be greatly appreciated.
The nice thing about rails is that in your controller you don't have to validate anything. You are suppose to do all of this in your model. so in your model you should be doing something like
validates :value_that_you_care_about, :numericality => { :greater_than_or_equal_to => 0 }
or
validates :buyer_name, presence: true, :length => {:minimum => 4}
or
validates :delivery_location, presence: true
If any of these fail this will stop the object from being saved and if you are using rails scaffolding will actually highlight the field that is incorrect and give them and error message explaining what is wrong. You can also write your own validations such as
def enough_red_flowers inventory
if inventory.total_red_flowers-self.red_flower_quantity < 0
self.errors.add(:base, 'There are not enough Red Flowers Currently')
return false
end
inventory.total_red_flowers = inventory.total_red_flowers-self.red_flower_quantity
inventory.save
true
end
To write your own custom message just follow the example of self.errors.add(:base, 'your message')
You can find more validations here
Better way it's create custom validator
class FileValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# some logic for validation
end
end
then in model:
validates :file, file: true

How to validate virtual attribute in Rails?

I have this class:
class Project < ActiveRecord::Base
validates :hourly_rate, :numericality => { :greater_than_or_equal_to => 0 },
:allow_blank => true
def hourly_rate=(number)
self.hourly_rate_in_cents = number.present? ? number.to_d * 100 : nil
end
end
Essentially, any new hourly_rate that gets entered by the user will get saved to the database as an integer.
This works quite well for numbers.
But any string that is being entered, is automatically converted into 0.0 and gets saved without any validation message!
Isn't there a way to validate this using any of Rails' validation methods?
Thanks for any help.
You can create your own validate method and use that to check for the type of object.
For example (and forgive me if there's an error in this code, since it's just off the top of my head):
validate :hourly_rate_is_integer
def hourly_rate_is_integer
errors.add(:hourly_rate, "must be Integer") unless self.hourly_rate.is_a?(Integer)
end
If you have a reader method for this that converts the other way, it will work as you expect. You've only shown the assignment method here.
def hourly_rate
self.hourly_rate_in_cents and self.hourly_rate_in_cents.to_f / 100
end
All the validation routines do is call the given method and apply tests to the result.
You might want to ensure that presence is specifically tested:
validates :hourly_rate, :presence => true, ...

Validate only when

I need to validate a value's presence, but only AFTER the value is populated. When a User is created, it is not required to set a shortcut_url. However, once the user decides to pick a shorcut_url, they cannot remove it, it must be unique, it must exist.
If I use validates_presence_of, since the shortcut_url is not defined, the User isn't created. If I use :allowblank => true, Users can then have "" as a shortcut_url, which doesn't follow the logic of the site.
Any help would be greatly appreciated.
Here we are always making sure the shortcut_url is unique, but we only make sure it is present if the attribute shortcut_selected is set (or if it was set and now was changed)
class Account
validates_uniqueness_of :shortcut_url
with_options :if => lambda { |o| !o.new_record? or o.shortcut_changed? } do |on_required|
on_required.validates_presence_of :shortcut_url
end
end
You'll need to test to make sure this works well with new records.
Try the :allow_nil option instead of :allow_blank. That'll prevent empty strings from validating.
Edit: Is an empty string being assigned to the shortcut_url when the user is being created, then? Maybe try:
class User < ActiveRecord::Base
validates_presence_of :shortcut_url, :allow_nil => true
def shortcut_url=(value)
super(value.presence)
end
end
try conditional validations, something like:
validates_presence_of :shortcut_url, :if => :shortcut_url_already_exists?
validates_uniqueness_of :shortcut_url, :if => :shortcut_url_already_exists?
def shortcut_url_already_exists?
#shortcut_url_already_exists ||= User.find(self.id).shortcut_url.present?
end

Rails: how to require at least one field not to be blank

I know I can require a field by adding validates_presence_of :field to the model. However, how do I require at least one field to be mandatory, while not requiring any particular field?
thanks in advance
-- Deb
You can use:
validate :any_present?
def any_present?
if %w(field1 field2 field3).all?{|attr| self[attr].blank?}
errors.add :base, "Error message"
end
end
EDIT: updated from original answer for Rails 3+ as per comment.
But you have to provide field names manually.
You could get all content columns of a model with Model.content_columns.map(&:name), but it will include created_at and updated_at columns too, and that is probably not what you want.
Here's a reusable version:
class AnyPresenceValidator < ActiveModel::Validator
def validate(record)
unless options[:fields].any?{|attr| record[attr].present?}
record.errors.add(:base, :blank)
end
end
end
You can use it in your model with:
validates_with AnyPresenceValidator, fields: %w(field1 field2 field3)
Add a validate method to your model:
def validate
if field1.blank? and field2.blank? and field3.blank? # ...
errors.add_to_base("You must fill in at least one field")
end
end
I believe something like the following may work
class MyModel < ActiveRecord::Base
validate do |my_model|
my_model.my_validation
end
def my_validation
errors.add_to_base("Your error message") if self.blank?
#or self.attributes.blank? - not sure
end
end
Going further with #Votya's correct answer, here is a way to retrieve all columns besides created_at and updated_at (and optionally, any others you want to throw out):
# Get all column names as an array and reject the ones we don't want
Model.content_columns.map(&:name).reject {|i| i =~ /(created|updated)_at/}
For example:
1.9.3p327 :012 > Client.content_columns.map(&:name).reject {|i| i =~ /(created|updated)_at/}
=> ["primary_email", "name"]
If you only have two fields, this will get the job done:
validates :first_name, presence: true, if: :nick_name.blank?
validates :nick_name, presence: true, if: :first_name.blank?
This does not scale up well with more fields, but when you only have two, this is perhaps clearer than a custom validation method.
n.b. If they omit both, the error message will appear more restrictive than you intend. (e.g. First Name is required. Nick Name is required.) ¯\(ツ)/¯

Resources