How to add a instance variable through a mixin?

Hi,

I would like to have a mixin which adds an instance variable to a
class. I ve tried it the following way:

I ve defined a little module:
module MyApp
  module MyMixin
    def self.included(base)
      @should_be_an_instance_variable = nil
    end
end
end

which is included by my little class:
class MyClass
  include MyApp::MyMixin
end

However, it seems that @should_be_an_instance_variable is currently
defined per mixin, but I would like to have a unique one for each
object of MyClass.

Maybe anybody can help me?
Thanks a lot in advance.

···

--
Volker

base.instance_variable_set('@new_ivar',nil)
.included is run bound to MyMixin, not the object in base. Alternately there
is usage of the *eval methods.

···

On Feb 12, 2008 10:55 AM, MrBanabas@googlemail.com <MrBanabas@googlemail.com> wrote:

Hi,

I would like to have a mixin which adds an instance variable to a
class. I ve tried it the following way:

I ve defined a little module:
module MyApp
module MyMixin
   def self.included(base)
     @should_be_an_instance_variable = nil
   end
end
end

which is included by my little class:
class MyClass
include MyApp::MyMixin
end

However, it seems that @should_be_an_instance_variable is currently
defined per mixin, but I would like to have a unique one for each
object of MyClass.

Maybe anybody can help me?
Thanks a lot in advance.

--
Volker

--
Adam McCall || http://stadik.net

If you mix the module in after some instances of MyClass have already
been created, do you want it to dynamically create the instance
variables in those instances?

If you mix the module in before some instances of MyClass have been
created, do you want it to run code each time a new instance is
created?

···

On Feb 12, 11:54 am, "MrBana...@googlemail.com" <MrBana...@googlemail.com> wrote:

I ve defined a little module:
module MyApp
  module MyMixin
    def self.included(base)
      @should_be_an_instance_variable = nil
    end
end
end

which is included by my little class:
class MyClass
  include MyApp::MyMixin
end

However, it seems that @should_be_an_instance_variable is currently
defined per mixin, but I would like to have a unique one for each
object of MyClass.

You don't really add instance variables for the instances of classes
to classes in Ruby.

http://talklikeaduck.denhaven2.com/articles/2008/02/08/whose-variable-is-it-anyway

If you add a method to a class through a mixin which refers to an
instance variable, the variable will be created dynamically in the
instance as needed when the method is run. if you want to initialize
it to something other than nil, you can use techniques like lazy
initialization.

module M
    def iv
         @iv ||= 0 # or whatever you want the default value to be
    end
end

···

On 2/12/08, MrBanabas@googlemail.com <MrBanabas@googlemail.com> wrote:

Hi,

I would like to have a mixin which adds an instance variable to a
class. I ve tried it the following way:

I ve defined a little module:
module MyApp
  module MyMixin
    def self.included(base)
      @should_be_an_instance_variable = nil
    end
end
end

which is included by my little class:
class MyClass
  include MyApp::MyMixin
end

However, it seems that @should_be_an_instance_variable is currently
defined per mixin, but I would like to have a unique one for each
object of MyClass.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

If you mix the module in after some instances of MyClass have already
been created, do you want it to dynamically create the instance
variables in those instances?

If you mix the module in before some instances of MyClass have been
created, do you want it to run code each time a new instance is
created?

I would be interested in both cases, but for my particularly case it s
the second one.

···

--
Volker

Here's a way to do it:

module Rands
  def self.included( klass )
    # Modify existing instances
    ObjectSpace.each_object( klass ){ |inst|
      inst.initialize_rands
    }

    # Replace the initialization with your own
    klass.class_eval{
      # Beware name clashes
      alias_method :init_pre_rand, :initialize
      def initialize( *args )
        init_pre_rand( *args )
        initialize_rands
      end
    }
  end

  def initialize_rands
    @foo = "%.2f" % rand
  end
end

class Foo
  def initialize( id )
    @id = id
  end
end

f1 = Foo.new( 1 )
f2 = Foo.new( 2 )
class Foo
  include Rands
end
f3 = Foo.new( 3 )
f4 = Foo.new( 4 )

p f1, f2, f3, f4
#=> #<Foo:0x7ffa696c @id=1, @foo="0.92">
#=> #<Foo:0x7ffa691c @id=2, @foo="0.57">
#=> #<Foo:0x7ffa6908 @id=3, @foo="0.40">
#=> #<Foo:0x7ffa68cc @id=4, @foo="0.41">

···

On Feb 12, 12:54 pm, "MrBana...@googlemail.com" <MrBana...@googlemail.com> wrote:

> If you mix the module in after some instances of MyClass have already
> been created, do you want it to dynamically create the instance
> variables in those instances?

> If you mix the module in before some instances of MyClass have been
> created, do you want it to run code each time a new instance is
> created?

I would be interested in both cases, but for my particularly case it s
the second one.

You can do

module Foo
   attr_accessor :var

   def initialize(*a,&b)
     @var = 10
   end
end

And then

irb(main):015:0> class Bar
irb(main):016:1> include Foo
irb(main):017:1> end
=> Bar
irb(main):018:0> Bar.new.var
=> 10

Or

irb(main):026:0> class Oink
irb(main):027:1> include Foo
irb(main):028:1> def initialize
irb(main):029:2> super
irb(main):030:2> @x = 10
irb(main):031:2> end
irb(main):032:1> end
=> nil
irb(main):033:0> Oink.new.var
=> 10

Kind regards

  robert

···

On 12.02.2008 20:54, MrBanabas@googlemail.com wrote:

If you mix the module in after some instances of MyClass have already
been created, do you want it to dynamically create the instance
variables in those instances?

If you mix the module in before some instances of MyClass have been
created, do you want it to run code each time a new instance is
created?

I would be interested in both cases, but for my particularly case it s
the second one.

You have to be careful here, since it relies on invoking super in the
initialize method, and since the initialize method in the module
doesn't do this, it can break under the right conditions:

class A
  attr_reader :a_var

  def initialize
    @a_var = :a_var
  end

end

module M
  attr_reader :m_var
  def initialize
    @m_var = :m_var
  end
end

class B < A
  include M
end

B.new.instance_variables # => ["@m_var"]

Note that the A instance didn't get an @a_var instance variable.

The solution here is to invoke the superclass' initialize, which also
works for subclasses which don't have an initialize method themselves:

class A
  attr_reader :a_var

  def initialize
    @a_var = :a_var
  end

end

module M
  attr_reader :m_var
  def initialize
    super
    @m_var = :m_var
  end
end

class B < A
  include M
end

class C
  include M
end

B.new.instance_variables # => ["@a_var", "@m_var"]
C.new.instance_variables # => ["@m_var"]

But this is hard to do in general when the initialize methods take
different parameters.

module M
  attr_reader :m_var
  def initialize(*a, &b)
    super
    @m_var = :m_var
  end
end

class D
  include M
  def initialize(d_val)
    @d_var = d_val
    super
  end
end

D.new(10).instance_variables # =>
# ~> -:13:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
# ~> from -:13:in `initialize'
# ~> from -:30:in `initialize'
# ~> from -:35:in `new'
# ~> from -:35

···

On 2/12/08, Robert Klemme <shortcutter@googlemail.com> wrote:

You can do

module Foo
   attr_accessor :var

   def initialize(*a,&b)
     @var = 10
   end
end

And then

irb(main):015:0> class Bar
irb(main):016:1> include Foo
irb(main):017:1> end
=> Bar
irb(main):018:0> Bar.new.var
=> 10

Or

irb(main):026:0> class Oink
irb(main):027:1> include Foo
irb(main):028:1> def initialize
irb(main):029:2> super
irb(main):030:2> @x = 10
irb(main):031:2> end
irb(main):032:1> end
=> nil
irb(main):033:0> Oink.new.var
=> 10

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

You can do

module Foo
   attr_accessor :var

   def initialize(*a,&b)
     @var = 10
   end
end

And then

irb(main):015:0> class Bar
irb(main):016:1> include Foo
irb(main):017:1> end
=> Bar
irb(main):018:0> Bar.new.var
=> 10

Or

irb(main):026:0> class Oink
irb(main):027:1> include Foo
irb(main):028:1> def initialize
irb(main):029:2> super
irb(main):030:2> @x = 10
irb(main):031:2> end
irb(main):032:1> end
=> nil
irb(main):033:0> Oink.new.var
=> 10

You have to be careful here, since it relies on invoking super in the
initialize method, and since the initialize method in the module
doesn't do this, it can break under the right conditions:

Absolutely!

class A
  attr_reader :a_var

  def initialize
    @a_var = :a_var
  end

end

module M
  attr_reader :m_var
  def initialize
    @m_var = :m_var
  end
end

class B < A
  include M
end

B.new.instance_variables # => ["@m_var"]

Note that the A instance didn't get an @a_var instance variable.

The solution here is to invoke the superclass' initialize, which also
works for subclasses which don't have an initialize method themselves:

class A
  attr_reader :a_var

  def initialize
    @a_var = :a_var
  end

end

module M
  attr_reader :m_var
  def initialize
    super
    @m_var = :m_var
  end
end

class B < A
  include M
end

class C
  include M
end

B.new.instance_variables # => ["@a_var", "@m_var"]
C.new.instance_variables # => ["@m_var"]

But this is hard to do in general when the initialize methods take
different parameters.

Well, as long as you make it a convention to do this in modules

def initialize(*a,&b)
   super
   # more
end

or have no #initialize in modules and make any class pass the proper super according to its superclass. I for my part would even opt to apply the following changes to the language:

1. ignore all arguments to a module's initialize

2. automatically do a super in module constructors which passes on the arguments passed by the class's constructor.

In other words: keep invocation of a module's initialize in the call chain but automate it to an extend that only class initialize pass on data to their superclasses. At least it seems the language has some room for improvements in this area. But then again, people do seem to rarely stumble across this, or do they?

module M
  attr_reader :m_var
  def initialize(*a, &b)
    super
    @m_var = :m_var
  end
end

class D
  include M
  def initialize(d_val)
    @d_var = d_val
    super
  end
end

D.new(10).instance_variables # =>
# ~> -:13:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
# ~> from -:13:in `initialize'
# ~> from -:30:in `initialize'
# ~> from -:35:in `new'
# ~> from -:35

But this will also break without the module because Object does not accept arguments to #initialize.

Kind regards

  robert

···

On 13.02.2008 14:37, Rick DeNatale wrote:

On 2/12/08, Robert Klemme <shortcutter@googlemail.com> wrote: