假设我们有一个Virtus模型User
class User
include Virtus.model
attribute :name, String, default: 'John', lazy: true
end
然后我们创建一个这个模型的实例,并从Virtus.model
扩展到动态添加另一个属性:
user = User.new
user.extend(Virtus.model)
user.attribute(:active, Virtus::Attribute::Boolean, default: true, lazy: true)
当前输出:
user.active? # => true
user.name # => 'John'
但是,当我尝试获取attributes
或通过as_json
(或to_json
)或Hash
通过to_h
将对象转换为JSON时,我只获得扩展后属性active
:
user.to_h # => { active: true }
导致问题的原因是什么?如何在不丢失数据的情况下转换对象?
我找到了github issue,但似乎它根本没有修复(推荐的方法也没有稳定运行)。
答案 0 :(得分:5)
根据Adrian的发现,这里有一种修改Virtus以允许你想要的方法。所有规格都通过了此修改。
基本上,Virtus已经拥有父AttributeSet
的概念,但只有在class中包含Virtus.model
时才会这样。
我们可以扩展它以考虑实例,甚至在同一个对象中允许多个extend(Virtus.model)
(虽然这听起来不是最佳的):
require 'virtus'
module Virtus
class AttributeSet
def self.create(descendant)
if descendant.respond_to?(:superclass) && descendant.superclass.respond_to?(:attribute_set)
parent = descendant.superclass.public_send(:attribute_set)
elsif !descendant.is_a?(Module)
if descendant.respond_to?(:attribute_set, true) && descendant.send(:attribute_set)
parent = descendant.send(:attribute_set)
elsif descendant.class.respond_to?(:attribute_set)
parent = descendant.class.attribute_set
end
end
descendant.instance_variable_set('@attribute_set', AttributeSet.new(parent))
end
end
end
class User
include Virtus.model
attribute :name, String, default: 'John', lazy: true
end
user = User.new
user.extend(Virtus.model)
user.attribute(:active, Virtus::Attribute::Boolean, default: true, lazy: true)
p user.to_h # => {:name=>"John", :active=>true}
user.extend(Virtus.model) # useless, but to show it works too
user.attribute(:foo, Virtus::Attribute::Boolean, default: false, lazy: true)
p user.to_h # => {:name=>"John", :active=>true, :foo=>false}
也许值得为Virtus做一个PR,你觉得怎么样?
答案 1 :(得分:3)
我还没有进一步调查它,但似乎每次包含或扩展Virtus.model时,它都会初始化一个新的AttributeSet并将其设置为User类的@attribute_set实例变量(source )。 to_h
或attributes
所做的是他们调用新的attribute_set实例(source)的get
方法。因此,您只能在最后一次包含或Virtus.model
的扩展名后获取属性。
class User
include Virtus.model
attribute :name, String, default: 'John', lazy: true
end
user = User.new
user.instance_variables
#=> []
user.send(:attribute_set).object_id
#=> 70268060523540
user.extend(Virtus.model)
user.attribute(:active, Virtus::Attribute::Boolean, default: true, lazy: true)
user.instance_variables
#=> [:@attribute_set, :@active, :@name]
user.send(:attribute_set).object_id
#=> 70268061308160
如您所见,扩展前后的object_id
实例的attribute_set
不同,这意味着前者和后者的attribute_set是两个不同的对象。
我现在建议的一个黑客是:
(user.instance_variables - [:@attribute_set]).each_with_object({}) do |sym, hash|
hash[sym.to_s[1..-1].to_sym] = user.instance_variable_get(sym)
end