Reputation: 14180
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
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