rails model validattion ( not containing a substring) - ruby-on-rails

Is there a simple way to validate that a fields not containing a sub string. I would like a email field not containing 'yahoo.com'. If it contains 'yahoo.com' I would lile to return an error messaage.

no need to add a custom validator just use Rails validates_format_for
validates_format_of :email, without: /yahoo\.com/i, message: "your custom message"
see documentation
http://apidock.com/rails/ActiveModel/Validations/HelperMethods/validates_format_of

As the comment suggests - build a custom validator
require 'active_model'
class NotYahooEmailValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
if value.include? "yahoo.com"
object.errors[attribute] << (options[:message] || "is a yahoo.com email address")
end
end
end
You would use this as follows:
class User < ActiveRecord::Base
validates :email, :presence => true, :not_yahoo_email => true
end

Related

How custom validators is auto included into model

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

Use value in override error message in Rails 4 for custom Validator

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

Pass field name as parameter to custom validation method Rails 4

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

Rails 4 - concerns for generic validation

I just ran across Rails concerns and I want to use them for the validations of my models. But I want the validations to be generic, so that the validation is used only if the Class in which I include my concern has the attribute. I thought it would be easy, but I have tried many ways like using column_names, constantize, send and many other but nothing works. What is the right way to do it? The code:
module CommonValidator
extend ActiveSupport::Concern
included do
validates :email, presence: { message: I18n.t(:"validations.commons.email_missing") },
format: { with: /\A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i,
message: I18n.t(:"validations.commons.email_wrong_format"),
allow_blank: true } if self.column_names.include? :email
end
end
class Restaurant < ActiveRecord::Base
include CommonValidator
.
.
.
end
Restaurant of course has an email attribute. Is it possible to check the existence of an attribute in the class in which in include my concern? I want include my CommonValidations into many models which will not have email attribute. I'm using rails 4.
You can use respond_to? on the current instance as follows:
validates :email, presence: { message: I18n.t(:"validations.commons.email_missing") },
format: { with: /\A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i,
message: I18n.t(:"validations.commons.email_wrong_format"),
allow_blank: true },
if: lambda { |o| o.respond_to?(:email) }
Another option as suggested by #coreyward is to define a class extending EachValidator. For example, for email validation:
# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i
record.errors[attribute] << (options[:message] || I18n.t(:"validations.commons.email_wrong_format"))
end
end
end
Then you could update the validation call as:
validates :email,
presence: { message: I18n.t(:"validations.commons.email_missing") },
email: true,
allow_blank: true
I was looking for something similar, but with custom validations.
I ended up with something that I think could be shared, including generic tests.
First, set up the concern app/models/concern/my_concern.rb.
Please note that we don't define the validate_my_field into a ClassMethods module.
module MyConcern
extend ActiveSupport::Concern
included do
validate :my_field, :validate_my_field
end
private
def validate_my_field
...
end
end
Include concern into your model app/models/my_model.rb
class MyModel < ActiveRecord::Base
include MyConcern
end
Load concerns shared examples in spec/support/rails_helper:
…
Dir[Rails.root.join('spec/concerns/**/*.rb')].each { |f| require f }
…
Create concern shared examples spec/concerns/models/my_field_concern_spec.rb:
RSpec.shared_examples_for 'my_field_concern' do
let(:model) { described_class } # the class that includes the concern
it 'has a valid my_field' do
instance = create(model.to_s.underscore.to_sym, my_field: …)
expect(instance).not_to be_valid
…
end
end
Then finally call shared examples into your model spec spec/models/my_model_spec.rb:
require 'rails_helper'
RSpec.describe MyModel do
include_examples 'my_field_concern'
it_behaves_like 'my_field_concern'
end
I hope this could help.

Devise validate email property with exclusion of words and #myapp.com

Trying to validate the email field in a rails devise app with a word exclusion.
validates :email, :exclusion => {:in => ["admin", "root",,
:message => "is reserved"}
Wich works great on the :username field but not on the email.
I suspect that Devise is "taken over" the validation of the email field and I need to super/ overrule the registrations controller of Devise.
How could I:
Prevent emails with words based on an :exclusion
Prevent emails with #myapp.com so users don't use the domain the app runs on.
You can always write custom validations to achieve what you want. Example from the guide:
The easiest way to add custom validators for validating individual
attributes is with the convenient ActiveModel::EachValidator. In this
case, the custom validator class must implement a validate_each method
which takes three arguments: record, attribute and value which
correspond to the instance, the attribute to be validated and the
value of the attribute in the passed instance.
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] || "is not an email")
end
end
end
class Person < ActiveRecord::Base
validates :email, :presence => true, :email => true
end
After checking if it is an e-mail, I would split the two validations. One for exclusion, and other for domain. You can retrieve the current domain when using passenger.
validate :email_username_is_not_on_blacklist, :email_domain_is_allowed
EXCLUDED_USERNAMES = "admin", "root"
EXCLUDED_DOMAINS = "myapp.com"
def email_username_is_not_on_blacklist
user, domain = email.split("#")
errors.add(:email, "E-mail user is not allowed") if self.class.EXCLUDED_USERNAMES.include?(user)
end
def email_domain_is_allowed
user, domain = email.split("#")
errors.add(:email, "E-mail domain is not allowed") if self.class.EXCLUDED_DOMAINS.include?(domain)
end

Resources