Wednesday, May 24, 2006

Ruby extend and include

Module.include(module, ..) is commonly used to mix modules into classes and other modules. When a module is included the constants, methods, and module variables of the module are added to the including module or class instances.
module Actions
def left(steps)
Movement.new(self, steps)
end
end
The Actions module can be included to allow a class to generate movements.
class Car
include Actions
end
After Actions is included left can be executed by any instance of Car.
car = Car.new
movement = car.left

Similarly, Object.extend(module, ..) adds the instance methods from each module given as a parameter. However, extend adds the methods to one instance, not to all instances.
car = Car.new
car.extend Actions
movement = car.left
The above code gives car the same behavior as include would, except only the instance that called extend would have this new behavior. Therefore, the above code is valid, but this code would result in an error:
car = Car.new
car.extend Actions
car2 = Car.new
movement = car2.left #calling 'left' here is invalid because car2 does not extend Actions
extend is commonly used to mix instance methods of a module into a class.
module AttributeInitializer
def attr_init(name, klass)
eval "define_method(name) { @#{name} ||= klass.new }"
end
end

class Statement
extend AttributeInitializer

attr_init :charges, Array
end
Many DSL style class methods are added by extending a module instead of including it.

For another example of correct extend usage see Forwardable from the standard library.

5 comments:

  1. Anonymous11:14 PM

    I have been using an Module.included? to add class methods as well:

    module Extras
    def included?(base)
    base.class_eval do
    # this will be a new class method
    def do_something
    puts "it works!"
    end
    end
    end
    end

    class MyClass
    include Extras
    end

    MyClass.do_something


    it has been very handy the past couple of days, though I am not doing a DSL.

    ReplyDelete
  2. Anonymous8:32 AM

    If you prefer to use include you can override the append_features(mod) callback. I do exactly that in my previous post.
    Ruby Delegation Module

    ReplyDelete
  3. Thanks. I've been reading the code for authlogic, and now I finally understand why he writes things like:

    ...
    module Methods
    def self.included(klass)
    klass.class_eval do
    extend ClassMethods
    include InstanceMethods
    ...

    ReplyDelete
  4. Thank you for explanation. I created a DSL method for my implementation using "extend" method.

    ReplyDelete
  5. Anonymous10:42 AM

    'The above code gives car the same behavior as include would, except only the instance that called extend would have this new behavior.'

    It seems that when including a module its contents are treated as a superclass of the class that is including it, whereas when extending a module this is not the case.

    ReplyDelete

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