【问题标题】:Initialize with String or Hash in Virtus Ruby object在 Virtus Ruby 对象中使用字符串或哈希进行初始化
【发布时间】:2017-10-25 17:29:44
【问题描述】:

我需要能够创建一个基于 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 参数的字符串进行初始化。

【问题讨论】:

    标签: ruby object virtus


    【解决方案1】:

    Virtus’ author’s opinion 是您不应该使用 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))
    

    【讨论】:

    • 我非常同意这种观点,但我从未使用过virtus,因此探索新的代码库很有趣。 :)
    【解决方案2】:

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

    如果你真的想要,你可以像这样覆盖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">
    

    【讨论】:

    • 这太棒了,而且非常接近我正在寻找的东西。我更新了我的示例代码,其中的数据看起来始终如一。介意最后看一下吗?
    • @JaisonBrooks 您是否真的尝试过这个,因为它按预期工作。我已经更新了一个例子
    • 我试过了,我一直收到以下错误。 undefined method `key?' for "1234":String。您的第一个示例输出是完美的。我不会使用数组类型条目。因为数据将是一个散列,除非它是那个单一的字符串。再次感谢您的帮助/拯救我。
    • @JaisonBrooks 很高兴它有帮助
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多