Pergunta

Lets say I have a UserModel and an InvitationModel both which contain an attribute email. Let's say that I need to have the same validation for the email across both these models (for example, length should be less than 100 characters).

Rather than duplicate this in the two models, which would make it difficult to change the maximum cap later (more so if there are five/six or more models with the email), I would like to keep the validation in one place. But I would rather make this a composition than an inheritance(since User has an email and not user is an email).

What is the best way I can achieve this with Ruby/Rails?

Foi útil?

Solução 2

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?

Outras dicas

You want to place your validations in a module and include it in the models that need those validations.

module EmailValidation
    extend ActiveSupport::Concern

    included do
        validates :email, length: {minimum: 3, maximum: 100}, presence: true,   uniqueness: true
    end
end 

Then just include the module

class User < ActiveRecord::Base
    include EmailValidation
    ...
end

You can write your own validator:

class MyEmailValidator < ActiveModel::Validator
  def validate(record)
    # Whatever validation you want on the record
  end
end

And then use it in both your classes:

class User < ActiveRecord::Base
    validates_with MyEmailValidator
end

class InvitationModel < ActiveRecord::Base
    validates_with MyEmailValidator
end

Of course, you can combine this with modules or subclasses as well

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top