Class: Mobility::Backends::ActiveRecord::KeyValue

Inherits:
Object
  • Object
show all
Includes:
Mobility::Backends::ActiveRecord, KeyValue
Defined in:
lib/mobility/backends/active_record/key_value.rb

Overview

Implements the KeyValue backend for ActiveRecord models.

Examples:

class Post < ApplicationRecord
  extend Mobility
  translates :title, backend: :key_value, association_name: :translations, type: :string
end

post = Post.create(title: "foo")
post.translations
#=> #<ActiveRecord::Associations::CollectionProxy ... >
post.translations.first.value
#=> "foo"
post.translations.first.class
#=> Mobility::ActiveRercord::StringTranslation

Defined Under Namespace

Classes: StringTranslation, TextTranslation, Translation, Visitor

Backend Configuration collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from KeyValue

#association_name, #class_name, #each_locale, #read, #write

Methods included from Mobility::Backends::ActiveRecord

included

Class Method Details

.apply_scope(relation, predicate, locale = Mobility.locale, invert: false) ⇒ ActiveRecord::Relation

Joins translations using either INNER/OUTER join appropriate to the query.

Parameters:

  • relation (ActiveRecord::Relation)

    Relation to scope

  • predicate (Object)

    Arel predicate

  • locale (Symbol) (defaults to: Mobility.locale)

    (Mobility.locale) Locale

  • [Boolean] (Hash)

    a customizable set of options

Returns:

  • (ActiveRecord::Relation)

    relation Relation with joins applied (if needed)



60
61
62
63
64
65
66
# File 'lib/mobility/backends/active_record/key_value.rb', line 60

def apply_scope(relation, predicate, locale = Mobility.locale, invert: false)
  visitor = Visitor.new(self, locale)
  visitor.accept(predicate).inject(relation) do |rel, (attr, join_type)|
    join_type &&= ::Arel::Nodes::InnerJoin if invert
    join_translations(rel, attr, locale, join_type)
  end
end

.build_node(attr, locale) ⇒ Mobility::Plugins::Arel::Attribute

Returns Arel attribute for aliased translation table value column.

Parameters:

  • attr (String)

    Attribute name

  • _locale (Symbol)

    Locale

Returns:



49
50
51
52
# File 'lib/mobility/backends/active_record/key_value.rb', line 49

def build_node(attr, locale)
  aliased_table = class_name.arel_table.alias(table_alias(attr, locale))
  Plugins::Arel::Attribute.new(aliased_table, value_column, locale, self, attr.to_sym)
end

.configure(options) ⇒ Object

Parameters:

  • options (Hash)

    a customizable set of options

Options Hash (options):

  • type (Symbol, String)

    Column type to use

  • association_name (Symbol) — default: :<type>_translations

    Name of association method, defaults to <type>_translations

  • class_name (Symbol)

    Translation class, defaults to Mobility::<ORM>::<type>Translation

Raises:

  • (ArgumentError)

    if type is not set, and both class_name and association_name are also not set



34
35
36
37
38
39
40
41
42
# File 'lib/mobility/backends/active_record/key_value.rb', line 34

def configure(options)
  super
  if type = options[:type]
    options[:association_name] ||= :"#{options[:type]}_translations"
    options[:class_name]       ||= const_get("#{type.capitalize}Translation")
  end
rescue NameError
  raise ArgumentError, "You must define a Mobility::Backends::ActiveRecord::KeyValue::#{type.capitalize}Translation class."
end

.define_after_destroy_callback(klass) ⇒ Object

Called from setup block. Can be overridden to customize behaviour.



118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/mobility/backends/active_record/key_value.rb', line 118

def define_after_destroy_callback(klass)
  # Ensure we only call after destroy hook once per translations class
  b = self
  translation_classes = [class_name, *Mobility::Backends::ActiveRecord::KeyValue::Translation.descendants].uniq
  klass.after_destroy do
    @mobility_after_destroy_translation_classes = [] unless defined?(@mobility_after_destroy_translation_classes)
    (translation_classes - @mobility_after_destroy_translation_classes).each do |translation_class|
      translation_class.where(b.belongs_to => self).destroy_all
    end
    @mobility_after_destroy_translation_classes += translation_classes
  end
end

.define_before_save_callback(klass) ⇒ Object

Called from setup block. Can be overridden to customize behaviour.



108
109
110
111
112
113
114
115
# File 'lib/mobility/backends/active_record/key_value.rb', line 108

def define_before_save_callback(klass)
  b = self
  klass.before_save do
    send(b.association_name).select { |t| t.send(b.value_column).blank? }.each do |translation|
      send(b.association_name).destroy(translation)
    end
  end
end

.define_has_many_association(klass, attributes) ⇒ Object

Called from setup block. Can be overridden to customize behaviour.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/mobility/backends/active_record/key_value.rb', line 69

def define_has_many_association(klass, attributes)
  # 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 = (klass.instance_variable_get(:"@#{attrs_method_name}") || []) + attributes
  klass.instance_variable_set(:"@#{attrs_method_name}", association_attributes)

  b = self

  klass.has_many association_name, ->{ where b.key_column => association_attributes },
    as: belongs_to,
    class_name: class_name.name,
    inverse_of: belongs_to,
    autosave:   true
end

.define_initialize_dup(klass) ⇒ Object

Called from setup block. Can be overridden to customize behaviour.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/mobility/backends/active_record/key_value.rb', line 89

def define_initialize_dup(klass)
  b = self
  module_name = "MobilityArKeyValue#{association_name.to_s.camelcase}"
  unless const_defined?(module_name)
    callback_methods = Module.new do
      define_method :initialize_dup do |source|
        super(source)
        self.send("#{b.association_name}=", source.send(b.association_name).map(&:dup))
        # Set inverse on associations
        send(b.association_name).each do |translation|
          translation.send(:"#{b.belongs_to}=", self)
        end
      end
    end
    klass.include const_set(module_name, callback_methods)
  end
end

Instance Method Details

#translation_for(locale) ⇒ Mobility::Backends::ActiveRecord::KeyValue::TextTranslation, Mobility::Backends::ActiveRecord::KeyValue::StringTranslation

Returns translation for a given locale, or builds one if none is present.



225
226
227
228
229
230
231
# File 'lib/mobility/backends/active_record/key_value.rb', line 225

def translation_for(locale, **)
  translation = translations.find do |t|
    t.send(key_column) == attribute && t.locale == locale.to_s
  end
  translation ||= translations.build(locale: locale, key_column => attribute)
  translation
end