Skip to content

How to migrate from KeyValue to Container backend in mobility gem

Published: at 12:00 AM

One of the tasks I’m currently working on is migration from KeyValue backend to Container backend in the project powered with mobility gem. Today we’ll try to perform such migration. Of course - you can adapt code below to migrate between other backends.

Firstly, you need to change your backend in the config file config/initializers/mobility.rb if you want to act globally

Mobility.configure do
  plugins do
    # backend :key_value
    backend :container

    # ........
  end
end

or in model

class Tag < ApplicationRecord
  # translates :title, backend: :key_value
  translates :title, backend: :container
end

Let’s collect information about translated models. There are 2 database tables which are used to store translations on key-value backend - mobility_string_translations and “mobility_text_translations`. Those tables contain data in the following format:

Let’s collect information about translated models

app_dev=# select distinct translatable_type from mobility_string_translations;
 translatable_type
-------------------
 Country
 Tag
 Interest
(3 rows)


app_dev=# select distinct translatable_type from mobility_text_translations;
 translatable_type
-------------------
(0 rows)

As you see - there are only strings, but no text translations, so let’s move on.

Next step - let’s add required columns to our tables

$ rails g migration MigrateToContainerMobilityBackend
class MigrateToContainerMobilityBackend < ActiveRecord::Migration[7.1]
  def change
    %w[tags countries interests].each do |table|
      change_table table.to_sym, bulk: true do |t|
        t.jsonb :translations, default: {}
      end
    end
  end
end

Don’t forget to remove type from mobility DSL in models

class Tag < ApplicationRecord
  extend Mobility

  # translates :title, type: :string, locale_accessors: true
  translates :title, locale_accessors: true
end

Then we will write some code to copy data. You can use it in the rake task, new migration or console - as you wish.

ActiveRecord::Base.connection.execute("select * from mobility_string_translations").each do |tr|
  tr["translatable_type"].constantize.find(tr["translatable_id"]).update("#{tr["key"]}_#{tr["locale"]}": tr["value"])
end

ActiveRecord::Base.connection.execute("select * from mobility_text_translations").each do |tr|
  tr["translatable_type"].constantize.find(tr["translatable_id"]).update("#{tr["key"]}_#{tr["locale"]}": tr["value"])
end

Latest step - now you can drop unneccessary tables and columns

$ rails g migration DropMobilityTablesAndUnusedColumns
class DropMobilityTablesAndUnusedColumns < ActiveRecord::Migration[7.1]
  def change
    drop_table :mobility_string_translations
    drop_table :mobility_text_translations

    # if you haven't removed those columns before
    remove_column :tags, :title
    remove_column :interests, :title
    remove_column :countries, :title
  end
end

Easy, huh? Hope this recipe will be useful for you.

If you like this post, you can hire me as an independent consultant for your project.
Just drop me a message using my contacts on the About me page