Dustin Frazier
Dustin Frazier

Reputation: 293

moving class-level dynamic method creation into a module in ruby

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

Answers (1)

Ryan LeCompte
Ryan LeCompte

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

Related Questions