Ruby конвертирует объект в хэш

127

Допустим, у меня есть Giftобъект с @name = "book"& @price = 15.95. Как лучше всего преобразовать это в хэш {name: "book", price: 15.95}в Ruby, а не в Rails (хотя не стесняйтесь давать и ответ Rails)?

ma11hew28
источник
16
Подойдет ли @ gift.attributes.to_options?
Mr. L
1
1) Является ли подарок объектом ActiveRecord? 2) можем ли мы предположить, что @ name / @ price - это не только переменные экземпляра, но и средства доступа для чтения? 3) вы хотите только имя и цену или все атрибуты в подарке, какими бы они ни были?
Tokland
@tokland, 1) нет, Giftэто так же , как @nash определил , за исключением того , 2) убедитесь, что переменные экземпляра могут иметь читателя аксессоров. 3) Вся атрибутика в подарок.
ma11hew28
Хорошо. Вопрос о доступе к переменным экземпляра / читателям заключался в том, чтобы узнать, нужен ли внешний доступ (nash) или внутренний метод (levinalex). Я обновил свой ответ на «внутренний» подход.
Tokland

Ответы:

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}

В качестве альтернативы 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}
Василий Ермолович
источник
3
Вы можете использовать inject, чтобы пропустить инициализацию переменной: gift.instance_variables.inject ({}) {| hash, var | хэш [var.to_s.delete ("@")] = gift.instance_variable_get (var); hash}
Jordan
8
Ницца. Я заменил var.to_s.delete("@")на, var[1..-1].to_symчтобы получить символы.
ma11hew28
3
Не используйте инъекцию, используйте gift.instance_variables.each_with_object({}) { |var,hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }и избавьтесь от ; hash
следа
1
Я никогда не пойму рубиновый фетиш для each. mapи injectнамного мощнее. Это одна из проблем дизайна, которые у меня есть с Ruby: mapи injectони реализованы с помощью each. Это просто плохая информатика.
Нейт Саймер
Чуть более сжато:hash = Hash[gift.instance_variables.map { |var| [var.to_s[1..-1], gift.instance_variable_get(var)] } ]
Марвин
293

Просто скажи (текущий объект) .attributes

.attributesвозвращает hashлюбой из object. И он намного чище.

Остин Маруско
источник
138
Обратите внимание, что это метод, специфичный для ActiveModel, а не метод Ruby.
Bricker
6
В случае с Sequel - используйте .values: sequel.jeremyevans.net/rdoc/classes/Sequel/Model/…
dimitarvp
instance_valuesможет использоваться для всех объектов Ruby для аналогичного вывода.
bishal
48

Реализовать #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
источник
Технически это должно быть .to_hash, поскольку # указывает методы класса.
Калеб
6
Вообще-то, нет. В документации RDoc говорится: Use :: for describing class methods, # for describing instance methods, and use . for example code(источник: ruby-doc.org/documentation-guidelines.html ) Кроме того, официальная документация (например, ruby ​​CHANGELOG, github.com/ruby/ruby/blob/v2_1_0/NEWS ) использует #методы экземпляра и точку для методов класса довольно последовательно.
levinalex
Пожалуйста, используйте инъекцию вместо этого антипаттерна.
YoTengoUnLCD
each_with_objectinstance_variables.each_with_object(Hash.new(0)) { |element, hash| hash["#{element}".delete("@").to_sym] = instance_variable_get(element) }
Однострочный
43
Gift.new.instance_values # => {"name"=>"book", "price"=>15.95}
Эрик Ридстрем
источник
10
Это Rails, которого у самого Ruby нет instance_values. Обратите внимание, что Мэтт попросил использовать Ruby, а не Rails.
Christopher Creutzig
28
Он также сказал, что не стесняйтесь давать ответ Rails ... я так и сделал.
Эрик Ридстром
Боже, чтобы увидеть здесь обе версии;) Понравилось
Себастьян Шюрманн
17

Вы можете использовать as_jsonметод. Он преобразует ваш объект в хэш.

Но этот хеш будет иметь значение для имени этого объекта в качестве ключа. В твоем случае,

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

Если вам нужен хэш, хранящийся в объекте, используйте as_json(root: false). Думаю по умолчанию root будет ложным. Для получения дополнительной информации обратитесь к официальному руководству по рубину.

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

vicke4
источник
13

Для объектов 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

а потом просто позвони

gift.to_hash()
purch.to_hash() 
Моника Мевенкамп
источник
2
забавно, что это не часть структуры Rails. Кажется, это полезно иметь там.
Magne
Метод атрибутов возвращает новый хэш со значениями внутри - поэтому нет необходимости создавать другой в методе to_hash. Примерно так: attribute_names.each_with_object ({}) {| name, attrs | attrs [имя] = атрибут чтения (имя)}. См. Здесь: github.com/rails/rails/blob/master/activerecord/lib/…
Крис Кимптон
вы могли бы сделать это с помощью карты, ваша реализация побочного эффекта задевает мой разум!
Nate Symer
11

Если вы не работаете в среде Rails (т.е. у вас нет ActiveRecord), это может быть полезно:

JSON.parse( object.to_json )
Андреас Райо Книп
источник
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
источник
6

Вы можете написать очень элегантное решение, используя функциональный стиль.

class Object
  def hashify
    Hash[instance_variables.map { |v| [v.to_s[1..-1].to_sym, instance_variable_get v] }]
  end
end
Нейт Саймер
источник
4

Вы должны переопределить inspectметод своего объекта, чтобы он возвращал желаемый хэш, или просто реализовать аналогичный метод, не отменяя поведение объекта по умолчанию.

Если вы хотите стать более привлекательным, вы можете перебирать переменные экземпляра объекта с помощью object.instance_variables

Dominic
источник
4

Рекурсивно преобразуйте ваши объекты в хэш с помощью хешируемого гема ( https://rubygems.org/gems/hashable ). Пример

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
источник
4

Может, хочу попробовать instance_values. Это сработало для меня.

охотник
источник
1

Создает неглубокую копию в виде хеш-объекта только атрибутов модели

my_hash_gift = gift.attributes.dup

Проверить тип полученного объекта

my_hash_gift.class
=> Hash
Шон
источник
0

Если вам нужно преобразовать и вложенные объекты.

# @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 # }}}
Серджио Сантьяго
источник
0

Gift.new.attributes.symbolize_keys

Лалит Кумар Маурья
источник
0

Чтобы сделать это без Rails, чистый способ - хранить атрибуты в константе.

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

А затем, чтобы преобразовать экземпляр Giftв a Hash, вы можете:

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

Это хороший способ сделать это, потому что он будет включать только то, что вы определяете attr_accessor, а не каждую переменную экземпляра.

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}
Винисиус Бразил
источник
0

Я начал использовать структуры, чтобы упростить хэш-преобразования. Вместо использования простой структуры я создаю свой собственный класс, производный от хэша, это позволяет вам создавать свои собственные функции и документировать свойства класса.

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
источник