Ruby convertir un objet en hachage

127

Disons que j'ai un Giftobjet avec @name = "book"& @price = 15.95. Quelle est la meilleure façon de convertir cela en Hash {name: "book", price: 15.95}dans Ruby, pas en Rails (bien que n'hésitez pas à donner la réponse Rails aussi)?

ma11hew28
la source
16
Est-ce que @ gift.attributes.to_options ferait l'affaire?
M. L
1
1) Le cadeau est-il un objet ActiveRecord? 2) Pouvons-nous supposer que @ name / @ price ne sont pas seulement des variables d'instance mais aussi des accesseurs de lecteur? 3) Vous ne voulez que le nom et le prix ou tous les attributs d'un cadeau quels qu'ils soient?
tokland
@tokland, 1) non, Giftest exactement comme @nash a défini , sauf, 2) bien sûr, les variables d'instance peuvent avoir des accesseurs de lecture. 3) Tous les attributs du cadeau.
ma11hew28
D'accord. La question sur les variables d'instance / accès des lecteurs était de savoir si on voulait un accès extérieur (nash) ou une méthode intérieure (levinalex). J'ai mis à jour ma réponse pour l'approche «à l'intérieur».
tokland

Réponses:

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}

Alternativement avec 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
la source
3
Vous pouvez utiliser inject pour ignorer l'initialisation de la variable: gift.instance_variables.inject ({}) {| hash, var | hash [var.to_s.delete ("@")] = gift.instance_variable_get (var); hash}
Jordan
8
Agréable. J'ai remplacé var.to_s.delete("@")par var[1..-1].to_sympour obtenir des symboles.
ma11hew28
3
Ne pas utiliser d'injection, utiliser gift.instance_variables.each_with_object({}) { |var,hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }et se débarrasser de la traîne; hash
Narfanator
1
Je ne comprendrai jamais le fétiche du rubis each. mapet injectsont beaucoup plus puissants. C'est un problème de conception que j'ai avec Ruby: mapet injectsont implémentés avec each. C'est tout simplement une mauvaise informatique.
Nate Symer
Un peu plus concis:hash = Hash[gift.instance_variables.map { |var| [var.to_s[1..-1], gift.instance_variable_get(var)] } ]
Marvin
293

Dites simplement (objet actuel) .attributes

.attributesrenvoie a hashde any object. Et c'est aussi beaucoup plus propre.

Austin Marusco
la source
138
Notez qu'il s'agit d'une méthode spécifique à ActiveModel et non d'une méthode Ruby.
bricker
6
Dans le cas de Sequel - utilisez .values: sequel.jeremyevans.net/rdoc/classes/Sequel/Model/…
dimitarvp
instance_valuespeut être utilisé pour tous les objets ruby ​​pour une sortie similaire.
bishal
48

Mettre en œuvre #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
la source
Techniquement, cela devrait être .to_hash, puisque # indique les méthodes de classe.
Caleb
6
En fait non. La documentation RDoc dit: Use :: for describing class methods, # for describing instance methods, and use . for example code(source: ruby-doc.org/documentation-guidelines.html ) De plus, la documentation officielle (comme le ruby ​​CHANGELOG, github.com/ruby/ruby/blob/v2_1_0/NEWS ) utilise #par exemple des méthodes et le point pour les méthodes de classe de manière assez cohérente.
levinalex
Veuillez utiliser inject au lieu de cet anti-modèle.
YoTengoUnLCD
Variante à une ligne utilisant 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
la source
10
C'est Rails, Ruby lui-même n'a pas instance_values. Notez que Matt a demandé un moyen Ruby, en particulier pas Rails.
Christopher Creutzig
28
Il a également dit que n'hésitez pas à donner la réponse Rails aussi ... alors je l'ai fait.
Erik Reedstrom
Dieu de voir les deux versions ici;) J'ai aimé
Sebastian Schürmann
17

Vous pouvez utiliser la as_jsonméthode. Cela convertira votre objet en hachage.

Mais, ce hachage viendra en tant que valeur du nom de cet objet en tant que clé. Dans ton cas,

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

Si vous avez besoin d'un hachage stocké dans l'objet, utilisez as_json(root: false). Je pense que la racine par défaut sera fausse. Pour plus d'informations, reportez-vous au guide officiel de rubis

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

vicke4
la source
13

Pour les objets d'enregistrement actifs

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

et puis juste appeler

gift.to_hash()
purch.to_hash() 
monika mevenkamp
la source
2
drôle ça ne fait pas partie du framework Rails. Cela semble être une chose utile à avoir là-bas.
Magne
La méthode attributes renvoie un nouveau hachage avec les valeurs in - donc pas besoin d'en créer un autre dans la méthode to_hash. Comme ceci: attribut_noms.each_with_object ({}) {| name, attrs | attrs [nom] = read_attribute (nom)}. Voir ici: github.com/rails/rails/blob/master/activerecord/lib/…
Chris Kimpton
vous auriez pu le faire avec map, votre implémentation des effets secondaires me fait mal à l'esprit!
Nate Symer
11

Si vous n'êtes pas dans un environnement Rails (c'est-à-dire que vous n'avez pas ActiveRecord disponible), cela peut être utile:

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

Vous pouvez écrire une solution très élégante en utilisant un style fonctionnel.

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

Vous devez remplacer la inspectméthode de votre objet pour renvoyer le hachage souhaité, ou simplement implémenter une méthode similaire sans remplacer le comportement par défaut de l'objet.

Si vous voulez être plus sophistiqué, vous pouvez parcourir les variables d'instance d'un objet avec object.instance_variables

Dominique
la source
4

Convertissez récursivement vos objets en hachage en utilisant un gem 'hashable' ( https://rubygems.org/gems/hashable ) Exemple

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
la source
4

Je veux peut-être essayer instance_values. Cela a fonctionné pour moi.

chasseur
la source
1

Produit une copie superficielle en tant qu'objet de hachage des seuls attributs du modèle

my_hash_gift = gift.attributes.dup

Vérifiez le type de l'objet résultant

my_hash_gift.class
=> Hash
Sean
la source
0

Si vous avez également besoin de convertir des objets imbriqués.

# @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
la source
0

Gift.new.attributes.symbolize_keys

Lalit Kumar Maurya
la source
0

Pour ce faire sans Rails, une méthode propre consiste à stocker les attributs sur une constante.

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

Et puis, pour convertir une instance de Giften a Hash, vous pouvez:

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

C'est un bon moyen de le faire car il n'inclura que ce que vous définissez attr_accessor, et non toutes les variables d'instance.

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
la source
0

J'ai commencé à utiliser des structures pour faciliter les conversions de hachage. Au lieu d'utiliser une structure nue, je crée ma propre classe dérivée d'un hachage, cela vous permet de créer vos propres fonctions et de documenter les propriétés d'une 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
la source