Rails返回JSON序列化属性with_indifferent_access

时间:2013-01-28 18:48:09

标签: ruby-on-rails ruby

我之前有过:

serialize :params, JSON

但是这会返回JSON并将哈希键符号转换为字符串。我想使用符号引用哈希,这在使用哈希时最常见。我给它添加符号,Rails返回字符串。为了避免这种情况,我创建了自己的getter / setter。 setter很简单(JSON编码),getter是:

  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序列化以返回无关紧要的访问哈希(或不将符号转换为字符串),并且仍然具有如上所述的哈希工作与合并?如果没有,我可以做些什么来改善我的吸气剂,以便model.params.merge!有效?

我希望得到一些符合某种意义的东西:

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

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

更好的是,只需让Rails返回一个具有无差别访问权限的哈希值,或者不将我的符号转换为字符串!感谢任何帮助。

4 个答案:

答案 0 :(得分:16)

使用内置的serialize方法:

class Whatever < ActiveRecord::Base
 serialize :params, HashWithIndifferentAccess
end

请参阅ActiveRecord::Base docs on serialization了解详情。

答案 1 :(得分:7)

根据@ fguillen的要求发表评论作为回答......警告:我通常不是Rubyist ......所以这可能不是惯用或有效的。从功能上说,它让我得到了我想要的东西。似乎在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

最终,我意识到我只想要符号,而不是无关紧要的访问,但通过调整加载方法,你可以得到任何一种行为。

答案 2 :(得分:3)

我最终使用了bimsapi解决方案的变体,您可以使用 使用简单的非嵌套JSON但任何 JSON。< / p>

一旦加载......

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

然后而不是调用

JSON.load(http_response)
你打电话

JsonHelper::JsonWithIndifferentAccess.load(http_response)

同样的事情,但所有嵌套的哈希都是无关紧要的访问。

应该为您提供良好的服务,但在将其作为所有解析的默认方法之前要考虑一下,因为大量的JSON有效负载将在本机JSON解析器之上添加重要的ruby操作,该解析器在C中进行了优化,并且更加完全针对性能而设计。

答案 3 :(得分:3)

使用HashWithIndifferentAccess很棒,但它仍然像Hash一样,它只能在数据库中序列化为YAML。

我的偏好,使用Postgres 9.3及更高版本,是在Postgres中使用json列类型。这意味着当读取表时,ActiveRecord将直接从Postgres获取Hash。

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

ActiveRecord serialize要求您为其提供一个类,该类负责读取/写入数据序列化/反序列化。

因此,您可以通过继承HashWithIndifferentAccess或我的偏好Hashie::Mash来创建完成工作的对象。然后,您将序列化实现为dumpload类方法。

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

如果你没有在Postgres中使用json列类型,那么实现会略有改变

此处提供完整的代码和文档:using a JSON column typeusing a string column type