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

时间:2015-04-22 11:04:18

标签: ruby metaprogramming immutability

背景

Entity类是一个基类,它由几个子类继承,这些子类包含通过REST API接收的实体。实体类是不可变的,并且只要尝试进行更改,就应该返回自己的新实例。

Entity类有一个.update()方法,它接受值的哈希值进行更新,如果更改没有真正更改则返回自身,如果有真正的更改,则返回自身的新实例实例化之前发生的变化。

为了方便用户实体还允许直接分配属性(这样,如果Entity的子类具有name属性,您可以执行instance.name = 'New Name'),它也会返回该类的新实例。这是使用在实例化类时创建的动态方法更新实现的。

他们就是问题所在。

问题

Entity类中的代码部分看起来像这样(对于完整的代码列表和测试,请查看Github repo: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=()方法会返回您指定的值。

因此,在上面的示例中,.update()返回Entity子类的新实例,但.attr=()返回'New Name' ...

我尝试捕获.attr=()方法中的输出并在返回之前记录它,以便我有这个:

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 repo在rspec中测试你可以运行,失败的那些现在正在集中,一些额外的日志记录在Entity类中捕获不同的内部步骤。

欢迎评论,链接和/或拉取请求。

0 个答案:

没有答案