使用 define_singleton_method 创建的赋值方法 returns 错误的值

Assignment method created using define_singleton_method returns the wrong value

背景

实体 class 是一个基础 class,由多个子class 继承,这些子class 保存通过 REST API 接收的实体。实体 class 是不可变的,每当尝试更改时都应该 return 自己的新实例。

实体 class 有一个 .update() 方法,该方法采用散列值进行更新,如果更改没有真正改变它 returns 本身,如果有真实的将其更改为 return 自身的新实例,更改在实例化之前生效。

为了用户友好,实体还允许直接分配给属性(这样,如果实体的子class 具有名称属性,您可以这样做 instance.name = 'New Name')这也 return是 class 的一个新实例。这是通过使用实例化 class 时创建的动态方法的更新来实现的。

他们就是问题所在。

问题

实体 class 中的代码部分看起来像这样(有关完整的代码清单和测试,请查看 Github 存储库:https://github.com/my-codeworks/fortnox-api.git):

require "virtus"
require "ice_nine"

class Entity

  extend Forwardable
  include Virtus.model

  def initialize( hash = {} )
    super
    create_attribute_setter_methods
    IceNine.deep_freeze( self )
  end

  def update( hash )
    attributes = self.to_hash.merge( hash )
    return self if attributes == self.to_hash
    self.class.new( attributes )
  end

private

  def create_attribute_setter_methods
    attribute_set.each do |attribute|
      name = attribute.options[ :name ]

      create_attribute_setter_method( name )
    end
  end

  def create_attribute_setter_method( name )
    self.define_singleton_method "#{name}=" do | value |
      self.update( name => value )
    end
  end

end

这样做:

instance.update( name: 'New Name' )

还有这个:

instance.name = 'New Name'

从字面上看应该是一样的,因为一个是根据另一个实现的。

虽然 .update() 完美地工作 .attr=() 方法 return 您分配的值。

因此在上面的示例中 .update() return 是实体子 class 的新实例,但是 .attr=() return 是 'New Name' 。 ..

我尝试在 .attr=() 方法中捕获输出并在 returning 之前记录它,这样我就有了这个:

self.define_singleton_method "#{name}=" do | value |
  p "Called as :#{name}=, redirecting to update( #{name}: #{value} )"
  r = self.update( name => value )
  p "Got #{r} back from update"
  return r
end

日志行说:

 "Called as :name=, redirecting to update( name: 'New Name' )"
 "Got #<TestEntity:0x007ffedbd0ad18> back from update"

但我得到的只是字符串 'New Name'...

我的前额流血了,我发现没有任何帖子显示与此相近的内容。我打赌我做错了什么,但我找不到它。

变脏

Github 存储库在 rspec 中有测试,您可以 运行,失败的测试现在集中在实体 class 中捕获不同的内部步骤。

评论、链接and/or欢迎拉取请求。

原来 = 方法总是 return 被分配的值。

o = Struct.new(:key).new(1)
o.define_singleton_method("something") { @something }
o.define_singleton_method("something=") do |v|
  @something = v
  return 6
end

如您所见,每次调用 something= 时,我都会将 'fixed' 的 return 值设为 6。让我们看看它是否有效:

o.something = 1 #=> outputs 1, not 6
o.something #=> outputs 1, so the method did indeed run

结论?我的猜测是 = 方法将 return 您通过它分配的值。在我看来,这样更好;一个原因是确保分配链的正常运行:

new_val = o.something = some_val