I am trying to add a validation to exclude a list of reserved usernames:
validates :rss_mailbox, exclusion: { in: %w(admin support info), message: " %{value} is not available" }
But the list is extremely long. Is there a way to call a method for the in: property instead of hardcoding a huge list?
This is what the docs for validates_exclusion_of says about :in:
:in - An enumerable object of items that the value shouldn't be part of. This can be supplied as a proc, lambda or symbol which returns an enumerable. If the enumerable is a range the test is performed with
This makes it quit flexible, I think you can also use a class constant:
RESERVED = %w(admin support info etc)
validates :rss_mailbox, exclusion: { in: RESERVED, message: " %{value} is not available" }
Not tested, but I think you can use a method by referring to it with a symbol:
validates :rss_mailbox, exclusion: { in: :reserved_names, message: " %{value} is not available" }
def reserved_names
# Lookup and return all reserved names, can be array or db-call
end
You can polish this implementation using a separate custom validator class as show in this guide.
class MyModel < ActiveRecord::Base
validates_with CustomValidator
end
class CustomValidator < ActiveModel::Validator
def validate(record)
if invalid_condition_implementation(record)
record.errors[:rss_mailbox] << "My custom error message"
end
end
end
Related
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.
I have the (simplified) model structure below:
class Customer < ApplicationRecord
validates :full_name, format: { without: /[^a-zA-Z .,']+/, message: "cannot contain " + /[^a-zA-Z .,']+/.match(self.full_name).to_s}
end
I want to validate the user-provided full_name with a regular-expression and if the validation fails, I want to show which part of the full_name fails the regular-expression validation.
However, this returns "undefined method full_name" and I tried a bunch of other things for self.full_name but can't seem to figure out how to pass that data there.
How can I do this? Thanks for any feedback and answer, in advance.
According to the docs, Rails allows to pass a Proc message. Thus, in your case it's possible to customise the message:
class Customer < ApplicationRecord
validates :full_name,
format: {
without: /[^a-zA-Z .,']+/,
message: ->(object, data) do
"cannot contain " + /[^a-zA-Z .,']+/.match(object.full_name).to_s
end
}
end
I am on Rails 4.2 I have written a custom Validator which will check if a value being entered exists in another table. I have been reviewing some other posts and it seems there is either a context specific or rails version preferred way to reuse the value being validated. In the rails docs i see examples such as:
validates :subdomain, exclusion: { in: %w(www us ca jp),
message: "%{value} is reserved." }
however, if I try to use %{value} in my custom message override it does not interpolate, but just prints "%{value}". I have seen various ways of calling "value". I also could not get %{value} to work in my Validator definition, but could get #{value} to work (New to ruby, if#{value} getting it from validate_each?).
I have also been struggling with various formats of the validation statement and putting in the custom message. Some things which look repeatable from the docs are not. If the way I am declaring my custom message is causing the error, please let me know how to correct?
class ExistingGroupValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless Group.where(:code => value).any?
record.errors[attribute] << (options[:message] || "#{value} is not a valid
group code")
end
end
end
class Example < ActiveRecord::Base
validates :group_code, presence: true
validates :group_code, :existing_group => {:message => "The code you have enterd ( **what goes here?** ) is not a valid code, please check with your teacher or group
leader for the correct code." }
end
Rails automatically puts the value at the beginning of the phrase, so you can just do this:
class ExistingGroupValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless Group.where(code: value).any?
record.errors[attribute] << (options[:message] || 'is not a valid group code')
end
end
end
class Example < ActiveRecord::Base
validates :group_code, presence: true
validates :group_code, existing_group: {message: 'is not a valid code, please check with your teacher or group leader for the correct code.' }
end
Separately, note that it is always "#{1+1}" to interpolate not %.
Rails is using Internationalization style string interpolation to add the value to the message. You can use the I18n.interpolate method to accomplish this. Something like this should do the trick:
class ExistingGroupValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless Group.where(:code => value).any?
record.errors[attribute] << (I18n.interpolate(options[:message], {value: value}) || "is not a valid group code")
end
end
end
class Example < ActiveRecord::Base
validates :group_code, presence: true
validates :group_code, :existing_group => {:message => "The code you have entered, "%{value}", is not a valid code, please check with your teacher or group leader for the correct code." }
end
On any given model:
class MyTestModel
include Mongoid::Document
include Mongoid::Timestamps
field :my_field
validates : my_field, inclusion: { in: proc { |my_instance| [ my_instance.some_stuf ] } }
def some_stuff
'some generated value'
end
end
So this works and validates properly, if my_field does not contain 'some generated value'
But the error validation is not really explicit and I have the feeling that I am using inclusion for no good reason here. What I would like to write for the validation is:
validates : my_field, acceptance: { accept: proc { |my_instance| my_instance.some_stuf } }
But this will never pass validation ? and I haven't found a way to see what the validation is expecting as a value to check what is wrong. Any idea why one is working and not the other ? Is that a mongoid bug ?
why not write a custom validator method. Then it's clear what you're doing - rather than having multiple layers of obscuring procs.
validate :my_field_in_some_stuff
def my_field_in_some_stuff
errors.add(:my_field, "my field must... but it wasn't") unless ['some generated value'].include?(self.my_field)
end
or if the some_stuff method is used elsewhere
validate :my_field_in_some_stuff
def my_field_in_some_stuff
errors.add(:my_field, "my field must... but it wasn't") unless some_stuff.include?(self.my_field)
end
def some_stuff
'some generated value'
end
I've done some searching online, and understand how ActiveRecord validations can be built with "if" statements and separately defined methods. However, I'm wondering if it's possibly to simply combine two validations together, and if either is true, the whole thing passes.
What I'm trying to do is have a user input a contact field that can either be an email or a phone number, but not both. Obviously the code I have below isn't working, but I'm wondering if something similar to it could work?
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
VALID_PHONE = /\d{10}/
validates :contact, presence: true, format: { with: VALID_EMAIL_REGEX } || presence: true, length: { is: 10 }, format: { with: VALID_PHONE }
I was going to recommend using a standard :if option for the validates callback, but having done a little more research, I found something you may benefit from:
Custom Validations
According to the Rails guide:
#app/models/concerns/my_validator.rb
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.starts_with? 'X'
record.errors[:name] << 'Need a name starting with X please!'
end
end
end
#app/models/person.rb
class Person
include ActiveModel::Validations
validates_with MyValidator
end
This allows you to create your own validation method, allowing you to append error messages directly into the instance variable (which is then shown on the form). For your question, I'd to do this:
#app/models/concerns/phone_email_validator.rb
class PhoneEmailValidator < ActiveModel::Validator
def validate(record)
contact = record.contact
phone = /\d{10}/
email = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
if contact.validate(phone) || contact.validate(email)
if contact.validate(phone) && contact.length < 10
error = "Length too short for phone number!"
end
else
error = 'Needs To Be Phone Or Email '
end
record.errors[:contact] << error
end
end
#app/models/person.rb
class Person
include ActiveModel::Validations
validates_with MyValidator
end
The function is too verbose, and might not work with the validation regex; but it's an idea either way!