class Mobility::Backends::ActiveRecord::Table

Implements the {Mobility::Backends::Table} backend for ActiveRecord models.

To generate a translation table for a model Post, you can use the included mobility:translations generator:

rails generate mobility:translations post title:string content:text

This will create a migration which can be run to create the translation table. If the translation table already exists, it will create a migration adding columns to that table.

@example Model with table backend

class Post < ApplicationRecord
  extend Mobility
  translates :title, backend: :table
end

post = Post.create(title: "foo")
#<Post:0x00... id: 1>

post.title
#=> "foo"

post.translations
#=> [#<Post::Translation:0x00...
#  id: 1,
#  locale: "en",
#  post_id: 1,
#  title: "foo">]

Post::Translation.first
#=> #<Post::Translation:0x00...
#  id: 1,
#  locale: "en",
#  post_id: 1,
#  title: "foo">

@example Model with multiple translation tables

class Post < ActiveRecord::Base
  extend Mobility
  translates :title,   backend: :table, table_name: :post_title_translations,   association_name: :title_translations
  translates :content, backend: :table, table_name: :post_content_translations, association_name: :content_translations
end

post = Post.create(title: "foo", content: "bar")
#<Post:0x00... id: 1>

post.title
#=> "foo"

post.content
#=> "bar"

post.title_translations
#=> [#<Post::TitleTranslation:0x00...
#  id: 1,
#  locale: "en",
#  post_id: 1,
#  title: "foo">]

post.content_translations
#=> [#<Post::ContentTranslation:0x00...
#  id: 1,
#  locale: "en",
#  post_id: 1,
#  content: "bar">]

Post::TitleTranslation.first
#=> #<Post::TitleTranslation:0x00...
#  id: 1,
#  locale: "en",
#  post_id: 1,
#  title: "foo">

Post::ContentTranslation.first
#=> #<Post::ContentTranslation:0x00...
#  id: 1,
#  locale: "en",
#  post_id: 1,
#  title: "bar">

Public Class Methods

apply_scope(relation, predicate, locale = Mobility.locale, invert: false) click to toggle source

Joins translations using either INNER/OUTER join appropriate to the query. @param [ActiveRecord::Relation] relation Relation to scope @param [Object] predicate Arel predicate @param [Symbol] locale (Mobility.locale) Locale @option [Boolean] invert @return [ActiveRecord::Relation] relation Relation with joins applied (if needed)

# File lib/mobility/backends/active_record/table.rb, line 131
def apply_scope(relation, predicate, locale = Mobility.locale, invert: false)
  visitor = Visitor.new(self, locale)
  if join_type = visitor.accept(predicate)
    join_type &&= Visitor::INNER_JOIN if invert
    join_translations(relation, locale, join_type)
  else
    relation
  end
end
build_node(attr, locale) click to toggle source

@param [String] attr Attribute name @param [Symbol] _locale Locale @return [Mobility::Plugins::Arel::Attribute] Arel node for column on translation table

# File lib/mobility/backends/active_record/table.rb, line 119
def build_node(attr, locale)
  aliased_table = model_class.const_get(subclass_name).arel_table.alias(table_alias(locale))
  Plugins::Arel::Attribute.new(aliased_table, attr, locale, self)
end
configure(options) click to toggle source

@!group Backend Configuration @option options [Symbol] association_name (:translations)

Name of association method

@option options [Symbol] table_name Name of translation table @option options [Symbol] foreign_key Name of foreign key @option options [Symbol] subclass_name (:Translation) Name of subclass

to append to model class to generate translation class
# File lib/mobility/backends/active_record/table.rb, line 102
def configure(options)
  table_name = model_class.table_name
  options[:table_name]  ||= "#{table_name.singularize}_translations"
  options[:foreign_key] ||= table_name.downcase.singularize.camelize.foreign_key
  if (association_name = options[:association_name]).present?
    options[:subclass_name] ||= association_name.to_s.singularize.camelize.freeze
  else
    options[:association_name] = :translations
    options[:subclass_name] ||= :Translation
  end
  %i[foreign_key association_name subclass_name table_name].each { |key| options[key] = options[key].to_sym }
end

Private Class Methods

already_joined?(relation, locale, join_type) click to toggle source
# File lib/mobility/backends/active_record/table.rb, line 152
def already_joined?(relation, locale, join_type)
  if join = get_join(relation, locale)
    return true if (join_type == Visitor::OUTER_JOIN) || (Visitor::INNER_JOIN === join)
    relation.joins_values -= [join]
  end
  false
end
get_join(relation, locale) click to toggle source
# File lib/mobility/backends/active_record/table.rb, line 160
def get_join(relation, locale)
  relation.joins_values.find { |v| (::Arel::Nodes::Join === v) && (v.left.name == table_alias(locale).to_s) }
end
join_translations(relation, locale, join_type) click to toggle source
# File lib/mobility/backends/active_record/table.rb, line 143
def join_translations(relation, locale, join_type)
  return relation if already_joined?(relation, locale, join_type)
  m = model_class.arel_table
  t = model_class.const_get(subclass_name).arel_table.alias(table_alias(locale))
  relation.joins(m.join(t, join_type).
                 on(t[foreign_key].eq(m[:id]).
                    and(t[:locale].eq(locale))).join_sources)
end

Public Instance Methods

translation_for(locale, **) click to toggle source

Returns translation for a given locale, or builds one if none is present. @param [Symbol] locale

# File lib/mobility/backends/active_record/table.rb, line 291
def translation_for(locale, **)
  translation = translations.in_locale(locale)
  translation ||= translations.build(locale: locale)
  translation
end