-3

Reading the documentation of rails about custom validation I see 3 way of doing this :

  1. Custom validator inheriting from ActiveModel::Validator -> prototype : def validate(record) we use it with validate_with
  2. Custom validator inheriting from ActiveModel::EachValidator -> prototype def validate_each(record, attribute, value), we use it when we do validates :email, unique: true e.g
  3. Custom methods - no parameters valide :name_of_method

Now what I'd like to understand is: Why is there 3 choices of accomplish this ? Which scenario make you choose which options ? I mean we probably can code everything with only one of the option. Just curious about the use-case you're using one of the option in. Are you following any convention ?

Edit : I believe I wasn't clear enough, I am looking for real and concrete example about the usage of those custom validations, which kind of design could involve having ActiveModel::Validator e.g ? On my end I found this afternoon a pretty good usage for custom methods :

validate :validate_client_id_not_changed, on: :update


def validate_client_id_not_changed
  errors.add :client_id, :changed if client_id_changed?
end

I am looking for concrete example running on production, like the example above, not generic one.

user229044
  • 232,980
  • 40
  • 330
  • 338
Alex
  • 307
  • 3
  • 7
  • 1
    There are a near infinite number of real-world examples. These kinds of open-ended questions are not suitable for Stack Overflow. You can check Github for uses of `ActiveModel::Validator` if you want to find projects making use of it. – user229044 Aug 13 '19 at 19:34

2 Answers2

2

These are not equivalent, each exists for a reason, which is described in the documentation.

  1. Custom validator inheriting from ActiveModel::Validator

This option provides a way to define reusable custom validation that applies to a whole record, not to a specific property. You can define custom validators that test complex state, and apply them to multiple models.

  1. Custom validator inheriting from ActiveModel::EachValidator

This option provides a way of defining reusable custom validation that applies to specific fields on a model. You can reuse this validation against a single field, but across many models.

  1. Custom methods - no parameters validate :name_of_method

This option provides a non-reusable custom validation that applies to one specific model. You can use this for one-off logic that is "private" to a specific model, and doesn't need to be reusable.

For example, given a Post model...


class Post < ActiveRecord::Base

  # Expects MyCustomPostValidator to inhert from ActiveModel::Validator
  # This validator receives the entire post instance, and can validate
  # the whole post based on any criteria
  validates_with :my_custom_post_validator

  # Expects MyCustomTitleValidator to inhert from ActiveModel::EachValidator
  # This validator receives the post and the attribute `:title`, and the
  # current value of post.title.
  # It should be limited to testing the validity of title in isolation
  validates :title, my_custom_title_validator: true

  validate :post_is_valid

  private

  def post_is_valid
    # This method should contain complex logic that cannot be expressed
    # through validating individual fields, and which is specific to
    # Posts and not worth distilling out a reusable external validator class
  end
end
Mark
  • 6,112
  • 4
  • 21
  • 46
user229044
  • 232,980
  • 40
  • 330
  • 338
1

1) Using a custom validator class is often very helpful when you have an attribute that needs the same kinds of validations across different classes, especially when the validations required are instance methods. That way you cannot call that class and stick these different attributes in it and bam they are validated without you having to have the same type of validations written for each attribute.

2) An each validator is very helpful for an attribute that fits into a pseudo bucket that must meet a certain requirement. So for example, email makes sense in this case because each email must meet certain requirements, so although each email is different there are standards that they must all meet.

3) A block validator can be used when an attribute needs a validation that is not met by Active Record's built in validation cases, for example length: { maximum: 6, message: 'cannot be empty' }. An example of one of these instances would be validating a json hash with a set kind of keys and values.

Here is a super helpful article explaining Active Record's different validation techniques and what they are used for: https://guides.rubyonrails.org/active_record_validations.html