85320

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

Question:

I am currently upgrading a Ruby on Rails app from 4.2 to 5.0 and am running into a roadblock concerning fields that store data as a serialized hash. For instance, I have

class Club serialize :social_media, Hash end

When creating new clubs and inputting the social media everything works fine, but for the existing social media data I'm getting:

<blockquote>

ActiveRecord::SerializationTypeMismatch: Attribute was supposed to be a Hash, but was a ActionController::Parameters.

</blockquote>

How can I convert all of the existing data from ActionController::Parameter objects to simple hashes? Database is mysql.

Answer1:

From the <a href="http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize" rel="nofollow">fine manual</a>:

<blockquote>

<strong>serialize</strong>(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.

</blockquote>

So when you say this:

serialize :social_media, Hash

ActiveRecord will require the unserialized social_media to be a Hash. However, as noted by <a href="https://stackoverflow.com/a/48773916/479863" rel="nofollow">vnbrs</a>, 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:

<ol><li>Pull each social_media out of the table as a string.</li> <li>Unpack that YAML string into a Ruby object: obj = YAML.load(str).</li> <li>Convert that object to a Hash: h = obj.to_unsafe_h.</li> <li>Write that Hash back to a YAML string: str = h.to_yaml.</li> <li>Put that string back into the database to replace the old one from <strong>(1)</strong>.</li> </ol>

Note the <a href="http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-to_unsafe_h" rel="nofollow">to_unsafe_h</a> call in <strong>(3)</strong>. Just calling <a href="http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-to_h" rel="nofollow">to_h</a> (or <a href="http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-to_hash" rel="nofollow">to_hash</a> 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.

<hr />

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.

Answer2:

The <a href="http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#actioncontroller-parameters-no-longer-inherits-from-hashwithindifferentaccess" rel="nofollow">official Ruby on Rails documentation</a> has a section about upgrading between Rails versions that explains more about the error you have:

<blockquote>

<strong>ActionController::Parameters No Longer Inherits from HashWithIndifferentAccess</strong><br /> Calling params in your application will now return an object instead of a hash. If your parameters are already permitted, then you will not need to make any changes. If you are regardless of permitted? you will need to upgrade your application to first permit and then convert to a hash.<br />

params.permit([:proceed_to, :return_to]).to_h </blockquote>

Recommend

  • CodeIgniter: View doesn't load if I use die() function
  • KnockoutJS observableArray to update when inner observable is changed
  • jgit - git diff based on file extension
  • Type or namespace 'Sqlite' could not be found in SQLite.cs
  • Firebase: Should I add GoogleService-Info.plist to .gitignore?
  • div after a div which position is fixed in css
  • Selection Sort in Java produces incorrect results
  • In PHP, what is a binary string (b'xxxx')?
  • How to access inner structures in R?
  • How to do the disintegration animation when clicking on Thanos' gauntlet when searching THANOS
  • when i am trying to save the data to mysql, an exception is being thrown( java.sql.SQLException)
  • I need to call another sql file within an sql file using sql plus
  • How to fix the beginner's ReactJS error?
  • Why stored procedure return -1
  • When reading a binary file with java, how? [closed]
  • How do i get rid of call __x86.get_pc_thunk.ax
  • uploading video to googlevideo.com
  • How to place UI widgets on top of multiple Z ordered Surface Views in Android
  • Best way to send continuous data in Java using Netty
  • Replace Windows command FTP -s:E:\\FtpScript.txt with SFTP? [duplicate]
  • Buildbot running sequential builders after they're finished
  • Full Height Image
  • Use animate() with series of levelplots in R raster
  • What's wrong with my PNG IDAT chunk?
  • Rails Template Error with Heroku
  • How do you place a variable inside a template tag's argument?
  • Texture streaming in DirectX11, Immutable vs Dynamic
  • Draw ring with given thickness, position, and radius. (Java2D)
  • Trigger powershell based on event log
  • How to manipulate content of a comment with Apache POI
  • async GET request with body from browser
  • Java 11 and E(fx)clipse JavaFX plugin on Eclipse 4.9: An error has occurred - see the log file
  • can you use embedded ruby in custom javascript files in rails?
  • android : speech recognition what are the technologies available
  • $this->a->b->c->d calling methods from a superclass in php
  • Adding horizontal slider to QTableWidget
  • How to check if a database and tables exist in sql server in a vb .net project?
  • Rotating Towards Path in OpenGL
  • Is there a better way for handling SpatialPolygons that cross the antimeridian (date line)?
  • How to change user identity when git pushing via ssh?