Pattern: auto-running module init code


#1

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


#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 = Foo.new

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

class Foo
include Mod
end

f = 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 = 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
~after~.

So you can have either extend before super = Foo, Mod, Bar (same as
include + super) or extend after super = 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


#3

In article removed_email_address@domain.invalid,
Sean O’Halpin removed_email_address@domain.invalid wrote:

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 :wink: And the user of the module has to
remember
to call ‘super’ somewhere in the constructor, I was trying for something
more
automatic :slight_smile:

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:

end
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


#4

On 11/23/05, Phil T. removed_email_address@domain.invalid wrote:

Ah, that would be too simple :wink: And the user of the module has to remember
to call ‘super’ somewhere in the constructor, I was trying for something more
automatic :slight_smile:

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 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?

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-level
initialization, not instance-level. That may or may not be what you want.

Indeed - it depends on what level you are trying to have the funky stuff
happen.
I guess it’s not really relevant to what you were trying to do. Sorry
for the noise!

Regards,

Sean


#5

Phil T. wrote:

However, what if you either want to define initialize in your class (to do
#set up the shared tuplespace:
following:
their constructor? The connection won’t be setup and ‘Thing’ won’t really be
end

(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


#6

In article 43846a75$0$1813$removed_email_address@domain.invalid,
Daniel S. removed_email_address@domain.invalid 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


#7

Phil T. wrote:

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 needseach’).

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 ofinitialized’,
though that doesn’t really make much of a difference for me.

Cheers,
Daniel


#8

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


#9

Phil T. 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


#10

Phil T. wrote:

problem with that would be if the user of the module did happen to include
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


#11

In article 4384be41$0$1773$removed_email_address@domain.invalid,
Daniel S. removed_email_address@domain.invalid wrote:

  obj.do_something(*args) # whatever

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.

And maybe it should be called instantiated' instead ofinitialized’,
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


#12

In article 4384cee3$0$1760$removed_email_address@domain.invalid,
Daniel S. removed_email_address@domain.invalid wrote:

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


#13

A different approach than the initialized' method, which would naturally be run after the class'sinitialize’ 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 forinitialize’ 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* itsinitialize’ method is called. That isn’t possible by
simply overriding Class#new…

Cheers,
Daniel

Cheers,
Daniel