I have a field in a form, and I need it to be registered only if its format equals one of the two regex I am using.
I have tried to use methods, the helper structure validates_format_of with Proc.new, but may not be using the correct syntax or the correct data type
private def format_field
errors.add(
:field, "The field does not have a valid format."
)unless[
/[0-9]{3}\.?[0-9]{3}\.?[0-9]{3}\-?[0-9]{2}/,
/[0-9]{2}\.?[0-9]{3}\.?[0-9]{3}\/?[0-9]{4}\-?[0-9]{2}/
].include?(field.format)
end
A simple approach to this is the following:
FORMAT_REGEXES = [
/[0-9]{3}\.?[0-9]{3}\.?[0-9]{3}\-?[0-9]{2}/,
/[0-9]{2}\.?[0-9]{3}\.?[0-9]{3}\/?[0-9]{4}\-?[0-9]{2}/
].freeze
def validate_format
return unless FORMAT_REGEXES.find { |regex| field.format =~ regex }
errors.add(:field, :invalid, message: 'The field does not have a valid format')
end
Related
Hi I have an array column in my model:
t.text :sphare, array: true, default: []
And I want to validate that it includes only the elements from the list ("Good", "Bad", "Neutral")
My first try was:
validates_inclusion_of :sphare, in: [ ["Good"], ["Bad"], ["Neutral"] ]
But when I wanted to create objects with more then one value in sphare ex(["Good", "Bad"] the validator cut it to just ["Good"].
My question is:
How to write a validation that will check only the values of the passed array, without comparing it to fix examples?
Edit added part of my FactoryGirl and test that failds:
Part of my FactoryGirl:
sphare ["Good", "Bad"]
and my rspec test:
it "is not valid with wrong sphare" do
expect(build(:skill, sphare: ["Alibaba"])).to_not be_valid
end
it "is valid with proper sphare" do
proper_sphare = ["Good", "Bad", "Neutral"]
expect(build(:skill, sphare: [proper_sphare.sample])).to be_valid
end
Do it this way:
validates :sphare, inclusion: { in: ["Good", "Bad", "Neutral"] }
or, you can be fancy by using the short form of creating the array of strings: %w(Good Bad Neutral):
validates :sphare, inclusion: { in: %w(Good Bad Neutral) }
See the Rails Documentation for more usage and example of inclusion.
Update
As the Rails built-in validator does not fit your requirement, you can add a custom validator in your model like following:
validate :correct_sphare_types
private
def correct_sphare_types
if self.sphare.blank?
errors.add(:sphare, "sphare is blank/invalid")
elsif self.sphare.detect { |s| !(%w(Good Bad Neutral).include? s) }
errors.add(:sphare, "sphare is invalid")
end
end
You can implement your own ArrayInclusionValidator:
# app/validators/array_inclusion_validator.rb
class ArrayInclusionValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# your code here
record.errors.add(attribute, "#{attribute_name} is not included in the list")
end
end
In the model it looks like this:
# app/models/model.rb
class YourModel < ApplicationRecord
ALLOWED_TYPES = %w[one two three]
validates :type_of_anything, array_inclusion: { in: ALLOWED_TYPES }
end
Examples can be found here:
https://github.com/sciencehistory/kithe/blob/master/app/validators/array_inclusion_validator.rb
https://gist.github.com/bbugh/fadf8c65b7f4d3eaa55e64acfc563ab2
I am working to solve my problem to another question and I think I am close to a solution, but I have run into another problem. I am trying to copy code I found in the rails source and modify it to fit my needs:
I have created the following custom validator:
class ExistingTwoValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless Group.where(:code => value).any?
record.errors.add(attribute, :invalid, options.merge(:value => value))
record.errors.add(attribute, "custom error", options.merge(:value => value))
record.errors.add(attribute, :madeup, options.merge(:value => value))
end
end
end
If I provide a custom error message in my model:
validates :group_code, existing_two: { message: "My custom message %{value} is not okay."}
For invalid input (e.g., "www") I get the following errors:
Group code My custom message www is not okay.
Group code custom error
Group code My custom message www is not okay.
If I do not provide a custom error message:
validates :group_code, existing_two: true
For invalid input (e.g., "www") I get the following errors:
Group code is invalid
Group code custom error
Group code translation missing: en.activerecord.errors.models.XX.attributes.group_code.madeup
The first record.errors.add(attribute, :invalid, ... will allow me to override the default message in :invalid, but if I provide literal string as the second argument (e.g. record.errors.add(attribute, "custom error", it will not let me override the message.
I want to understand the logic that says if it's a symbol it can be replaced but if its a string ignore the message passed in the options. Also, is there any way to provide the custom message without using a symbol or is there a way to define the symbol without adding it to a language file somewhere?
Reviewing the following snippets from active model errors.
# File activemodel/lib/active_model/errors.rb, Line 299
def add(attribute, message = :invalid, options = {})
message = normalize_message(attribute, message, options)
if exception = options[:strict]
exception = ActiveModel::StrictValidationFailed if exception == true
raise exception, full_message(attribute, message)
end
self[attribute] << message
end
#File activemodel/lib/active_model/errors.rb, line 438
def normalize_message(attribute, message, options)
case message
when Symbol
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
when Proc
message.call
else
message
end
end
# File activemodel/lib/active_model/errors.rb, line 412
def generate_message(attribute, type = :invalid, options = {})
type = options.delete(:message) if options[:message].is_a?(Symbol)
if #base.class.respond_to?(:i18n_scope)
defaults = #base.class.lookup_ancestors.map do |klass|
[ :"#{#base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
:"#{#base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
end
else
defaults = []
end
defaults << options.delete(:message)
defaults << :"#{#base.class.i18n_scope}.errors.messages.#{type}" if #base.class.respond_to?(:i18n_scope)
defaults << :"errors.attributes.#{attribute}.#{type}"
defaults << :"errors.messages.#{type}"
defaults.compact!
defaults.flatten!
key = defaults.shift
value = (attribute != :base ? #base.send(:read_attribute_for_validation, attribute) : nil)
options = {
default: defaults,
model: #base.model_name.human,
attribute: #base.class.human_attribute_name(attribute),
value: value
}.merge!(options)
I18n.translate(key, options)
end
You're calling errors.add, which calls the private method normalize_message, the normalise_message method takes the message and if it's a Symbol calls generate_message. In the generate_message method it ends up falling back to the default message because it is unable to find the translation in activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE or activemodel.errors.models.MODEL.MESSAGE
I strongly suggest you read the source code link I've provided as it is well documented and is very in depth.
Active Model Errors
This is how can you can do it simply
class Example < ActiveRecord::Base
validates :group_code, presence: true
validate do |example|
unless Group.where(:code => example.group_code).any?
example.errors.add(:base, "The code you have entered #{example.group_code} is not a valid code, please check with your teacher or group")
end
end
end
Of course, you can also extract the the above logic and put it into custom validator.
Note that , i am passing :base not attribute.
Basically, the interpolation is done with i18n gem.
You can see here
I want to write a validation for email filed .But some different way.
I will allow user to enter email in two format like "name 'email#example.com' " and simple 'email#example.com' .so basically i want to write a validation which will check that is valid email format in present in the value or not.
Just need a custom validation for check the valid email format is present in the input email value.
My model look like :
class Contact < ActiveRecord::Base
validates :email ,presence: true
validate :email_format
def email_format
??? what to write here ???
end
end
How can i write a validation for this.
You need to slightly modify the regular expression in your case.
validates :email, format: { with: /(\A([a-z]*\s*)*\<*([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\>*\Z)/i }
This will match the following formats.
soundar.rathinsamy#gmail.com
Soundar<soundar.rathinsamy#gmail.com>
Soundar <soundar.rathinsamy#gmail.com>
soundar<soundar.rathinsamy#gmail.com>
Soundar Rathinsamy<soundar.rathinsamy#gmail.com>
Soundar Rathinsamy <soundar.rathinsamy#gmail.com>
soundar rathinsamy <soundar.rathinsamy#gmail.com>
If you need some change continue editing this regular expression at rubular.com
validates :email, format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }
Taken from apidock.com: http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates
This code extracts from the email a string between "<" and ">" but if it doesn't find a match then it takes the last word in the email... in either case it then takes that string and tests for a valid email.
So it should work for "John john#example.com" and "John<john#example.com>"
def email_format
test_string = $1 if email =~ /\<([^\>]+)\>/
test_string = email.split(' ').last unless test_string
return if test_string =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
errors.add(:email, "not a valid email")
end
With this model:
validates_presence_of :email, :message => "We need your email address"
as a rather contrived example. The error comes out as:
Email We need your email address
How can I provide the format myself?
I looked at the source code of ActiveModel::Errors#full_messages and it does this:
def full_messages
full_messages = []
each do |attribute, messages|
messages = Array.wrap(messages)
next if messages.empty?
if attribute == :base
messages.each {|m| full_messages << m }
else
attr_name = attribute.to_s.gsub('.', '_').humanize
attr_name = #base.class.human_attribute_name(attribute, :default => attr_name)
options = { :default => "%{attribute} %{message}", :attribute => attr_name }
messages.each do |m|
full_messages << I18n.t(:"errors.format", options.merge(:message => m))
end
end
end
full_messages
end
Notice the :default format string in the options? So I tried:
validates_presence_of :email, :message => "We need your email address", :default => "something"
But then the error message actually appears as:
Email something
So then I tried including the interpolation string %{message}, thus overriding the %{attribute} %{message} version Rails uses by default. This causes an Exception:
I18n::MissingInterpolationArgument in SubscriptionsController#create
missing interpolation argument in "%{message}" ({:model=>"Subscription", :attribute=>"Email", :value=>""} given
Yet if I use the interpolation string %{attribute}, it doesn't error, it just spits out the humanized attribute name twice.
Anyone got any experience with this? I could always have the attribute name first, but quite often we need some other string (marketing guys always make things more complicated!).
Errors on :base are not specific to any attribute, so the humanized attribute name is not appended to the message. This allows us to add error messages about email, but not attach them to the email attribute, and get the intended result:
class User < ActiveRecord::Base
validate :email_with_custom_message
...
private
def email_with_custom_message
errors.add(:base, "We need your email address") if
email.blank?
end
end
Using internationalization for this is probably your best bet. Take a look at
http://guides.rubyonrails.org/i18n.html#translations-for-active-record-models
Particularly this section:
5.1.2 Error Message Interpolation
The translated model name, translated
attribute name, and value are always
available for interpolation.
So, for example, instead of the
default error message "can not be
blank" you could use the attribute
name like this : "Please fill in your
%{attribute}"
I'm answering my own questions - just putting this up here for google-fu in case it helps someone else. This code allows you to validate the presence of one field in a list. See comments in code for usage. Just paste this into lib/custom_validations.rb and add require 'custom_validations' to your environment.rb
#good post on how to do stuff like this http://www.marklunds.com/articles/one/312
module ActiveRecord
module Validations
module ClassMethods
# Use to check for this, that or those was entered... example:
# :validates_presence_of_at_least_one_field :last_name, :company_name - would require either last_name or company_name to be filled in
# also works with arrays
# :validates_presence_of_at_least_one_field :email, [:name, :address, :city, :state] - would require email or a mailing type address
def validates_presence_of_at_least_one_field(*attr_names)
msg = attr_names.collect {|a| a.is_a?(Array) ? " ( #{a.join(", ")} ) " : a.to_s}.join(", ") +
"can't all be blank. At least one field (set) must be filled in."
configuration = {
:on => :save,
:message => msg }
configuration.update(attr_names.extract_options!)
send(validation_method(configuration[:on]), configuration) do |record|
found = false
attr_names.each do |a|
a = [a] unless a.is_a?(Array)
found = true
a.each do |attr|
value = record.respond_to?(attr.to_s) ? record.send(attr.to_s) : record[attr.to_s]
found = !value.blank?
end
break if found
end
record.errors.add_to_base(configuration[:message]) unless found
end
end
end
end
end
This works for me in Rails 3, although I'm only validating whether one or the other field is present:
validates :last_name, :presence => {unless => Proc.new { |a| a.company_name.present? }, :message => "You must enter a last name, company name, or both"}
That will only validate presence of last_name if company name is blank. You only need the one because both will be blank in the error condition, so to have a validator on company_name as well is redundant. The only annoying thing is that it spits out the column name before the message, and I used the answer from this question regarding Humanized Attributes to get around it (just setting the last_name humanized attribute to ""