One of the tasks I’m currently working on is a migration from KeyValue backend to Container backend in the project powered by 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:
id- record idlocale- locale of the translated string/textkey- translated key, in code sample above, it istitlevalue- comments are unnecessarytranslatable_typeandtranslatable_id- class and id of translated model, in code sample above it isTagwith some idcreated_atandupdated_at- timestamps
Let’s check which models actually use translations:
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
Last step - now you can drop unnecessary 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? I hope this recipe will be useful for you.