Подтвердить что ты не робот

Rails возвращает JSON-сериализованный атрибут with_indifferent_access

Я ранее имел:

serialize :params, JSON

Но это вернет JSON и преобразует символы хеш-ключа в строки. Я хочу ссылаться на хеш с помощью символов, как это наиболее часто встречается при работе с хэшами. Я кормлю его символами, Rails возвращает строки. Чтобы этого избежать, я создал свой собственный приемник/сеттер. Установщик достаточно прост (JSON-кодирование), получателем является:

  def params
    read_attribute(:params) || JSON.parse(read_attribute(:params).to_json).with_indifferent_access
  end

Я не мог ссылаться на params напрямую, потому что это вызовет цикл, поэтому я использую read_attribute, и теперь мои хеш-ключи могут ссылаться на символы или строки. Однако это не обновляет хэш:

model.params.merge!(test: 'test')
puts model.params # => returns default params without merge

Что заставляет меня думать, что хэш ссылается на копию.

Мой вопрос двоякий. Могу ли я продлить активную запись JSON-сериализации, чтобы возвращать безразличный хэш доступа (или не преобразовывать символы в строки), и все еще иметь работу hash, как указано выше, слиянием? Если нет, что я могу сделать, чтобы улучшить мой геттер, чтобы model.params.merge! работал?

Я надеялся на что-то похожее (что работает):

  def params_merge!(hash)
    write_attribute(:params, read_attribute(:params).merge(hash))
  end

  # usage: model.params_merge!(test: 'test')

Еще лучше, просто запустите Rails, чтобы вернуть хэш с равнодушным доступом или не преобразовать мои символы в строки! Цените любую помощь.

4b9b3361

Ответ 2

Проводка комментария как ответа, по запросу @fguillen... Предостережение: Я обычно не рубист... поэтому это может быть не идиоматично или эффективно. Функционально, он получил меня, что я хотел. Кажется, работает в Rails 3.2 и 4.0...

В application_helper.rb:

module ApplicationHelper
  class JSONWithIndifferentAccess
    def self.load(str)
      obj = HashWithIndifferentAccess.new(JSON.load(str))
      #...or simply: obj = JSON.load(str, nil, symbolize_names:true)
      obj.freeze #i also want it set all or nothing, not piecemeal; ymmv
      obj
    end
    def self.dump(obj)
      JSON.dump(obj)
    end
  end
end

В моей модели у меня есть поле под названием rule_spec, сериализованное в поле text:

serialize :rule_spec, ApplicationHelper::JSONWithIndifferentAccess

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

Ответ 3

Использование HashWithIndifferentAccess велико, но оно по-прежнему действует как Hash, и оно может только сериализоваться как YAML в базе данных.

Мои предпочтения, используя Postgres 9.3 и выше, - использовать тип столбца json в Postgres. Это означает, что когда таблица будет прочитана, ActiveRecord получит Hash непосредственно из Postgres.

create_table "gadgets" do |t|
  t.json "info"
end

ActiveRecord serialize требует, чтобы вы предоставили ему один класс, который отвечает за чтение/запись данных и сериализацию/десериализацию.

Таким образом, вы можете создать объект, который выполняет задание, наследуя от HashWithIndifferentAccess или моих предпочтений, Hashie::Mash. Затем вы реализуете сериализацию как методы класса dump и load.

class HashieMashStoredAsJson < Hashie::Mash
  def self.dump(obj)
    ActiveSupport::JSON.encode(obj.to_h)
  end


  def self.load(raw_hash)
    new(raw_hash || {}) 
  end
end

В вашей модели вы можете указать этот класс для сериализации.

class Gadget < ActiveRecord::Base
  serialize :info, HashieMashStoredAsJson

  # This allows the field to be set as a Hash or anything compatible with it.
  def info=(new_value)
    self[:info] = HashieMashStoredAsJson.new new_value
  end
end

Если вы не используете тип столбца json в Postgres, реализация немного меняется

Полный код и документация здесь: с использованием типа столбца JSON и используя тип столбца строки.

Ответ 4

В итоге я использовал вариант решения bimsapi, который позволяет отображать всю структуру JSON в равнодушные хэши (т.е. массивы хэшей массивов и т.д.).

module JsonHelper

  class JsonWithIndifferentAccess
    def self.load(str)
      self.indifferent_access JSON.load(str)
    end

    def self.dump(obj)
      JSON.dump(obj)
    end

    private

      def self.indifferent_access(obj)
        if obj.is_a? Array
          obj.map!{|o| self.indifferent_access(o)}
        elsif obj.is_a? Hash
          obj.with_indifferent_access
        else
          obj
        end
      end
  end

end