20

I want a child class to inherit a class-level instance variable from its parent, but I can't seem to figure it out. Basically I'm looking for functionality like this:

class Alpha
  class_instance_inheritable_accessor :foo #
  @foo = [1, 2, 3]
end

class Beta < Alpha
  @foo << 4
  def self.bar
    @foo
  end
end

class Delta < Alpha
  @foo << 5
  def self.bar
    @foo
  end
end

class Gamma < Beta
  @foo << 'a'
  def self.bar
    @foo
  end
end

And then I want this to output like this:

> Alpha.bar
# [1, 2, 3]

> Beta.bar
# [1, 2, 3, 4]

> Delta.bar
# [1, 2, 3, 5]

> Gamma.bar
# [1, 2, 3, 4, 'a']

Obviously, this code doesn't work. Basically I want to define a default value for a class-level instance variables in the parent class, which its subclasses inherit. A change in a subclass will be the default value then for a sub-subclass. I want this all to happen without a change in one class's value affecting its parent or siblings. Class_inheritable_accessor gives exactly the behavior I want... but for a class variable.

I feel like I might be asking too much. Any ideas?

Michael Durrant
  • 93,410
  • 97
  • 333
  • 497
wmjbyatt
  • 686
  • 5
  • 15
  • 1
    What's that `class_instance_inheritable_accessor`? – Sergio Tulentsev May 23 '12 at 22:34
  • 1
    It's nothing. It's a dream. That's what I WANT to be able to do. I want a functionality just like class_inheritable_accessor, but for class-level instance variables, instead of class variables. – wmjbyatt May 23 '12 at 22:48
  • Good luck with that. :) Out of curiosity, what do you want to accomplish with this? – Sergio Tulentsev May 23 '12 at 22:51
  • I'm using Resque to build a job engine with possibly several thousand jobs, so I want to be able to organize the jobs into an intelligent class hierarchy. Resque requires that a job class have @ queue set. I'd like to be able to define @ queue at a parent so that I don't have to define it for every single job. – wmjbyatt May 23 '12 at 23:08
  • 2
    This is where ruby becomes more sophisticated than most of its users. Instead of class-level-instance variables I use class methods. These get inherited in a more anticipated way. – pcv Sep 19 '13 at 22:28

3 Answers3

13

Rails has this built into the framework as a method called class_attribute. You could always check out the source for that method and make your own version or copy it verbatim. The only thing to watch out for is that you don't change the mutable items in place.

Community
  • 1
  • 1
Peter Brown
  • 50,956
  • 18
  • 113
  • 146
  • 2
    I knew this had to exist but my googlefu was letting me down. Thanks, very clean solution. – toxaq Nov 08 '14 at 13:02
10

What I did in my project for using resque is to define a base

class ResqueBase
  def self.inherited base
    base.instance_variable_set(:@queue, :queuename)
  end
end

In the other child jobs, the queue instance will be set by default. Hope it can help.

Oscar Jiang
  • 305
  • 3
  • 8
  • but does this only set a var, not add one to an array? – New Alexandria Nov 12 '12 at 23:59
  • Yes for this piece of code. But this way gives you full control of the child class who inherits from the base class. If you needs to add one to array, I think you can set an array instance variable in the base class in the same way as the queue name and get it, add to it in the child class. – Oscar Jiang Nov 13 '12 at 22:25
  • 1
    Remember about calling super – sheerun Sep 26 '13 at 12:52
7

Use a mixin:

module ClassLevelInheritableAttributes
  def self.included(base)
    base.extend(ClassMethods)    
  end

  module ClassMethods
    def inheritable_attributes(*args)
      @inheritable_attributes ||= [:inheritable_attributes]
      @inheritable_attributes += args
      args.each do |arg|
        class_eval %(
          class << self; attr_accessor :#{arg} end
        )
      end
      @inheritable_attributes
    end

    def inherited(subclass)
      @inheritable_attributes.each do |inheritable_attribute|
        instance_var = "@#{inheritable_attribute}"
        subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
      end
    end
  end
end

Including this module in a class, gives it two class methods: inheritable_attributes and inherited.
The inherited class method works the same as the self.included method in the module shown. Whenever a class that includes this module gets subclassed, it sets a class level instance variable for each of declared class level inheritable instance variables (@inheritable_attributes).

Michael Durrant
  • 93,410
  • 97
  • 333
  • 497
  • Why do `self.included` and a special class methods subclass have to be utilized instead of just using `extend` in the calling class? Is it necessary that the module also needs to be included? Or is this just preference. – konsolebox Nov 01 '22 at 03:39