module Interface def self.included(other_module) other_module.module_eval do @__iface_methods = [] # Include the passed method symbols as parts of this interface. # # Usage: # # module Driveable # include Interface # interface_methods :honk, :steer, :gas # end def self.interface_methods(*args) @__iface_methods |= args end # Returns whether this interface is implemented by the object passed into it. # raises Interface::MethodsNotDefinedError if interface_methods was not properly used to list interface methods def self.implemented_by?(thing) if @__iface_methods.empty? raise InterfaceMethodsNotDefinedError, "Use 'interface_methods :name1, :name2' in your interface module to define interface methods" else @__iface_methods.all? { |m| thing.class.public_method_defined?(m) } end end # If this interface is implemented by the object passed to it, it will yield the object, thus # making it safe to perform operations on it. # # MyInterface.implemented_by(Something.new) do |thing| # thing.iface_method # thing.another_iface_method # end # # This is really kind of pointless, since you could still do the following: # # MyInterface.implemented_by(Something.new) do |thing| # thing.not_an_iface_method # end # # And "not_and_iface_method" could still raise MethodNotDefinedError def self.implemented_by(thing) if implemented_by?(thing) && block_given? yield thing end end end end class InterfaceMethodsNotDefinedError < StandardError; end end