Search code examples
rubyinstance-variablesclass-variables

ruby : how can I avoid hardcoding the classes names?


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


Solution

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