Rails plug and play validations - ruby-on-rails

Here is my example
class User < ActiveRecord::Base
validates_with EmailValidator
end
class EmailValidator < ActiveModel::Validator
def validate(record)
if record != someregex
record.errors.add(:email, 'invalid email')
end
end
end
Now I can use this EmailValidator for any model. But my requirement is to validate its uniqueness and presence also for that particular model.
If I can achieve this I can use this EmailValidator for any model email validation with functionalities unique, presence.
Then I can achieve more reusable Validator.

You can do this by rewriting your validator to be EachValidator:
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if attribute !~ someregex
record.errors.add attribute, (options[:message] || 'invalid email')
end
end
end
Then in your model:
validates :email, email: true, uniqueness: true, presence: true
Hope that helps.

Related

confusion over validates_with custom validations syntax shown in rails guide

I am attempting to use the validates_with custom validations helper with Rails 4.
The following code is working in my application:
class Photo
validates_with CleanValidator
include ActiveModel::Validations
end
class CleanValidator < ActiveModel::Validator
def validate(record)
if record.title.include? "foo"
record.errors[:title] << "Photo failed! restricted word"
end
end
end
However I want to pass this helper to multiple attributes in multiple models, not just :title.
There is an example in validates_with section of guide that contains the following example:
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if options[:fields].any?{|field| record.send(field) == "Evil" }
record.errors[:base] << "This person is evil"
end
end
end
class Person < ApplicationRecord
validates_with GoodnessValidator, fields: [:first_name, :last_name]
end
This is what I want to achieve, substituting [:fields] for [:title] in my code example so that I can use CleanValidator for multiple models and multiple attributes (User.name, Photo.title etc).
I think you want the other example from the guides, each validator. You should be able to do
class CleanValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless ["Evil", "Other", "Restricted", "Words"].include?(value)
record.errors[attribute] << (options[:message] || "is a restricted word")
end
end
end
class Photo
include ActiveModel::Validations
attr_accessor :title
validates :title, clean: true
end

ActiveModel custom EachValidator not found

My EachValidator cannot work on Rails 4.1.5.
My Product model:
class Product < ActiveRecord::Base
has_many :tags
validates :tags, tags_size: {minimum: 1, maximum: 10}
end
My Validator, I put it in app/validators/tags_size_validator.rb
class TagsSizeValidator < ActiveModel::Validator
def validate_each(record, attribute, value)
if value.size < options[:maximum]
record.errors[attribute] << (options[:message] || "must have at most #{options[:maximum]} tags.")
end
if value.size > options[:minimum]
record.errors[attribute] << (options[:message] || "must have at lease #{options[:minimum]} tags.")
end
end
end
end
I have made it autoload in application.rb
config.autoload_paths += %W["#{Rails.root}/app/validators/"]
When I put the validator at the same file as the Product model, it worked perfectly. But in separated file it failed. Are there any steps I have missed? Please advise. Thanks.
Try inheriting from ActiveModel::EachValidator rather than ActiveModel::Validator:
class TagsSizeValidator < ActiveModel::EachValidator
...
Also you shouldn't have to add it to your autoload_paths in Rails 4.1.

Custom validation rails

I have EmailValidator class inside module like:
module ActiveModel
module Validations
class EmailValidator < EachValidator
def validate_each(record, attribute, value)
if value.presence && (value =~ /\A[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]+\z/).nil?
record.errors[attribute] << (options[:message] || "is invalid")
end
rescue => e
record.errors[attribute] << (options[:message] || "is invalid")
end
end
end
end
I am trying to use this inside my model but facing load error when I try to start rails server => email_validator.rb to define EmailValidator (LoadError)
Can anyone help me with this?
I recommend using the valid_email gem instead, because validating email adresses is a real pain!
class User < ActiveRecord::Base
validates :email, :presence => true, :email => true
# ...
end
Also see this humongous regular expression which actually validates email addresses according to RFC 822.
validates_format_of :email, :with =>~ /\A[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+.[A-Za-z]+\z/
You can try this kind of syntax and implement your code according this blog that should be very helpful:-
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if value.presence && (value =~ /\A[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]+\z/).nil?
record.errors[attribute] << (options[:message] || "is invalid")
end
rescue => e
record.errors[attribute] << (options[:message] || "is invalid")
end
end
just put this file in app/validators/email_validator.rb
for rails 3 custom validation please read this blog.
In Rails 4
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 foo < ActiveRecord::Base
validates :email, presence: true, email: true
end
At last very simple and dry use reg. exp.:-
/\A[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]+\z/
That was adapted from http://www.regular-expressions.info/email.html

Generic method for validation of paperclip files in different models

I have 2 models users , companies
User model:
has_attached_file :avatar,
...
:whiny=>false
validates_with ImageSizeValidator
validates_with ImageTypeValidator
validates_with ImageConvertionValidator
Company model:
has_attached_file :logo,
#the rest is similar
I have done validation for users and put it in validation_helper
class ImageSizeValidator < ActiveModel::Validator
def validate(record)
if record.avatar_file_name.present?
record.errors[:base] << (I18n.t :in_between, scope: "activerecord.errors.models.user.attributes.avatar_file_size") unless record.avatar_file_size.to_i < 200000
end
end
end
class ImageTypeValidator < ActiveModel::Validator
def validate(record)
if record.avatar_file_name.present?
record.errors[:base] << (I18n.t :file_type, scope: "activerecord.errors.models.user.attributes") unless ['image/jpeg', 'image/gif','image/png'].include?(record.avatar_content_type)
end
end
end
My problem is that the names will be different so avatar_file_name for users and logo for companies.
Do I have to do a specific method for each?
How can I work this around?
you just need to add options. If you take a look at documentation, you can pass arguments in block:
#model
validates_with ImageSizeValidator, paperclip_field_name: :avatar
#validator
def validate(record)
if record.send(options[:paperclip_field_name].to_s+"_file_name").present?
record.errors[:base] << (I18n.t :in_between, scope: "activerecord.errors.models.user.attributes.#{options[:paperclip_field_name]}_file_size") unless record.send(options[:paperclip_field_name].to_s+"_file_name").to_i < 200000
end
end
but much easier to use validate_each method
#model
validates :avatar, image_size: true, image_type: true, image_conversion: true
#validator
def validate_each(record, attribute, value)
if record.send(attribute.to_s+"_file_name").present?
record.errors[:base] << (I18n.t :in_between, scope: "activerecord.errors.models.user.attributes.#{attribute}_file_name)") unless record.send(attribute.to_s+"_file_name").to_i < 200000
end
end
Paperclip has built-in support for validations (https://github.com/thoughtbot/paperclip#validations). If their validation are not a fit for your problem you can look on how they are doing it: https://github.com/thoughtbot/paperclip/tree/master/lib/paperclip/validators

rails3 doesn't load my validators in lib

I put EmailValidator in lib/validators/email_validator and it's not workings (I put root/lib in the load_path)
here is the code.. I put the class in module validators as the parent folder name
class Validators::EmailValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
unless value =~ /^([^#\s]+)#([a-z0-9]+\.)+[a-z]{2,}$/i
object.errors[attribute] << (options[:message] || "is not formatted properly")
end
end
end
I get the error Unknown validator: 'email'
You have two options:
Either put your custom validator under config/initializers.
Or add lib/validators to the autoload path in config/application.rb.
config.autoload_paths << "#{config.root}/lib/validators"
Personally I would go with the second option as lib/validators makes for good encapsulation.
Since you put your custom validator in the Validators:: in the lib/validators, you have to reference it with that namespace also.
validates :email, presence: true, :'validators/email' => true
UPDATE: You need this:
module Validators
class EmailValidator < ActiveModel::EachValidator
def validate(object, attribute, value)
unless value =~ /^([^#\s]+)#([a-z0-9]+\.)+[a-z]{2,}$/i
object.errors[attribute] << (options[:message] || "is not formatted properly")
end
end
end
end
class YourModel < ActiveRecord::Base
include Validators
validates :email, :presence => true, :email => true
end
Otherwise, you need to put your validator class under the ActiveModel::Validations namespace. When you namespace a class, ActiveRecord isn't going to see it, if that namespace isn't a namespace it has already included.

Resources