我正在尝试创建一个灵活的dsl。我已经有了DSL模块,比如module DSL
。 DSL用户可以将其作为一个类创建衍生产品。 DSL的要点是允许用户使用自定义渲染方法创建Feature
对象。有许多丑陋和非DRY代码支持Feature
,因此是抽象,但用户需要对该功能的呈现方式进行大量控制,而我的元编程无法完成任务。让我告诉你它是如何设置的。
DSL看起来像这样:
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
它的实现看起来像这样:
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
最后,要素类本身就在DSL中,如下所示:
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
答案 0 :(得分:0)
我正在寻找的魔力:define_singleton_method
。
module DSL
#...
private
class Feature
attr_accessor :name
def initialize(name, args)
#...
define_singleton_method :render do
if self.render_with.kind_of? Symbol
content = self.send(self.render_with)
else
content = self.render_with.call
end
content.present? ? content.to_s.html_safe : '–'
end
end
end
end
现在在DSL中我可以迭代所有功能并渲染它们。它会发送自己:default_render
或其他:predefined_render
,或使用提供的块。但是,这不允许用户在DSLSpinOff
内定义方法并将其传入,因为这些方法将委派给DSLSpinOff
类而不是DSLSpinOff::Column
类。
我怀疑他们必须做类似的事情:
feature :three, render_with: self.method(:user_defined_render)
def user_defined_render
#...
end
我发现了一种允许使用默认方法,用户定义的lambdas,预定义方法和用户定义方法的简洁方法:
module DSL
#...
class Feature
attr_accessor :name, render_with
def initialize(name, *args)
self.name = name
self.render_with = args.has_key?(:render_with) ? args[:render_with] : :default_render
define_singleton_method :render do |object|
render_method = self.render_with.is_a?(Proc) ? renderer : method(renderer)
render_method.call
end
end
private
def default_render
#...
end
def predefined_render
#...
end
end
end
如果用户传入lambdas或procs,它将获取lambdas或procs,否则,它将使用method
方法返回对Feature
上定义的方法的引用。 call
方法适用于所有三种方法。
要支持用户定义的渲染方法,只需在初始化程序中打开该类,即可通过method
方法找到它:
#initializers/custom_dsl_renders.rb
class DSL::Feature
def user_defined_render
#...
end
end