动态扩展Virtus实例属性

pot*_*hin 9 ruby ruby-on-rails ruby-on-rails-4 virtus

假设我们有一个Virtus模型 User

class User
  include Virtus.model
  attribute :name, String, default: 'John', lazy: true
end
Run Code Online (Sandbox Code Playgroud)

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

user = User.new
user.extend(Virtus.model)
user.attribute(:active, Virtus::Attribute::Boolean, default: true, lazy: true)
Run Code Online (Sandbox Code Playgroud)

当前输出:

user.active? # => true
user.name # => 'John'
Run Code Online (Sandbox Code Playgroud)

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

user.to_h # => { active: true }
Run Code Online (Sandbox Code Playgroud)

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

PS

我发现了一个github问题,但似乎它毕竟没有修复(推荐的方法也没有稳定运行).

ere*_*gon 5

在Adrian的发现的基础上,这是一种修改Virtus以允许你想要的东西的方法.所有规格都通过了此修改.

从本质上讲,Virtus已经有了父母的概念AttributeSet,但只有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}
Run Code Online (Sandbox Code Playgroud)

也许值得为Virtus做一个公关,你怎么看?