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:
id
- record idlocale
- locale of the translated string/textkey
- translated key, in code sample above, it istitle
value
- comments are unnecessarytranslatable_type
andtranslatable_id
- class and id of translated model, in code sample above it isTag
with some idcreated_at
andupdated_at
- timestamps
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.