From: Tim Bates Date: 2003-08-06T14:55:31+09:00 Subject: Class variables, module inclusion and instance_eval Hi all, I've been writing a library that one uses by including it in one's own classes. The purpose of the library is not important - the problem is to do with including modules in classes, in particular to do with class variables. Exhibit A: irb(main):001:0> module Mod irb(main):002:1> @@foo = "Mod" irb(main):003:1> end => "Mod" irb(main):004:0> class KlassA irb(main):005:1> include Mod irb(main):006:1> end => KlassA irb(main):007:0> class KlassB irb(main):008:1> include Mod irb(main):009:1> @@foo = "B" irb(main):010:1> end => "B" irb(main):011:0> class KlassA irb(main):012:1> puts @@foo irb(main):013:1> end B => nil If I create a class variable from within a module, that variable is shared between all classes that include it. So far, so good - this is useful. Exhibit B: irb(main):001:0> module Mod irb(main):002:1> end => nil irb(main):003:0> class KlassA irb(main):004:1> include Mod irb(main):005:1> @@foo = "A" irb(main):006:1> end => "A" irb(main):007:0> class KlassB irb(main):008:1> include Mod irb(main):009:1> @@foo = "B" irb(main):010:1> end => "B" irb(main):011:0> class KlassA irb(main):012:1> puts @@foo irb(main):013:1> end A => nil If I create a class variable from within a class, then no matter how many modules or superclasses the classes have in common, it doesn't get shared. No surprises thus far - POLS, and all. Exhibit B.2: irb(main):014:0> module Mod irb(main):015:1> @@foo = "Mod" irb(main):016:1> end => "Mod" irb(main):017:0> class KlassA irb(main):018:1> puts @@foo irb(main):019:1> end A => nil irb(main):020:0> class KlassB irb(main):021:1> puts @@foo irb(main):022:1> end B => nil Even if I subsequently define the variable in the module, it still doesn't get shared. This is probably also POLS - having variables clobber each other later is somewhat surprising. Now, here comes the fun bit. When you include a module in a class, the module's append_features method gets called, and the class passed as an argument. Watch this: irb(main):001:0> module Mod irb(main):002:1> def self.append_features(aModule) irb(main):003:2> super irb(main):004:2> aModule.instance_eval do irb(main):005:3* @@foo = aModule.name irb(main):006:3> end irb(main):007:2> end irb(main):008:1> end => nil irb(main):009:0> class KlassA irb(main):010:1> include Mod irb(main):011:1> end => KlassA irb(main):012:0> class KlassB irb(main):013:1> include Mod irb(main):014:1> end => KlassB irb(main):015:0> class KlassA irb(main):016:1> puts @@foo irb(main):017:1> end KlassB => nil Somehow, even though the class variable is defined within the context of the class (via instance_eval) it still gets shared, contrary to the example of Exhibit B. This is frustrating me, because I want my library to be able to define a class variable that belongs to only one class, and I can't find a way to do it. Currently, I'm doing this: irb(main):001:0> module Mod irb(main):002:1> @@foo = {} irb(main):003:1> def self.append_features(aModule) irb(main):004:2> super irb(main):005:2> aModule.module_eval do irb(main):006:3* @@foo[__id__] = aModule.name irb(main):007:3> end irb(main):008:2> end irb(main):009:1> end => nil irb(main):010:0> class KlassA irb(main):011:1> include Mod irb(main):012:1> end => KlassA irb(main):013:0> class KlassB irb(main):014:1> include Mod irb(main):015:1> end => KlassB irb(main):016:0> class KlassA irb(main):017:1> puts @@foo[__id__] irb(main):018:1> end KlassA => nil It works, but it's messy and hackish and breaks encapsulation - if you know the class's id, you can get at its "class variable". Can anyone see a better way to do this, and/or explain to me why Ruby behaves this way in the first place? Tim Bates -- tim@bates.id.au