Rails Return JSON Serialized Attribute With_Indifferent_Access

Rails return JSON serialized attribute with_indifferent_access

use the built-in serialize method :

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

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

Is there a way to serialize ActiveRecord's JSON properties using HashWithIndifferentAccess?

| You can use a custom serializer so you can access the JSON object with symbols as well.

# app/models/user.rb
class User < ActiveRecord::Base
serialize :preferences, HashSerializer
end

# app/serializers/hash_serializer.rb
class HashSerializer
def self.dump(hash)
hash
end

def self.load(hash)
(hash || {}).with_indifferent_access
end
end

Full credit - sans googling - goes to http://nandovieira.com/using-postgresql-and-jsonb-with-ruby-on-rails.

RSpec: comparing a hash with string keys against a hash with symbol keys?

You could do:

it "should match" do
{:a => 1, :b => 2}.stringify_keys.should =~ {"a" => 1, "b" => 2}
end

Postgresql JSON column as HashWithIndifferentAccess

Serialize alone wont work here since HashWithIndifferentAccess does not respond to both load and dump methods, but you can do this:

class THEModel < ActiveRecord::Base
def my_hash_attribute
read_attribute(:my_hash_attribute).with_indifferent_access
end
end

See also Custom serialization for fields in Rails

How to get hash values from a multiple check_box in rails?

Look at the api of check_box_tag:

check_box_tag(name, value = "1", checked = false, options = {}) 
  • The first argument is the name: what the value is stored under, and this usually shows up in params
  • The second argument is the value, and this gets stringified in order to make the html tag.
  • Third is the beginning checked state
  • Finally is the options

So since you are passing an object to value, rails is calling .to_s on that object in order to create the html tag. That is why you are getting the no implicit conversion of Symbol into Integer error, because you are calling […] on a string, which returns the nth character of the string. It's not the best error message, unfortunately, because it doesn't mention that you are accessing a string, not a hash, and it would be the same error message for an array as well. What you probably want is to serialize the value so that you can unserialize it later:

  • Try calling .to_json on the second argument. This should change your value in params[:product_lots] to ['{"bol_id": 3086, "product_lot_id": 6021}'].
  • Notice that this is still a string, so you need to deserialize it (although there is a chance rails could automatically deserialize this, depending on configuration and filters, etc.).
  • try params[:product_lots].map!{|data| JSON.parse(data, object_class: HashWithIndifferentAccess)}. This should now give you an array of hashes (with indifferent access, so you can use symbol keys). (See also Json::parse)
  • you can now get the values you wish
  • consider moving this into a helper, before action/filter, etc., to be able to use it multiple times, potentially for multiple parameters

Existing data serialized as hash produces error when upgrading to Rails 5

From the fine manual:

serialize(attr_name, class_name_or_coder = Object)

[...] If class_name is specified, the serialized object must be of that class on assignment and retrieval. Otherwise SerializationTypeMismatch will be raised.

So when you say this:

serialize :social_media, Hash

ActiveRecord will require the unserialized social_media to be a Hash. However, as noted by vnbrs, ActionController::Parameters no longer subclasses Hash like it used to and you have a table full of serialized ActionController::Parameters instances. If you look at the raw YAML data in your social_media column, you'll see a bunch of strings like:

--- !ruby/object:ActionController::Parameters...

rather than Hashes like this:

---\n:key: value...

You should fix up all your existing data to have YAMLized Hashes in social_media rather than ActionController::Parameters and whatever else is in there. This process will be somewhat unpleasant:

  1. Pull each social_media out of the table as a string.
  2. Unpack that YAML string into a Ruby object: obj = YAML.load(str).
  3. Convert that object to a Hash: h = obj.to_unsafe_h.
  4. Write that Hash back to a YAML string: str = h.to_yaml.
  5. Put that string back into the database to replace the old one from (1).

Note the to_unsafe_h call in (3). Just calling to_h (or to_hash for that matter) on an ActionController::Parameters instance will give you an exception in Rails5, you have to include a permit call to filter the parameters first:

h = params.to_h                   # Exception!
h = params.permit(:whatever).to_h # Indifferent access hash with one entry

If you use to_unsafe_h (or to_unsafe_hash) then you get the whole thing in a HashWithIndifferentAccess. Of course, if you really want a plain old Hash then you'd say:

h = obj.to_unsafe_h.to_h

to unwrap the indifferent access wrapper as well. This also assumes that you only have ActionController::Parameters in social_media so you might need to include an obj.respond_to?(:to_unsafe_hash) check to see how you unpack your social_media values.

You could do the above data migration through direct database access in a Rails migration. This could be really cumbersome depending on how nice the low level MySQL interface is. Alternatively, you could create a simplified model class in your migration, something sort of like this:

class YourMigration < ...
class ModelHack < ApplicationRecord
self.table_name = 'clubs'
serialize :social_media
end

def up
ModelHack.all.each do |m|
# Update this to match your real data and what you want `h` to be.
h = m.social_media.to_unsafe_h.to_h
m.social_media = h
m.save!
end
end

def down
raise ActiveRecord::IrreversibleMigration
end
end

You'd want to use find_in_batches or in_batches_of instead all if you have a lot of Clubs of course.


If your MySQL supports json columns and ActiveRecord works with MySQL's json columns (sorry, PostgreSQL guy here), then this might be a good time to change the column to json and run far away from serialize.



Related Topics



Leave a reply



Submit