lib/mobility/backends/sequel/key_value.rb in mobility-1.0.7 vs lib/mobility/backends/sequel/key_value.rb in mobility-1.1.0
- old
+ new
@@ -30,18 +30,17 @@
super
if type = options[:type]
options[:association_name] ||= :"#{options[:type]}_translations"
options[:class_name] ||= const_get("#{type.capitalize}Translation")
end
- options[:table_alias_affix] = "#{model_class}_%s_#{options[:association_name]}"
rescue NameError
raise ArgumentError, "You must define a Mobility::Sequel::#{type.capitalize}Translation class."
end
# @!endgroup
def build_op(attr, locale)
- QualifiedIdentifier.new(table_alias(attr, locale), :value, locale, self, attr)
+ QualifiedIdentifier.new(table_alias(attr, locale), value_column, locale, self, attr)
end
# @param [Sequel::Dataset] dataset Dataset to prepare
# @param [Object] predicate Predicate
# @param [Symbol] locale Locale
@@ -56,14 +55,14 @@
def join_translations(dataset, attr, locale, join_type)
dataset.join_table(join_type,
class_name.table_name,
{
- key: attr.to_s,
- locale: locale.to_s,
- translatable_type: model_class.name,
- translatable_id: ::Sequel[:"#{model_class.table_name}"][:id]
+ key_column => attr.to_s,
+ :locale => locale.to_s,
+ :"#{belongs_to}_type" => model_class.name,
+ :"#{belongs_to}_id" => ::Sequel[:"#{model_class.table_name}"][:id]
},
table_alias: table_alias(attr, locale))
end
# @return [Hash] Hash of attribute/join_type pairs
@@ -125,72 +124,81 @@
end
backend = self
setup do |attributes, options|
- association_name = options[:association_name]
- translations_class = options[:class_name]
+ association_name = options[:association_name]
+ translation_class = options[:class_name]
+ key_column = options[:key_column]
+ value_column = options[:value_column]
+ belongs_to = options[:belongs_to]
+ belongs_to_id = :"#{belongs_to}_id"
+ belongs_to_type = :"#{belongs_to}_type"
+ # Track all attributes for this association, so that we can limit the scope
+ # of keys for the association to only these attributes. We need to track the
+ # attributes assigned to the association in case this setup code is called
+ # multiple times, so we don't "forget" earlier attributes.
+ #
attrs_method_name = :"#{association_name}_attributes"
association_attributes = (instance_variable_get(:"@#{attrs_method_name}") || []) + attributes
instance_variable_set(:"@#{attrs_method_name}", association_attributes)
one_to_many association_name,
- reciprocal: :translatable,
- key: :translatable_id,
+ reciprocal: belongs_to,
+ key: belongs_to_id,
reciprocal_type: :one_to_many,
- conditions: { translatable_type: self.to_s, key: association_attributes },
- adder: proc { |translation| translation.update(translatable_id: pk, translatable_type: self.class.to_s) },
- remover: proc { |translation| translation.update(translatable_id: nil, translatable_type: nil) },
- clearer: proc { send(:"#{association_name}_dataset").update(translatable_id: nil, translatable_type: nil) },
- class: translations_class
+ conditions: { belongs_to_type => self.to_s, key_column => association_attributes },
+ adder: proc { |translation| translation.update(belongs_to_id => pk, belongs_to_type => self.class.to_s) },
+ remover: proc { |translation| translation.update(belongs_to_id => nil, belongs_to_type => nil) },
+ clearer: proc { send_(:"#{association_name}_dataset").update(belongs_to_id => nil, belongs_to_type => nil) },
+ class: translation_class
callback_methods = Module.new do
define_method :before_save do
super()
- send(association_name).select { |t| attributes.include?(t.key) && Util.blank?(t.value) }.each(&:destroy)
+ send(association_name).select { |t| attributes.include?(t.__send__(key_column)) && Util.blank?(t.__send__(value_column)) }.each(&:destroy)
end
define_method :after_save do
super()
attributes.each { |attribute| mobility_backends[attribute].save_translations }
end
end
include callback_methods
- include DestroyKeyValueTranslations
+ # Clean up *all* leftover translations of this model, only once.
+ translation_classes = [translation_class, *Mobility::Backends::Sequel::KeyValue::Translation.descendants].uniq
+ define_method :after_destroy do
+ super()
+
+ @mobility_after_destroy_translation_classes = [] unless defined?(@mobility_after_destroy_translation_classes)
+ (translation_classes - @mobility_after_destroy_translation_classes).each do |klass|
+ klass.where(belongs_to_id => id, belongs_to_type => self.class.name).destroy
+ end
+ @mobility_after_destroy_translation_classes += translation_classes
+ end
include(mod = Module.new)
backend.define_column_changes(mod, attributes)
end
# Returns translation for a given locale, or initializes one if none is present.
# @param [Symbol] locale
# @return [Mobility::Backends::Sequel::KeyValue::TextTranslation,Mobility::Backends::Sequel::KeyValue::StringTranslation]
def translation_for(locale, **)
- translation = model.send(association_name).find { |t| t.key == attribute && t.locale == locale.to_s }
- translation ||= class_name.new(locale: locale, key: attribute)
+ translation = model.send(association_name).find { |t| t.__send__(key_column) == attribute && t.locale == locale.to_s }
+ translation ||= class_name.new(locale: locale, key_column => attribute)
translation
end
# Saves translation which have been built and which have non-blank values.
def save_translations
cache.each_value do |translation|
- next unless present?(translation.value)
+ next unless present?(translation.__send__ value_column)
translation.id ? translation.save : model.send("add_#{singularize(association_name)}", translation)
end
end
- # Clean up *all* leftover translations of this model, only once.
- module DestroyKeyValueTranslations
- def after_destroy
- super
- [:string, :text].freeze.each do |type|
- Mobility::Backends::Sequel::KeyValue.const_get("#{type.capitalize}Translation").
- where(translatable_id: id, translatable_type: self.class.name).destroy
- end
- end
- end
-
class CacheRequired < ::StandardError; end
module Cache
include KeyValue::Cache
@@ -210,58 +218,83 @@
@attribute_name = attribute_name || column
super(table, column)
end
end
- module Translation
- def self.included(base)
+ class Translatable < Module
+ attr_reader :key_column, :value_column, :belongs_to, :id_column, :type_column
+
+ def initialize(key_column, value_column, belongs_to)
+ @key_column = key_column
+ @value_column = value_column
+ @belongs_to = belongs_to
+ @id_column = :"#{belongs_to}_id"
+ @type_column = :"#{belongs_to}_type"
+ end
+
+ # Strictly these are not "descendants", but to keep terminology
+ # consistent with ActiveRecord KeyValue backend.
+ def descendants
+ @descendants ||= Set.new
+ end
+
+ def included(base)
+ @descendants ||= Set.new
+ @descendants << base
+
+ mod = self
+ key_column = mod.key_column
+ id_column = mod.id_column
+ type_column = mod.type_column
+
base.class_eval do
plugin :validation_helpers
# Paraphased from sequel_polymorphic gem
#
model = underscore(self.to_s)
plural_model = pluralize(model)
- many_to_one :translatable,
+ many_to_one mod.belongs_to,
reciprocal: plural_model.to_sym,
reciprocal_type: :many_to_one,
setter: (proc do |able_instance|
- self[:translatable_id] = (able_instance.pk if able_instance)
- self[:translatable_type] = (able_instance.class.name if able_instance)
+ self[id_column] = (able_instance.pk if able_instance)
+ self[type_column] = (able_instance.class.name if able_instance)
end),
dataset: (proc do
- translatable_type = send :translatable_type
- translatable_id = send :translatable_id
+ translatable_type = send type_column
+ translatable_id = send id_column
return if translatable_type.nil? || translatable_id.nil?
klass = self.class.send(:constantize, translatable_type)
klass.where(klass.primary_key => translatable_id)
end),
eager_loader: (proc do |eo|
id_map = {}
eo[:rows].each do |model|
- model_able_type = model.send :translatable_type
- model_able_id = model.send :translatable_id
- model.associations[:translatable] = nil
+ model_able_type = model.send type_column
+ model_able_id = model.send id_column
+ model.associations[belongs_to] = nil
((id_map[model_able_type] ||= {})[model_able_id] ||= []) << model if !model_able_type.nil? && !model_able_id.nil?
end
id_map.each do |klass_name, id_map|
klass = constantize(camelize(klass_name))
klass.where(klass.primary_key=>id_map.keys).all do |related_obj|
id_map[related_obj.pk].each do |model|
- model.associations[:translatable] = related_obj
+ model.associations[belongs_to] = related_obj
end
end
end
end)
- def validate
- super
- validates_presence [:locale, :key, :translatable_id, :translatable_type]
- validates_unique [:locale, :key, :translatable_id, :translatable_type]
+ define_method :validate do
+ super()
+ validates_presence [:locale, key_column, id_column, type_column]
+ validates_unique [:locale, key_column, id_column, type_column]
end
end
end
end
+ Translation = Translatable.new(:key, :value, :translatable)
class TextTranslation < ::Sequel::Model(:mobility_text_translations)
include Translation
end