ruby - 包装一个方法,并将一个实例变量传递给它
问题描述
我正在尝试定义一个包含另一种方法的宏,然后将实例传递给宏。这是一个人为的例子......
module Wrapper
def wrap(method_name, options: {})
class_eval do
new_method = "_wrap_#{method_name}"
alias_method new_method, method_name
define_method method_name do |*args, &block|
# binding.pry
puts "Options... #{options}"
send new_method, *args, &block
end
end
end
end
class Thing
extend Wrapper
class << self
extend Wrapper
def bar
puts 'Bar!'
end
wrap :bar, options: -> { bar_options }
private
def bar_options
{ bar: 2 }
end
end
def foo
puts 'Foo!'
end
wrap :foo, options: -> { foo_options }
private
def foo_options
{ foo: 1 }
end
end
所以,我知道我可以从内部访问实例define_method
,但我认为我不应该在我的类中创建/重命名/别名方法以符合模块 - 我想传递配置,所以说话。我觉得instance_exec/eval
是我的朋友,在这里,但我似乎无法得到正确的咒语。我也尝试将块传递给代码,但yield
行为与 proc 相同。也许binding
吧,但出于某种原因,我根本无法理解这一点。
这是在define_method
通话中...
> Thing.new.foo
=> Options... #<Proc:0x00007fcb0782cfd8@(irb):24>
Foo!
> self.class
=> Thing
> self
=> #<Thing:0x00007fdfb78b88e8>
> options
=> #<Proc:0x00007fcb0782cfd8@(irb):24>
> options.call
# NameError: undefined local variable or method `foo_options` for Thing:Class
> foo_options
=> {:foo=>1}
> self.class.instance_eval &options
# NameError: undefined local variable or method `foo_options` for Thing:Class
> self.class.instance_exec &options
# NameError: undefined local variable or method `foo_options` for Thing:Class
我了解 proc 如何捕获范围以供以后使用,因此我可以在这里看到 proc/lambda 的使用是如何不正确的。当类加载时,该wrap
方法“包装”该方法并foo_options
在类级别捕获以供稍后调用 - 这在类级别不存在。调用options: foo_options
做同样的事情,只是在负载时爆炸。
任何一点帮助...谢谢!
解决方案
我是如此接近......在 内define_method
,我可以访问实例,并且可以调用instance_exec
self,而不是 self.class!此外,自 ruby 2.0 以来,还有一种更新的、首选的包装方法。
module Wrapper
def wrap(method_name, options: {})
proxy = Module.new
proxy.define_method(method_name) do |*args, &block|
options = instance_exec(&options) if options.is_a?(Proc)
target = is_a?(Module) ? "#{self}." : "#{self.class}#"
puts "#{target}#{method_name} is about to be called. `wrap` options #{options}"
super *args, &block
end
self.prepend proxy
end
end
输出:
> Thing.new.foo
Thing#foo is about to be called. `wrap` options {:foo=>1}
Foo!
=> nil
> Thing.bar
Thing.bar is about to be called. `wrap` options {:bar=>2}
Bar!
=> nil
这比 Sergio 在评论中提到的“旧方式”要干净得多。这个问题帮助了我!
这种方法的另一个巨大好处是您可以在文件顶部定义宏(可以说是它们所属的位置)。
推荐阅读
- python - keras Sequential() 使内核崩溃
- javascript - javascript:我将如何通过解构来转换它?
- python - 逐行计算二维 np 数组的平均值,直到某个不同的元素
- html - 如何更好地设置复选框的样式?Toggleclass 不起作用,它似乎需要一个 id - 我怎样才能在检查之前使这个复选标记图标不可见
- c# - C# | 如何在 DataGrindView 上显示 Excel 工作表?
- parsing - Double.parse(exponential notation) 不解析 17 位数值
- loops - Dafny:快速指数计算(循环)
- c# - 为什么发布到 IIS 后我的 Blazor 服务器页面为空?
- python - 在新的 M1 芯片 Mac 上安装 Gensim
- sql - 如何查询给定日期范围内更新/注册的成员列表