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—subclasses using instance variables of the parent
One quick way to achieve your goal would be to use the little known SimpleDelegator class:
class A
attr_reader :var
def initialize(var)
@var = var
end
end
require 'delegate'
class B < SimpleDelegator
def test
puts var
end
end
a1 = A.new('one')
a2 = A.new('two')
b1 = B.new(a1)
b1 = B.new(a1)
b2 = B.new(a2)
b3 = B.new(a2)
b3.test
#=> 'two'
b2.test
#=> 'two'
b1.test
#=> 'one'
This blog article ("5 ways of forwarding your work to some other object in Ruby") might be of interest to you.
Inheritance and instance variable in Ruby
B
inherits initialize
from A
.
At object creation, initialize
is invoked. So you get @x
set to 2
even for objects of class B
.
I think, the sentences you are quoting refer to this scenario:
class A
def initialize
@x = 42
end
end
class B < A
def initialize
@x = 23
end
end
h = B.new
Now, h
has just one instance variable @x
with value 23
. It is not like there is one @x
from B
and one from A
. You can see this here:
class A
def initialize
@x = 42
end
def set_x_from_a
@x = 12
end
def print_x_from_a
puts @x
end
end
class B < A
def initialize
@x = 23
end
def set_x_from_b
@x = 9
end
def print_x_from_b
puts @x
end
end
h = B.new
h.print_x_from_a # => 23
h.print_x_from_b # => 23
h.set_x_from_a
h.print_x_from_b # => 12
h.set_x_from_b
h.print_x_from_a # => 9
Ruby class instance variables and inheritance
Since you mention that the attributes are "fixed" and "unchanging", I am assuming that you mean that you will never change their value once the object is created. In that case, something like the following should work:
class Foo
ATTRS = ['title', 'authors', 'location']
def attributes
ATTRS
end
end
class Bar < Foo
ATTRS = ['ISBN', 'pages']
def attributes
super + ATTRS
end
end
You are manually implementing a reader method (instead of letting attr_accessor
create it for you) that disguises the internal name of the array. In your subclass, you simply call the ancestor class' reader function, tack on the additional fields associated with the child class, and return that to the caller. To the user, this appears like a read-only member variable named attributes
that has additional values in the sub-class.
Instance Variables Inheritance
You are right, the book is wrong, or at least poorly worded
I would argue that the book is simply wrong, or at best, it's making a quite muddy explanation.
In all OO languages, the superclass and derived class don't have separate objects. When you create an instance of the derived class, it is also an instance of the superclass. There is one object and it is both classes at once.
Since there is only one object, there is only one set of instance variables.
This is the same as all other OO systems. The weird argument that book makes about how it just matters which method is run and how the methods themselves are what are really inherited does not add much in the way of clarity.
The problem with the terminology is that, sure, in a dynamically typed system there is no declaration in the first place, and so certainly the definition of the subclass doesn't inherit any field declarations ... because of course there aren't any. But just because there are no types to inherit doesn't make the opposite statement ("instance variables are not inherited") any more true, and it adds quite a bit of confusion because it implies that somehow the parent would have different instance variables, which is the nonsensical result of trying to talk about the objects the way they do.
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
Initial value for class instance variable in inheritance chain
While the suggestion provided by @Stefan in comments is probably the way to go, it’s still possible with metaprogramming; one just needs to handle inheritance.
module Foo
def self.included(base)
base.class_eval { @foos = [] }
class << base
attr_reader :foos
def inherited(other)
Foo.included(other)
end
end
end
end
class Bar
include Foo
end
class Baz < Bar; end
Bar.foos #⇒ []
Baz.foos #⇒ []
Ruby class instance variable vs. class variable
Instance variable on a class:
class Parent
@things = []
def self.things
@things
end
def things
self.class.things
end
end
class Child < Parent
@things = []
end
Parent.things << :car
Child.things << :doll
mom = Parent.new
dad = Parent.new
p Parent.things #=> [:car]
p Child.things #=> [:doll]
p mom.things #=> [:car]
p dad.things #=> [:car]
Class variable:
class Parent
@@things = []
def self.things
@@things
end
def things
@@things
end
end
class Child < Parent
end
Parent.things << :car
Child.things << :doll
p Parent.things #=> [:car,:doll]
p Child.things #=> [:car,:doll]
mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new
[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
With an instance variable on a class (not on an instance of that class) you can store something common to that class without having sub-classes automatically also get them (and vice-versa). With class variables, you have the convenience of not having to write self.class
from an instance object, and (when desirable) you also get automatic sharing throughout the class hierarchy.
Merging these together into a single example that also covers instance variables on instances:
class Parent
@@family_things = [] # Shared between class and subclasses
@shared_things = [] # Specific to this class
def self.family_things
@@family_things
end
def self.shared_things
@shared_things
end
attr_accessor :my_things
def initialize
@my_things = [] # Just for me
end
def family_things
self.class.family_things
end
def shared_things
self.class.shared_things
end
end
class Child < Parent
@shared_things = []
end
And then in action:
mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new
Parent.family_things << :house
papa.family_things << :vacuum
mama.shared_things << :car
papa.shared_things << :blender
papa.my_things << :quadcopter
joey.my_things << :bike
suzy.my_things << :doll
joey.shared_things << :puzzle
suzy.shared_things << :blocks
p Parent.family_things #=> [:house, :vacuum]
p Child.family_things #=> [:house, :vacuum]
p papa.family_things #=> [:house, :vacuum]
p mama.family_things #=> [:house, :vacuum]
p joey.family_things #=> [:house, :vacuum]
p suzy.family_things #=> [:house, :vacuum]
p Parent.shared_things #=> [:car, :blender]
p papa.shared_things #=> [:car, :blender]
p mama.shared_things #=> [:car, :blender]
p Child.shared_things #=> [:puzzle, :blocks]
p joey.shared_things #=> [:puzzle, :blocks]
p suzy.shared_things #=> [:puzzle, :blocks]
p papa.my_things #=> [:quadcopter]
p mama.my_things #=> []
p joey.my_things #=> [:bike]
p suzy.my_things #=> [:doll]
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.
Create a new instance of parent's class variable for each child class
As @CarySwoveland comments, in your use case, you should use a class instance variable:
class ListBase
@list = []
def self.list
@list
end
end
Not even sure why you thought of using a class variable for your use case.
Related Topics
Rails - Displaying Foreign Key References in a Form
Ruby CSV Parsing String with Escaped Quotes
Pg Error Could Not Connect to Server: Connection Refused Is the Server Running on Port 5432
Are Ruby 1.9 Regular Expressions Equally Powerful to a Context Free Grammar
"Errno::Eaccess...Permission Denied" Running Compass Watch
Model Using Modules in Rails Application
Open and Save Base64 Encoded Image Data Uri in Ruby
How to Start the Ruby Debugger on Exception
Cucumber + Webrat + Selenium Guide
Parallel Http Requests in Ruby
Simple Ruby Input Validation Library
Sinatra Static Assets Are Not Found When Using Rackup