Reputation: 293
I'm working on an application where I have a plugin-style architecture with multiple abstract classes that define an API, and specific implementations with subclasses that provide methods for the attributes defined on the abstract classes. Think of a generic database adapter with abstract classes like Connection, Table, Column, etc. and specific subclasses of these for a particular database.
In an attempt to be DRY, I declare the attributes of each abstract class (with default values) as a class constant Array called ATTRIBUTES
. Since ruby doesn't support true abstract classes, I dynamically create methods that raise an exception if they're called on the abstract class. For example:
module MyApplication
class Operation
ATTRIBUTES = { name: '', parameters: [], type: :void }
ATTRIBUTES.keys.each do |a|
str = "Method '%s' called on abstract 'MyApplication::Operation' class"
define_method(a) { raise (str % a) }
define_method("#{a}=") { |x| raise (str % "#{a}=") }
end
end
end
I hard code the class name in the message because I can't use the name of the current class, since it might be a subclass of the abstract Operation
class, and I want the message to be clear that the method being called is on the abstract class.
I have more than a handful of these abstract classes, and I've copied nearly the same code into each one to create these stub methods (only the attributes list and class name are different). I've tried numerous ways to put this code into a module that I can include (or extend?) from each abstract class, but my brain is now folded in on itself trying to sort out the meta-programming details of how to get this to work. :)
Does anyone have the magic bullet to pull this common, class-level (but instance method-making) code into a module to avoid repeating the same code over and over?
Upvotes: 2
Views: 312
Reputation: 4321
This should do what you want:
module PluginHelpers
def stub_required_methods(*method_names)
method_names.each do |method_name|
define_method(method_name) do
raise NotImplementedError, "Method #{__method__} must be implemented in #{self.class}"
end
end
end
end
class A
class B
class C
extend PluginHelpers
stub_required_methods(:foo, :bar)
end
end
end
# usage
A::B::C.new.foo # => NotImplementedError: Method foo must be implemented in A::B::C
Upvotes: 2