首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >动态扩展Virtus实例属性

动态扩展Virtus实例属性
EN

Stack Overflow用户
提问于 2017-05-18 11:33:04
回答 2查看 907关注 0票数 9

假设我们有一个Virtus模型User

代码语言:javascript
复制
class User
  include Virtus.model
  attribute :name, String, default: 'John', lazy: true
end

然后,我们创建该模型的一个实例,并从Virtus.model扩展到动态添加另一个属性:

代码语言:javascript
复制
user = User.new
user.extend(Virtus.model)
user.attribute(:active, Virtus::Attribute::Boolean, default: true, lazy: true)

当前产出:

代码语言:javascript
复制
user.active? # => true
user.name # => 'John'

但是,当我试图获取attributes或通过as_json(或to_json)或Hash通过to_h将对象转换为JSON时,我只得到后扩展的属性active

代码语言:javascript
复制
user.to_h # => { active: true }

是什么导致了这个问题,如何在不丢失数据的情况下转换对象?

附注:

我已经找到了一个github问题,但似乎它毕竟没有被修复(推荐的方法也不能稳定地工作)。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-05-25 12:39:08

在禤浩焯的发现的基础上,这里有一个修改Virtus的方法,以满足你的需求。所有规格都通过此修改。

本质上,Virtus已经有了父AttributeSet的概念,但只有在班级中包含Virtus.model时才会这样。我们也可以扩展它来考虑实例,甚至允许在同一个对象中使用多个extend(Virtus.model) (尽管这听起来不太理想):

代码语言:javascript
复制
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}

也许这值得对维塔斯做个公关,你觉得呢?

票数 5
EN

Stack Overflow用户

发布于 2017-05-23 16:36:07

我还没有进一步研究它,但是似乎每次包含或扩展Virtus.model时,它都初始化一个新的AttributeSet,并将其设置为用户类的@attribute_set实例变量(来源)。to_hattributes所做的是调用新attribute_set实例(来源)的get方法。因此,您只能在Virtus.model的最后一个包含或扩展之后获取属性。

代码语言:javascript
复制
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 of attribute_set实例是不同的,这意味着前者和后者attribute_set是两个不同的对象。

我现在可以建议的一个问题是:

代码语言:javascript
复制
(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
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44046603

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档