Ruby konwertuje Object na Hash

131

Powiedzmy, że mam Giftobiekt z @name = "book"& @price = 15.95. Jaki jest najlepszy sposób na przekonwertowanie tego na Hash {name: "book", price: 15.95}w Ruby, a nie Railsy (chociaż możesz też udzielić odpowiedzi Railsom)?

ma11hew28
źródło
17
Czy @ gift.attributes.to_options zrobi?
Pan L
1
1) Czy prezent jest obiektem ActiveRecord? 2) Czy możemy założyć, że @ nazwa / @ cena to nie tylko zmienne instancji, ale także akcesory czytników? 3) chcesz tylko nazwę i cenę lub wszystkie atrybuty prezentu, czymkolwiek one są?
tokland
@tokland, 1) nie, Giftjest dokładnie takie, jak zdefiniował @nash , z wyjątkiem 2) oczywiście , zmienne instancji mogą mieć dostęp do czytnika. 3) Wszystkie atrybuty prezentu.
ma11hew28
Ok. Pytanie o dostęp do zmiennych instancji / czytników polegało na tym, aby wiedzieć, czy potrzebna jest metoda dostępu zewnętrznego (nash), czy metody wewnętrznej (levinalex). Zaktualizowałem swoją odpowiedź dotyczącą podejścia „wewnętrznego”.
tokland

Odpowiedzi:

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}

Alternatywnie z 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}
Wasilij Ermolowicz
źródło
3
Możesz użyć inject, aby pominąć inicjalizację zmiennej: gift.instance_variables.inject ({}) {| hash, var | hash [var.to_s.delete ("@")] = gift.instance_variable_get (var); hash}
Jordan,
8
Ładny. Wymieniłem var.to_s.delete("@")z var[1..-1].to_symdostać symboli.
ma11hew28
3
Nie używaj wstrzykiwań, używaj gift.instance_variables.each_with_object({}) { |var,hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }i pozbądź się końcowego; hash
Narfanator,
1
Nigdy nie zrozumiem fetyszu rubinów each. mapi injectsą znacznie potężniejsze. To jedna z wad projektowych, które mam w Rubim: mapi injectsą one realizowane z each. To po prostu zła informatyka.
Nate Symer
Nieco bardziej zwięźle:hash = Hash[gift.instance_variables.map { |var| [var.to_s[1..-1], gift.instance_variable_get(var)] } ]
Marvin
294

Po prostu powiedz (bieżący obiekt) .attributes

.attributeszwraca hashz any object. Jest też dużo czystszy.

Austin Marusco
źródło
141
Zauważ, że jest to metoda specyficzna dla ActiveModel, a nie metoda Rubiego.
Bricker
6
W przypadku Sequela.values
dimitarvp
instance_valuesmoże być użyty dla wszystkich obiektów ruby ​​dla podobnego wyniku.
bishal
48

Wdrożyć #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
źródło
Technicznie powinno to być .to_hash, ponieważ # wskazuje metody klas.
Caleb
6
Właściwie nie. Dokumentacja RDoc mówi: Use :: for describing class methods, # for describing instance methods, and use . for example code(źródło: ruby-doc.org/documentation-guidelines.html ) Ponadto oficjalna dokumentacja (taka jak ruby CHANGELOG , github.com/ruby/ruby/blob/v2_1_0/NEWS ) używa #na przykład metod i kropki dla metod klasowych dość konsekwentnie.
levinalex
Proszę użyć wstrzyknięcia zamiast tego anty-wzoru.
YoTengoUnLCD
Wariant each_with_objectinstance_variables.each_with_object(Hash.new(0)) { |element, hash| hash["#{element}".delete("@").to_sym] = instance_variable_get(element) }
jednoliniowy
45
Gift.new.instance_values # => {"name"=>"book", "price"=>15.95}
Erik Reedstrom
źródło
10
To jest Rails, sam Ruby nie ma instance_values. Zauważ, że Matt poprosił o sposób w Ruby, a konkretnie nie o Railsy.
Christopher Creutzig
28
Powiedział również, że nie krępuj się udzielić odpowiedzi Railsom ... więc tak zrobiłem.
Erik Reedstrom
Boże, żeby zobaczyć obie wersje tutaj;) Podobało mi się
Sebastian Schürmann
17

Możesz użyć as_jsonmetody. Przekształci twój obiekt w hash.

Ale ten skrót będzie wartością dla nazwy tego obiektu jako klucza. W Twoim przypadku,

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

Jeśli potrzebujesz skrótu przechowywanego w obiekcie, użyj as_json(root: false). Myślę, że domyślnie root będzie fałszywy. Aby uzyskać więcej informacji, zapoznaj się z oficjalnym przewodnikiem po Rubim

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

vicke4
źródło
13

Dla obiektów Active Record

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

a potem po prostu zadzwoń

gift.to_hash()
purch.to_hash() 
monika mevenkamp
źródło
2
zabawne, że nie jest częścią frameworka Rails. Wydaje się, że warto tam mieć.
Magne,
Metoda atrybutów zwraca nowy hash z wartościami w - więc nie ma potrzeby tworzenia kolejnego w metodzie to_hash. Tak jak to: nazwy_atrybutów.each_with_object ({}) {| nazwa, atrybuty | attrs [name] = read_attribute (name)}. Zobacz tutaj: github.com/rails/rails/blob/master/activerecord/lib/…
Chris Kimpton
mogłeś to zrobić z mapą, twój efekt uboczny rani mój umysł!
Nate Symer
11

Jeśli nie jesteś w środowisku Railsów (tj. Nie masz dostępnego ActiveRecord), może to być pomocne:

JSON.parse( object.to_json )
Andreas Rayo Kniep
źródło
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
źródło
6

Możesz napisać bardzo eleganckie rozwiązanie używając funkcjonalnego stylu.

class Object
  def hashify
    Hash[instance_variables.map { |v| [v.to_s[1..-1].to_sym, instance_variable_get v] }]
  end
end
Nate Symer
źródło
4

Powinieneś zastąpić inspectmetodę obiektu, aby zwrócić żądany skrót, lub po prostu zaimplementować podobną metodę bez zastępowania domyślnego zachowania obiektu.

Jeśli chcesz być bardziej wyrafinowany, możesz iterować zmienne instancji obiektu za pomocą object.instance_variables

Dominic
źródło
4

Rekurencyjnie konwertuj swoje obiekty na hash za pomocą klejnotu „hashable” ( https://rubygems.org/gems/hashable ) Przykład

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
źródło
4

Może zechcesz spróbować instance_values. To zadziałało dla mnie.

Łowca
źródło
1

Tworzy płytką kopię jako obiekt skrótu tylko atrybutów modelu

my_hash_gift = gift.attributes.dup

Sprawdź typ wynikowego obiektu

my_hash_gift.class
=> Hash
Sean
źródło
0

Jeśli potrzebujesz również konwersji zagnieżdżonych obiektów.

# @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
źródło
0

Gift.new.attributes.symbolize_keys

Lalit Kumar Maurya
źródło
0

Aby to zrobić bez Railsów, czystym sposobem jest przechowywanie atrybutów w stałej.

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

Następnie, aby przekonwertować wystąpienie Giftna a Hash, możesz:

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

Jest to dobry sposób na zrobienie tego, ponieważ będzie obejmował tylko to, co zdefiniujesz attr_accessor, a nie każdą zmienną instancji.

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
źródło
0

Zacząłem używać struktur, aby ułatwić konwersje haszujące. Zamiast używać czystej struktury, tworzę własną klasę wywodzącą się z skrótu, co pozwala na tworzenie własnych funkcji i dokumentuje właściwości klasy.

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}
Alexander Oh
źródło