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
Related
I have a class Person that has first_name, middle_name, last_name.
I have a customed Each validator for these 3 attributes like so
validates :first_name, :middle_name, :last_name, nameValidator: true
In this validator I want to check if any one of these 3 attributes changed and given a few more conditions I'll validate name. For that I'm trying attribute_changed? but it doesn't work.
I've checked different methods from ActiveModel::Dirty and Activerecod::Dirty but nothing seems to work to check changes in each attribute. What am I missing?
module Person
class nameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return unless can_normalize?(record)
#Normalization code
end
def can_normalize?(record)
anything_new = record.new_record? || attribute_changed?
end
end
end
If you need to check that some attribute was changed, you need to call attribute_changed? method on the record and pass this attribute like this
return unless record.new_record? || record.attribute_changed?(attribute)
Or may be use metaprogramming method like this
return unless record.new_record? || record.public_send("#{attribute}_changed?")
Some notes:
In Ruby we usually use PascalCase for class names and snake_case for hash keys
Validations are used for data validation, not for some normalization. It is usually used to add validation errors
Let's say I have lots of attributes that can only have a specific set of string values.
Typically we'd see the following.
class User < ApplicationRecord
validates :foo, inclusion: { in: ['some', 'array'] }
validates :bar, inclusion: { in: ['another', 'array'] }
validates :moo, inclusion: { in: ['one_more', 'array'] }
end
I have lots of these types of validations in my model and I want to DRY them up. So I tried the below but I get a error undefined method 'validates' for #User:0x00007fdc10370408.
class User < ApplicationRecord
VALIDATION_ENUMS = {
foo: %w[foo1 foo2],
bar: %w[bar1 bar2]
}.freeze
validate :validate_enums
def validate_enums
VALIDATION_ENUMS.each_key do |attribute|
validates attribute, inclusion: { in: VALIDATION_ENUMS[attribute] }
end
end
end
How do I get access to the ActiveModel::Validations helper methods from within my function?
Or is there a better way to do this?
Remember that validates is a class method, only executed once when the class is loaded to establish what will be validated. validate is calling an instance method.
A better way might be to execute the DRY code immediately when loading the class.
class User < ApplicationRecord
validate_enums = {
foo: %w[foo1 foo2],
bar: %w[bar1 bar2]
}.freeze
validate_enums.each do |key, array|
validates key, inclusion: { in: array }
end
Note that as you don't reference validate_enums ever again, you don't need to make it a class constant, which is why I didn't.
But you don't really save any lines and add complexity, so I'd stick with the explicit validates, myself.
This approach won't fly. The validation methods are class methods that modify the class itself while you are writing an instance method that get called on an instance of the class when #valid? is called.
If you want to dynamically add existing validations to the class you need to create a class method:
class User < ApplicationRecord
def self.add_inclusion_validations(hash)
# don't use each_key if you're iterating over both keys and values
hash.each do |key, values|
validates_presence_of key, in: values
end
end
add_inclusion_validations(
foo: %w[foo1 foo2],
bar: %w[bar1 bar2]
)
end
Of course you could also just skip the method completely:
class User < ApplicationRecord
{
foo: %w[foo1 foo2],
bar: %w[bar1 bar2]
}.each do |key, values|
validates_presence_of key, in: values
end
end
If what you instead want is to write a validation method that uses the existing functionality of other validations you would create a ActiveRecord::Validator or ActiveRecord::EachValidator subclass and use the existing validations there. But you really need to start by reading the guides and API docs so that you have a base understanding of how that works.
Currently, I've created custom email validator for rails model.
models/concerns/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || "provided format for email is not valid")
end
end
end
So now I can use it like:
validates :email, email: true, uniqueness: true
I'm just curious how it's auto included into model? I mean we are not including it explicitly, by using include method.
Everything under the app/ folder is auto-loaded. So, since you've placed it in models/concerns and models is under app/, it is auto-loaded. Once it is auto-loaded, it will be used as the name is inferred from the option name you pass to validates :email. You can place it in app/foo/bar/baz/email_validator.rb and it will be auto-loaded as well. Move this validator to lib/email_validator.rb and this will not work (as long as you have not required the whole lib/ folder).
Additionally validator classes may be in another namespace and still used within any class.
validates :email, :'custom_validators/email' => true
Module CustomValidators
class EmailValidator < ActiveModel::EachValidator
# Code
end
end
Please refer this link for more info
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 }