【问题标题】:Wrapping a method, and passing an instance variable to it包装一个方法,并将一个实例变量传递给它
【发布时间】:2021-06-29 07:18:07
【问题描述】:

我正在尝试定义一个包含另一个方法的宏,然后将实例传递给宏。这是一个人为的例子......

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 做同样的事情,只是在加载时爆炸。

任何帮助...谢谢!

【问题讨论】:

  • 这个例子/用例对我来说没有多大意义。您将 instance_evalinstance_exec 作为类方法调用,但它们应该在实例上调用(例如,instance_eval docu)。您还覆盖了控制器中自动存在的参数的定义。可能想查看Concerns 上的文档并使用模型的用例对其进行更新。
  • 已澄清。是的,这正是问题所在!

标签: ruby metaprogramming


【解决方案1】:

我是如此接近...在define_method 中,我可以访问实例,并且可以在self 上调用instance_exec,而不是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 在 cmets 中提到的“旧方式”要干净得多。 This Question帮了我一把!

这种方法的另一个好处是您可以在文件顶部定义宏(可以说是它们所属的位置)。

【讨论】:

  • 顺便说一句,使用alias_method + 替换方法是旧方法。它已被Module.prepend 取代
  • 很高兴知道,谢谢!两者我都知道,但是我从 Gem::Deprecate 的 Ruby 源代码中复制了这个示例。
猜你喜欢
  • 2015-12-25
  • 2012-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-10
  • 2015-03-22
  • 2021-08-20
  • 1970-01-01
相关资源
最近更新 更多