I'm learning Ruby and the difference between class variables & instance variables.
I'm working on a piece of code where I have (a lot of) classes inheriting other classes.
class childImporter < parentImporter
def self.infos
parentImporter.infos.merge({
:name=> 'My Importer',
})
end
def self.schema
schema = parentImporter.schema.deep_merge({
'selectors' => {
'track' => {
'artist'=> {'path'=>{'default'=>'//creator'}},
'title'=> {'path'=>{'default'=>['//name'}},
}
}
})
@@schema = schema
end
def initialize(params = {})
super(params,childImporter.schema)
end
end
I have two class variables: infos (importer informations) and schema (json schema).
I need them to be able to get them outside an instance (that is why they are class variables), and to be an extension of their parent value (that is why I deep_merge them), and
My example actually works, but I wonder if there is a way not to hardcode the classes names childImporter and parentImporter and rather use a reference to the parent class, for example having
schema = PARENTCLASS.schema.deep_merge({
instead of
schema = parentImporter.schema.deep_merge({
or
super(params,THISCLASS.schema)
instead of
super(params,childImporter.schema).
Is there a way to achieve this ?
Currently, if I try
super(params,@@schema)
I get
NameError: uninitialized class variable @@schema in childImporter
Thanks
I wonder if there is a way not to hardcode the classes names childImporter and parentImporter and rather use a reference to the parent class, for example having
schema = PARENTCLASS.schema.deep_merge({
instead of
schema = parentImporter.schema.deep_merge({
The method you are looking for is superclass
– it returns the receiver's parent class. From within a class body or class method, you can call it without an explicit receiver:
class ParentImporter
def self.infos
{ name: 'Parent Importer', type: 'Importer' }
end
end
class ChildImporter < ParentImporter
def self.infos
superclass.infos.merge(name: 'Child Importer')
end
end
ParentImporter.infos #=> {:name=>"Parent Importer", :type=>"Importer"}
ChildImporter.infos #=> {:name=>"Child Importer", :type=>"Importer"}
But there's an even easier way. Classes inherit both, the class methods and the instance methods from their parent class. And in both variants, you can simply call super
to invoke the parent's implementation:
class ChildImporter < ParentImporter
def self.infos
super.merge(name: 'Child Importer')
end
end
ParentImporter.infos #=> {:name=>"Parent Importer", :type=>"Importer"}
ChildImporter.infos #=> {:name=>"Child Importer", :type=>"Importer"}
In addition, you might want to memoize the values so they are not re-created every time the methods are called:
class ParentImporter
def self.infos
@infos ||= { name: 'Parent Importer', type: 'Importer' }
end
end
class ChildImporter < ParentImporter
def self.infos
@infos ||= super.merge(name: 'Child Importer')
end
end
Those @infos
are so-called class instance variables, i.e. instance variables in the scope of the class object(s). They behave exactly like instance variables in casual instances. In particular, there's no connection between the @infos
in ParentImporter
and the one in ChildImporter
.
or
super(params,THISCLASS.schema)
instead of
super(params,childImporter.schema).
To get an object's class, you can call its class
method:
importer = ChildImporter.new
importer.class #=> ChildImporter
importer.class.infos #=> {:name=>"Child Importer", :type=>"Importer"}
The same works from within an instance method:
def initialize(params = {})
super(params, self.class.schema)
end
Note that the class
method must always be called with an explicit receiver. Omitting the receiver and just writing class.schema
results in an error.
Bottom note: I wouldn't use @@
class variables at all. Just call your class methods.