Forum: Ruby How to add a instance variable through a mixin?

05b76b92415862cc4783d09a9009641b?d=identicon&s=25 MrBanabas@googlemail.com (Guest)
on 2008-02-12 19:55
(Received via mailing list)
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.
681cd7899ece502913de84cd428a1bf8?d=identicon&s=25 Scytrin dai Kinthra (Guest)
on 2008-02-12 20:13
(Received via mailing list)
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>
852a62a28f1de229dc861ce903b07a60?d=identicon&s=25 Gavin Kistner (phrogz)
on 2008-02-12 20:20
(Received via mailing list)
On Feb 12, 11:54 am, "MrBana...@googlemail.com"
<MrBana...@googlemail.com> wrote:
> 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.

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?
05b76b92415862cc4783d09a9009641b?d=identicon&s=25 MrBanabas@googlemail.com (Guest)
on 2008-02-12 20:55
(Received via mailing list)
> 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.
852a62a28f1de229dc861ce903b07a60?d=identicon&s=25 Gavin Kistner (phrogz)
on 2008-02-12 22:15
(Received via mailing list)
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.

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">
8f6f95c4bd64d5f10dfddfdcd03c19d6?d=identicon&s=25 Rick Denatale (rdenatale)
on 2008-02-12 22:19
(Received via mailing list)
On 2/12/08, MrBanabas@googlemail.com <MrBanabas@googlemail.com> wrote:
>     end
> 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/0...

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

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2008-02-12 22:55
(Received via mailing list)
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 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
8f6f95c4bd64d5f10dfddfdcd03c19d6?d=identicon&s=25 Rick Denatale (rdenatale)
on 2008-02-13 14:38
(Received via mailing list)
On 2/12/08, Robert Klemme <shortcutter@googlemail.com> wrote:
> And then
> 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:

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

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2008-02-13 19:06
(Received via mailing list)
On 13.02.2008 14:37, Rick DeNatale wrote:
>>
>>
>
> 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!

>   attr_reader :m_var
>
>     @a_var = :a_var
> end
> 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?

>   def initialize(d_val)
> # ~>   from -:35
>

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

Kind regards

  robert
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.