Initializing instance variables in a module


#1

Have a module:

Module Foo
def add_stuff(key, stuff)
@foo_the_stuff[key] = stuff
end
end

class Bar
include Foo
end

Is there a standard or correct way to initialize @foo_the_stuff in this
example? Should the add_stuff method check whether the variable has
been initialized every time, or should this be done by implementing a
callback like append_features or included? (That’s how I’d want to do
it, but it’s not clear which or how…)

Thanks,
Jeff


#2

module Foo
def add_stuff(key, stuff)
(@foo_the_stuff ||= {})[key] = stuff
end
end

If @foo_the_stuff is null, it will be initialized to an empty hash,
otherwise the existing value will be used. I consider this use of ||=
to be a Ruby idiom.

Ryan


#3

That’s what I was afraid of. In this example case it’s no big deal, but
in a module where you are accessing a more complex variable many times
it’s a real pain, let alone inefficient. We are working on an event
system, so the hash is a hash of arrays or possibly a hash of hashes.
Any of 4 methods could be called, which would require an initialized
value. Having this in every method is sorta lame…

handlers = (@event_handlers ||= Hash.new {|k,v| k[v] = []})[key]

Of course we can pull it out to a method:

def handlers(key)
@event_handlers ||= Hash.new {|k,v| k[v] = []})[key]
end

but just initializing on inclusion seems to be the most reasonable
way, doesn’t it?

-Jeff


#4

Jeff R. wrote:

but just initializing on inclusion seems to be the most reasonable
way, doesn’t it?

Put the initialization code in the module’s initialize method and have
every class that includes the module call super from its initialize
method:

module Foo
def initialize(*args, &block)
# whatever…
end
end

class Bar
include Foo
def initialize(*args, &block)
super # calls Foo’s initialize
# more stuff
end
end


#5

Hi –

On Tue, 23 May 2006, Jeff R. wrote:

Thanks,
to be a Ruby idiom.
handlers = (@event_handlers ||= Hash.new {|k,v| k[v] = []})[key]

Of course we can pull it out to a method:

def handlers(key)
@event_handlers ||= Hash.new {|k,v| k[v] = []})[key]
end

but just initializing on inclusion seems to be the most reasonable way,
doesn’t it?

Not if it leads to code you strongly dislike :slight_smile: It seems OK to wrap
it in a method, perhaps like this:

def event_handlers
@event_handlers ||= Hash.new {|k,v| k[v] = []}
end

def whatever …
handlers = event_handlers[key]
end

David


#6

Jeff, I tried a few combinations of class_eval and module_eval and I
couldn’t figure it out. What seems to be desired is for the mixed-in
module code to create an instance variable in the class which is doing
the mixin. I’ll bet there’s a way to do it.


#7

Well, it took a while but I found the answer. This is done in
ActiveRecord to wrap a possible setup method in the unit tests for
auto-instrumentation of fixtures. Once I knew what to look for,
however, I found an old post by Florian G. that lays out the exact
answer nicely:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/129834

For educational purposes though, I’ll explain a few things I found…

  1. The append_features method is deprecated, and the included method is
    now the preferred callback. This method is called when a module is
    included, and it is passed the module that has done the including.

  2. There is another callback, method_added, which is a meta-class method
    that is called every time a new method is defined for its class, and the
    symbol for the method name is passed.

So, the trick is to add a custom method_added method to the meta-class
of the class that does the including by extending it in the included
callback. The custom method_added needs to redefine initialize after it
is added so that it does the custom setup, then calls the original.

Phew… Too bad we can’t just have another callback that lets us do
this easily. Maybe that feels too much like multiple inheritance or
something?

module Foo
def module_initialize
@myvar = Hash.new {|k,v| k[v] = []}
end

def bar(key)
@mybar[key]
end
end

Isn’t this a common thing to want?

-Jeff


#8

You still have to call it every time you use the variable.


#9

Hi –

On Tue, 23 May 2006, Jeff R. wrote:

the preferred callback. This method is called when a module is included, and

end
end

Isn’t this a common thing to want?

I’m probably being thick but what’s wrong with just writing a method
that wraps @myvar and initializes it if necessary, and then returns
it?

David


#10

Francis C. schrieb:

You still have to call it every time you use the variable.

Do you realy need to use the variable directly?
My scripts tend to have very little use of ‘@’.

module Foo
def bar(key)
@myvar ||= Hash.new {|k,v| k[v] = []}
@myvar[key]
end
end

class Test
include Foo
end

t = Test.new
t.bar(‘baz’) << ‘eeek’
p t.bar(‘baz’)

I didn’t followed the whole thread so please ignore me
if this was suggested before.

cheers

Simon


#11

Looks promising but what if you have to assign to the variable? This
code
throws an exception:

module Mixin
def myvar
@myvar ||= 100
@myvar
end

def inc_myvar
myvar += 1
end
end

class Klass
include Mixin
end

Klass.new.inc_myvar


#12

On Tue, 23 May 2006, Jeff R. wrote:

the preferred callback. This method is called when a module is included, and

end
end

Isn’t this a common thing to want?

and precisely one of the reasons i wrote traits - with traits you can
simply
ignore the issue altogether:

harp:~ > gem install traits

harp:~ > cat a.rb
require ‘rubygems’
require ‘traits’

module Foo
trait ‘myvar’ => Hash.new{|k,v| k[v] = []}
def bar(key) myvar[key] end
end

class Bar
include Foo
end

b = Bar.new

p b.bar(:k)

p b.myvar

harp:~ > ruby a.rb
[]
{:k=>[]}

:wink:

-a


#13

On Tue, 23 May 2006, Francis C. wrote:

myvar += 1
end
end

class Klass
include Mixin
end

Klass.new.inc_myvar

harp:~ > cat a.rb
require ‘traits’

module Mixin
trait ‘myvar’ => 100

 def inc_myvar() myvar( myvar + 1) end

end

class Klass
include Mixin
end

p Klass.new.inc_myvar

harp:~ > ruby a.rb
101

-a


#14

Francis C. wrote:

myvar += 1
self.myvar += 1


#15

Francis C. wrote:

Looks promising but what if you have to assign to the variable? This code
throws an exception:

[…]

def inc_myvar
myvar += 1
end
end

this is equivalent to

myvar = myvar + 1

which tries to initialize a local variable, thats not what you want.
try self.myvar += 1

this of course does only work if you define a setter for myvar.

module Mixin
attr_writer :myvar

def myvar
@myvar ||= 100
@myvar
end

def inc_myvar
self.myvar += 1
end
end

class Klass
include Mixin
end

Klass.new.inc_myvar

cheers

Simon


#16

Joel VanderWerf wrote:

Francis C. wrote:

Looks promising but what if you have to assign to the variable? This code
throws an exception:

module Mixin
def myvar
@myvar ||= 100
@myvar
end

Oops… forgot this, obviously (attr_writer is an alternative):

def myvar=(val)
  @myvar = val
end

#17

We’ve definitely wandered into one of Ruby’s darker, mustier corners.
None
of these solutions really sing.


#18

Hi,

In message “Re: initializing instance variables in a module”
on Tue, 23 May 2006 00:51:35 +0900, Jeff R. removed_email_address@domain.invalid
writes:

|Is there a standard or correct way to initialize @foo_the_stuff in this
|example?

This is not the answer, but I have been aware of this issue. I’m
planning to add method combination a la CLOS in the future to support
this issue. It is another option that adding a new hook like
‘module_initialize’ which is called at instantiation time for all
modules.

						matz.

#19

On Tue, 23 May 2006, Yukihiro M. wrote:

this issue. It is another option that adding a new hook like
‘module_initialize’ which is called at instantiation time for all
modules.

  					matz.

hi matz-

(rcr ahead)

the other day i realized that one could detect class creation using

class Class
def inherited

end
end

but that no such hook existed for class Module. could one be added such
as

class Module
def instantiated

end
end

to hook into rb_define_module/rb_define_module_under?

regards.

-a


#20

Hi,

On May 22, 2006, at 12:20 PM, Tim H. wrote:

def initialize(*args, &block)
end
What is wrong with what Tim suggests? I must be missing something.
Here is a specific example:

module Foo
def initialize
super
@foo_the_stuff = {}
end

def add_stuff(key, stuff)
@foo_the_stuff[key] = stuff
end
end

class Bar
include Foo

def show_stuff
puts “stuff: #{@foo_the_stuff.inspect}”
end
end

b1 = Bar.new
b2 = Bar.new
b1.add_stuff(“one”, 1)
b2.add_stuff(“two”, 2)
b1.add_stuff(“three”, 3)
b2.add_stuff(“four”, 4)

b1.show_stuff
b2.show_stuff

Cheers,
Bob


Bob H. – blogs at <http://www.recursive.ca/
hutch/>
Recursive Design Inc. – http://www.recursive.ca/
Raconteur – http://www.raconteur.info/
xampl for Ruby – http://rubyforge.org/projects/xampl/