lib/mobility/backends/table.rb
# frozen_string_literal: true
require "mobility/plugins/cache"
module Mobility
module Backends
=begin
Stores attribute translation as rows on a model-specific translation table
(similar to Globalize[https://github.com/globalize/globalize]). By default,
the table name for a model +Post+ with table +posts+ will be
+post_translations+, and the translation class will be +Post::Translation+. The
translation class is dynamically created when the backend is initialized on the
model class, and subclasses
{Mobility::Backends::ActiveRecord::Table::Translation} (for AR models) or
inherits {Mobility::Backends::Sequel::Table::Translation} (for Sequel models).
The backend expects the translations table (+post_translations+) to have:
- a string column named +locale+ to store the locale of the translation
- columns for each translated attribute that uses the table (in general, this
will be all attributes of the model)
- an integer column with name +post_id+ (where +post+ is the name of the model class)
If you are using Rails, you can use the +mobility:translations+ generator to
create a migration generating this table with:
rails generate mobility:translations post title:string content:text
Unlike Globalize, attributes need not all be on one table. Mobility supports
any number of translation tables for a given model class (all of the structure
described above), provided the +association_name+ option is different for each.
Some translations can be stored on one translation table, others on
another, and Mobility will handle mapping reads/writes to each. The subclass
used in this case will be generated from the +association_name+ by
singularizing it and converting it to camelcase.
For more details, see examples in {Mobility::Backends::ActiveRecord::Table}.
==Backend Options
===+association_name+
Name of association on model. Defaults to +:translations+. If specified,
ensure name does not overlap with other methods on model or with the
association name used by other backends on model (otherwise one will overwrite
the other).
===+table_name+
Name of translations table. By default, if the table used by the model is
+posts+, the table name used for translations will be +post_translations+.
===+foreign_key+
Foreign key to use in defining the association on the model. By default, if the
model is a +Post+, this will be +post_id+. Generally this does not need to be
set.
===+subclass_name+
Subclass to use when dynamically generating translation class for model, by
default +:Translation+. Should be a symbol. Generally this does not need to be
set.
@see Mobility::Backends::ActiveRecord::Table
@see Mobility::Backends::Sequel::Table
=end
module Table
# @!method association_name
# Returns the name of the translations association.
# @return [Symbol] Name of the association
# @!method subclass_name
# Returns translation subclass under model class namespace.
# @return [Symbol] Name of translation subclass
# @!method foreign_key
# Returns foreign_key for translations association.
# @return [Symbol] Name of foreign key
# @!method table_name
# Returns name of table where translations are stored.
# @return [Symbol] Name of translations table
# @!group Backend Accessors
# @!macro backend_reader
def read(locale, **options)
translation_for(locale, **options).send(attribute)
end
# @!macro backend_writer
def write(locale, value, **options)
translation_for(locale, **options).send("#{attribute}=", value)
end
# @!endgroup
# @!macro backend_iterator
def each_locale
translations.each { |t| yield t.locale.to_sym }
end
private
def translations
model.send(association_name)
end
def self.included(backend_class)
backend_class.extend ClassMethods
backend_class.option_reader :association_name
backend_class.option_reader :subclass_name
backend_class.option_reader :foreign_key
backend_class.option_reader :table_name
end
module ClassMethods
def valid_keys
[:association_name, :subclass_name, :foreign_key, :table_name]
end
# Apply custom processing for cache plugin
def include_cache
include self::Cache
end
def table_alias(locale)
"#{table_name}_#{Mobility.normalize_locale(locale)}"
end
end
# Simple hash cache to memoize translations as a hash so they can be
# fetched quickly.
module Cache
def translation_for(locale, **options)
return super(locale, options) if options.delete(:cache) == false
if cache.has_key?(locale)
cache[locale]
else
cache[locale] = super(locale, **options)
end
end
def clear_cache
cache.clear
end
private
def cache
if model.instance_variable_defined?(cache_name)
model.instance_variable_get(cache_name)
else
model.instance_variable_set(cache_name, {})
end
end
def cache_name
@cache_name ||= :"@__mobility_#{association_name}_cache"
end
end
end
end
end