将Procs与Ruby的DSLS一起使用
-
16-10-2019 - |
题
为了用户方便和更干净的代码,我想编写可以这样使用的类:
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时,变量未在定理类的上下文中评估。通常,我想使用method_missing将每个参数都放入类Theora类的类变量中,但是我找不到条目的正确方法。
谁能将我指向正确的方向?
解决方案
阿法克(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
我相信binding.eval仅在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
不隶属于 StackOverflow