Ruby convierte objetos a hash

127

Digamos que tengo un Giftobjeto con @name = "book"& @price = 15.95. ¿Cuál es la mejor manera de convertir eso al Hash {name: "book", price: 15.95}en Ruby, no a Rails (aunque siéntase libre de responder también a los Rails)?

ma11hew28
fuente
16
¿Lo haría @ gift.attributes.to_options?
Sr. L
1
1) ¿El regalo es un objeto ActiveRecord? 2) ¿podemos suponer que @ name / @ price no son solo variables de instancia sino también lectores de acceso? 3) ¿quieres solo el nombre y el precio o todos los atributos de un regalo, sean los que sean?
tokland
@tokland, 1) no, Giftes exactamente como @nash ha definido , excepto que, 2) seguro, las variables de instancia pueden tener accesores de lector. 3) Todos los atributos en regalo.
ma11hew28
Okay. La pregunta sobre el acceso a las variables de instancia / lectores era saber si se quería un acceso externo (nash) o un método interno (levinalex). Actualicé mi respuesta para el enfoque "interno".
tokland

Respuestas:

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 con 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
fuente
3
Puede usar inyectar para omitir la inicialización de la variable: gift.instance_variables.inject ({}) {| hash, var | hash [var.to_s.delete ("@")] = gift.instance_variable_get (var); hash}
Jordania
8
Agradable. Reemplacé var.to_s.delete("@")con var[1..-1].to_sympara obtener símbolos.
ma11hew28
3
No uses inyectar, usar gift.instance_variables.each_with_object({}) { |var,hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }y deshacerte del final; hash
Narfanator
1
Nunca entenderé el fetiche del rubí each. mapy injectson mucho más poderosos Este es un problema de diseño que tengo con Ruby: mapy que injectse implementa con each. Es simplemente una mala informática.
Nate Symer
Un poco más conciso:hash = Hash[gift.instance_variables.map { |var| [var.to_s[1..-1], gift.instance_variable_get(var)] } ]
Marvin el
293

Solo di (objeto actual) .attributes

.attributesdevuelve uno hashde cualquiera object. Y es mucho más limpio también.

Austin Marusco
fuente
138
Tenga en cuenta que este es un método específico de ActiveModel, no un método Ruby.
bricker
66
En el caso de Sequel - use .values: sequel.jeremyevans.net/rdoc/classes/Sequel/Model/…
dimitarvp
instance_valuesse puede usar para todos los objetos de rubí para la salida similar.
bishal
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
fuente
Técnicamente, debería ser .to_hash, ya que # indica métodos de clase.
Caleb
66
En realidad no. La documentación de RDoc dice: Use :: for describing class methods, # for describing instance methods, and use . for example code(fuente: ruby-doc.org/documentation-guidelines.html ) Además, la documentación oficial (como ruby ​​CHANGELOG, github.com/ruby/ruby/blob/v2_1_0/NEWS ) utiliza #métodos de ejemplo y el punto para métodos de clase de manera bastante consistente.
levinalex
Utilice inyectar en lugar de este antipatrón.
YoTengoUnLCD
Variante de una línea usando each_with_object:instance_variables.each_with_object(Hash.new(0)) { |element, hash| hash["#{element}".delete("@").to_sym] = instance_variable_get(element) }
anothermh
43
Gift.new.instance_values # => {"name"=>"book", "price"=>15.95}
Erik Reedstrom
fuente
10
Esto es Rails, Ruby en sí no tiene instance_values. Tenga en cuenta que Matt pidió una manera de Ruby, específicamente no Rails.
Christopher Creutzig
28
También dijo que siéntase libre de responder a los Rails también ... así que lo hice.
Erik Reedstrom
Que Dios vea ambas versiones aquí;) Me gustó
Sebastian Schürmann
17

Puedes usar el as_jsonmétodo. Convertirá tu objeto en hash.

Pero, ese hash vendrá como un valor para el nombre de ese objeto como una clave. En tu caso,

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

Si necesita un hash que está almacenado en el uso del objeto as_json(root: false). Creo que, por defecto, la raíz será falsa. Para obtener más información, consulte la guía oficial de ruby

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

vicke4
fuente
13

Para objetos de registro activo

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

y luego solo llama

gift.to_hash()
purch.to_hash() 
monika mevenkamp
fuente
2
Es curioso que no forme parte del marco de Rails. Parece algo útil tener allí.
Magne
El método de atributos devuelve un nuevo hash con los valores, por lo que no es necesario crear otro en el método to_hash. Así: atributos_nombres.each_with_object ({}) {| name, attrs | attrs [name] = read_attribute (name)}. Ver aquí: github.com/rails/rails/blob/master/activerecord/lib/…
Chris Kimpton
podrías haber hecho esto con el mapa, ¡tu implementación de efectos secundarios está dañando mi mente, hombre!
Nate Symer
11

Si no está en un entorno Rails (es decir, no tiene ActiveRecord disponible), esto puede ser útil:

JSON.parse( object.to_json )
Andreas Rayo Kniep
fuente
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
fuente
6

Puedes escribir una solución muy elegante usando un 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
fuente
4

Debe anular el inspectmétodo de su objeto para devolver el hash deseado, o simplemente implementar un método similar sin anular el comportamiento predeterminado del objeto.

Si desea ser más elegante, puede iterar sobre las variables de instancia de un objeto con object.instance_variables

Dominic
fuente
4

Convierta recursivamente sus objetos a hash usando gemas 'hashables' ( https://rubygems.org/gems/hashable ) Ejemplo

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
fuente
4

Tal vez quiera intentarlo instance_values. Eso funcionó para mí.

Cazador
fuente
1

Produce una copia superficial como un objeto hash de solo los atributos del modelo

my_hash_gift = gift.attributes.dup

Verifique el tipo del objeto resultante

my_hash_gift.class
=> Hash
Sean
fuente
0

Si necesita convertir objetos anidados también.

# @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
fuente
0

Gift.new.attributes.symbolize_keys

Lalit Kumar Maurya
fuente
0

Para hacer esto sin Rails, una forma limpia es almacenar atributos en una constante.

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

Y luego, para convertir una instancia de Gifta Hash, puede:

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

Esta es una buena manera de hacerlo porque solo incluirá lo que defina attr_accessory no todas las variables de instancia.

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
fuente
0

Comencé a usar estructuras para hacer conversiones fáciles de hash. En lugar de utilizar una estructura simple, creo mi propia clase derivada de un hash, esto le permite crear sus propias funciones y documenta las propiedades de una clase.

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
fuente