Ruby: Inherit Code That Works With Class Variables

Ruby: Inherit code that works with class variables

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.

How to inherit class variables from parents included module in Ruby

class_attribute takes care of the required inheritance semantics for you. As you've seen, it's not quite as simple as putting an instance variable on the class, because that leaves it nil on subclasses: they're instance variables, and that's a separate instance.

The examples you mention do other, more specialised and more complicated, things... but you'll find plenty of straightforward examples in the Rails source that do use class_attribute.

ruby: share class variable inheritance

Try the following:

module M
def self.included(base)
base.extend ClassMethods
base.instance_eval do
cattr_accessor :toto, instance_writer: false, instance_reader: false do
{}
end
end
end

module ClassMethods
# class methods
end
end


class A
include M
end

class B < A
end

puts A.toto
puts B.toto
puts A.toto = 12
puts B.toto

# {}
# {}
# 12
# 12

How to declare Class Instance Variable in inherited class automatically

I don't know if this is the best way, but here is how I might do it:

class Entity
singleton_class.send(:attr_writer, :instance_counter)
def self.instance_counter; @instance_counter ||= 0; end
def initialize
self.class.instance_counter += 1
end
end

Inherit class-level instance variables in Ruby?

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

Ruby and class variables in inherit class

Here is a nice article on the subject - Class and Instance Variables In Ruby.

Basically, what you can do is:

class A
class << self
attr_accessor :class_var
end

def set_class_var(value)
self.class.class_var = value
end

def get_class_var
self.class.class_var
end
end

class B < A; end

A.class_var = 'a'
B.class_var = 'b'
puts A.class_var # => a
puts B.class_var # => b

A.new.set_class_var 'aa'
B.new.set_class_var 'bb'
puts A.new.get_class_var # => aa
puts B.new.get_class_var # => bb

To understand it you should think about A as an instance of Class class (and that's how it is in Ruby). But every object in Ruby has its own singleton class that stores object-specific stuff like methods defined on object itself:

a = A.new
def a.foo
puts 'foo'
end

In that case foo is method defined only for a object and not for every instance of A class. And another way to define method in object's singleton class is like that:

class << a # open a's singleton class
def bar # define method that will be available only on 'a' object
puts 'bar'
end
end

In the first code snippet we use that approach to define class_var attribute accessor in the context of singleton class of our A class (it's a bit tricky, so you need to think about it). As the result class itself has class_var variable as well as its descendant class B. The difference is that every one of them has its own class_var variable that do not interfere.

Use cases of class variables

I interpret what you mean by strange as the fact that class variables are not different among each class but are shared among the inheritance hierarchy of classes.

As you mention and is also pointed out in Jörg W Mittag's answer to this question, it you wanted a variable to be consistent withing a class but differ outside of the class, then you can use a class instance variable on that class. That means, class variables should behave differently from that if it has any reason to exist. And class variables are indeed shared among classes within the inheritance hierarchy.

class A
@@foo = 3
end
class B < A
puts @@foo
end
# => 3

Probably, it may be useful when you want to share something that are common to classes in the hierarchy. For example, suppose you have some feature in a class that would be used in the classes that inherit this class. Suppose that that feature is dependent on a parameter, and you want that parameter change to be consistent among the classes that inherit that feature. Then, class variable will be useful.

For example, suppose you have this method:

class Array
def join_with_delimiter
join(@@delimiter)
end
end
class A < Array; end

By setting @@delimiter once, you can expect consistent behavior across Array and A.

class Array; @@delimiter = "*" end
Array.new([:a, :b, :c]).join_with_delimiter # => "a*b*c"
A.new([:a, :b, :c]).join_with_delimiter # => "a*b*c"

class A; @@delimiter = "#" end
Array.new([:a, :b, :c]).join_with_delimiter # => "a#b#c"
A.new([:a, :b, :c]).join_with_delimiter # => "a#b#c"

If you do that with a class instance variable, you would not get consistent behavior:

class Array
def self.delimiter; @delimiter end
def join_with_delimiter
join(self.class.delimiter)
end
end
class A < Array; end

class Array; @delimiter = "*" end
Array.new([:a, :b, :c]).join_with_delimiter # => "a*b*c"
A.new([:a, :b, :c]).join_with_delimiter # => "abc"

class A; @delimiter = "#" end
Array.new([:a, :b, :c]).join_with_delimiter # => "a*b*c"
A.new([:a, :b, :c]).join_with_delimiter # => "a#b#c"


Related Topics



Leave a reply



Submit