Rails return JSON serialized attribute with_indifferent_access

前端 未结 4 1436
日久生厌
日久生厌 2021-02-07 05:55

I previously had:

serialize :params, JSON

But this would return the JSON and convert hash key symbols to strings. I want to reference the hash

相关标签:
4条回答
  • 2021-02-07 06:31

    Using HashWithIndifferentAccess is great, but it still acts like a Hash, and it can only serialize as YAML in the database.

    My preference, using Postgres 9.3 and higher, is to use the json column type in Postgres. This means that when the table is read, ActiveRecord will get a Hash directly from Postgres.

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

    ActiveRecord serialize requires that you provide it a single class that is both responsible for reading/writing the data and serializing/deserializing it.

    So you can create an object that does the job by inheriting from HashWithIndifferentAccess, or my preference, Hashie::Mash. Then you implement the serialization as the dump and load class methods.

    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
    

    In your model, you can specify this class for serialization.

    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
    

    If you don't use the json column type in Postgres, the implementation changes slightly

    Full code and documentation here: using a JSON column type and using a string column type.

    0 讨论(0)
  • 2021-02-07 06:36

    use the built-in serialize method :

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

    see ActiveRecord::Base docs on serialization for more info.

    0 讨论(0)
  • 2021-02-07 06:38

    Posting comment as answer, per @fguillen's request... Caveat: I am not typically a Rubyist… so this may not be idiomatic or efficient. Functionally, it got me what I wanted. Seems to work in Rails 3.2 and 4.0...

    In 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
    

    In my model, I have a field called rule_spec, serialized into a text field:

    serialize :rule_spec, ApplicationHelper::JSONWithIndifferentAccess
    

    Ultimately, I realized I just wanted symbols, not indifferent access, but by tweaking the load method you can get either behavior.

    0 讨论(0)
  • 2021-02-07 06:53

    I ended up using a variation on bimsapi's solution that you can use not only with simple un-nested JSON but any JSON.

    Once this is loaded...

    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
    

    then instead of calling

    JSON.load(http_response)
    

    you just call

    JsonHelper::JsonWithIndifferentAccess.load(http_response)
    

    Does the same thing but all the nested hashes are indifferent access.

    Should serve you well but think a little before making it your default approach for all parsing as massive JSON payloads will add significant ruby operations on top of the native JSON parser which is optimised in C and more fully designed for performance.

    0 讨论(0)
提交回复
热议问题