David Elner David Elner - 6 months ago 10
Ruby Question

Call super in module-defined method from inheriting class

The title kind of sucks, so take this code instead:

module MyModule
def foo
puts "Foo!"
super if defined?(super)
end
end

class MyClass
include MyModule
end

class MySubclass < MyClass
include MyModule
end


If I call the following, "Foo!" prints once.

MySubclass.new.foo
# Foo!
# => nil


I'd like it to print twice, by calling the
MySubclass
one first, then the
MyClass
one second by doing
super
.

The
MySubclass
ancestry looks like:

[MySubclass, MyClass, MyModule, Object, Kernel, BasicObject]


I might be misunderstanding how Ruby does class, method ancestry, so if this isn't the way to do it, is there another clever way to accomplish this?

EDIT: Okay, sounds like I need to give a more useful use case. Let's say the module does something like ActiveRecord or Mongoid where you declare an attribute.

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

module ClassMethods
attr_reader :fields
def field(name)
(@fields ||= []) << name.to_sym
end
end
end


Then you use that to declare a field on class and subclass:

class MyClass
include MyModule
field(:a)
end

class MySubclass < MyClass
include MyModule
field(:b)
end


If you invoked
MySubclass.fields
, I don't want it to return
[:b]
I'd like it to return
[:a, :b]
, by calling
fields
on its parent class and concatenating its own onto the result.

Answer

Because these are class methods, you can kind of do an end-run around worrying about super by doing this:

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

  module ClassMethods
    def fields
      (@fields || []) + (superlcass.respond_to?(:fields) ? superclass.fields : []) 
    end
    def field(name)
      (@fields ||= []) << name.to_sym
    end
  end
end