使用预定义定义和自定义块自定义 Ruby 结构

Customizing a Ruby Struct with pre-defined definitions and a custom block

给出以下程序,我想在其中:

  1. 用一些键创建一个Struct
  2. 提供一些默认的自定义设置
  3. 允许传递块以进行进一步自定义
module Magic
    def self.MagicStruct(keys_array, &block)
        Struct.new(*keys_array) do
            @keys = keys_array
            def self.magic_class_method
                puts "constructed with #{@keys}"
            end

            def magic_instance_method
                puts "instance method"
            end

            # --- DOESN'T WORK, CONTEXT IS OUTSIDE OF MODULE --- #
            # yield if block_given?

            instance_eval(&block) if block_given?
        end
    end
end

Foo = Magic.MagicStruct([:a, :b, :c]) do
    puts "class customizations executing..."

    def self.custom_class_method
        puts "custom class method"
    end

    def custom_instance_method
        puts "custom instance method"
    end
end

Foo.magic_class_method  # works
Foo.custom_class_method # works

x = Foo.new({a: 10, b: 20, c: 30})
x.magic_instance_method  # works
x.custom_instance_method # fails

输出:

class customizations executing...
constructed with [:a, :b, :c]
custom class method
instance method
Traceback (most recent call last):
`<main>': undefined method `custom_instance_method' for #<struct Foo a={:a=>10, :b=>20, :c=>30}, b=nil, c=nil> (NoMethodError)

为什么 self.custom_class_method 正确地添加到 Foo class,但 custom_instance_method 不是?这种用法在 the Struct documentation 中有明确说明,所以恐怕我在这里遗漏了某种范围界定或上下文问题。

我更愿意保留漂亮的 def method() ... end 语法,而不是诉诸于在自定义块中使用 define_method("method") 的严格要求,这确实有效。

在 Ruby 中有一个 current class 的概念,它是关键字的目标,例如 def.

当您使用instance_eval时,当前class设置为self.singleton_class。换句话说,def xdef self.x 是等价的。在您的代码中,custom_instance_method 是在新创建的 Struct 的单例 class 上定义的,使其成为 class 方法。

当您使用class_eval时,当前class设置为self。由于此方法仅在 classes 上可用,它会将当前 class 设置为您调用该方法的那个。换句话说,def x 将定义一个可用于 class 的所有对象的方法。这就是你想要的。

有关详细信息,请参阅 my other answer