Rails Put Validation in a Module Mixin

Rails put validation in a module mixin?


module Validations
extend ActiveSupport::Concern

included do
validates :name, :length => { :minimum => 2 }, :presence => true, :uniqueness => true
validates :name_seo, :length => { :minimum => 2 }, :presence => true, :uniqueness => true
end
end

The validates macro must be evaluated in the context of the includer, not of the module (like you probably were doing).

Rails accessing a shared custom validation in a module within a model

I tried the following and it worked for me:

Created validations.rb in models/concerns

module Validations
extend ActiveSupport::Concern

def email_format_validation
if self.email.present?
if !validates_format_of :email, with: email_regex
self.errors.add(:email, "doesn't exist")
end
end
end


def email_regex
/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
end
end

In the model:

include Validations

class User < ApplicationRecord
validate :email_format_validation
validates :user_email, with: :email_regex
end

user_email is the field name

Adding custom validations to ActiveRecord module via extend?

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

Removing or overriding an ActiveRecord validation added by a superclass or mixin

I ended up "solving" the problem with the following hack:

  1. look for an error on the :email attribute of type :taken
  2. check if the email is unique for this account (which is the validation I wanted to do)
  3. remove the error if the email is unique for this account.

Sounds reasonable until you read the code and discover how I remove an error. ActiveRecord::Errors has no methods to remove errors once added, so I have to grab hold of it's internals and do it myself. Super duper mega ugly.

This is the code:

def validate
super
remove_spurious_email_taken_error!(errors)
end

def remove_spurious_email_taken_error!(errors)
errors.each_error do |attribute, error|
if error.attribute == :email && error.type == :taken && email_unique_for_account?
errors_hash = errors.instance_variable_get(:@errors)
if Array == errors_hash[attribute] && errors_hash[attribute].size > 1
errors_hash[attribute].delete_at(errors_hash[attribute].index(error))
else
errors_hash.delete(attribute)
end
end
end
end

def email_unique_for_account?
match = account.users.find_by_email(email)
match.nil? or match == self
end

If anyone knows of a better way, I would be very grateful.

Rails Apply Same Validation to Same Attribute in Different Models

There are a couple of ways you could do this, I prefer to store global constants is under the initializer folder.

For example:

Create an initializer file as /config/initializers/my_constants.rb

class MyConstants
MAX_LENGTH = 100
##.. other constants
end

After this use it in your models as:

validates :email, length: { maximum: MyConstants::MAX_LENGTH }

Please note every time you update this initializer file with new global constants, you must restart with rails s to reload the changes.

Say you have two models User with attributes username and email and Campaign with attributes name and email

  • This approach would be more useful, when you need somewhat same validation to attributes with same/ different names across different models i.e., in the above case you can apply same validation for both User#email and Campaign#email. Also, you have the freedom to use same validations for User#username and Campaign#name even though they are named differently.
  • Even in cases when you need one option (say minimum in length validation) to have same value BUT another option(say maximum in length validation) to have different values.
    Say in case of User#username and Campaign#name, you need minimum to be 2 characters for both and maximum of User#username to be 30 and minimum of Campaign#name to be 50 then you can achieve that easily.

Like I said, you can achieve this in various ways. Another approach suggested by @CaptChrisD
is also good for the case where you need exactly same validation and you don't plan on tweaking it in future anyhow.

In that case you can make use of ActiveSupport::Concern

You would need to place the module in app/models/concerns directory and name the file as email_validation.rb.

module EmailValidation
extend ActiveSupport::Concern
included do
validates :email, length: { minimum: 5, maximum: 100 }
end
end

After this you would need to include the module in the models where you need the same validation on email field.

class MyModel < ActiveRecord::Base
include EmailValidation ## Add this
##..
end

This is an excellent read on Ruby Mixins & ActiveSupport::Concern for your reference.

You can also check out this SO Answer: Rails put validation in a module mixin?

Rails 4: Validating associated ActiveRecord models when using a Form Object

I like what you're doing using a form object. validates_associated :user, :account might help, but the error messages might be kind of odd. Rather, I might use mixins for common validations:

class Account < ActiveRecord::Base
module Validations
extend ActiveSupport::Concern
included do
validates :slug, presence: true
end
end
include Validations
end

class Signup
include ActiveModel::Model
include Account::Validations
extend Forwardable
def_delegators :account, :slug
end

How to mix in additional validations to ActiveModel::Model?

Okay.
One more snippet.
Im not sure, but how about this?

# This should be locate in lib/your_custom_validator.rb
class YourCustomValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# validation login is here
end
end

I think, now you can use that validator in any model.


# You can use that validator like this.
class RegistrationForm < ActiveRecord::Base
validates :some_column_name, your_custom_validator: true
end

cheers, Sangil.

Apply validation module to model in certain controllers only

One alternative is to include the following in your Model:

attr_accessor :guest

def run_special_validations?
guest
end

validate :special_validation1, if: run_special_validations?
validate :special_validation2, if: run_special_validations?

Then, by having the controller set @object.guest = true, you will tell the object to run the conditional validations.

Share validations across models with rails 4

Create the module in app/models/concerns, then include them in your classes with:

include ContactValidations

In this way Rails will automatically load the shared modules and make them available for you to include.

How would I implement my own Rails-style validates() method in Ruby?

So here are two alternative ways of doing it:

With "direct" access

module MyMixin

def self.included(base)
base.extend(ClassMethods)
end

def wordy?(value)
value.length > 2
end
module ClassMethods
def validates_wordiness_of(*attrs)
define_method(:valid?) do
attrs.all? do |attr|
wordy?(send(attr))
end
end
end
end
end

class MyClass
include MyMixin

validates_wordiness_of :foo, :bar

def foo
"a"
end

def bar
"asrtioenarst"
end
end

puts MyClass.new.valid?

The downside to this approach is that several consecutive calls to validates_wordiness_of will overwrite each other.

So you can't do this:

validates_wordiness_of :foo
validates_wordiness_of :bar

Saving validated attribute names in the class

You could also do this:

require 'set'
module MyMixin
def self.included(base)
base.extend(ClassMethods)
end

module Validation
def valid?
self.class.wordy_attributes.all? do |attr|
wordy?(self.send(attr))
end
end

def wordy?(value)
value.length > 2
end
end

module ClassMethods
def wordy_attributes
@wordy_attributes ||= Set.new
end

def validates_wordiness_of(*attrs)
include(Validation) unless validation_included?
wordy_attributes.merge(attrs)
end

def validation_included?
ancestors.include?(Validation)
end
end
end

class MyClass
include MyMixin

validates_wordiness_of :foo, :bar

def foo
"aastrarst"
end

def bar
"asrtioenarst"
end
end

MyClass.new.valid?
# => true

I chose to make the valid? method unavailable until you actually add a validation. This may be unwise. You could probably just have it return true if there are no validations.

This solution will quickly become unwieldy if you introduce other kinds of validations. In that case I would start wrapping validations in validator objects.



Related Topics



Leave a reply



Submit