1

ユーザーの便宜とよりクリーンなコードのために、次のように使用できるクラスを作成したいと思います。

Encoder::Theora.encode do
  infile = "path/to/infile"
  outfile = "path/to/outfile"
  passes = 2
  # ... more params
end

ここでの課題は、そのパラメーターをエンコードメソッドで使用できるようにすることです。

module Encoder
  class Theora
    def self.encode(&proc)
      proc.call
      # do some fancy encoding stuff here
      # using the parameters from the proc
    end
  end
end

このアプローチは機能しません。Procが呼び出されると、変数はTheoraクラスのコンテキストで評価されません。通常、method_missingを使用して、すべてのパラメーターをクラスTheoraのクラス変数に入れたいのですが、エントリの正しい方法が見つかりません。

誰かが私を正しい方向に向けることができますか?

4

4 に答える 4

3

DSLに割り当てを使用させることが可能かどうかはわかりませんが、Rubyインタープリターは、inがそのコンテキストのローカル変数であると常に想定していると思いinfileますinfile = 'path/to/something'(ただし、self.infile = 'path/to/something'動作させることはできます)。ただし、その特定の詳細がなくても生活できる場合は、次のようにDSLを実装できます。

module Encoder
  class Theora
    def self.encode(&block)
      instance = new
      instance.instance_eval(&block)
      instance
    end

    def infile(path=nil)
      @infile = path if path
      @infile
    end
  end
end

次のように使用します。

Encoder::Theora.encode do
  infile 'path/somewhere'
end

(他のプロパティも同様に実装します)。

于 2011-01-21T08:28:44.990 に答える
1

あなたが書いたようにそれを行うことはできません、AFAIK。procの本体には独自のスコープがあり、そのスコープ内で作成された変数は、そのスコープの外側には表示されません。

慣用的なアプローチは、構成オブジェクトを作成してブロックに渡すことです。これは、そのオブジェクトのメソッドまたは属性を使用して実行される作業を記述します。次に、作業を行うときにそれらの設定が読み取られます。create_tableこれは、たとえばActiveRecordの移行で採用されているアプローチです。

したがって、次のようなことができます。

module Encoder
  class Theora
    Config = Struct.new(:infile, :outfile, :passes)

    def self.encode(&proc)
      config = Config.new
      proc.call(config)
      # use the config settings here
      fp = File.open(config.infile)       # for example
      # ...
    end
  end
end

# then use the method like this:
Encoder::Theora.encode do |config|
  config.infile = "path/to/infile"
  config.outfile = "path/to/outfile"
  config.passes = 2
  # ...
end
于 2011-01-21T08:25:56.730 に答える
0

これをいじってみると、次のようになりました。これは必ずしもお勧めしません。必要な構文には完全には適合しませんが、割り当て(一種)を使用できます。したがって、完全性の精神で熟読してください。

module Encoder
  class Theora
    def self.encode(&proc)
      infile = nil
      outfile = nil
      yield binding
    end
  end
end

Encoder::Theora.encode do |b|
  b.eval <<-ruby
    infile = "path/to/infile"
    outfile = "path/to/outfile"
  ruby
end

Binding.evalはRuby1.9でのみ機能すると思います。また、ローカル変数はyieldの前に宣言する必要があるようです。そうしないと、機能しません。理由は誰にもわかりません。

于 2011-01-21T09:02:20.150 に答える
0

OK、最初に、pmdboiの答えは非常にエレガントで、ほぼ間違いなく正しいと言わなければなりません。

それでも、あなたが次のようなスーパーカットダウンDSLが必要な場合に備えて

Encoder::Theora.encode do
  infile "path/to/infile"
  outfile "path/to/outfile"
  passes 2
end

あなたはこのような醜いことをすることができます:

require 'blockenspiel'
module Encoder
  class Theora
    # this replaces pmdboi's elegant Struct
    class Config
      include Blockenspiel::DSL
      def method_missing(method_id, *args, &blk)
        if args.length == 1
          instance_variable_set :"@#{method_id}", args[0]
        else
          instance_variable_get :"@#{method_id}"
        end
      end
    end

    def self.encode(&blk)
      config = Config.new
      Blockenspiel.invoke blk, config
      # now you can do things like
      puts config.infile
      puts config.outfile
      puts config.passes
    end
  end
end
于 2011-01-27T15:49:55.917 に答える