0

我需要能够创建一个基于 Virtus 的对象,该对象接受哈希或字符串。如果它是一个哈希,那么正常行为是完美的。如果它是纯字符串,那么我需要将其转换为{'id' => "STRING"}. 目前,我不确定如何/在哪里重写initialize执行此功能的方法。或者也许有不同的方式。非常感谢您的专业知识。

class Contact
  include Virtus.model

  attribute :id, String
end

class Account
  include Virtus.model

  attribute :id, String
  attribute :contact, Contact
  attribute :name, String
end 

account = Account.new("1234")
account.id #>1234

# and still work like this

account = Account.new(id: '1234', contact: '123456', name: 'Bob Jones')
account.id #>1234
account.contact.id #>123456
account.name #>Bob Jones

样本数据

{'id' => '1234', 'contact' => '123456', 'name' => 'Bob Jones'}

所以在联系人和帐户之间。我需要它们能够使用填充@id 参数的字符串进行初始化。

4

2 回答 2

3

Virtus 的作者认为您不应该使用 Virtus 进行消毒。正如您从@engineersmnky 的回答中看到的那样,覆盖该initialize方法所需的工作比它应该做的要多。

相反,包装您传入的对象会更容易:

class Hashable
  def initialize(hashable_object)
    @obj = hashable_object
  end

  def to_hash
    case @obj
    when Hash then @obj
    when String
      { "id" => @obj }
    else
      raise "No conversion from #{@obj.class} to hash"
    end
  end
end

Account.new(Hashable.new(relationship))
于 2017-10-25T19:00:05.570 回答
1

注意:这仅用于启发目的,可能会产生难以调试的意外后果。

Virtus::InstanceMethods::Constructor如果你真的想要,你可以覆盖这样的:

module VirtusOverride
  def self.included(base)
    raise "#{base.name} must include Virtus.model prior to including VirtusOverride" unless base.included_modules.include?(Virtus::InstanceMethods)
  end

  def initialize(*attributes)
    super(construct(attributes))
  end

  private
    def construct(attributes)
      return attributes.first if valid_constructor?(attributes)
      build_attributes_from_array(attributes) 
    end 
    def valid_constructor?(attributes)
      return false unless attributes.count == 1 
      constructor = attributes.first
      constructor.is_a?(Hash) &&  
      !(attribute_set.flat_map {|a| [a.name,a.name.to_s]} & constructor.keys).empty?
    end
    def build_attributes_from_array(attributes)
      attribute_set.map(&:name).zip(attributes).to_h
    end
end 

然后根据需要包含它

class Account
  include Virtus.model
  include VirtusOverride

  attribute :id, String
  attribute :contact, Hash
end 

现在您可以将位置选项传递给属性定义(例如Account.new(id,contact))或作为Hash.

例子:

Account.new("1234",{name: 'mnky'})
#=> #<Account:0x2a071b8 @id="1234", @contact={:name=>"mnky"}>

Account.new(id: "1234", contact: {name: 'mnky'})
#=> #<Account:0x2b42b78 @id="1234", @contact={:name=>"mnky"}>

您可以使用猴子补丁Virtus::InstanceMethods::Constructor来执行相同的操作,但我不是这种理念的大力支持者,因为它可能会给其他开发人员带来混乱,因为模块包含提供了粒度和清晰度。

更新

class Account
  include Virtus.model
  include VirtusOverride

  attribute :id, String
  attribute :contact, Contact
end 

class Contact
  include Virtus.model
  include VirtusOverride

  attribute :id, String
end  

 Account.new(id: '1234', contact: '123456', name: 'Bob Jones')
 #=>  #<Account:0x2bcb060 @id="1234", @contact=#<Contact:0x2bc9c50 @id="123456">, @name="Bob Jones">
 Account.new('1234', '123456', 'Bob Jones')
 #=> #<Account:0x2faea00 @id="1234", @contact=#<Contact:0x2fae880 @id="123456">, @name="Bob Jones">
 Account.new('id' => '1234', 'contact' => '123456', 'name' => 'Bob Jones')
 #=> #<Account:0x2faffc0 @id="1234", @contact=#<Contact:0x2fafb70 @id="123456">, @name="Bob Jones">
于 2017-10-25T18:44:28.837 回答