mobility 0.1.20 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -0
- data/CHANGELOG.md +17 -0
- data/CONTRIBUTING.md +55 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +2 -56
- data/README.md +64 -15
- data/Rakefile +17 -17
- data/lib/mobility.rb +43 -40
- data/lib/mobility/active_model.rb +0 -1
- data/lib/mobility/active_model/backend_resetter.rb +1 -1
- data/lib/mobility/active_record.rb +8 -15
- data/lib/mobility/active_record/backend_resetter.rb +2 -0
- data/lib/mobility/active_record/model_translation.rb +1 -1
- data/lib/mobility/active_record/string_translation.rb +2 -0
- data/lib/mobility/active_record/text_translation.rb +2 -0
- data/lib/mobility/attributes.rb +74 -71
- data/lib/mobility/backend.rb +64 -25
- data/lib/mobility/backend/orm_delegator.rb +17 -7
- data/lib/mobility/backend/stringify_locale.rb +18 -0
- data/lib/mobility/backend_resetter.rb +8 -5
- data/lib/mobility/backends.rb +4 -0
- data/lib/mobility/backends/active_record.rb +20 -0
- data/lib/mobility/{backend → backends}/active_record/column.rb +25 -13
- data/lib/mobility/{backend → backends}/active_record/column/query_methods.rb +5 -3
- data/lib/mobility/backends/active_record/hstore.rb +29 -0
- data/lib/mobility/{backend → backends}/active_record/hstore/query_methods.rb +2 -2
- data/lib/mobility/{backend → backends}/active_record/jsonb.rb +6 -6
- data/lib/mobility/{backend → backends}/active_record/jsonb/query_methods.rb +4 -2
- data/lib/mobility/{backend → backends}/active_record/key_value.rb +10 -44
- data/lib/mobility/{backend → backends}/active_record/key_value/query_methods.rb +4 -2
- data/lib/mobility/{backend/active_record/hash_valued.rb → backends/active_record/pg_hash.rb} +13 -21
- data/lib/mobility/{backend → backends}/active_record/query_methods.rb +2 -2
- data/lib/mobility/{backend → backends}/active_record/serialized.rb +12 -28
- data/lib/mobility/{backend → backends}/active_record/serialized/query_methods.rb +5 -8
- data/lib/mobility/{backend → backends}/active_record/table.rb +11 -60
- data/lib/mobility/{backend → backends}/active_record/table/query_methods.rb +5 -3
- data/lib/mobility/{backend → backends}/column.rb +8 -4
- data/lib/mobility/backends/hash_valued.rb +29 -0
- data/lib/mobility/{backend → backends}/hstore.rb +4 -4
- data/lib/mobility/{backend → backends}/jsonb.rb +4 -4
- data/lib/mobility/backends/key_value.rb +111 -0
- data/lib/mobility/{backend → backends}/null.rb +4 -4
- data/lib/mobility/backends/sequel.rb +20 -0
- data/lib/mobility/backends/sequel/column.rb +52 -0
- data/lib/mobility/{backend → backends}/sequel/column/query_methods.rb +5 -3
- data/lib/mobility/backends/sequel/hstore.rb +29 -0
- data/lib/mobility/{backend → backends}/sequel/hstore/query_methods.rb +4 -3
- data/lib/mobility/{backend → backends}/sequel/jsonb.rb +6 -6
- data/lib/mobility/{backend → backends}/sequel/jsonb/query_methods.rb +4 -3
- data/lib/mobility/{backend → backends}/sequel/key_value.rb +28 -39
- data/lib/mobility/{backend → backends}/sequel/key_value/query_methods.rb +4 -5
- data/lib/mobility/backends/sequel/pg_hash.rb +46 -0
- data/lib/mobility/{backend → backends}/sequel/postgres_query_methods.rb +1 -2
- data/lib/mobility/{backend → backends}/sequel/query_methods.rb +6 -4
- data/lib/mobility/{backend → backends}/sequel/serialized.rb +17 -38
- data/lib/mobility/backends/sequel/serialized/query_methods.rb +17 -0
- data/lib/mobility/{backend → backends}/sequel/table.rb +29 -60
- data/lib/mobility/{backend → backends}/sequel/table/query_methods.rb +5 -3
- data/lib/mobility/{backend → backends}/serialized.rb +27 -5
- data/lib/mobility/{backend → backends}/table.rb +69 -29
- data/lib/mobility/configuration.rb +40 -0
- data/lib/mobility/{orm.rb → loaded.rb} +0 -0
- data/lib/mobility/plugins.rb +35 -0
- data/lib/mobility/plugins/active_model.rb +6 -0
- data/lib/mobility/plugins/active_model/dirty.rb +81 -0
- data/lib/mobility/plugins/active_record.rb +6 -0
- data/lib/mobility/plugins/active_record/dirty.rb +59 -0
- data/lib/mobility/plugins/cache.rb +54 -0
- data/lib/mobility/plugins/cache/translation_cacher.rb +40 -0
- data/lib/mobility/plugins/default.rb +73 -0
- data/lib/mobility/plugins/dirty.rb +61 -0
- data/lib/mobility/{backend → plugins}/fallbacks.rb +36 -31
- data/lib/mobility/plugins/fallthrough_accessors.rb +66 -0
- data/lib/mobility/plugins/locale_accessors.rb +84 -0
- data/lib/mobility/{backend → plugins}/presence.rb +15 -6
- data/lib/mobility/plugins/sequel.rb +6 -0
- data/lib/mobility/plugins/sequel/dirty.rb +59 -0
- data/lib/mobility/sequel.rb +5 -14
- data/lib/mobility/sequel/backend_resetter.rb +4 -6
- data/lib/mobility/sequel/column_changes.rb +4 -4
- data/lib/mobility/sequel/model_translation.rb +1 -1
- data/lib/mobility/sequel/string_translation.rb +2 -0
- data/lib/mobility/sequel/text_translation.rb +2 -0
- data/lib/mobility/translates.rb +1 -5
- data/lib/mobility/util.rb +126 -0
- data/lib/mobility/version.rb +1 -1
- data/lib/mobility/wrapper.rb +1 -1
- data/lib/rails/generators/mobility/translations_generator.rb +7 -3
- metadata +85 -55
- metadata.gz.sig +0 -0
- data/lib/mobility/backend/active_model.rb +0 -7
- data/lib/mobility/backend/active_model/dirty.rb +0 -95
- data/lib/mobility/backend/active_record.rb +0 -29
- data/lib/mobility/backend/active_record/dirty.rb +0 -54
- data/lib/mobility/backend/active_record/hstore.rb +0 -29
- data/lib/mobility/backend/cache.rb +0 -117
- data/lib/mobility/backend/dirty.rb +0 -38
- data/lib/mobility/backend/key_value.rb +0 -85
- data/lib/mobility/backend/sequel.rb +0 -29
- data/lib/mobility/backend/sequel/column.rb +0 -39
- data/lib/mobility/backend/sequel/dirty.rb +0 -57
- data/lib/mobility/backend/sequel/hash_valued.rb +0 -51
- data/lib/mobility/backend/sequel/hstore.rb +0 -29
- data/lib/mobility/backend/sequel/serialized/query_methods.rb +0 -20
- data/lib/mobility/core_ext/object.rb +0 -30
- data/lib/mobility/core_ext/string.rb +0 -16
- data/lib/mobility/fallthrough_accessors.rb +0 -57
- data/lib/mobility/locale_accessors.rb +0 -55
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
require "mobility/plugins/cache"
|
|
2
|
+
|
|
1
3
|
module Mobility
|
|
2
|
-
module
|
|
4
|
+
module Backends
|
|
3
5
|
=begin
|
|
4
6
|
|
|
5
7
|
Stores attribute translation as rows on a model-specific translation table
|
|
@@ -30,14 +32,14 @@ another, and Mobility will handle mapping reads/writes to each. The subclass
|
|
|
30
32
|
used in this case will be generated from the +association_name+ by
|
|
31
33
|
singularizing it and converting it to camelcase.
|
|
32
34
|
|
|
33
|
-
For more details, see examples in {Mobility::
|
|
35
|
+
For more details, see examples in {Mobility::Backends::ActiveRecord::Table}.
|
|
34
36
|
|
|
35
37
|
==Backend Options
|
|
36
38
|
|
|
37
39
|
===+association_name+
|
|
38
40
|
|
|
39
|
-
Name of association on model. Defaults to +:
|
|
40
|
-
|
|
41
|
+
Name of association on model. Defaults to +:translations+. If specified,
|
|
42
|
+
ensure name does not overlap with other methods on model or with the
|
|
41
43
|
association name used by other backends on model (otherwise one will overwrite
|
|
42
44
|
the other).
|
|
43
45
|
|
|
@@ -58,39 +60,77 @@ Subclass to use when dynamically generating translation class for model, by
|
|
|
58
60
|
default +:Translation+. Should be a symbol. Generally this does not need to be
|
|
59
61
|
set.
|
|
60
62
|
|
|
61
|
-
@see Mobility::
|
|
62
|
-
@see Mobility::
|
|
63
|
+
@see Mobility::Backends::ActiveRecord::Table
|
|
64
|
+
@see Mobility::Backends::Sequel::Table
|
|
63
65
|
=end
|
|
64
66
|
module Table
|
|
65
|
-
|
|
67
|
+
extend Backend::OrmDelegator
|
|
68
|
+
|
|
69
|
+
# @!macro backend_constructor
|
|
70
|
+
# @option options [Symbol] association_name Name of association
|
|
71
|
+
def initialize(model, attribute, options = {})
|
|
72
|
+
super
|
|
73
|
+
@association_name = options[:association_name]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @!group Backend Accessors
|
|
77
|
+
# @!macro backend_reader
|
|
78
|
+
def read(locale, options = {})
|
|
79
|
+
translation_for(locale, options).send(attribute)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @!macro backend_writer
|
|
83
|
+
def write(locale, value, options = {})
|
|
84
|
+
translation_for(locale, options).tap { |t| t.send("#{attribute}=", value) }.send(attribute)
|
|
85
|
+
end
|
|
86
|
+
# @!endgroup
|
|
87
|
+
|
|
88
|
+
# @!macro backend_iterator
|
|
89
|
+
def each_locale
|
|
90
|
+
translations.each { |t| yield t.locale.to_sym }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def translations
|
|
96
|
+
model.send(association_name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.included(backend)
|
|
100
|
+
backend.extend ClassMethods
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
module ClassMethods
|
|
104
|
+
# Apply custom processing for plugin
|
|
105
|
+
# @param (see Backend::Setup#apply_plugin)
|
|
106
|
+
# @return (see Backend::Setup#apply_plugin)
|
|
107
|
+
def apply_plugin(name)
|
|
108
|
+
if name == :cache
|
|
109
|
+
include self::Cache
|
|
110
|
+
true
|
|
111
|
+
else
|
|
112
|
+
super
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
66
116
|
|
|
67
117
|
# Simple hash cache to memoize translations as a hash so they can be
|
|
68
118
|
# fetched quickly.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# @yield [locale] Yields locale to block in case attribute is not yet
|
|
72
|
-
# cached, expects a new translation for that locale.
|
|
73
|
-
# @raise [ArgumentError] if block is not given
|
|
74
|
-
def initialize
|
|
75
|
-
raise ArgumentError, "missing block" unless block_given?
|
|
76
|
-
super() { |hash, locale| hash[locale] = yield(locale) }
|
|
77
|
-
end
|
|
119
|
+
module Cache
|
|
120
|
+
include Plugins::Cache::TranslationCacher.new(:translation_for)
|
|
78
121
|
|
|
79
|
-
|
|
80
|
-
# @param [String] attribute
|
|
81
|
-
# @return [Class] Hash-like wrapper object to be used as attribute cache
|
|
82
|
-
def for(attribute)
|
|
83
|
-
cache = self
|
|
122
|
+
private
|
|
84
123
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
end
|
|
124
|
+
def cache
|
|
125
|
+
model_cache || model.instance_variable_set(:"@__mobility_#{association_name}_cache", {})
|
|
126
|
+
end
|
|
89
127
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
128
|
+
def model_cache
|
|
129
|
+
model.instance_variable_get(:"@__mobility_#{association_name}_cache")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def clear_cache
|
|
133
|
+
model_cache && model_cache.clear
|
|
94
134
|
end
|
|
95
135
|
end
|
|
96
136
|
end
|
|
@@ -5,6 +5,8 @@ Stores shared Mobility configuration referenced by all backends.
|
|
|
5
5
|
|
|
6
6
|
=end
|
|
7
7
|
class Configuration
|
|
8
|
+
RESERVED_OPTION_KEYS = %i[backend model_class].freeze
|
|
9
|
+
|
|
8
10
|
# Alias for mobility_accessor (defaults to +translates+)
|
|
9
11
|
# @return [Symbol]
|
|
10
12
|
attr_accessor :accessor_method
|
|
@@ -13,6 +15,26 @@ Stores shared Mobility configuration referenced by all backends.
|
|
|
13
15
|
# @return [Symbol]
|
|
14
16
|
attr_accessor :query_method
|
|
15
17
|
|
|
18
|
+
# Default set of options. These will be merged with any backend options
|
|
19
|
+
# when defining translated attributes (with +translates+). Default options
|
|
20
|
+
# may not include the keys 'backend' or 'model_class'.
|
|
21
|
+
# @return [Hash]
|
|
22
|
+
attr_reader :default_options
|
|
23
|
+
def default_options=(options)
|
|
24
|
+
if (keys = options.keys & RESERVED_OPTION_KEYS).present?
|
|
25
|
+
raise ReservedOptionKey,
|
|
26
|
+
"Default options may not contain the following reserved keys: #{keys.join(', ')}"
|
|
27
|
+
else
|
|
28
|
+
@default_options = options
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Plugins to apply. Order of plugins is important, as this becomes the
|
|
33
|
+
# order in which plugins modules are included into the backend class or
|
|
34
|
+
# attributes instance.
|
|
35
|
+
# @return [Array<Symbol>]
|
|
36
|
+
attr_accessor :plugins
|
|
37
|
+
|
|
16
38
|
# Default fallbacks instance
|
|
17
39
|
# @return [I18n::Locale::Fallbacks]
|
|
18
40
|
def default_fallbacks(fallbacks = {})
|
|
@@ -41,6 +63,24 @@ Stores shared Mobility configuration referenced by all backends.
|
|
|
41
63
|
@query_method = :i18n
|
|
42
64
|
@default_fallbacks = lambda { |fallbacks| I18n::Locale::Fallbacks.new(fallbacks) }
|
|
43
65
|
@default_accessor_locales = lambda { I18n.available_locales }
|
|
66
|
+
@default_options = {
|
|
67
|
+
cache: true,
|
|
68
|
+
dirty: false,
|
|
69
|
+
fallbacks: nil,
|
|
70
|
+
presence: true,
|
|
71
|
+
default: nil
|
|
72
|
+
}
|
|
73
|
+
@plugins = %i[
|
|
74
|
+
cache
|
|
75
|
+
dirty
|
|
76
|
+
fallbacks
|
|
77
|
+
presence
|
|
78
|
+
default
|
|
79
|
+
fallthrough_accessors
|
|
80
|
+
locale_accessors
|
|
81
|
+
]
|
|
44
82
|
end
|
|
83
|
+
|
|
84
|
+
class ReservedOptionKey < Exception; end
|
|
45
85
|
end
|
|
46
86
|
end
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Mobility
|
|
2
|
+
=begin
|
|
3
|
+
|
|
4
|
+
Plugins allow modular customization of backends independent of the backend
|
|
5
|
+
itself. They are enabled through the {Configuration.plugins} configuration
|
|
6
|
+
setting, which takes an array of symbols corresponding to plugin names. The
|
|
7
|
+
order of these names is important since it determines the order in which
|
|
8
|
+
plugins will be applied.
|
|
9
|
+
|
|
10
|
+
So if our {Configuration.plugins} is an array +[:foo]+, and we call
|
|
11
|
+
`translates` on our model, +Post+, like this:
|
|
12
|
+
|
|
13
|
+
class Post
|
|
14
|
+
translates :title, foo: true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Then the +Foo+ plugin will be applied with the option value +true+. Applying a
|
|
18
|
+
module calls a class method, +apply+ (in this case +Foo.apply+), which takes
|
|
19
|
+
two arguments:
|
|
20
|
+
|
|
21
|
+
- an instance of the {Attributes} class, +attributes+, from which the backend
|
|
22
|
+
can configure the backend class (+attributes.backend_class+) and the model
|
|
23
|
+
(+attributes.model_class+), and the +attributes+ module itself (which
|
|
24
|
+
will be included into the backend).
|
|
25
|
+
- the value of the +option+ passed into the model with +translates+ (in this
|
|
26
|
+
case, +true+).
|
|
27
|
+
|
|
28
|
+
Typically, the plugin will include a module into either
|
|
29
|
+
+attributes.backend_class+ or +attributes+ itself, configured according to the
|
|
30
|
+
option value. For examples, see classes under the {Mobility::Plugins} namespace.
|
|
31
|
+
|
|
32
|
+
=end
|
|
33
|
+
module Plugins
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
module Mobility
|
|
4
|
+
module Plugins
|
|
5
|
+
=begin
|
|
6
|
+
|
|
7
|
+
Dirty tracking for models which include the +ActiveModel::Dirty+ module.
|
|
8
|
+
|
|
9
|
+
Assuming we have an attribute +title+, this module will add support for the
|
|
10
|
+
following methods:
|
|
11
|
+
- +title_changed?+
|
|
12
|
+
- +title_change+
|
|
13
|
+
- +title_was+
|
|
14
|
+
- +title_will_change!+
|
|
15
|
+
- +title_previously_changed?+
|
|
16
|
+
- +title_previous_change+
|
|
17
|
+
- +restore_title!+
|
|
18
|
+
|
|
19
|
+
In addition, the private method +restore_attribute!+ will also restore the
|
|
20
|
+
value of the translated attribute if passed to it.
|
|
21
|
+
|
|
22
|
+
@see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html Rails documentation for Active Model Dirty module
|
|
23
|
+
|
|
24
|
+
=end
|
|
25
|
+
module ActiveModel
|
|
26
|
+
module Dirty
|
|
27
|
+
# @!group Backend Accessors
|
|
28
|
+
# @!macro backend_writer
|
|
29
|
+
# @param [Hash] options
|
|
30
|
+
def write(locale, value, options = {})
|
|
31
|
+
locale_accessor = Mobility.normalize_locale_accessor(attribute, locale)
|
|
32
|
+
if model.changed_attributes.has_key?(locale_accessor) && model.changed_attributes[locale_accessor] == value
|
|
33
|
+
model.attributes_changed_by_setter.except!(locale_accessor)
|
|
34
|
+
elsif read(locale, options.merge(fallback: false)) != value
|
|
35
|
+
model.send(:attribute_will_change!, locale_accessor)
|
|
36
|
+
end
|
|
37
|
+
super
|
|
38
|
+
end
|
|
39
|
+
# @!endgroup
|
|
40
|
+
|
|
41
|
+
# Builds module which adds suffix/prefix methods for translated
|
|
42
|
+
# attributes so they act like normal dirty-tracked attributes.
|
|
43
|
+
class MethodsBuilder < Module
|
|
44
|
+
def initialize(*attribute_names)
|
|
45
|
+
attribute_names.each do |name|
|
|
46
|
+
method_suffixes.each do |suffix|
|
|
47
|
+
define_method "#{name}#{suffix}".freeze do
|
|
48
|
+
__send__("attribute#{suffix}".freeze, Mobility.normalize_locale_accessor(name))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
define_method "restore_#{name}!".freeze do
|
|
53
|
+
locale_accessor = Mobility.normalize_locale_accessor(name)
|
|
54
|
+
if attribute_changed?(locale_accessor)
|
|
55
|
+
__send__("#{name}=".freeze, changed_attributes[locale_accessor])
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
define_method :restore_attribute! do |attr|
|
|
61
|
+
attribute_names.include?(attr.to_s) ? send("restore_#{attr}!".freeze) : super(attr)
|
|
62
|
+
end
|
|
63
|
+
private :restore_attribute!
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Get method suffixes. Creating an object just to get the list of
|
|
69
|
+
# suffixes is not very efficient, but the most reliable way given that
|
|
70
|
+
# they change from Rails version to version.
|
|
71
|
+
def method_suffixes
|
|
72
|
+
@method_suffixes ||=
|
|
73
|
+
Class.new do
|
|
74
|
+
include ::ActiveModel::Dirty
|
|
75
|
+
end.attribute_method_matchers.map(&:suffix).select { |m| m =~ /\A_/ }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
require "mobility/plugins/active_model/dirty"
|
|
3
|
+
|
|
4
|
+
module Mobility
|
|
5
|
+
module Plugins
|
|
6
|
+
=begin
|
|
7
|
+
|
|
8
|
+
Dirty tracking for AR models. See {Mobility::Plugins::ActiveModel::Dirty} for
|
|
9
|
+
details on usage.
|
|
10
|
+
|
|
11
|
+
=end
|
|
12
|
+
module ActiveRecord
|
|
13
|
+
module Dirty
|
|
14
|
+
include ActiveModel::Dirty
|
|
15
|
+
|
|
16
|
+
# Builds module which patches a few AR methods to handle changes to
|
|
17
|
+
# translated attributes just like normal attributes.
|
|
18
|
+
class MethodsBuilder < ActiveModel::Dirty::MethodsBuilder
|
|
19
|
+
def initialize(*attribute_names)
|
|
20
|
+
super
|
|
21
|
+
@attribute_names = attribute_names
|
|
22
|
+
|
|
23
|
+
changes_applied_method = ::ActiveRecord::VERSION::STRING < '5.1' ? :changes_applied : :changes_internally_applied
|
|
24
|
+
define_method changes_applied_method do
|
|
25
|
+
@previously_changed = changes
|
|
26
|
+
super()
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
define_method :clear_changes_information do
|
|
30
|
+
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
|
31
|
+
super()
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
define_method :previous_changes do
|
|
35
|
+
(@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new).merge(super())
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Overrides +ActiveRecord::AttributeMethods::ClassMethods#has_attribute+ to treat fallthrough attribute methods
|
|
40
|
+
# just like "real" attribute methods.
|
|
41
|
+
#
|
|
42
|
+
# @note Patching +has_attribute?+ is necessary as of AR 5.1 due to this commit[https://github.com/rails/rails/commit/4fed08fa787a316fa51f14baca9eae11913f5050].
|
|
43
|
+
# (I have voiced my opposition to this change here[https://github.com/rails/rails/pull/27963#issuecomment-310092787]).
|
|
44
|
+
# @param [Attributes] attributes
|
|
45
|
+
def included(model_class)
|
|
46
|
+
names = @attribute_names
|
|
47
|
+
method_name_regex = /\A(#{names.join('|'.freeze)})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
|
|
48
|
+
has_attribute = Module.new do
|
|
49
|
+
define_method :has_attribute? do |attr_name|
|
|
50
|
+
super(attr_name) || !!method_name_regex.match(attr_name)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
model_class.extend has_attribute
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require "mobility/plugins/cache/translation_cacher"
|
|
2
|
+
|
|
3
|
+
module Mobility
|
|
4
|
+
module Plugins
|
|
5
|
+
=begin
|
|
6
|
+
|
|
7
|
+
Caches values fetched from the backend so subsequent fetches can be performed
|
|
8
|
+
more quickly. The cache stores cached values in a simple hash, which is not
|
|
9
|
+
optimal for some storage strategies, so some backends (KeyValue, Table) use a
|
|
10
|
+
custom module through the {Mobility::Backend::Setup#apply_plugin} hook. For
|
|
11
|
+
details see the documentation for these backends.
|
|
12
|
+
|
|
13
|
+
The cache is reset when one of a set of events happens (saving, reloading,
|
|
14
|
+
etc.). See {BackendResetter} for details.
|
|
15
|
+
|
|
16
|
+
Values are added to the cache in two ways:
|
|
17
|
+
|
|
18
|
+
1. first read from backend
|
|
19
|
+
2. any write to backend
|
|
20
|
+
|
|
21
|
+
=end
|
|
22
|
+
module Cache
|
|
23
|
+
# Applies cache plugin to attributes.
|
|
24
|
+
# @param [Attributes] attributes
|
|
25
|
+
# @param [Boolean] option
|
|
26
|
+
def self.apply(attributes, option)
|
|
27
|
+
if option
|
|
28
|
+
backend_class = attributes.backend_class
|
|
29
|
+
backend_class.include(self) unless backend_class.apply_plugin(:cache)
|
|
30
|
+
|
|
31
|
+
model_class = attributes.model_class
|
|
32
|
+
model_class.include BackendResetter.for(model_class).new(attributes.names) { clear_cache }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @group Backend Accessors
|
|
37
|
+
#
|
|
38
|
+
# @!macro backend_reader
|
|
39
|
+
# @option options [Boolean] cache
|
|
40
|
+
# *false* to disable cache.
|
|
41
|
+
# @!method read(locale, value, options = {})
|
|
42
|
+
include TranslationCacher.new(:read)
|
|
43
|
+
|
|
44
|
+
# @!macro backend_writer
|
|
45
|
+
# @option options [Boolean] cache
|
|
46
|
+
# *false* to disable cache.
|
|
47
|
+
def write(locale, value, **options)
|
|
48
|
+
return super if options.delete(:cache) == false
|
|
49
|
+
cache[locale] = super
|
|
50
|
+
end
|
|
51
|
+
# @!endgroup
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Mobility
|
|
2
|
+
module Plugins
|
|
3
|
+
module Cache
|
|
4
|
+
=begin
|
|
5
|
+
|
|
6
|
+
Creates a module to cache a given translation fetch method. The cacher defines
|
|
7
|
+
private methods +cache+ and +clear_cache+ to access and clear, respectively, a
|
|
8
|
+
translations hash.
|
|
9
|
+
|
|
10
|
+
This cacher is used to cache translation values in {Mobility::Plugins::Cache},
|
|
11
|
+
and also to cache translation *records* in {Mobility::Backends::Table} and
|
|
12
|
+
{Mobility::Backends::KeyValue}.
|
|
13
|
+
|
|
14
|
+
=end
|
|
15
|
+
class TranslationCacher < Module
|
|
16
|
+
# @param [Symbol] fetch_method Name of translation fetch method to cache
|
|
17
|
+
def initialize(fetch_method)
|
|
18
|
+
define_method fetch_method do |locale, **options|
|
|
19
|
+
return super(locale, options) if options.delete(:cache) == false
|
|
20
|
+
if cache.has_key?(locale)
|
|
21
|
+
cache[locale]
|
|
22
|
+
else
|
|
23
|
+
cache[locale] = super(locale, options)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
define_method :cache do
|
|
28
|
+
@cache ||= {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
define_method :clear_cache do
|
|
32
|
+
@cache = {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private :cache, :clear_cache
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|