lib/mobility/backends/key_value.rb
# frozen_string_literal: true
require "mobility/plugins/cache"
module Mobility
module Backends
=begin
Stores attribute translation as attribute/value pair on a shared translations
table, using a polymorphic relationship between a translation class and models
using the backend. By default, two tables are assumed to be present supporting
string and text translations: a +mobility_text_translations+ table for
text-valued translations and a +string_translations+ table for
string-valued translations (the only difference being the column type of the
+value+ column on the table).
==Backend Options
===+type+
Currently, either +:text+ or +:string+ is supported, but any value is allowed
as long as a corresponding +class_name+ can be found (see below). Determines
which class to use for translations, which in turn determines which table to
use to store translations (by default +text_translations+ for text type,
+string_translations+ for string type).
===+class_name+
Class to use for translations when defining association. By default,
{Mobility::ActiveRecord::TextTranslation} or
{Mobility::ActiveRecord::StringTranslation} for ActiveRecord models (similar
for Sequel models). If string is passed in, it will be constantized to get the
class.
===+association_name+
Name of association on model. Defaults to +<type>_translations+, which will
typically be either +:text_translations+ (if +type+ is +:text+) or
+:string_translations (if +type+ is +:string+). 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).
@see Mobility::Backends::ActiveRecord::KeyValue
@see Mobility::Backends::Sequel::KeyValue
=end
module KeyValue
# @!method association_name
# Returns the name of the polymorphic association.
# @return [Symbol] Name of the association
# @!method class_name
# Returns translation class used in polymorphic association.
# @return [Class] Translation class
# @!group Backend Accessors
# @!macro backend_reader
def read(locale, **options)
translation_for(locale, **options).send(value_column)
end
# @!macro backend_writer
def write(locale, value, **options)
translation_for(locale, **options).send(:"#{value_column}=", value)
end
# @!endgroup
# @!macro backend_iterator
def each_locale
translations.each { |t| yield(t.locale.to_sym) if t.send(key_column) == attribute }
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 :class_name
backend_class.option_reader :key_column
backend_class.option_reader :value_column
backend_class.option_reader :belongs_to
end
module ClassMethods
def valid_keys
[:type, :association_name, :class_name, :key_column, :value_column, :belongs_to]
end
# @!group Backend Configuration
# @option options [Symbol,String] type Column type to use
# @option options [Symbol] association_name (:<type>_translations) Name
# of association method, defaults to +<type>_translations+
# @option options [Symbol] class_name Translation class, defaults to
# +Mobility::<ORM>::<type>Translation+
# @raise [ArgumentError] if +type+ is not set, and both +class_name+
# and +association_name+ are also not set
def configure(options)
options[:type] &&= options[:type].to_sym
options[:association_name] &&= options[:association_name].to_sym
options[:class_name] &&= Util.constantize(options[:class_name])
options[:key_column] ||= :key
options[:value_column] ||= :value
options[:belongs_to] ||= :translatable
if !(options[:type] || (options[:class_name] && options[:association_name]))
raise ArgumentError, "KeyValue backend requires an explicit type option, either text or string."
end
end
# Apply custom processing for cache plugin
def include_cache
include self::Cache
end
def table_alias(attr, locale)
"#{model_class}_#{attr}_#{Mobility.normalize_locale(locale)}_#{options[:association_name]}"
end
end
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 = {}
end
private
def cache
@cache ||= {}
end
end
end
end
end