Вопрос

Для удобства пользователя и более чистого кода я хотел бы написать класс, который можно использовать так:

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

Задача теперь заключается в том, чтобы эти параметры были доступны в моем методе Encode.

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, чтобы помещать каждый параметр в переменную класса теоры класса, но я не нахожу правильный путь для записи.

Кто -нибудь может указать мне в правильном направлении?

Это было полезно?

Решение

Это не может быть сделано так, как вы написали, 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

Другие советы

Я не уверен, что можно заставить DSL использовать задание, я думаю, что переводчик Ruby всегда будет предполагать, что 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

(Реализуйте другие свойства с одинаковым).

Играя с этим, я пришел к следующему, что я не обязательно рекомендую, и это не совсем соответствует требуемому синтаксису, но что позволяет вам использовать назначение (своего рода). Так просмотрите в духе полноты:

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

Я считаю, что обязательство. Эваль работает только в Ruby 1.9. Кроме того, кажется, что местные переменные должны быть объявлены, прежде чем уступить, или это не сработает - кто -нибудь знает почему?

Хорошо, сначала я должен сказать, что ответ 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top