Использование Procs с DSLS Ruby
-
16-10-2019 - |
Вопрос
Для удобства пользователя и более чистого кода я хотел бы написать класс, который можно использовать так:
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