【问题标题】:Rails return JSON serialized attribute with_indifferent_accessRails 返回 JSON 序列化属性 with_indifferent_access
【发布时间】:2013-01-28 18:48:08
【问题描述】:

我以前有:

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 返回一个具有无关访问权限的哈希,或者不将我的符号转换为字符串!感谢任何帮助。

【问题讨论】:

  • model.params.merge!(test: 'test').save ?
  • 不,我不想保存到数据库中。目前主要用于测试目的。我发现的唯一其他解决方法是model.params = model.params.merge(test: 'test')
  • 我希望可能有一些红宝石魔法,比如我可以为参数添加的特殊方法,比如params{}
  • model.params[:test] = 'test' 有机会吗?
  • 变量赋值仍然有效,正如我用model.params = ... 说明的那样,它是Hash.merge 我不能直接在属性上执行,即使它返回更新的哈希值。

标签: ruby-on-rails ruby


【解决方案1】:

我最终使用了 bimsapi 解决方案的变体,您可以将 不仅 用于简单的非嵌套 JSON,还可以使用 任何 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

然后而不是调用

JSON.load(http_response)

你打电话

JsonHelper::JsonWithIndifferentAccess.load(http_response)

做同样的事情,但所有嵌套的哈希都是无差别的访问。

应该为您提供良好的服务,但在将其设为所有解析的默认方法之前请三思,因为大量 JSON 有效负载将在原生 JSON 解析器之上添加重要的 ruby​​ 操作,该解析器在 C 中进行了优化,并且更全面地为性能而设计。

【讨论】:

  • 完美!如果没有现成的解决方案,这正是我正要写的那种东西。
  • 刚刚复制了课程并使用了serialize :params, JsonWithIndifferentAccess 并像魅力一样工作!
【解决方案2】:

使用内置的serialize 方法:

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

请参阅ActiveRecord::Base docs on serialization 了解更多信息。

【讨论】:

  • 这意味着我必须为它提供一个具有无关紧要访问权限的哈希。我只想退货一个。不过谢谢。这似乎是正确的方向。
  • 是的......我不确定你要去哪里。我不知道任何具有Json 对象的库,您可以在其上使用#merge!(在ruby 中,任何JSON 都只是一个字符串......)。正如我所说,您应该推出自己的代理类。
  • 昨晚研究了同样的问题...因为您可以传递任何具有loaddump 方法的类来序列化,所以我只是用我自己的类JSONWithSymbolizedNames 包装了JSON ,传入symbolize_names:true
  • 当我使用 Hash 初始化属性并尝试保存实例时,Rails 4 中出现错误:Attribute was supposed to be a ActiveSupport::HashWithIndifferentAccess, but was a Hash.
  • @fguillen - 已发布...希望对您有所帮助。并为第一次拼错你的名字道歉;)
【解决方案3】:

根据@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

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

【讨论】:

    【解决方案4】:

    使用HashWithIndifferentAccess 很好,但它仍然像哈希一样,它只能在数据库中序列化为 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-06-12
      • 2021-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多