注意:这仅用于启发目的,可能会产生难以调试的意外后果。
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">