Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby execute self.included(base) method on sub classes without explicit include declaration?

I have a number of classes that inherit from one superclass. The superclass as a module defined. Inside of a module is a self.included(base) method that sets some instance variables.

So something like this:

module MyModule
  def self.included(base)
    base.instance_variable_set("@my_instance_variable", {})
  end
end

class MySuperClass
  include MyModule
end

class ClassA < MySuperClass
end

class ClassB < MySuperClass
end

Unless I explicitly include MyModule in ClassA and ClassB then my instance variable will not get set in these two classes.

Is there a way of making sure the modules self.included(base) method is executed in each sub class without the need to explicitly include the module? Since it's already included in the superclass.

like image 548
KJF Avatar asked Apr 04 '12 12:04

KJF


1 Answers

Class instance variable are private to the class. Inherited classes can't access them directly. There is a couple of ways here.

1. Define accessor

module MyModule
  def self.included(base)
    base.instance_variable_set :@my_instance_variable, {}
    base.extend ClassMethods
  end

  module ClassMethods
    def my_instance_variable
      # self is ClassA here, so we need to call superclass
      superclass.instance_variable_get :@my_instance_variable # => {}
    end
  end
end

class MySuperClass
  include MyModule
end

class ClassA < MySuperClass; end

ClassA.my_instance_variable # => {}

2. More metaprogrammatic

But there's a hook that gets called every time when a class is inherited from you. You can set variables at this point. Check this out.

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

  module ClassMethods
    # make it a class method. It will be called on target classes later.
    def enhance klass 
      klass.instance_variable_set("@my_instance_variable", {})
    end
  end
end

class MySuperClass
  include MyModule

  # this hook will get called on 'class ClassA < MySuperClass`
  def self.inherited klass
    # set class instance var on child class
    enhance klass
  end
end

class ClassA < MySuperClass; end

ClassA.instance_variable_get '@my_instance_variable' # => {}

NOTE: in this sample each inherited class gets its own class instance var (and base class doesn't get one). This might be more appropriate for your case, might be not.

like image 141
Sergio Tulentsev Avatar answered Oct 13 '22 23:10

Sergio Tulentsev