views:

595

answers:

1

The situation: I have multiple classes that should each hold a variable with a configuration hash; a different hash for each class but the same for all instances of a class.

At first, i tried like this

class A
  def self.init config
    @@config = config
  end

  def config
    @@config
  end
end

class B < A; end
class C < A; end

But soon noticed that it wouldn't work that way because @@config is held in the context of A, not B or C, thus:

B.init "bar"
p B.new.config  # => "bar"
p C.new.config  # => "bar" - which would be nil if B had it's own @@config

C.init "foo"
p B.new.config  # => "foo" - which would still be "bar" if C had it's own @@config
p C.new.config  # => "foo"

I thought of using it like this:

modules = [B, C]
modules.each do |m|
  m.init(@config[m.name])
end
# ...
B.new  # which should then have the correct config

Now, it's clear to me why that happens, but I'm not sure about the reason for it being like this.

Couldn't it work the other way too, holding the class variable in the context of the subclass?

What i also found irritating was the fact that self is always the subclass even when called 'in' the superclass. From this, I first expected the code from the superclass is "executed in the context of" the subclass.

Some enlightenment about this would be greatly appreciated.

On the other hand, I likely have to accept it works that way and that I have to find another way to do this.

Is there a "meta" way to do this? (I tried with class_variable_set etc. but with no luck)

Or maybe is the whole idea of that 'init' method flawed in the first place and there's some other "pattern" to do this?

I could just make @@config a hash, holding all the configs and always pick the right one, but I find that a little awkward.. (isn't inheritance there to solve this kind of problem? ;)

+4  A: 

The @@variables aren't class variables. They are class hierarchy variables, i.e. they are shared between the entire class hierarchy, including all subclasses and all instances of all subclasses. (It has been suggested that one should think of @@variables more like $$variables, because they actually have more in common with $globals than with @ivars. That way lies less confusion. Others have gone further and suggest that they should simply be removed from the language.)

Ruby doesn't have class variables in the sense that, say, Java (where they are called static fields) has them. It doesn't need class variables, because classes are also objects, and so they can have instance variables just like any other object. All you have to do is to remove the extraneous @s. (And you will have to provide an accessor method for the class instance variable.)

class A
  def self.init config
    @config = config
  end

  def self.config # This is needed for access from outside
    @config
  end

  def config
    self.class.config # this calls the above accessor on self's class
  end
end

Let's simplify this a bit, since A.config is clearly just an attribute_reader:

class A
  class << self
    def init config
      @config = config
    end

    attr_reader :config
  end

  def config
    self.class.config
  end
end

And, in fact, A.init is just a writer with a funny name, so let's rename it to A.config= and make it a writer, which in turn means that our pair of methods is now just an accessor pair. (Since we changed the API, the test code has to change as well, obviously.)

class A
  class << self
    attr_accessor :config
  end

  def config
    self.class.config
  end
end

class B < A; end
class C < A; end

B.config = "bar"
p B.new.config  # => "bar"
p C.new.config  # => nil

C.config = "foo"
p B.new.config  # => "bar"
p C.new.config  # => "foo"

However, I can't shake the feeling that there is something more fundamentally iffy about the design, if you need this at all.

Jörg W Mittag
I don't see how the design is iffy. It seems like a reasonable enough thing to do in general.
Chuck
That's exactly what i needed to know, thank you very much! :)Don't really know what else to say, it's all so clear now.The init method was intended to setup multiple variables, i got with the config example for simplicity. But now you mention it, it's probably still cleaner with accessors ;)Again, thanks very much!
@Chuck: For example, there's an instance method (`A#config`) which neither calls an instance method nor accesses instance state nor gets overridden. That might be an artifact of the trimmed down example, it might be a legitimate design, but, maybe, it's not. Also, B and C inherit from A, but don't override anything, yet, they are somehow expected to have different behavior both from each other and from A, even though they are all identical. Again: maybe sensible, maybe not. It all depends on the context, which of course is much too small in this example to reach any sensible conclusion.
Jörg W Mittag
Or, maybe (very likely, actually) I just don't understand what it's all about :-)
Jörg W Mittag
Okay, I'll try to explain in short. This example of course is very simplified. I have a drb-like server that is invoked with a class and serves an instance of this class per connection. And actually, it serves not only one but multiple classes. Currently, there is nothing special about these classes, it initializes them and calls #send according to what the client requests. I am now attempting to pass configuration/logger/authentication info to each class making it accessible to instance methods that are called by the client. Hope that makes sense ;)
Thanks, this is the best explanation I saw until now. I had exactly the same necessity. Because I wanted each of my class to load an ERB template. But each instance is run for each server request.
yogsototh