[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

How to add a instance variable through a mixin?

MrBanabas@googlemail.com

2/12/2008 6:55:00 PM

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

8 Answers

Scytrin dai Kinthra

2/12/2008 7:13:00 PM

0

[Note: parts of this message were removed to make it a legal post.]

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://...

Phrogz

2/12/2008 7:15:00 PM

0

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.

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?

MrBanabas@googlemail.com

2/12/2008 7:55:00 PM

0

> 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

Phrogz

2/12/2008 9:13:00 PM

0

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

Rick DeNatale

2/12/2008 9:19:00 PM

0

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.

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

http://talklikeaduck.denh...articles/2008/02/08/whose-variable-is...

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.denh...

Robert Klemme

2/12/2008 9:50:00 PM

0

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

Rick DeNatale

2/13/2008 1:37:00 PM

0

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

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.denh...

Robert Klemme

2/13/2008 6:01:00 PM

0

On 13.02.2008 14:37, Rick DeNatale wrote:
> 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
>
> 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