Skip to content
Chris Salzberg edited this page Mar 29, 2017 · 23 revisions

The KeyValue backend stores translations on a set of two shared tables, one for string-valued translations and one for text-valued translations. These tables are generated when you run the default install generator (rails generate mobility:install) and then migrate your database (rake db:migrate).

A detailed description of the backend is described as "Strategy #3" in this blog post.

The tables generated by the default migration look like this:

create_table "mobility_string_translations", force: :cascade do |t|
  t.string   "locale"
  t.string   "key"
  t.string   "value"
  t.integer  "translatable_id"
  t.string   "translatable_type"
  t.datetime "created_at",        null: false
  t.datetime "updated_at",        null: false
  t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_string_translations_on_translatable_attribute"
  t.index ["translatable_id", "translatable_type", "locale", "key"], name: "index_mobility_string_translations_on_keys", unique: true
  t.index ["translatable_type", "key", "value", "locale"], name: "index_mobility_string_translations_on_query_keys"
end

create_table "mobility_text_translations", force: :cascade do |t|
  t.string   "locale"
  t.string   "key"
  t.text     "value"
  t.integer  "translatable_id"
  t.string   "translatable_type"
  t.datetime "created_at",        null: false
  t.datetime "updated_at",        null: false
  t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_text_translations_on_translatable_attribute"
  t.index ["translatable_id", "translatable_type", "locale", "key"], name: "index_mobility_text_translations_on_keys", unique: true
end

Both tables have a translatable_type string column, because these will be used in a polymorphic relationship with translated models.

To understand how this works, suppose we have a model Post:

class Post < ActiveRecord::Base
  include Mobility
  translates :title,   backend: :key_value, type: :string
  translates :content, backend: :key_value, type: :text
end

The backend will create associations on the class roughly resulting in:

class Post < ActiveRecord::Base
  has_many :mobility_string_translations, ->{ where key: [:title] },
    as:         :translatable,
    class_name: Mobility::ActiveRecord::StringTranslation,
    dependent:  :destroy,
    inverse_of: :translatable,
    autosave:   true

  has_many :mobility_text_translations,   ->{ where key: [:content] },
    as:         :translatable,
    class_name: Mobility::ActiveRecord::TextTranslation,
    dependent:  :destroy,
    inverse_of: :translatable,
    autosave:   true
end

So the backend adds associations for each type, scoped to only include translations whose keys are :title and :content, respectively.

The classes Mobility::ActiveRecord::StringTranslation and Mobility::ActiveRecord::TextTranslation both inherit from Mobility::ActiveRecord::Translation, which is an abstract class:

module Mobility
  module ActiveRecord
    class Translation < ::ActiveRecord::Base
      self.abstract_class = true

      belongs_to :translatable, polymorphic: true

      validates :key, presence: true, uniqueness: { scope: [:translatable_id, :translatable_type, :locale] }
      validates :translatable, presence: true
      validates :locale, presence: true
    end
  end
end

Since the class itself is abstract, it has no table; the table is set in the descendants Mobility::ActiveRecord::StringTranslation and Mobility::ActiveRecord::TextTranslation to mobility_string_translations and mobility_text_translations, respectively. The different tables is actually the only difference between these classes, and the only difference between the tables is that their value columns are of different types (string/text), and that the string table has one additional index on value.