0

柔軟な DSL を作成しようとしています。私はすでに DSL モジュールを持っていますmodule DSL。DSL ユーザーは、これのスピンオフをクラスとして作成できます。DSL の主なポイントは、ユーザーがFeatureカスタム render メソッドでオブジェクトを作成できるようにすることです。をサポートする多くの醜い非 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
4

1 に答える 1

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 : '&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

編集:

デフォルト メソッド、ユーザー定義ラムダ、事前定義メソッド、およびユーザー定義メソッドの使用を許可するクリーンな方法を見つけました。

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

ユーザーがそれらを渡した場合、これはラムダまたはプロシージャを取得します。それ以外の場合は、メソッドを使用して、でmethod定義されたメソッドへの参照を返しFeatureます。このcall方法は、3 つすべてで機能します。

ユーザー定義のレンダー メソッドをサポートするには、イニシャライザでクラスを開いて、methodメソッドで検索できるようにします。

#initializers/custom_dsl_renders.rb
class DSL::Feature
  def user_defined_render
    #...
  end
end
于 2012-08-23T18:11:12.730 に答える