【问题标题】:Creating a flexible ruby DSL with lambdas使用 lambdas 创建灵活的 ruby​​ DSL
【发布时间】:2012-08-23 18:11:12
【问题描述】:

我正在尝试创建一个灵活的 dsl。我已经有了 DSL 模块,比如module DSL。 DSL 用户可以将其作为一个类创建衍生产品。 DSL 的要点是允许用户使用自定义渲染方法创建Feature 对象。 Feature 背后有很多丑陋且非 DRY 的代码,因此是抽象的,但用户需要对该功能的呈现方式进行大量控制,而我的元编程无法胜任这项任务。让我告诉你它是如何设置的。

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

【问题讨论】:

    标签: ruby-on-rails ruby metaprogramming


    【解决方案1】:

    我一直在寻找的魔法: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 : '&ndash;'
          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
    

    编辑:

    我找到了一种允许使用默认方法、用户定义的 lambda、预定义的方法和用户定义的方法的简洁方法:

    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,这将获取它们,否则,它将使用method 方法返回对Feature 上定义的方法的引用。 call 方法适用于所有三个。

    要支持用户定义的渲染方法,只需在初始化程序中打开类,使其可被method 方法找到:

    #initializers/custom_dsl_renders.rb
    class DSL::Feature
      def user_defined_render
        #...
      end
    end
    

    【讨论】:

    • 在允许的情况下给自己选择的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-28
    • 1970-01-01
    • 1970-01-01
    • 2018-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多