Read more

ActiveRecord::Store: migrate data in store

Daniel Straßner
May 24, 2018Software engineer at makandra GmbH

When you need to store structured data (like Ruby hashes) in a single database column with ActiveRecord, a simple way is to use PostgreSQL's jsonb columns. ActiveRecord will automatically serialize and deserialize your Hash to and from JSON, and you can index JSON paths for fast reads.

Illustration web development

Do you need DevOps-experts?

Your development team has a full backlog? No time for infrastructure architecture? Our DevOps team is ready to support you!

  • We build reliable cloud solutions with Infrastructure as code
  • We are experts in security, Linux and databases
  • We support your dev team to perform
Read more Show archive.org snapshot

As an alternative, ActiveRecord::Store Show archive.org snapshot offers a way to store hashes in a single database column. This card will show you how to migrate those hashes in an ActiveRecord::Migration by example:

Let's assume you have got a model User with settings stored as YAML in a single column:

class User < ActiveRecord::Base
  typed_store :settings, coder: YAML do |s|
    s.string :lang
    s.integer :show_number_of_posts
  end
end

Note: in this example we used a typed_store Show archive.org snapshot , which basically is a store with type constraints.

Now you notice that the setting lang was not named well and you'd rather have language. So let's rename the settings key:

class RenameLanguageKeyInUser < ActiveRecord::Migration

  class User < ActiveRecord::Base
    def rename_settings_key!(from, to)
      from, to = from.to_s, to.to_s
      old_settings = YAML.load(self.settings)
      new_settings = old_settings.dup
      
      if new_settings.has_key?(from)
        new_settings[to] = new_settings.delete(from)
        self.update_attributes!(settings: YAML.dump(new_settings))
        puts "Migrated User settings from #{old_settings.to_s} to #{new_settings.to_s}"
      else
        puts "Did not migrate User settings, '#{from}' was not a settings key"
      end
    end
  end

  def up
    User.find_each { |user| user.rename_settings_key!(:lang, :language) }
  end

  def down
    User.find_each { |user| user.rename_settings_key!(:language, :lang) }
  end

end

Here are some key pointers of this migration:

  • you should never use your actual application models in migrations as those might change over time. That's why we embed the model into the migration
  • use the specified coder to deserialize/serialize your store
  • manipulate the data in between
  • the output is optional of course but might be helpful when you first test the migration and rollback
Posted by Daniel Straßner to makandra dev (2018-05-24 11:15)