Sunday, December 17, 2006

Ruby: instance and class methods from a module

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.

7 comments:

  1. Anonymous11:06 AM

    I think you mean "extend" instead of "exclude" in the last code block.

    ReplyDelete
  2. Anonymous11:36 AM

    updated. thanks.

    ReplyDelete
  3. Anonymous12:35 PM

    Using the included callback to extend class methods is a Rails idiom, imo.

    ReplyDelete
  4. Anonymous2:38 AM

    "Using the included callback to extend class methods is a Rails idiom, imo."

    Been around longer than rails. in anycase #class_extension the best overall approach (see facets, or google it)

    ReplyDelete
  5. "Then, you extend the module to add the class methods to your presenter." I would say that you're extending the presenter with the module.

    ReplyDelete
  6. Anonymous7:49 PM

    how is this:

    class AccountInformationPresenter
    include Presenter::Validations
    extend Presenter::Validations::ClassMethods

    ..
    end

    different from

    class AccountInformationPresenter << Presenter::Validations

    or does it result in the same thing without requiring changing the inheritance chain.

    Thanks,
    Sarah

    ReplyDelete
  7. I'd consider this an idiom now, that is, when I include a module, I fully expect it to go wild on an including class.

    ReplyDelete

Note: Only a member of this blog may post a comment.