Ruby converte Objeto em Hash

127

Digamos que eu tenho um Giftobjeto com @name = "book"& @price = 15.95. Qual é a melhor maneira de converter isso para o Hash {name: "book", price: 15.95}em Ruby, não para o Rails (embora fique à vontade para dar a resposta do Rails também)?

ma11hew28
fonte
16
@ Gift.attributes.to_options faria?
Sr. L
1
1) O presente é um objeto ActiveRecord? 2) podemos assumir que @ name / @ price não são apenas variáveis ​​de instância, mas também acessadores de leitores? 3) você deseja apenas nome e preço ou todos os atributos de um presente, sejam eles quais forem?
tokland
@tokland, 1) não, Gifté exatamente como o @nash definiu , exceto 2) com certeza, as variáveis ​​da instância podem ter acessadores de leitor. 3) Todos os atributos presentes.
Ma11hew28
Está bem. A questão sobre o acesso às variáveis ​​/ leitores da instância era saber se queria um acesso externo (nash) ou método interno (levinalex). Atualizei minha resposta para a abordagem "interna".
tokland

Respostas:

80
class Gift
  def initialize
    @name = "book"
    @price = 15.95
  end
end

gift = Gift.new
hash = {}
gift.instance_variables.each {|var| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}

Alternativamente com each_with_object:

gift = Gift.new
hash = gift.instance_variables.each_with_object({}) { |var, hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}
Vasiliy Ermolovich
fonte
3
Você pode usar o inject para ignorar a inicialização da variável: gift.instance_variables.inject ({}) {| hash, var | hash [var.to_s.delete ("@")] = presente.instância_variavel_get (var); hash}
Jordan
8
Agradável. Substituí var.to_s.delete("@")por var[1..-1].to_sympara obter símbolos.
Ma11hew28
3
Não use injetar, uso gift.instance_variables.each_with_object({}) { |var,hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }e livrar-se da fuga; hash
Narfanator
1
Eu nunca vou entender o fetiche por rubi each. mape injectsão muito mais poderosos. Este é um aspecto de design que eu tenho com o Ruby: mape injecté implementado com each. É simplesmente ciência da computação ruim.
Nate Symer 21/04
Um pouco mais conciso:hash = Hash[gift.instance_variables.map { |var| [var.to_s[1..-1], gift.instance_variable_get(var)] } ]
Marvin
293

Apenas diga (objeto atual) .attributes

.attributesretorna um hashde qualquer object. E é muito mais limpo também.

Austin Marusco
fonte
138
Observe que este é um método específico do ActiveModel, não um método Ruby.
bricker
6
No caso do Sequel - uso .values: sequel.jeremyevans.net/rdoc/classes/Sequel/Model/...
dimitarvp
instance_valuespode ser usado para todos os objetos ruby ​​para uma saída semelhante.
bishal 22/01/19
48

Implementar #to_hash?

class Gift
  def to_hash
    hash = {}
    instance_variables.each { |var| hash[var.to_s.delete('@')] = instance_variable_get(var) }
    hash
  end
end


h = Gift.new("Book", 19).to_hash
levinalex
fonte
Tecnicamente, deve ser .to_hash, já que # indica métodos de classe.
Caleb
6
Na verdade não. A documentação do RDoc diz: Use :: for describing class methods, # for describing instance methods, and use . for example code(fonte: ruby-doc.org/documentation-guidelines.html ) Além disso, a documentação oficial (como o ruby CHANGELOG , github.com/ruby/ruby/blob/v2_1_0/NEWS ) usa, #por exemplo, métodos e o ponto para métodos de classe de maneira bastante consistente.
levinalex
Por favor, use injetar em vez deste antipadrão.
YoTengoUnLCD
Variante de uma linha usando each_with_object:instance_variables.each_with_object(Hash.new(0)) { |element, hash| hash["#{element}".delete("@").to_sym] = instance_variable_get(element) }
anothermh 28/03
43
Gift.new.instance_values # => {"name"=>"book", "price"=>15.95}
Erik Reedstrom
fonte
10
Este é o Rails, o próprio Ruby não possui instance_values. Note que Matt pediu uma maneira Ruby, especificamente não Rails.
Christopher Creutzig 28/03
28
Ele também disse que sinta-se livre para dar uma resposta aos Rails também ... então eu fiz.
Erik Reedstrom
Deus veja as duas versões aqui;) Gostei
Sebastian Schürmann 26/17
17

Você pode usar o as_jsonmétodo Ele converterá seu objeto em hash.

Mas, esse hash será um valor para o nome desse objeto como uma chave. No seu caso,

{'gift' => {'name' => 'book', 'price' => 15.95 }}

Se você precisar de um hash armazenado no objeto use as_json(root: false). Eu acho que por padrão a raiz será falsa. Para mais informações, consulte o guia oficial do ruby

http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json

vicke4
fonte
13

Para objetos de registro ativo

module  ActiveRecordExtension
  def to_hash
    hash = {}; self.attributes.each { |k,v| hash[k] = v }
    return hash
  end
end

class Gift < ActiveRecord::Base
  include ActiveRecordExtension
  ....
end

class Purchase < ActiveRecord::Base
  include ActiveRecordExtension
  ....
end

e depois é só ligar

gift.to_hash()
purch.to_hash() 
monika mevenkamp
fonte
2
engraçado, não faz parte da estrutura do Rails. Parece uma coisa útil para ter lá.
Magne
O método attribute retorna um novo hash com os valores em - portanto, não é necessário criar outro no método to_hash. Assim: attribute_names.each_with_object ({}) {| name, attrs | attrs [nome] = read_attribute (nome)}. Veja aqui: github.com/rails/rails/blob/master/activerecord/lib/…
Chris Kimpton
você poderia ter feito isso com o mapa, sua implementação de efeito colateral está machucando minha mente, cara!
Nate Symer # 23/16
11

Se você não está em um ambiente Rails (ou seja, não possui o ActiveRecord disponível), isso pode ser útil:

JSON.parse( object.to_json )
Andreas Rayo Kniep
fonte
11
class Gift
  def to_hash
    instance_variables.map do |var|
      [var[1..-1].to_sym, instance_variable_get(var)]
    end.to_h
  end
end
tokland
fonte
6

Você pode escrever uma solução muito elegante usando um estilo funcional.

class Object
  def hashify
    Hash[instance_variables.map { |v| [v.to_s[1..-1].to_sym, instance_variable_get v] }]
  end
end
Nate Symer
fonte
4

Você deve substituir o inspectmétodo do seu objeto para retornar o hash desejado ou apenas implementar um método semelhante sem substituir o comportamento padrão do objeto.

Se você quiser ficar mais sofisticado, pode iterar sobre as variáveis ​​de instância de um objeto com object.instance_variables

Dominic
fonte
4

Recursively converter seus objetos em Hash usando 'Hashable' gem ( https://rubygems.org/gems/hashable ) Exemplo

class A
  include Hashable
  attr_accessor :blist
  def initialize
    @blist = [ B.new(1), { 'b' => B.new(2) } ]
  end
end

class B
  include Hashable
  attr_accessor :id
  def initialize(id); @id = id; end
end

a = A.new
a.to_dh # or a.to_deep_hash
# {:blist=>[{:id=>1}, {"b"=>{:id=>2}}]}
mustafaturan
fonte
4

Pode querer tentar instance_values. Isso funcionou para mim.

Caçador
fonte
1

Produz uma cópia superficial como um objeto de hash apenas dos atributos do modelo

my_hash_gift = gift.attributes.dup

Verifique o tipo do objeto resultante

my_hash_gift.class
=> Hash
Sean
fonte
0

Se você precisar que objetos aninhados também sejam convertidos.

# @fn       to_hash obj {{{
# @brief    Convert object to hash
#
# @return   [Hash] Hash representing converted object
#
def to_hash obj
  Hash[obj.instance_variables.map { |key|
    variable = obj.instance_variable_get key
    [key.to_s[1..-1].to_sym,
      if variable.respond_to? <:some_method> then
        hashify variable
      else
        variable
      end
    ]
  }]
end # }}}
Sergio Santiago
fonte
0

Gift.new.attributes.symbolize_keys

Lalit Kumar Maurya
fonte
0

Para fazer isso sem o Rails, uma maneira limpa é armazenar atributos em uma constante.

class Gift
  ATTRIBUTES = [:name, :price]
  attr_accessor(*ATTRIBUTES)
end

E então, para converter uma instância de Giftpara a Hash, você pode:

class Gift
  ...
  def to_h
    ATTRIBUTES.each_with_object({}) do |attribute_name, memo|
      memo[attribute_name] = send(attribute_name)
    end
  end
end

Essa é uma boa maneira de fazer isso, pois incluirá apenas o que você define attr_accessore não todas as variáveis ​​de instância.

class Gift
  ATTRIBUTES = [:name, :price]
  attr_accessor(*ATTRIBUTES)

  def create_random_instance_variable
    @xyz = 123
  end

  def to_h
    ATTRIBUTES.each_with_object({}) do |attribute_name, memo|
      memo[attribute_name] = send(attribute_name)
    end
  end
end

g = Gift.new
g.name = "Foo"
g.price = 5.25
g.to_h
#=> {:name=>"Foo", :price=>5.25}

g.create_random_instance_variable
g.to_h
#=> {:name=>"Foo", :price=>5.25}
Vinicius Brasil
fonte
0

Comecei a usar estruturas para facilitar as conversões de hash. Em vez de usar uma estrutura simples, crio minha própria classe derivada de um hash, isso permite que você crie suas próprias funções e documenta as propriedades de uma classe.

require 'ostruct'

BaseGift = Struct.new(:name, :price)
class Gift < BaseGift
  def initialize(name, price)
    super(name, price)
  end
  # ... more user defined methods here.
end

g = Gift.new('pearls', 20)
g.to_h # returns: {:name=>"pearls", :price=>20}
Alex
fonte