I’m trying to create a flexible dsl. I already have the DSL module, say module DSL. The DSL user can create spin-offs of this as a class. The main point of the DSL is to allow the user to create Feature object with a custom render method. There was a lot of ugly and non-DRY code backing the Feature, hence the abstraction, but the user needs a lot of control on how that feature renders, and my meta-programming is not up to the task. Let me show you how it’s set up.
The DSL looks something like this:
module DSL
module ClassMethods
attr_accessor :features
def column(name, *args)
arguments = args.pop || {}
self.features = [] if self.features.nil?
self.features << Feature.new(name, arguments)
end
end
def self.included(base)
base.extend ClassMethods
end
end
end
An implementation of it would look something like this:
class DSLSpinOff
include DSL
feature :one
feature :two, render_with: :predefined_render
feature :three, render_with: :user_defined_render
feature :four, render_with: lambda {
puts "Go nuts, user!"
puts "Do as you please!"
}
def user_defined_render
#...
end
end
And finally, the feature class itself lies within the DSL, like so:
module DSL
#...
private
class Feature
attr_accessor :name, :render_with
def initialize(name, *args)
self.name = name
attributes = args.pop || {}
# somehow delegate attributes[:render_with] to the render function, handling defaults, lamdbas, function string names, etc
self.render_with = attributes.fetch(:render_with, :default_render)
end
private
def default_render
#...
end
def predefined_render
#...
end
end
end
The magic I was looking for:
define_singleton_method.Now within the DSL I can iterate over all features and render them. It’ll send itself
:default_render, or some other:predefined_render, or use the block provided instead. However, this does not let users define methods on within theDSLSpinOffand pass them in, since those methods would get delegated to theDSLSpinOffclass instead of theDSLSpinOff::Columnclass.I suspect they would have to do something like:
Edit:
I found a clean way to allow use of a default method, user-defined lambdas, pre-defined methods, and user-defined methods:
This will grab lambdas or procs if the user passes those in, otherwise, it’ll use the
methodmethod to return a reference to the method defined on theFeature. Thecallmethod works on all three.To support user-defined render methods, just have the open the class in an initializer to make it findable by the
methodmethod: