Context: You have a presenter class that needs to validate attributes. You like the way ActiveRecord allows you to define validations using class methods. You also want to use a
valid?
method on your presenter instances to determine if they are valid or not.
Step one: You define the validation methods in a module.
module Presenters::Validations
def validates_presence_of(*args)
..
end
def validates_format_of(*args)
..
end
end
Then, you
extend
the module to add the class methods to your presenter.
class AccountInformationPresenter
extend Presenters::Validations
validates_presence_of :username, :password
..
This implementation also requires that you define
valid?
in each presenter class.
def valid?
self.class.validations.collect do |validation|
unless validation.valid?(self)
self.errors.add(validation.on, validation.message)
end
end.all?
end
end
When building the second presenter it should be clear that the
valid?
should be abstracted. This abstraction could result in another module. The new module could be included thus providing
valid?
as an instance method.
Step two: Another common approach is to define the class methods in a ClassMethods module inside the Presenters::Validations module.
module Presenters::Validations
module ClassMethods
def validates_presence_of(*args)
..
end
def validates_format_of(*args)
..
end
end
def self.included(base)
base.extend(ClassMethods)
end
def valid?
self.class.validations.collect do |validation|
unless validation.valid?(self)
self.errors.add(validation.on, validation.message)
end
end.all?
end
end
This approach, while common in Rails, can generate some dislike. A counter argument is that
include
is designed to add instance methods and using
self.included
is clever, but provides unexpected behavior. I've found that people who dislike the
self.included
trick prefer to explicitly use both include and extend.
class AccountInformationPresenter
include Presenter::Validations
extend Presenter::Validations::ClassMethods
..
end
Which approach is better?
I tend to prefer explicitness; however, if something is used often enough it can turn from an anti-pattern to an idiom. I'm not sure if that is the case or not here, but I think it might be.