pattern: auto-running module init code

Discussion in 'Ruby' started by Phil Tomson, Nov 23, 2005.

  1. Phil Tomson

    Phil Tomson Guest

    Maybe you've run into this problem: you have a module and you want to make
    sure that certain intialization code is run in that module. Of course a module can
    have an initialize method like so:

    module Foo
    def initialize(a,b)
    @a=a
    @b=b
    end
    end


    and then when you include the Foo module in your class later on that becomes
    the initialize for your class:

    class Bar
    include Foo
    end

    Now Bar's new takes two arguments a and b.

    However, what if you either want to define initialize in your class (to do
    some class-specific things) or
    what if you have a module that sets up some sort of connection,
    perhaps a Rinda tuplespace, for example:

    module Broadcaster
    PROTOCOL = "druby"
    HOST = "localhost"
    PORT = 9999
    def setup_connection
    @uri = "#{@protocol||PROTOCOL}://#{@host||HOST}:#{@port||PORT}"
    #set up the shared tuplespace:
    DRb.start_service(@uri,Rinda::TupleSpace.new)
    puts "#{$0}: setting up broadcaster..."
    #now set up broadcaster:
    @ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil,@uri))
    end

    #other related methods that access the tuplespace
    end

    Now if you want your class to also be a Broadcaster you could do the
    following:

    class Thing
    include Broadcaster
    def initialize(*args)
    #...
    setup_connection
    end
    end

    However, what if the user forgets to include the call to 'setup_connection' in
    their constructor? The connection won't be setup and 'Thing' won't really be
    doing any broadcasting.

    How can we get setup_connection to be called automatically when 'Thing' is
    extended with Broadcaster?

    Add the following methods to the Broadcaster module:

    module Broadcaster
    def self.extended(obj)
    obj.setup_connection
    end

    def self.included(mod)
    warn "Be sure to run #{self}.setup_connection somewhere in #{mod}'s\
    initialize method!"
    warn "OR: instead of 'include #{self}', use 'extend #{self}' in \
    #{mod}'s initialize method and it will be called automatically."
    end
    end #module

    Then instead of 'include Broadcaster' use 'extend' like so:

    class Thing
    def initialize(*args)
    #...
    extend Broadcaster # setup_connection called automagically
    end
    end


    What are the pros & cons of this approach? Is there really any advantage to
    doing things this way?

    (I can think of one con: if setup_connection took arguments this approach
    would not work)

    It does seem advantagous to call the setup code automatically this way, but it
    comes at the cost of using extend instead of include (and include tends to be
    used a lot more than extend ).

    Phil
    Phil Tomson, Nov 23, 2005
    #1
    1. Advertising

  2. Hi Phil,

    You can also use super:

    class Bar
    def initialize(*args, &block)
    puts "in Bar"
    super
    end
    end

    class Foo < Bar
    def initialize(*args, &block)
    puts "in Foo"
    super
    end
    end

    f =3D Foo.new

    module Mod
    def initialize(*args, &block)
    puts "in Mod"
    super
    end
    end

    class Foo
    include Mod
    end

    f =3D Foo.new

    --- OUTPUT ---
    in Foo
    in Bar
    in Foo
    in Mod
    in Bar

    or Module.included

    class Bar
    def initialize(*args, &block)
    puts "in Bar"
    super
    end
    end

    class Foo < Bar
    def initialize(*args, &block)
    puts "in Foo"
    super
    end
    end

    module Mod
    def self.included(obj)
    puts "in Mod"
    end
    end

    class Foo
    include Mod
    end

    f =3D Foo.new

    --- OUTPUT ---
    in Mod
    in Foo
    in Bar

    but note the different order of initialization.

    With extend, you get to choose whether you extend ~before~ the super or ~af=
    ter~.

    So you can have either extend before super =3D Foo, Mod, Bar (same as
    include + super) or extend after super =3D Foo, Bar, Mod.

    Whether this is a pro or a con is up to you to decide.

    My own preference in this case is to use include + super.

    Regards,

    Sean
    Sean O'Halpin, Nov 23, 2005
    #2
    1. Advertising

  3. Phil Tomson

    Phil Tomson Guest

    In article <>,
    Sean O'Halpin <> wrote:
    >Hi Phil,
    >
    >You can also use super:
    >
    >class Bar
    > def initialize(*args, &block)
    > puts "in Bar"
    > super
    > end
    >end
    >
    >class Foo < Bar
    > def initialize(*args, &block)
    > puts "in Foo"
    > super
    > end
    >end
    >
    >f =3D Foo.new
    >
    >module Mod
    > def initialize(*args, &block)
    > puts "in Mod"
    > super
    > end
    >end
    >
    >class Foo
    > include Mod
    >end
    >
    >f =3D Foo.new



    Ah, that would be too simple ;-) And the user of the module has to remember
    to call 'super' somewhere in the constructor, I was trying for something more
    automatic :)

    Hmmm...This brings up another thought: Just like C++ programmers are often
    advised to go ahead and make their methods virtual in case someone comes along
    and subclasses, should we be generally calling super in our constructors in case
    someone comes along and mixes-in a module with an 'initialize' method defined?



    >
    >--- OUTPUT ---
    >in Foo
    >in Bar

    # after the 2nd f.new:
    >in Foo
    >in Mod
    >in Bar
    >
    >or Module.included
    >
    >class Bar
    > def initialize(*args, &block)
    > puts "in Bar"
    > super
    > end
    >end
    >
    >class Foo < Bar
    > def initialize(*args, &block)
    > puts "in Foo"
    > super
    > end
    >end
    >
    >module Mod
    > def self.included(obj)
    > puts "in Mod"
    > end
    >end


    Perhaps, but isn't 'obj' in this case actually the including class?

    to make it more clear, change to:
    module Mod
    def self.included(obj)
    pus "in Mod: obj is: #{obj} #{obj.class}"
    end
    end

    >
    >class Foo
    > include Mod
    >end
    >


    Then the output will be:

    in Mod: obj is: Foo Class

    and it is called even before there is any instance of Foo created. So any
    initialization code you want to run by using 'included' will be class-level
    initialization, not instance-level. That may or may not be what you want.

    Phil
    Phil Tomson, Nov 23, 2005
    #3
  4. Phil Tomson wrote:
    > Maybe you've run into this problem: you have a module and you want to make
    > sure that certain intialization code is run in that module. Of course a module can
    > have an initialize method like so:
    >
    > module Foo
    > def initialize(a,b)
    > @a=a
    > @b=b
    > end
    > end
    >
    >
    > and then when you include the Foo module in your class later on that becomes
    > the initialize for your class:
    >
    > class Bar
    > include Foo
    > end
    >
    > Now Bar's new takes two arguments a and b.
    >
    > However, what if you either want to define initialize in your class (to do
    > some class-specific things) or
    > what if you have a module that sets up some sort of connection,
    > perhaps a Rinda tuplespace, for example:
    >
    > module Broadcaster
    > PROTOCOL = "druby"
    > HOST = "localhost"
    > PORT = 9999
    > def setup_connection
    > @uri = "#{@protocol||PROTOCOL}://#{@host||HOST}:#{@port||PORT}"
    > #set up the shared tuplespace:
    > DRb.start_service(@uri,Rinda::TupleSpace.new)
    > puts "#{$0}: setting up broadcaster..."
    > #now set up broadcaster:
    > @ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil,@uri))
    > end
    >
    > #other related methods that access the tuplespace
    > end
    >
    > Now if you want your class to also be a Broadcaster you could do the
    > following:
    >
    > class Thing
    > include Broadcaster
    > def initialize(*args)
    > #...
    > setup_connection
    > end
    > end
    >
    > However, what if the user forgets to include the call to 'setup_connection' in
    > their constructor? The connection won't be setup and 'Thing' won't really be
    > doing any broadcasting.
    >
    > How can we get setup_connection to be called automatically when 'Thing' is
    > extended with Broadcaster?
    >
    > Add the following methods to the Broadcaster module:
    >
    > module Broadcaster
    > def self.extended(obj)
    > obj.setup_connection
    > end
    >
    > def self.included(mod)
    > warn "Be sure to run #{self}.setup_connection somewhere in #{mod}'s\
    > initialize method!"
    > warn "OR: instead of 'include #{self}', use 'extend #{self}' in \
    > #{mod}'s initialize method and it will be called automatically."
    > end
    > end #module
    >
    > Then instead of 'include Broadcaster' use 'extend' like so:
    >
    > class Thing
    > def initialize(*args)
    > #...
    > extend Broadcaster # setup_connection called automagically
    > end
    > end
    >
    >
    > What are the pros & cons of this approach? Is there really any advantage to
    > doing things this way?
    >
    > (I can think of one con: if setup_connection took arguments this approach
    > would not work)
    >
    > It does seem advantagous to call the setup code automatically this way, but it
    > comes at the cost of using extend instead of include (and include tends to be
    > used a lot more than extend ).
    >
    > Phil


    Ideally, there would be a method hook that was called when a class
    including the module was instantiated.

    module Fooable
    def self.initialized(obj, *args, &block)
    puts "A class including Fooable has been instantiated!"
    obj.do_something(*args) # whatever
    end
    end


    Cheers,
    Daniel
    Daniel Schierbeck, Nov 23, 2005
    #4
  5. On 11/23/05, Phil Tomson <> wrote:
    > Ah, that would be too simple ;-) And the user of the module has to remem=

    ber
    > to call 'super' somewhere in the constructor, I was trying for something =

    more
    > automatic :)


    With your scheme, the user has to remember to call extend in the
    initialize method, rather than include at the class level. I've seen
    modules that require this cause quite a bit of confusion
    unfortunately.

    > Hmmm...This brings up another thought: Just like C++ programmers are oft=

    en
    > advised to go ahead and make their methods virtual in case someone comes =

    along
    > and subclasses, should we be generally calling super in our constructors =

    in case
    > someone comes along and mixes-in a module with an 'initialize' method def=

    ined?

    Well, I'm beginning to do this as a matter of course - it really does
    make all this kind of stuff much simpler once you get into the habit.

    >>module Mod
    >> def self.included(obj)
    >> puts "in Mod"
    >> end
    >>end


    > Perhaps, but isn't 'obj' in this case actually the including class?


    Yes, but I think of it as an object (too much playing about with singletons=
    !).

    [snip stuff about Module.included]

    > So any
    > initialization code you want to run by using 'included' will be class-lev=

    el
    > initialization, not instance-level. That may or may not be what you want=
    Sean O'Halpin, Nov 23, 2005
    #5
  6. This is a working implementation:

    class Class
    alias_method :__new__, :new

    def new(*args, &block)
    obj = __new__(*args, &block)
    included_modules.each do |mod|
    if mod.respond_to? :initialized
    mod.initialized(obj, *args, &block)
    end
    end
    return obj
    end
    end


    # Testing
    module A
    def self.initialized(obj, *args)
    puts "Module A"
    end
    end

    module B
    def self.initialized(obj, *args)
    puts "Module B"
    end
    end

    module C; end

    class Klass
    include A, B, C

    def initialize
    puts "Klass"
    end
    end

    Klass.new


    Cheers,
    Daniel
    Daniel Schierbeck, Nov 23, 2005
    #6
  7. Phil Tomson

    Phil Tomson Guest

    In article <43846a75$0$1813$>,
    Daniel Schierbeck <> wrote:

    >Ideally, there would be a method hook that was called when a class
    >including the module was instantiated.
    >
    > module Fooable
    > def self.initialized(obj, *args, &block)
    > puts "A class including Fooable has been instantiated!"
    > obj.do_something(*args) # whatever
    > end
    > end


    But as Sean pointed out we essentially get the same behavior by calling
    'super' somewhere in the including class's constructor.

    Phil
    Phil Tomson, Nov 23, 2005
    #7
  8. Phil Tomson wrote:
    > In article <43846a75$0$1813$>,
    > Daniel Schierbeck <> wrote:
    >
    >> Ideally, there would be a method hook that was called when a class
    >> including the module was instantiated.
    >>
    >> module Fooable
    >> def self.initialized(obj, *args, &block)
    >> puts "A class including Fooable has been instantiated!"
    >> obj.do_something(*args) # whatever
    >> end
    >> end

    >
    > But as Sean pointed out we essentially get the same behavior by calling
    > 'super' somewhere in the including class's constructor.
    >
    > Phil


    Yeah, but that requires the including class to add code to the
    `initialize' method, some I don't think should be necessary for a module
    to function. I think that the behaviour of a module should be kept
    within that module, unless it needs some specific methods (like
    Enumerable needs `each').

    The only issue I have with my current model (see my other post for an
    implementation) is that it might be better for the module initializer to
    be an instance method, instead of a module method.

    module Fooable
    def self.initialized(obj, *args, &block); end
    # versus
    def initialized(*args, &block); end
    end

    And maybe it should be called `instantiated' instead of `initialized',
    though that doesn't really make much of a difference for me.


    Cheers,
    Daniel
    Daniel Schierbeck, Nov 23, 2005
    #8
  9. Phil Tomson

    Phil Tomson Guest

    In article <4384be41$0$1773$>,
    Daniel Schierbeck <> wrote:
    >Phil Tomson wrote:
    >> In article <43846a75$0$1813$>,
    >> Daniel Schierbeck <> wrote:
    >>
    >>> Ideally, there would be a method hook that was called when a class
    >>> including the module was instantiated.
    >>>
    >>> module Fooable
    >>> def self.initialized(obj, *args, &block)
    >>> puts "A class including Fooable has been instantiated!"
    >>> obj.do_something(*args) # whatever
    >>> end
    >>> end

    >>
    >> But as Sean pointed out we essentially get the same behavior by calling
    >> 'super' somewhere in the including class's constructor.
    >>
    >> Phil

    >
    >Yeah, but that requires the including class to add code to the
    >`initialize' method, some I don't think should be necessary for a module
    >to function. I think that the behaviour of a module should be kept
    >within that module, unless it needs some specific methods (like
    >Enumerable needs `each').


    True, that's essentially what I was trying to get around (having to know to
    explicitly call 'super' or some other initialization method defined in the
    module.

    >
    >The only issue I have with my current model (see my other post for an
    >implementation) is that it might be better for the module initializer to
    >be an instance method, instead of a module method.
    >
    > module Fooable
    > def self.initialized(obj, *args, &block); end
    > # versus
    > def initialized(*args, &block); end
    > end
    >
    >And maybe it should be called `instantiated' instead of `initialized',
    >though that doesn't really make much of a difference for me.



    I do kind of like your solution, but it does mean that the implementor of the
    module needs to know to define an 'initialized' method to make the mechanism
    work. What if you just test for mod.respond_to? :initialize ? I suppose the
    problem with that would be if the user of the module did happen to include
    'super' in their constructor - if they did then the module initialization
    would be called twice.

    Phil
    Phil Tomson, Nov 23, 2005
    #9
  10. Phil Tomson wrote:
    > I do kind of like your solution, but it does mean that the implementor of the
    > module needs to know to define an 'initialized' method to make the mechanism
    > work.

    That's no different from Module#included and Class.inherited. I think
    the method belongs in the module.

    > What if you just test for mod.respond_to? :initialize ? I suppose the
    > problem with that would be if the user of the module did happen to include
    > 'super' in their constructor - if they did then the module initialization
    > would be called twice.

    Yeah, and there would be the problem that multiple included modules
    would override each other's `initialize' methods (as would the including
    class.)


    Cheers,
    Daniel
    Daniel Schierbeck, Nov 23, 2005
    #10
  11. Phil Tomson

    Phil Tomson Guest

    In article <4384cee3$0$1760$>,
    Daniel Schierbeck <> wrote:
    >Phil Tomson wrote:
    >> I do kind of like your solution, but it does mean that the implementor of the
    >> module needs to know to define an 'initialized' method to make the mechanism
    >> work.

    >That's no different from Module#included and Class.inherited. I think
    >the method belongs in the module.
    >
    >> What if you just test for mod.respond_to? :initialize ? I suppose the
    >> problem with that would be if the user of the module did happen to include
    >> 'super' in their constructor - if they did then the module initialization
    >> would be called twice.

    >Yeah, and there would be the problem that multiple included modules
    >would override each other's `initialize' methods (as would the including
    >class.)


    Actually, with your approach where you look for all the included modules and
    call each of their 'initialize' methods it shouldn't be a problem since you
    are bypassing the super mechanism.

    Phil
    Phil Tomson, Nov 23, 2005
    #11
  12. Phil Tomson wrote:
    > In article <4384cee3$0$1760$>,
    > Daniel Schierbeck <> wrote:
    >> Phil Tomson wrote:
    >>> I do kind of like your solution, but it does mean that the implementor of the
    >>> module needs to know to define an 'initialized' method to make the mechanism
    >>> work.

    >> That's no different from Module#included and Class.inherited. I think
    >> the method belongs in the module.
    >>
    >>> What if you just test for mod.respond_to? :initialize ? I suppose the
    >>> problem with that would be if the user of the module did happen to include
    >>> 'super' in their constructor - if they did then the module initialization
    >>> would be called twice.

    >> Yeah, and there would be the problem that multiple included modules
    >> would override each other's `initialize' methods (as would the including
    >> class.)

    >
    > Actually, with your approach where you look for all the included modules and
    > call each of their 'initialize' methods it shouldn't be a problem since you
    > are bypassing the super mechanism.
    >
    > Phil


    Yeah, but calling a module's method without first including the module
    in a class and then instantiating that class isn't easy. And for some
    reason my Object#method seems to fail. Weird...


    Cheers,
    Daniel
    Daniel Schierbeck, Nov 23, 2005
    #12
  13. A different approach than the `initialized' method, which would
    naturally be run after the class's `initialize' method had been called,
    would be an 'instantiated' method, which would be called *before*
    `initialize'. I actually think that's better, because that makes it
    possible for `initialize' to take advantage of the things changed by the
    module initialization code.

    module Fooable
    def self.instantiated(obj, *args, &block)
    obj.instance_eval do
    @foo = "something"
    end
    end
    end

    class Klass
    include Fooable

    def initialize
    puts @foo # -> something
    end
    end


    This will be a lot harder to implement though, as the module method
    `instantiated' must be called on all modules included by a class
    *before* its `initialize' method is called. That isn't possible by
    simply overriding Class#new...


    Cheers,
    Daniel

    Cheers,
    Daniel
    Daniel Schierbeck, Nov 25, 2005
    #13
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Tony Cheng
    Replies:
    1
    Views:
    8,181
    Juan T. Llibre
    Feb 24, 2006
  2. Replies:
    1
    Views:
    643
    Jules
    Aug 18, 2005
  3. Jess
    Replies:
    4
    Views:
    428
  4. linkswanted
    Replies:
    1
    Views:
    877
  5. news.aon.at
    Replies:
    11
    Views:
    626
    Ian Collins
    Jan 29, 2011
Loading...

Share This Page