From: Robert Klemme Date: 2008-02-14T03:05:01+09:00 Subject: Re: How to add a instance variable through a mixin? On 13.02.2008 14:37, Rick DeNatale wrote: > On 2/12/08, Robert Klemme 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