I have the following class:
class PatientPaymentSpreadsheetRow < ApplicationSpreadsheetRow
include ActiveModel::Validations
validate :date_format
def date_format
unless value('Transaction').split('/').last.length == 4
errors.add('Transaction', 'date format invalid')
end
end
end
This particular validation happens to act on value('Transaction'). I'd like my validator to be sufficiently generic that I can pass in any value (e.g. value('Date of birth')) and have it act on that value.
How can I accomplish this?
This is an old question, but I think this is more in the ballpark of what you were asking.
ActiveModel::EachValidator allows you to pass in options as a hash, which will be available in the class instance with options.
You can set up the validation in your model like
class SomeModel < ApplicationRecord
validates :transaction, date_format: { value: 'Transaction' }
end
And the custom validator would look like:
class DateFormatValidator < ActiveModel::EachValidator
def validate_each(model, attribute, value)
return if options[:value].split('/').last.length == 4
# ^^^^^^^ hash you pass is here
model.errors.add(attribute, 'date format invalid')
end
end
You can write custom 'each validator', like described in Guides:
class DateFormatValidator < ActiveModel::EachValidator
def validate_each(model, attribute, value)
return if value.split('/').last.length == 4
model.errors.add(attribute, 'date format invalid')
end
end
And use it like this:
class MyCustomModel
include ActiveModel::Validations
attr_accessor :my_date_attr
validates :my_date_attr, date_format: true
end
But presumably you want the validations to run unconditionally, so...
validate :date_format
DATES_TO_VALIDATE = ['Transaction', 'Date of birth', 'Other date']
def date_format
DATES_TO_VALIDATE.each do |key|
unless value(key).split('/').last.length == 4
errors.add(key, 'date format invalid')
end
end
end
This can be extracted to an each_validator as per Marek Lipka's answer, with a custom constant DATES_TO_VALIDATE for each model and access it in the validator as model.class::DATES_TO_VALIDATE
Related
I have a lot of models that contains a field called source_name. I need to implement a validator in each of them that will check if the source_name lives up to curtain conditions.
Now I also have another class called SourceNameManager. In this model I have a method called valid_source_name? which takes a source_name_name and returns true or false.
What is the simplest way to make a validation that just validates source_name by calling the external service class SourceNameManager.valid_source_name?('some_name').
I was thinking about something like:
validates :source_name, ->(record) { SourceNameManager.valid_source_name?(record.source_name) }
but I don't think that works
Create a file app/models/source_name_validator.rb:
class SourceNameValidator < ActiveModel::EachValidator
validate_each(record, attribute, value)
unless SourceNameManager.valid_source_name?(value)
record.errors[attribute] << 'is not valid'
end
end
end
Then in each model where you want to validate the source name, add:
validates :source_name, source_name: true
I have a custom validation method:
def my_custom_validation
errors.add(specific_field, "error message") if specific_field.delete_if { |i| i.blank? }.blank?
end
The goal is to disallow parameters which contains [""] pass through validation, but I need to call this method like:
validate :my_custom_validation #and somehow pass here my field names
For example:
validate :my_custom_validation(:industry)
Since you need to validate multiple attributes this way I would recommend a custom validator like so:
class EmptyArrayValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || "cannot be emtpy") if value.delete_if(&:blank?).empty?
end
end
Then validate as
validates :industry, empty_array: true
validates :your_other_attribute, empty_array: true
Or if you don't want to specifically create a class because it is only needed for 1 model you could include this in the model itself
validates_each :industry, :your_other_attribute, :and_one_more do |record, attr, value|
record.errors.add(attr, "cannot be emtpy") if value.delete_if(&:blank?).empty?
end
If you'd like to keep the method based validation you can use a Ruby lambda like below:
validate -> { my_custom_validation(some_model_field) }
See similar question
I need to validate email saving in email_list. For validation I have EmailValidator. But I cannot figure out how to use it in pair of validates_each. Are there any other ways to make validations?
class A < ActiveRecord::Base
serialize :email_list
validates_each :email_list do |r, a, v|
# what should it be here?
# EmailValidator.new(options).validate_each r, a, v
end
end
validates_each is for validating multiple attributes. In your case you have one attribute, and you need to validate it in a custom way.
Do it like this:
class A < ActiveRecord::Base
validate :all_emails_are_valid
...
private
def all_emails_are_valid
unless self.email_list.nil?
self.email_list.each do |email|
if # email is valid -- however you want to do that
errors.add(:email_list, "#{email} is not valid")
end
end
end
end
end
Note that you could also make a custom validator for this or put the validation in a proc on the validate call. See here.
Here's an example with a custom validator.
class A < ActiveRecord::Base
class ArrayOfEmailsValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.nil?
value.each do |email|
if # email is valid -- however you want to do that
record.errors.add(attribute, "#{email} is not valid")
end
end
end
end
validates :email_list, :array_of_emails => true
...
end
Of course you can put the ArrayOfEmailsValidator class in, i.e., lib/array_of_emails_validator.rb and load it where you need it. This way you can share the validator across models or even projects.
I ended up with this:
https://gist.github.com/amenzhinsky/c961f889a78f4557ae0b
You can write your own EmailValidator according to rails guide and use the ArrayValidator like:
validates :emails, array: { email: true }
I am trying to validate the format of exchange_rate in my Invoice class:
class Invoice < ActiveRecord::Base
attr_accessible :currency, :exchange_rate
validates :exchange_rate, :format => { :with => exchange_rate_format }
private
def exchange_rate_format
if currency != user.preference.base_currency
DECIMAL_REGEX
else
ANOTHER_REGEX
end
end
end
The problem is: It doesn't work at all. I guess I need to use a Proc here? I never really figured out how to use it though. Maybe somebody can help.
Thanks a lot.
Yes, you need to use a Proc or lambda so that the validation will be called at runtime.
validates :exchange_rate, format: { with: ->(invoice) { invoice.exchange_rate_format } }
# Note, I used Ruby 1.9 hash and lambda syntax here.
To do this you'll need to move the exchange_rate_format out of the private methods list since we're defining an explicit receiver (invoice). You can make it protected instead, if you'd like. Or you can put the conditional into the lambda.
One way to do it is with a custom validator:
class Invoice < ActiveRecord::Base
class ExchangeRateFormatValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if !value =~ record.exchange_rate_format
record.errors[attribute] << "your currency is weak sauce"
end
end
end
validates :exchange_rate, exchange_rate_format: true
# make public
def exchange_rate_format
if currency != user.preference.base_currency
DECIMAL_REGEX
else
ANOTHER_REGEX
end
end
end
I have a model with some attributes and a virtual attribute.
This virtual attribute is used to make a checkbox in the creation form.
class Thing < ActiveRecord::Base
attr_accessor :foo
attr_accessible :foo
end
Since the field is a checkbox in the form, the foo attribute will receive '0' or '1' as value. I would like it to be a boolean because of the following code:
class Thing < ActiveRecord::Base
attr_accessor :foo
attr_accessible :foo
before_validation :set_default_bar
private
def set_default_bar
self.bar = 'Hello' if foo
end
end
The problem here is that the condition will be true even when foo is '0'. I would like to use the ActiveRecord type casting mechanism but the only I found to do it is the following:
class Thing < ActiveRecord::Base
attr_reader :foo
attr_accessible :foo
before_validation :set_default_bar
def foo=(value)
#foo = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
private
def set_default_bar
self.bar = 'Hello' if foo
end
end
But I feel dirty doing it that way. Is there a better alternative without re-writing the conversion method ?
Thanks
Your solution from the original post looks like the best solution to me.
class Thing < ActiveRecord::Base
attr_reader :foo
def foo=(value)
#foo = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
end
If you wanted to clean things up a bit, you could always create a helper method that defines your foo= writer method for you using value_to_boolean.
I would probably create a module with a method called bool_attr_accessor so you could simplify your model to look like this:
class Thing < ActiveRecord::Base
bool_attr_accessor :foo
end
It seems like ActiveModel ought provide something like this for us, so that virtual attributes act more like "real" (ActiveRecord-persisted) attributes. This type cast is essential whenever you have a boolean virtual attribute that gets submitted from a form.
Maybe we should submit a patch to Rails...
In Rails 5 you can use attribute method. This method defines an attribute with a type on this model. It will override the type of existing attributes if needed.
class Thing < ActiveRecord::Base
attribute :foo, :boolean
end
Caution: there is incorrect behaviour of this attribute feature in rails 5.0.0 on models loaded from the db. Therefore use rails 5.0.1 or higher.
Look at validates_acceptance_of code (click Show source).
They implemented it with comparing to "0".
I'm using it in registrations forms in this way:
class User < ActiveRecord::Base
validates_acceptance_of :terms_of_service
attr_accessible :terms_of_service
end
If you really want cast from string etc you can use this:
def foo=(value)
self.foo=(value == true || value==1 || value =~ (/(true|t|yes|y|1)$/i)) ? true:false
end
Or add typecast method for String class and use it in model:
class String
def to_bool
return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
end
end
Why you don't do this :
def foo=(value)
#foo = value
#bar = 'Hello' if value == "1"
end