Custom validation in Rails on different model - ruby-on-rails

How can I use custom validation between two different models in Ruby on Rails?
I have two models how can I validate columns between these two models.

The cleanest way is to create separate validator class instead of just a custom validation method. A validator is really just a subclass of ActiveModel::Validator or ActiveModel::EachValidator that implements #validate or #validate_each:
class TitleCaseValidator < ActiveModel::Validator
def validate(record)
unless record.name[0] == record.name[0].upcase
record.errors[:title] << 'must be title cased'
end
end
end
class Album
validates_with TitleCaseValidator
end
class Book
validates_with TitleCaseValidator
end
class TitleCaseValidator < ActiveModel::EachValidator
def validate(record, attribute, value)
unless value[0] == value[0].upcase
record.errors[attribute] << 'must be title cased'
end
end
end
class Album
validates :title, title_case: true
end
class Artist
validates :name, title_case: true
end
See Performing Custom Validations.

Related

Mix create and update validations in single custom validator using ActiveModel::Validator

I found two alternatives to make use of the ActiveModel::Validator helper, but I am failing to find an easier or clean(er) approach than either checking for the presence of record.id or creating multiple custom validators.
I have a User class as follows:
class User < ApplicationRecord
include ActiveModel::Validations
validates_with UserValidator
...
end
and a custom validator
class UserValidator < ActiveModel::Validator
def validate(record)
# validate some stuff
end
end
The logic inside the validate method is executed no matter the action from the controller. So on create or on update (or save).
My goal is to split the logic and I have found multiple approaches that work but I don't really like either of them.
Approach 1: Check if a record.id exists
class UserValidator < ActiveModel::Validator
def validate(record)
if record.id.nil?
# `on: :create` validations
else
# `on: :update` validations
end
end
end
Approach 2: Create multiple validator classes
class User < ApplicationRecord
include ActiveModel::Validations
validates_with UserCreateValidator, on: :create
validates_with UserUpdateValidator, on: :update
...
end
Preferably I would only have a single class UserValidator and within that custom validator check for some attribute or pass an option that would allow me to distinguish between create or update. Is that somehow achievable?
class UserValidator < ActiveModel::Validator
def validate(record)
if record.new_record?
# `on: :create` validations
else
# `on: :update` validations
end
end
end
See ActiveRecord::Persistence.

How to use ActiveModel::EachValidator in rails 5

How to validate specific attribute using ActiveModel::EachValidator.
I have written the below snippet of code. This validation will not call on saving or validating object.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record,attribute,value)
# Logic to check email is valid or not
end
end
This will work with rails 3.
You can put your EmailValidator class inside the models/concerns directory. Then inside your model you can validate the email attribute using the example below.
class User < ApplicationRecord
validates :email, presence: true, email: true
end
Rails will look for the EmailValidatorclass within the scope when it encounters email: true then validate the attribute using the validate_each method.
A simple base class that can be used along with ActiveModel::Validations::ClassMethods#validates_with
class User
include ActiveModel::Validations
validates_with EmailValidator
end
class EmailValidator < ActiveModel::Validator
def validate(record)
# Logic to check email is valid or not
record.errors.add :email, "This is some complex validation"
end
end
Any class that inherits from ActiveModel::Validator must implement a method called validate which accepts a record.
To cause a validation error, you must add to the record's errors directly from within the validators message.
For more details you can check here.
If you are looking for only email validation then you can try this.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attr_name, value)
unless value =~ /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
record.errors.add(attr_name, :email, options.merge(:value => value))
end
end
end

Rails Custom Validation method

I am fairly new to Rails validations. I have an Activity model that has many attributes (listed in attributes array below). I need to validate that every activity has a name and a at least one of the other attributes. I was think of something like the following but it looks a little messy. Any advice?
class Activity < ActiveRecord::Base
belongs_to :user
validate :valid_activity
def valid_activity
attributes = [reps, distance, meal, post_meal, yoga, reminder, duration]
if name.present? && self.include? (activity)
end
end
end
You would create a new validator class like so
class ActivityValidator < ActiveModel::Validator
def valid_activity
attributes = [:reps, :distance, :meal, :post_meal, :yoga, :reminder, :duration]
unless name.present? && attributes.any?{ |a| self.activity == a }
errors[:user] << 'Need to add an activity'
end
end
end
Then in your user.rb file, include the validator module and use the validates_with method.
include ActiveModel::Validations
validates_with ActivityValidator

How do a custom method applicable to various models

I have the following method called capitalizeEachWord. Inside this method there is an attribute called company
class BusCompany < ActiveRecord::Base
attr_accessible :company
before_save :capitalizeEachWord
validates :company,presence: true,
uniqueness: { case_sensitive: false },
format: /^([a-zA-z0-9]+\s?){1,}$/
def capitalizeEachWord
self.company=self.company.downcase.split.map(&:capitalize).join(' ')
end
end
I would like that this method not use the attribute company directly, but receives this attribute as a parameter for doesn't do it dependent of the model BusCompany. Something as the following. The problem is that this method I going to use in various models and don't want to write it in each model but use the inheritance
class BusCompany < ActiveRecord::Base
attr_accessible :company
before_save :capitalizeEachWord(self.company)
validates :company,presence: true,
uniqueness: { case_sensitive: false },
format: /^([a-zA-z0-9]+\s?){1,}$/
def capitalizeEachWord(attribute)
self.attribute=self.attribute.downcase.split.map(&:capitalize).join(' ')
end
end
Add the following code into config/initializers/capitalizer.rb
module Capitalizer
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def capitalize(*attributes)
#attributes_to_capitalize = attributes
before_save :capitalize_each_word
end
def attributes_to_capitalize
Array.new(#attributes_to_capitalize)
end
end
def capitalize_each_word
self.class.attributes_to_capitalize.each do |attr|
if value = send(attr)
self.send("#{attr}=", value.strip.titleize)
end
end
end
end
And then in your class:
class BusCompany < ActiveRecord::Base
include Capitalizer
capitalize :company
...
end
First, I'd recommend you override the setter for company instead of using error prone callbacks, like this:
class BusCompany < ActiveRecord::Base
# you can also use #titleize instead of capitalize each word
# also use try, in case `arg` is nil
def company=(arg)
super arg.try(:titleize)
end
end
Then you can use modules to wrap this functionality into a reusable unit. Throw this in a file in your concerns folder, or just in to the models folder:
module CapitalizedSetter
def capitalize_setter(*attr_names)
# for each attr name, redifine the setter so it supers the titleized argument instead
attr_names.each do |attr|
define_method(:"#{attr}=") { |arg| super arg.try(:titleize) }
end
end
end
Finally extend it into the desired models:
class BusCompany
extend CapitalizedSetter
capitalized_setter :company
end

Adding custom validations to ActiveRecord module via extend?

I'm trying to move my validations to a module. I want to extend an existing object an aribtrary set of validators, but I'm struggling to figure out how to get them to execute. Any ideas?
Active Record Object
class Test < ActiveRecord::Base
has_many :resources
end
Validator
module TestValidator
extend ActiveSupport::Concern
included do
validates_associated :resources
end
end
Console
t = Test.new
t.extend TestValidator
t.valid?
# true ... should be false
I hope this can help
6.1 Custom Validators
Custom validators are classes that extend ActiveModel::Validator. These classes must implement a validate method which takes a record as an argument and performs the validation on it. The custom validator is called using the validates_with method.
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
class Person
include ActiveModel::Validations
validates_with MyValidator
end
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
As shown in the example, you can also combine standard validations with your own custom validators.
https://guides.rubyonrails.org/active_record_validations.html#custom-validators

Resources