dan-klasson
dan-klasson

Reputation: 14180

Using overridden class constants in class methods

To override class constants in a subclass one can do:

class Foo
  CONST = [:foo, :baz]

  def self.const
    self::CONST
  end

end

class Bar < Foo
  CONST = [:foo, :bar]
end

print Foo.const # [:foo, :baz]
print Bar.const # [:foo, :bar]

This works as expected. The problem is when I try to call it from a class method, e.g when using define_method:

class Foo
  CONST = [:foo, :baz]

  def self.const
    self::CONST
  end

  self.const.each do |c|
    define_method("#{c}?") {
      "#{c} exists"
    }
  end
end


foo = Foo.new
print foo.baz? # baz exists.

bar = Bar.new
print bar.bar? # undefined method `bar?'

How would one go about overriding the class constant so the right method bar? will get defined in this case, without having to duplicate the code in the subclass? Is there a DRY way of doing this without having to use class variables instead of class constants?

Upvotes: 2

Views: 484

Answers (1)

james2m
james2m

Reputation: 1580

Because define_method is running in lexical scope, i.e. it's inline in the body of the Foo class definition so there is nothing to cause it to run in Bar.

class Foo
  CONST = [:foo, :baz]

  def self.define_const_methods(const)
    const.each do |c|
      define_method("#{c}?") { "#{c} exists" }
    end
  end

  define_const_methods(CONST)
end

class Bar < Foo
  CONST = [:foo, :bar]
  define_const_methods(CONST)
end

That should do the trick. So you call define_const_methods at the end of the Foo class in it's lexical scope. And you also call it on any class that inherits from it. The inheriting class should find it's own version of that constant.

But this is pretty ugly, so you could dispense with the constants altogether and just use the define_const_methods to define them. Just like ActiveRecord does when it defines the association methods (has_one, has_many etc). So then you can whittle it down to;

class Foo
  def self.define_my_methods(meths)
    meths.each do |c|
      define_method("#{c}?") { "#{c} exists" }
    end
  end

  define_my_methods [:foo, :baz]
end

class Bar < Foo
  define_my_methods [:foo, :bar]
end

Upvotes: 3

Related Questions