mobility 0.8.10 → 1.0.0.beta2
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 +0 -0
- data/CHANGELOG.md +66 -0
- data/Gemfile +50 -18
- data/Gemfile.lock +36 -101
- data/README.md +183 -91
- data/Rakefile +6 -4
- data/lib/mobility.rb +44 -166
- data/lib/mobility/arel.rb +1 -1
- data/lib/mobility/arel/nodes/pg_ops.rb +1 -1
- data/lib/mobility/backend.rb +27 -51
- data/lib/mobility/backends.rb +20 -0
- data/lib/mobility/backends/active_record.rb +4 -0
- data/lib/mobility/backends/active_record/column.rb +2 -0
- data/lib/mobility/backends/active_record/container.rb +6 -7
- data/lib/mobility/backends/active_record/hstore.rb +3 -1
- data/lib/mobility/backends/active_record/json.rb +2 -0
- data/lib/mobility/backends/active_record/jsonb.rb +2 -0
- data/lib/mobility/backends/active_record/key_value.rb +6 -4
- data/lib/mobility/backends/active_record/pg_hash.rb +1 -1
- data/lib/mobility/backends/active_record/serialized.rb +6 -0
- data/lib/mobility/backends/active_record/table.rb +6 -4
- data/lib/mobility/backends/column.rb +0 -6
- data/lib/mobility/backends/container.rb +10 -1
- data/lib/mobility/backends/hash.rb +39 -0
- data/lib/mobility/backends/hash_valued.rb +4 -0
- data/lib/mobility/backends/hstore.rb +0 -1
- data/lib/mobility/backends/json.rb +0 -1
- data/lib/mobility/backends/jsonb.rb +1 -2
- data/lib/mobility/backends/key_value.rb +31 -26
- data/lib/mobility/backends/null.rb +2 -0
- data/lib/mobility/backends/sequel.rb +5 -2
- data/lib/mobility/backends/sequel/column.rb +2 -0
- data/lib/mobility/backends/sequel/container.rb +6 -6
- data/lib/mobility/backends/sequel/hstore.rb +3 -1
- data/lib/mobility/backends/sequel/json.rb +3 -0
- data/lib/mobility/backends/sequel/jsonb.rb +3 -1
- data/lib/mobility/backends/sequel/key_value.rb +8 -6
- data/lib/mobility/backends/sequel/serialized.rb +6 -0
- data/lib/mobility/backends/sequel/table.rb +5 -2
- data/lib/mobility/backends/serialized.rb +1 -3
- data/lib/mobility/backends/table.rb +29 -26
- data/lib/mobility/pluggable.rb +56 -0
- data/lib/mobility/plugin.rb +260 -0
- data/lib/mobility/plugins.rb +27 -24
- data/lib/mobility/plugins/active_model.rb +17 -0
- data/lib/mobility/plugins/active_model/cache.rb +26 -0
- data/lib/mobility/plugins/active_model/dirty.rb +119 -78
- data/lib/mobility/plugins/active_record.rb +34 -0
- data/lib/mobility/plugins/active_record/backend.rb +25 -0
- data/lib/mobility/plugins/active_record/cache.rb +28 -0
- data/lib/mobility/plugins/active_record/dirty.rb +34 -17
- data/lib/mobility/plugins/active_record/query.rb +48 -34
- data/lib/mobility/plugins/active_record/uniqueness_validation.rb +60 -0
- data/lib/mobility/plugins/attribute_methods.rb +29 -20
- data/lib/mobility/plugins/attributes.rb +72 -0
- data/lib/mobility/plugins/backend.rb +161 -0
- data/lib/mobility/plugins/backend_reader.rb +34 -0
- data/lib/mobility/plugins/cache.rb +68 -26
- data/lib/mobility/plugins/default.rb +22 -17
- data/lib/mobility/plugins/dirty.rb +12 -33
- data/lib/mobility/plugins/fallbacks.rb +52 -44
- data/lib/mobility/plugins/fallthrough_accessors.rb +25 -25
- data/lib/mobility/plugins/locale_accessors.rb +22 -35
- data/lib/mobility/plugins/presence.rb +28 -21
- data/lib/mobility/plugins/query.rb +8 -17
- data/lib/mobility/plugins/reader.rb +50 -0
- data/lib/mobility/plugins/sequel.rb +34 -0
- data/lib/mobility/plugins/sequel/backend.rb +25 -0
- data/lib/mobility/plugins/sequel/cache.rb +24 -0
- data/lib/mobility/plugins/sequel/dirty.rb +33 -22
- data/lib/mobility/plugins/sequel/query.rb +21 -6
- data/lib/mobility/plugins/writer.rb +44 -0
- data/lib/mobility/translations.rb +95 -0
- data/lib/mobility/version.rb +12 -1
- data/lib/rails/generators/mobility/templates/initializer.rb +96 -78
- metadata +28 -27
- metadata.gz.sig +0 -0
- data/lib/mobility/active_model.rb +0 -4
- data/lib/mobility/active_model/backend_resetter.rb +0 -26
- data/lib/mobility/active_record.rb +0 -23
- data/lib/mobility/active_record/backend_resetter.rb +0 -26
- data/lib/mobility/active_record/uniqueness_validator.rb +0 -60
- data/lib/mobility/attributes.rb +0 -324
- data/lib/mobility/backend/orm_delegator.rb +0 -44
- data/lib/mobility/backend_resetter.rb +0 -50
- data/lib/mobility/configuration.rb +0 -138
- data/lib/mobility/fallbacks.rb +0 -28
- data/lib/mobility/interface.rb +0 -0
- data/lib/mobility/loaded.rb +0 -4
- data/lib/mobility/plugins/active_record/attribute_methods.rb +0 -38
- data/lib/mobility/plugins/cache/translation_cacher.rb +0 -40
- data/lib/mobility/sequel.rb +0 -9
- data/lib/mobility/sequel/backend_resetter.rb +0 -23
- data/lib/mobility/translates.rb +0 -73
|
@@ -40,29 +40,44 @@ locale suffix, so +title_en+, +title_pt_br+, etc.)
|
|
|
40
40
|
|
|
41
41
|
=end
|
|
42
42
|
module Dirty
|
|
43
|
-
|
|
44
|
-
# @param [Attributes] attributes
|
|
45
|
-
def included(model_class)
|
|
46
|
-
super
|
|
43
|
+
extend Plugin
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
requires :dirty, include: false
|
|
46
|
+
requires :active_model_dirty, include: :before
|
|
47
|
+
|
|
48
|
+
initialize_hook do
|
|
49
|
+
if options[:dirty]
|
|
50
|
+
include InstanceMethods
|
|
49
51
|
end
|
|
52
|
+
end
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# In earlier versions of Rails, these are needed to avoid an
|
|
55
|
-
# exception when including the AR Dirty module outside of an
|
|
56
|
-
# AR::Base class. Eventually we should be able to drop them.
|
|
57
|
-
def self.after_create; end
|
|
58
|
-
def self.after_update; end
|
|
59
|
-
|
|
60
|
-
include ::ActiveRecord::AttributeMethods::Dirty
|
|
61
|
-
end)
|
|
62
|
-
end
|
|
54
|
+
included_hook do |_, backend_class|
|
|
55
|
+
if options[:dirty]
|
|
56
|
+
backend_class.include BackendMethods
|
|
63
57
|
end
|
|
64
58
|
end
|
|
65
59
|
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def dirty_handler_methods
|
|
63
|
+
HandlerMethods
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Module which defines generic ActiveRecord::Dirty handler methods like
|
|
67
|
+
# +attribute_before_last_save+ that are patched to work with translated
|
|
68
|
+
# attributes.
|
|
69
|
+
HandlerMethods = ActiveModel::Dirty::HandlerMethodsBuilder.new(
|
|
70
|
+
Class.new do
|
|
71
|
+
# In earlier versions of Rails, these are needed to avoid an
|
|
72
|
+
# exception when including the AR Dirty module outside of an
|
|
73
|
+
# AR::Base class. Eventually we should be able to drop them.
|
|
74
|
+
def self.after_create; end
|
|
75
|
+
def self.after_update; end
|
|
76
|
+
|
|
77
|
+
include ::ActiveRecord::AttributeMethods::Dirty
|
|
78
|
+
end
|
|
79
|
+
)
|
|
80
|
+
|
|
66
81
|
module InstanceMethods
|
|
67
82
|
if ::ActiveRecord::VERSION::STRING >= '5.1' # define patterns added in 5.1
|
|
68
83
|
def saved_changes
|
|
@@ -98,5 +113,7 @@ locale suffix, so +title_en+, +title_pt_br+, etc.)
|
|
|
98
113
|
BackendMethods = ActiveModel::Dirty::BackendMethods
|
|
99
114
|
end
|
|
100
115
|
end
|
|
116
|
+
|
|
117
|
+
register_plugin(:active_record_dirty, ActiveRecord::Dirty)
|
|
101
118
|
end
|
|
102
119
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
|
+
require "active_record/relation"
|
|
3
|
+
|
|
2
4
|
module Mobility
|
|
3
5
|
module Plugins
|
|
4
6
|
=begin
|
|
@@ -15,45 +17,47 @@ enabled for any one attribute on the model.
|
|
|
15
17
|
=end
|
|
16
18
|
module ActiveRecord
|
|
17
19
|
module Query
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
extend Plugin
|
|
21
|
+
|
|
22
|
+
requires :query, include: false
|
|
23
|
+
|
|
24
|
+
included_hook do |klass, backend_class|
|
|
25
|
+
plugin = self
|
|
26
|
+
if options[:query]
|
|
27
|
+
raise MissingBackend, "backend required for Query plugin" unless backend_class
|
|
28
|
+
|
|
29
|
+
klass.class_eval do
|
|
21
30
|
extend QueryMethod
|
|
22
|
-
extend FindByMethods.new(*
|
|
23
|
-
singleton_class.send :alias_method,
|
|
31
|
+
extend FindByMethods.new(*plugin.names)
|
|
32
|
+
singleton_class.send :alias_method, plugin.query_method, :__mobility_query_scope__
|
|
24
33
|
end
|
|
25
|
-
|
|
34
|
+
backend_class.include BackendMethods
|
|
26
35
|
end
|
|
36
|
+
end
|
|
27
37
|
|
|
38
|
+
class << self
|
|
28
39
|
def attribute_alias(attribute, locale = Mobility.locale)
|
|
29
40
|
"__mobility_%s_%s__" % [attribute, ::Mobility.normalize_locale(locale)]
|
|
30
41
|
end
|
|
31
42
|
end
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
module BackendMethods
|
|
45
|
+
# @note We use +instance_variable_get+ here to get the +AttributeSet+
|
|
46
|
+
# rather than the hash of attributes. Getting the full hash of
|
|
47
|
+
# attributes is a performance hit and better to avoid if unnecessary.
|
|
48
|
+
# TODO: Improve this.
|
|
49
|
+
def read(locale, **)
|
|
50
|
+
if model.instance_variable_defined?(:@attributes) &&
|
|
51
|
+
(model_attributes = model.instance_variable_get(:@attributes)).key?(alias_ = Query.attribute_alias(attribute, locale))
|
|
52
|
+
model_attributes[alias_].value
|
|
53
|
+
else
|
|
54
|
+
super
|
|
55
|
+
end
|
|
43
56
|
end
|
|
44
57
|
end
|
|
45
58
|
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
def model_attributes_defined?
|
|
49
|
-
model.instance_variable_defined?(:@attributes)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def model_attributes
|
|
53
|
-
model.instance_variable_get(:@attributes)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
59
|
module QueryMethod
|
|
60
|
+
# This is required for UniquenessValidator.
|
|
57
61
|
def __mobility_query_scope__(locale: Mobility.locale, &block)
|
|
58
62
|
if block_given?
|
|
59
63
|
VirtualRow.build_query(self, locale, &block)
|
|
@@ -121,7 +125,7 @@ enabled for any one attribute on the model.
|
|
|
121
125
|
case opts
|
|
122
126
|
when Symbol, String
|
|
123
127
|
@klass.mobility_attribute?(opts) ? order({ opts => :asc }, *rest) : super
|
|
124
|
-
when Hash
|
|
128
|
+
when ::Hash
|
|
125
129
|
i18n_keys, keys = opts.keys.partition(&@klass.method(:mobility_attribute?))
|
|
126
130
|
return super if i18n_keys.empty?
|
|
127
131
|
|
|
@@ -139,8 +143,10 @@ enabled for any one attribute on the model.
|
|
|
139
143
|
|
|
140
144
|
if ::ActiveRecord::VERSION::STRING >= '5.0'
|
|
141
145
|
%w[pluck group select].each do |method_name|
|
|
142
|
-
define_method method_name do |*attrs|
|
|
143
|
-
return super(*attrs)
|
|
146
|
+
define_method method_name do |*attrs, &block|
|
|
147
|
+
return super(*attrs, &block) if (method_name == 'select' && block.present?)
|
|
148
|
+
|
|
149
|
+
return super(*attrs, &block) unless attrs.any?(&@klass.method(:mobility_attribute?))
|
|
144
150
|
|
|
145
151
|
keys = attrs.dup
|
|
146
152
|
|
|
@@ -154,7 +160,7 @@ enabled for any one attribute on the model.
|
|
|
154
160
|
@klass.mobility_backend_class(key).apply_scope(query, backend_node(key))
|
|
155
161
|
end
|
|
156
162
|
|
|
157
|
-
base.public_send(method_name, *keys)
|
|
163
|
+
base.public_send(method_name, *keys, &block)
|
|
158
164
|
end
|
|
159
165
|
end
|
|
160
166
|
end
|
|
@@ -180,7 +186,7 @@ enabled for any one attribute on the model.
|
|
|
180
186
|
|
|
181
187
|
class << self
|
|
182
188
|
def build(scope, where_opts, invert: false, &block)
|
|
183
|
-
return yield unless Hash === where_opts
|
|
189
|
+
return yield unless ::Hash === where_opts
|
|
184
190
|
|
|
185
191
|
opts = where_opts.with_indifferent_access
|
|
186
192
|
locale = opts.delete(:locale) || Mobility.locale
|
|
@@ -193,11 +199,11 @@ enabled for any one attribute on the model.
|
|
|
193
199
|
# Builds a translated relation for a given opts hash and optional
|
|
194
200
|
# invert boolean.
|
|
195
201
|
def _build(scope, opts, locale, invert)
|
|
196
|
-
return yield
|
|
202
|
+
return yield if (mods = attribute_modules(scope)).empty?
|
|
197
203
|
|
|
198
204
|
keys, predicates = opts.keys.map(&:to_s), []
|
|
199
205
|
|
|
200
|
-
query_map =
|
|
206
|
+
query_map = mods.inject(IDENTITY) do |qm, mod|
|
|
201
207
|
i18n_keys = mod.names & keys
|
|
202
208
|
next qm if i18n_keys.empty?
|
|
203
209
|
|
|
@@ -213,7 +219,11 @@ enabled for any one attribute on the model.
|
|
|
213
219
|
return yield if query_map == IDENTITY
|
|
214
220
|
|
|
215
221
|
relation = opts.empty? ? scope : yield(opts)
|
|
216
|
-
query_map[relation.where(predicates.inject(
|
|
222
|
+
query_map[relation.where(predicates.inject(:and))]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def attribute_modules(scope)
|
|
226
|
+
scope.model.ancestors.grep(::Mobility::Translations)
|
|
217
227
|
end
|
|
218
228
|
|
|
219
229
|
def build_predicate(node, values)
|
|
@@ -265,6 +275,10 @@ enabled for any one attribute on the model.
|
|
|
265
275
|
|
|
266
276
|
private_constant :QueryExtension, :FindByMethods
|
|
267
277
|
end
|
|
278
|
+
|
|
279
|
+
class MissingBackend < Mobility::Error; end
|
|
268
280
|
end
|
|
281
|
+
|
|
282
|
+
register_plugin(:active_record_query, ActiveRecord::Query)
|
|
269
283
|
end
|
|
270
284
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Mobility
|
|
2
|
+
module Plugins
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module UniquenessValidation
|
|
5
|
+
extend Plugin
|
|
6
|
+
|
|
7
|
+
requires :query, include: false
|
|
8
|
+
|
|
9
|
+
included_hook do |klass|
|
|
10
|
+
klass.class_eval do
|
|
11
|
+
unless const_defined?(:UniquenessValidator, false)
|
|
12
|
+
self.const_set(:UniquenessValidator, Class.new(UniquenessValidator))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
|
|
18
|
+
# @param [ActiveRecord::Base] record Translated model
|
|
19
|
+
# @param [String] attribute Name of attribute
|
|
20
|
+
# @param [Object] value Attribute value
|
|
21
|
+
def validate_each(record, attribute, value)
|
|
22
|
+
klass = record.class
|
|
23
|
+
|
|
24
|
+
if ([*options[:scope]] + [attribute]).any? { |name| klass.mobility_attribute?(name) }
|
|
25
|
+
return unless value.present?
|
|
26
|
+
relation = klass.unscoped.__mobility_query_scope__ do |m|
|
|
27
|
+
node = m.__send__(attribute)
|
|
28
|
+
options[:case_sensitive] == false ? node.lower.eq(value.downcase) : node.eq(value)
|
|
29
|
+
end
|
|
30
|
+
relation = relation.where.not(klass.primary_key => record.id) if record.persisted?
|
|
31
|
+
relation = mobility_scope_relation(record, relation)
|
|
32
|
+
relation = relation.merge(options[:conditions]) if options[:conditions]
|
|
33
|
+
|
|
34
|
+
if relation.exists?
|
|
35
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
|
36
|
+
error_options[:value] = value
|
|
37
|
+
|
|
38
|
+
record.errors.add(attribute, :taken, **error_options)
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def mobility_scope_relation(record, relation)
|
|
48
|
+
[*options[:scope]].inject(relation) do |scoped_relation, scope_item|
|
|
49
|
+
scoped_relation.__mobility_query_scope__ do |m|
|
|
50
|
+
m.__send__(scope_item).eq(record.send(scope_item))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
register_plugin(:active_record_uniqueness_validation, ActiveRecord::UniquenessValidation)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -12,30 +12,39 @@ attributes only.
|
|
|
12
12
|
|
|
13
13
|
=end
|
|
14
14
|
module AttributeMethods
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
extend Plugin
|
|
16
|
+
|
|
17
|
+
default true
|
|
18
|
+
requires :attributes
|
|
19
|
+
|
|
20
|
+
initialize_hook do |*names|
|
|
21
|
+
include InstanceMethods
|
|
22
|
+
|
|
23
|
+
define_method :translated_attributes do
|
|
24
|
+
super().merge(names.inject({}) do |attributes, name|
|
|
25
|
+
attributes.merge(name.to_s => send(name))
|
|
26
|
+
end)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Applies attribute_methods plugin for a given option value.
|
|
31
|
+
included_hook do
|
|
32
|
+
if options[:attribute_methods]
|
|
33
|
+
define_method :untranslated_attributes, ::ActiveRecord::Base.instance_method(:attributes)
|
|
24
34
|
end
|
|
35
|
+
end
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
else
|
|
34
|
-
raise ArgumentError, "#{model_class} does not support AttributeMethods plugin."
|
|
35
|
-
end
|
|
36
|
-
model_class.include module_builder.new(*attribute_names)
|
|
37
|
+
module InstanceMethods
|
|
38
|
+
def translated_attributes
|
|
39
|
+
{}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def attributes
|
|
43
|
+
super.merge(translated_attributes)
|
|
37
44
|
end
|
|
38
45
|
end
|
|
39
46
|
end
|
|
47
|
+
|
|
48
|
+
register_plugin(:attribute_methods, AttributeMethods)
|
|
40
49
|
end
|
|
41
50
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
module Mobility
|
|
3
|
+
module Plugins
|
|
4
|
+
=begin
|
|
5
|
+
|
|
6
|
+
Takes arguments, converts them to strings, and stores in an array +@names+,
|
|
7
|
+
made available with an +attr_reader+. Also provides some convenience methods
|
|
8
|
+
for aggregating attributes.
|
|
9
|
+
|
|
10
|
+
=end
|
|
11
|
+
module Attributes
|
|
12
|
+
extend Plugin
|
|
13
|
+
|
|
14
|
+
# Attribute names for which accessors will be defined
|
|
15
|
+
# @return [Array<String>] Array of names
|
|
16
|
+
attr_reader :names
|
|
17
|
+
|
|
18
|
+
initialize_hook do |*names|
|
|
19
|
+
@names = names.map(&:to_s).freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Yield each attribute name to block
|
|
23
|
+
# @yieldparam [String] Attribute
|
|
24
|
+
def each &block
|
|
25
|
+
names.each(&block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Show useful information about this module.
|
|
29
|
+
# @return [String]
|
|
30
|
+
def inspect
|
|
31
|
+
"#<Translations @names=#{names.join(", ")}>"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
included_hook do |klass|
|
|
35
|
+
names = @names
|
|
36
|
+
|
|
37
|
+
klass.class_eval do
|
|
38
|
+
extend ClassMethods
|
|
39
|
+
names.each { |name| mobility_attributes << name.to_s }
|
|
40
|
+
mobility_attributes.uniq!
|
|
41
|
+
rescue FrozenError
|
|
42
|
+
raise FrozenAttributesError, "Attempting to translate these attributes on #{klass}, which has already been subclassed: #{names.join(', ')}."
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module ClassMethods
|
|
47
|
+
# Return true if attribute name is translated on this model.
|
|
48
|
+
# @param [String, Symbol] Attribute name
|
|
49
|
+
# @return [Boolean]
|
|
50
|
+
def mobility_attribute?(name)
|
|
51
|
+
mobility_attributes.include?(name.to_s)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Return translated attribute names on this model.
|
|
55
|
+
# @return [Array<String>] Attribute names
|
|
56
|
+
def mobility_attributes
|
|
57
|
+
@mobility_attributes ||= []
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def inherited(klass)
|
|
61
|
+
super
|
|
62
|
+
attrs = mobility_attributes.freeze # ensure attributes are not modified after being inherited
|
|
63
|
+
klass.class_eval { @mobility_attributes = attrs.dup }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class FrozenAttributesError < Error; end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
register_plugin(:attributes, Attributes)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
module Mobility
|
|
3
|
+
module Plugins
|
|
4
|
+
=begin
|
|
5
|
+
|
|
6
|
+
Plugin for setting up a backend for a set of model attributes. All backend
|
|
7
|
+
plugins must depend on this.
|
|
8
|
+
|
|
9
|
+
Defines:
|
|
10
|
+
- instance method +mobility_backends+ which returns a hash whose keys are
|
|
11
|
+
attribute names and values a backend for each attribute.
|
|
12
|
+
- class method +mobility_backend_class+ which takes an attribute name and
|
|
13
|
+
returns the backend class for that name.
|
|
14
|
+
|
|
15
|
+
=end
|
|
16
|
+
module Backend
|
|
17
|
+
extend Plugin
|
|
18
|
+
|
|
19
|
+
requires :attributes, include: :before
|
|
20
|
+
|
|
21
|
+
# Backend class
|
|
22
|
+
# @return [Class] Backend class
|
|
23
|
+
attr_reader :backend_class
|
|
24
|
+
|
|
25
|
+
# Backend
|
|
26
|
+
# @return [Symbol,Class,Class] Name of backend, or backend class
|
|
27
|
+
attr_reader :backend
|
|
28
|
+
|
|
29
|
+
# Backend options
|
|
30
|
+
# @return [Hash] Options for backend
|
|
31
|
+
attr_reader :backend_options
|
|
32
|
+
|
|
33
|
+
def initialize(*args, **original_options)
|
|
34
|
+
super
|
|
35
|
+
|
|
36
|
+
# Validate that the default backend from config has valid keys
|
|
37
|
+
if (default = self.class.defaults[:backend])
|
|
38
|
+
name, backend_options = default
|
|
39
|
+
extra_keys = backend_options.keys - backend.valid_keys
|
|
40
|
+
raise InvalidOptionKey, "These are not valid #{name} backend keys: #{extra_keys.join(', ')}." unless extra_keys.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
include InstanceMethods
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Setup backend class, include modules into model class, include/extend
|
|
47
|
+
# shared modules and setup model with backend setup block (see
|
|
48
|
+
# {Mobility::Backend::Setup#setup_model}).
|
|
49
|
+
def included(klass)
|
|
50
|
+
super
|
|
51
|
+
|
|
52
|
+
klass.extend ClassMethods
|
|
53
|
+
|
|
54
|
+
if backend
|
|
55
|
+
@backend_class = backend.build_subclass(klass, backend_options)
|
|
56
|
+
|
|
57
|
+
backend_class.setup_model(klass, names)
|
|
58
|
+
|
|
59
|
+
names = @names
|
|
60
|
+
backend_class = @backend_class
|
|
61
|
+
|
|
62
|
+
klass.class_eval do
|
|
63
|
+
names.each { |name| mobility_backend_classes[name.to_sym] = backend_class }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
backend_class
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Include backend name in inspect string.
|
|
71
|
+
# @return [String]
|
|
72
|
+
def inspect
|
|
73
|
+
"#<Translations (#{backend}) @names=#{names.join(", ")}>"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def load_backend(backend)
|
|
77
|
+
Backends.load_backend(backend)
|
|
78
|
+
rescue Backends::LoadError => e
|
|
79
|
+
raise e, "could not find a #{backend} backend. Did you forget to include an ORM plugin like active_record or sequel?"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# Override to extract backend options from options hash.
|
|
85
|
+
def initialize_options(original_options)
|
|
86
|
+
super
|
|
87
|
+
|
|
88
|
+
case options[:backend]
|
|
89
|
+
when String, Symbol, Class
|
|
90
|
+
@backend, @backend_options = options[:backend], options
|
|
91
|
+
when Array
|
|
92
|
+
@backend, @backend_options = options[:backend]
|
|
93
|
+
@backend_options = @backend_options.merge(original_options)
|
|
94
|
+
when NilClass
|
|
95
|
+
@backend = @backend_options = nil
|
|
96
|
+
else
|
|
97
|
+
raise ArgumentError, "backend must be either a backend name, a backend class, or a two-element array"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
@backend = load_backend(backend)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Override default validation to exclude backend options, which may be
|
|
104
|
+
# mixed in with plugin options.
|
|
105
|
+
def validate_options(options)
|
|
106
|
+
return super unless backend
|
|
107
|
+
super(options.slice(*(options.keys - backend.valid_keys)))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Override default argument-handling in DSL to store kwargs passed along
|
|
111
|
+
# with plugin name.
|
|
112
|
+
def self.configure_default(defaults, key, backend = nil, backend_options = {})
|
|
113
|
+
defaults[key] = [backend, backend_options] if backend
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
module InstanceMethods
|
|
117
|
+
# Return a new backend for an attribute name.
|
|
118
|
+
# @return [Hash] Hash of attribute names and backend instances
|
|
119
|
+
# @api private
|
|
120
|
+
def mobility_backends
|
|
121
|
+
@mobility_backends ||= ::Hash.new do |hash, name|
|
|
122
|
+
next hash[name.to_sym] if String === name
|
|
123
|
+
hash[name] = self.class.mobility_backend_class(name).new(self, name.to_s)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def initialize_dup(other)
|
|
128
|
+
@mobility_backends = nil
|
|
129
|
+
super
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
module ClassMethods
|
|
134
|
+
# Return backend class for a given attribute name.
|
|
135
|
+
# @param [Symbol,String] Name of attribute
|
|
136
|
+
# @return [Class] Backend class
|
|
137
|
+
def mobility_backend_class(name)
|
|
138
|
+
mobility_backend_classes.fetch(name.to_sym)
|
|
139
|
+
rescue KeyError
|
|
140
|
+
raise KeyError, "No backend for: #{name}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def inherited(klass)
|
|
144
|
+
parent_classes = mobility_backend_classes.freeze # ensure backend classes are not modified after being inherited
|
|
145
|
+
klass.class_eval { @mobility_backend_classes = parent_classes.dup }
|
|
146
|
+
super
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
protected
|
|
150
|
+
|
|
151
|
+
def mobility_backend_classes
|
|
152
|
+
@mobility_backend_classes ||= {}
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
class InvalidOptionKey < Error; end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
register_plugin(:backend, Backend)
|
|
160
|
+
end
|
|
161
|
+
end
|