首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在Virtus Ruby对象中使用String或Hash初始化

在Virtus Ruby对象中使用String或Hash初始化
EN

Stack Overflow用户
提问于 2017-10-26 01:29:44
回答 2查看 459关注 0票数 0

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

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

样本数据

代码语言:javascript
复制
{'id' => '1234', 'contact' => '123456', 'name' => 'Bob Jones'}

所以在联系人和客户之间。我需要它们能够用一个字符串初始化,这个字符串填充了@id参数。

EN

回答 2

Stack Overflow用户

发布于 2017-10-26 03:00:06

Virtus’ author’s opinion的问题是你不应该使用Virtus进行清理。正如你从@engineersmnky的回答中看到的,覆盖initialize方法需要更多的工作。

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

代码语言:javascript
复制
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))
票数 3
EN

Stack Overflow用户

发布于 2017-10-26 02:44:29

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

如果您确实想这样做,您可以像这样覆盖Virtus::InstanceMethods::Constructor

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

然后根据需要将其包括在内

代码语言:javascript
复制
class Account
  include Virtus.model
  include VirtusOverride

  attribute :id, String
  attribute :contact, Hash
end 

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

示例:

代码语言:javascript
复制
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打补丁来实现同样的效果,但我并不是一个很支持这种哲学的人,因为这可能会给其他开发人员带来困惑,因为模块的包含提供了粒度和清晰度。

更新

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

https://stackoverflow.com/questions/46938598

复制
相关文章

相似问题

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