mobility 1.0.7 → 1.1.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 +0 -0
- data/CHANGELOG.md +3 -1
- data/Gemfile.lock +18 -3
- data/README.md +3 -4
- data/lib/mobility.rb +14 -7
- data/lib/mobility/backend.rb +3 -1
- data/lib/mobility/backends/active_record/key_value.rb +28 -27
- data/lib/mobility/backends/key_value.rb +11 -6
- data/lib/mobility/backends/sequel/key_value.rb +80 -47
- data/lib/mobility/plugins/active_record.rb +3 -1
- data/lib/mobility/plugins/active_record/backend.rb +5 -0
- data/lib/mobility/plugins/active_record/query.rb +35 -20
- data/lib/mobility/plugins/active_record/uniqueness_validation.rb +2 -2
- data/lib/mobility/plugins/attributes.rb +0 -6
- data/lib/mobility/plugins/sequel.rb +7 -1
- data/lib/mobility/plugins/sequel/backend.rb +5 -0
- data/lib/mobility/plugins/sequel/query.rb +35 -16
- data/lib/mobility/version.rb +2 -2
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b22675841d6b6330a4db417e3576767b42a81614b80cf376a208088ad9432de4
|
4
|
+
data.tar.gz: 1b2c44b38c87fc0b1e8ca5e463e9c265c09f0d307d0d663ba3aa39b6b5ca771b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 708d6ba16ec279e6a114abc811d107aa759590c711ca389d7f4b86407e100fbaed30a38b7b787ef98008291c480715949b0d4562efc2d5120eba73587fc375db
|
7
|
+
data.tar.gz: bd8f79dd23f7c482292c011bb800d7db20dc3b1fb06797b53fdf91ecdb9c9895c1634e3b1da06e90cff4e7b32e4aa5ebdf8f5d8b5c76f1fafbc08ca891ac99dc
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
mobility (1.0.
|
4
|
+
mobility (1.0.7)
|
5
5
|
i18n (>= 0.6.10, < 2)
|
6
6
|
request_store (~> 1.0)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
+
activemodel (6.0.3.4)
|
12
|
+
activesupport (= 6.0.3.4)
|
13
|
+
activerecord (6.0.3.4)
|
14
|
+
activemodel (= 6.0.3.4)
|
15
|
+
activesupport (= 6.0.3.4)
|
16
|
+
activesupport (6.0.3.4)
|
17
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
|
+
i18n (>= 0.7, < 2)
|
19
|
+
minitest (~> 5.1)
|
20
|
+
tzinfo (~> 1.1)
|
21
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
11
22
|
benchmark-ips (2.8.4)
|
12
23
|
byebug (11.1.3)
|
13
24
|
coderay (1.1.3)
|
@@ -37,6 +48,7 @@ GEM
|
|
37
48
|
rb-inotify (~> 0.9, >= 0.9.10)
|
38
49
|
lumberjack (1.2.8)
|
39
50
|
method_source (1.0.0)
|
51
|
+
minitest (5.14.3)
|
40
52
|
nenv (0.3.0)
|
41
53
|
notiffany (0.1.3)
|
42
54
|
nenv (~> 0.1)
|
@@ -68,15 +80,19 @@ GEM
|
|
68
80
|
diff-lcs (>= 1.2.0, < 2.0)
|
69
81
|
rspec-support (~> 3.10.0)
|
70
82
|
rspec-support (3.10.2)
|
71
|
-
sequel (5.41.0)
|
72
83
|
shellany (0.0.1)
|
73
84
|
thor (1.1.0)
|
85
|
+
thread_safe (0.3.6)
|
86
|
+
tzinfo (1.2.9)
|
87
|
+
thread_safe (~> 0.1)
|
74
88
|
yard (0.9.26)
|
89
|
+
zeitwerk (2.4.2)
|
75
90
|
|
76
91
|
PLATFORMS
|
77
92
|
ruby
|
78
93
|
|
79
94
|
DEPENDENCIES
|
95
|
+
activerecord (~> 6.0.0)
|
80
96
|
benchmark-ips
|
81
97
|
database_cleaner (~> 1.5, >= 1.5.3)
|
82
98
|
guard-rspec
|
@@ -85,7 +101,6 @@ DEPENDENCIES
|
|
85
101
|
pry-byebug
|
86
102
|
rake (~> 12, >= 12.2.1)
|
87
103
|
rspec (~> 3.0)
|
88
|
-
sequel (~> 5.0)
|
89
104
|
yard (~> 0.9.0)
|
90
105
|
|
91
106
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -52,17 +52,16 @@ section of the wiki.
|
|
52
52
|
Installation
|
53
53
|
------------
|
54
54
|
|
55
|
-
|
56
|
-
application's Gemfile:
|
55
|
+
Add this line to your application's Gemfile:
|
57
56
|
|
58
57
|
```ruby
|
59
|
-
gem 'mobility', '~> 1.0
|
58
|
+
gem 'mobility', '~> 1.1.0'
|
60
59
|
```
|
61
60
|
|
62
61
|
### ActiveRecord (Rails)
|
63
62
|
|
64
63
|
Requirements:
|
65
|
-
- ActiveRecord >= 5.0 (including 6.
|
64
|
+
- ActiveRecord >= 5.0 (including 6.x)
|
66
65
|
|
67
66
|
(Support for most backends and features is also supported with
|
68
67
|
ActiveRecord/Rails 4.2, but there are some tests still failing. To see exactly
|
data/lib/mobility.rb
CHANGED
@@ -31,8 +31,9 @@ So the above example is equivalent to:
|
|
31
31
|
`Mobility.translations_class` is a subclass of `Mobility::Translations` created
|
32
32
|
when `Mobility.configure` is called to configure Mobility. In fact, when you
|
33
33
|
call `Mobility.configure`, it is the subclass of `Mobility::Translations` which
|
34
|
-
is passed to the block as `config
|
35
|
-
applied to the same
|
34
|
+
is passed to the block as `config` (or as `self` if no argument is passed to
|
35
|
+
the block). Plugins and plugin configuration is all applied to the same
|
36
|
+
`Mobility.translations_class`.
|
36
37
|
|
37
38
|
There is another way to use Mobility, which is to create your own subclass or
|
38
39
|
subclasses of +Mobility::Translations+ and include them explicitly, without
|
@@ -83,8 +84,6 @@ module Mobility
|
|
83
84
|
require "mobility/plugins"
|
84
85
|
require "mobility/translations"
|
85
86
|
|
86
|
-
# General error for version compatibility conflicts
|
87
|
-
class VersionNotSupportedError < ArgumentError; end
|
88
87
|
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
|
89
88
|
private_constant :CALL_COMPILABLE_REGEXP
|
90
89
|
|
@@ -201,6 +200,14 @@ module Mobility
|
|
201
200
|
end
|
202
201
|
end
|
203
202
|
|
203
|
+
# Check that a non-nil locale is valid. (Does not actually parse locale to
|
204
|
+
# check its format.)
|
205
|
+
# @raise [InvalidLocale] if locale is not a Symbol or not available
|
206
|
+
def validate_locale!(locale)
|
207
|
+
raise Mobility::InvalidLocale.new(locale) unless Symbol === locale
|
208
|
+
enforce_available_locales!(locale) if I18n.enforce_available_locales
|
209
|
+
end
|
210
|
+
|
204
211
|
# Raises InvalidLocale exception if the locale passed in is present but not available.
|
205
212
|
# @param [String,Symbol] locale
|
206
213
|
# @raise [InvalidLocale] if locale is present but not available
|
@@ -217,7 +224,7 @@ module Mobility
|
|
217
224
|
# simply default to +I18n.available_locales+, we may define many more
|
218
225
|
# methods (in LocaleAccessors) than is really necessary.
|
219
226
|
def available_locales
|
220
|
-
if defined?(Rails) && Rails.application
|
227
|
+
if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
|
221
228
|
Rails.application.config.i18n.available_locales&.map(&:to_sym) || I18n.available_locales
|
222
229
|
else
|
223
230
|
I18n.available_locales
|
@@ -231,8 +238,8 @@ module Mobility
|
|
231
238
|
end
|
232
239
|
|
233
240
|
def set_locale(locale)
|
234
|
-
locale = locale.to_sym if locale
|
235
|
-
|
241
|
+
locale = locale.to_sym if String === locale
|
242
|
+
validate_locale!(locale) if locale
|
236
243
|
storage[:mobility_locale] = locale
|
237
244
|
end
|
238
245
|
end
|
data/lib/mobility/backend.rb
CHANGED
@@ -17,8 +17,10 @@ On top of this, a backend will normally:
|
|
17
17
|
- implement a +write+ instance method to write to the backend
|
18
18
|
- implement an +each_locale+ instance method to iterate through available locales
|
19
19
|
(used to define other +Enumerable+ traversal and search methods)
|
20
|
+
- implement a +valid_keys+ class method returning an array of symbols
|
21
|
+
corresponding to valid keys for configuring this backend.
|
20
22
|
- implement a +configure+ class method to apply any normalization to the
|
21
|
-
options hash
|
23
|
+
keys on the options hash included in +valid_keys+
|
22
24
|
- call the +setup+ method yielding attributes and options to configure the
|
23
25
|
model class
|
24
26
|
|
@@ -37,7 +37,6 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
|
|
37
37
|
options[:association_name] ||= :"#{options[:type]}_translations"
|
38
38
|
options[:class_name] ||= const_get("#{type.capitalize}Translation")
|
39
39
|
end
|
40
|
-
options[:table_alias_affix] = "#{model_class}_%s_#{options[:association_name]}"
|
41
40
|
rescue NameError
|
42
41
|
raise ArgumentError, "You must define a Mobility::Backends::ActiveRecord::KeyValue::#{type.capitalize}Translation class."
|
43
42
|
end
|
@@ -49,7 +48,7 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
|
|
49
48
|
# translation table value column
|
50
49
|
def build_node(attr, locale)
|
51
50
|
aliased_table = class_name.arel_table.alias(table_alias(attr, locale))
|
52
|
-
Plugins::Arel::Attribute.new(aliased_table,
|
51
|
+
Plugins::Arel::Attribute.new(aliased_table, value_column, locale, self, attr.to_sym)
|
53
52
|
end
|
54
53
|
|
55
54
|
# Joins translations using either INNER/OUTER join appropriate to the query.
|
@@ -73,10 +72,10 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
|
|
73
72
|
m = model_class.arel_table
|
74
73
|
t = class_name.arel_table.alias(table_alias(key, locale))
|
75
74
|
relation.joins(m.join(t, join_type).
|
76
|
-
on(t[
|
75
|
+
on(t[key_column].eq(key).
|
77
76
|
and(t[:locale].eq(locale).
|
78
|
-
and(t[:
|
79
|
-
and(t[:
|
77
|
+
and(t[:"#{belongs_to}_type"].eq(model_class.base_class.name).
|
78
|
+
and(t[:"#{belongs_to}_id"].eq(m[:id]))))).join_sources)
|
80
79
|
end
|
81
80
|
|
82
81
|
def already_joined?(relation, name, locale, join_type)
|
@@ -151,8 +150,11 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
|
|
151
150
|
end
|
152
151
|
|
153
152
|
setup do |attributes, options|
|
154
|
-
association_name
|
155
|
-
|
153
|
+
association_name = options[:association_name]
|
154
|
+
translation_class = options[:class_name]
|
155
|
+
key_column = options[:key_column]
|
156
|
+
value_column = options[:value_column]
|
157
|
+
belongs_to = options[:belongs_to]
|
156
158
|
|
157
159
|
# Track all attributes for this association, so that we can limit the scope
|
158
160
|
# of keys for the association to only these attributes. We need to track the
|
@@ -163,13 +165,13 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
|
|
163
165
|
association_attributes = (instance_variable_get(:"@#{attrs_method_name}") || []) + attributes
|
164
166
|
instance_variable_set(:"@#{attrs_method_name}", association_attributes)
|
165
167
|
|
166
|
-
has_many association_name, ->{ where
|
167
|
-
as:
|
168
|
-
class_name:
|
169
|
-
inverse_of:
|
168
|
+
has_many association_name, ->{ where key_column => association_attributes },
|
169
|
+
as: belongs_to,
|
170
|
+
class_name: translation_class.name,
|
171
|
+
inverse_of: belongs_to,
|
170
172
|
autosave: true
|
171
173
|
before_save do
|
172
|
-
send(association_name).select { |t| t.
|
174
|
+
send(association_name).select { |t| t.send(value_column).blank? }.each do |translation|
|
173
175
|
send(association_name).destroy(translation)
|
174
176
|
end
|
175
177
|
end
|
@@ -181,33 +183,32 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
|
|
181
183
|
super(source)
|
182
184
|
self.send("#{association_name}=", source.send(association_name).map(&:dup))
|
183
185
|
# Set inverse on associations
|
184
|
-
send(association_name).each
|
186
|
+
send(association_name).each do |translation|
|
187
|
+
translation.send(:"#{belongs_to}=", self)
|
188
|
+
end
|
185
189
|
end
|
186
190
|
end
|
187
191
|
include const_set(module_name, callback_methods)
|
188
192
|
end
|
189
193
|
|
190
|
-
|
194
|
+
# Ensure we only call after destroy hook once per translations class
|
195
|
+
translation_classes = [translation_class, *Mobility::Backends::ActiveRecord::KeyValue::Translation.descendants].uniq
|
196
|
+
after_destroy do
|
197
|
+
@mobility_after_destroy_translation_classes = [] unless defined?(@mobility_after_destroy_translation_classes)
|
198
|
+
(translation_classes - @mobility_after_destroy_translation_classes).each { |klass| klass.where(belongs_to => self).destroy_all }
|
199
|
+
@mobility_after_destroy_translation_classes += translation_classes
|
200
|
+
end
|
191
201
|
end
|
192
202
|
|
193
203
|
# Returns translation for a given locale, or builds one if none is present.
|
194
204
|
# @param [Symbol] locale
|
195
205
|
# @return [Mobility::Backends::ActiveRecord::KeyValue::TextTranslation,Mobility::Backends::ActiveRecord::KeyValue::StringTranslation]
|
196
206
|
def translation_for(locale, **)
|
197
|
-
translation = translations.find
|
198
|
-
|
199
|
-
translation
|
200
|
-
end
|
201
|
-
|
202
|
-
module DestroyKeyValueTranslations
|
203
|
-
def self.included(model_class)
|
204
|
-
model_class.after_destroy do
|
205
|
-
[:string, :text].each do |type|
|
206
|
-
Mobility::Backends::ActiveRecord::KeyValue.const_get("#{type.capitalize}Translation").
|
207
|
-
where(translatable: self).destroy_all
|
208
|
-
end
|
209
|
-
end
|
207
|
+
translation = translations.find do |t|
|
208
|
+
t.send(key_column) == attribute && t.locale == locale.to_s
|
210
209
|
end
|
210
|
+
translation ||= translations.build(locale: locale, key_column => attribute)
|
211
|
+
translation
|
211
212
|
end
|
212
213
|
|
213
214
|
class Translation < ::ActiveRecord::Base
|
@@ -55,18 +55,18 @@ other backends on model (otherwise one will overwrite the other).
|
|
55
55
|
# @!group Backend Accessors
|
56
56
|
# @!macro backend_reader
|
57
57
|
def read(locale, **options)
|
58
|
-
translation_for(locale, **options).
|
58
|
+
translation_for(locale, **options).send(value_column)
|
59
59
|
end
|
60
60
|
|
61
61
|
# @!macro backend_writer
|
62
62
|
def write(locale, value, **options)
|
63
|
-
translation_for(locale, **options).
|
63
|
+
translation_for(locale, **options).send(:"#{value_column}=", value)
|
64
64
|
end
|
65
65
|
# @!endgroup
|
66
66
|
|
67
67
|
# @!macro backend_iterator
|
68
68
|
def each_locale
|
69
|
-
translations.each { |t| yield(t.locale.to_sym) if t.
|
69
|
+
translations.each { |t| yield(t.locale.to_sym) if t.send(key_column) == attribute }
|
70
70
|
end
|
71
71
|
|
72
72
|
private
|
@@ -79,12 +79,14 @@ other backends on model (otherwise one will overwrite the other).
|
|
79
79
|
backend_class.extend ClassMethods
|
80
80
|
backend_class.option_reader :association_name
|
81
81
|
backend_class.option_reader :class_name
|
82
|
-
backend_class.option_reader :
|
82
|
+
backend_class.option_reader :key_column
|
83
|
+
backend_class.option_reader :value_column
|
84
|
+
backend_class.option_reader :belongs_to
|
83
85
|
end
|
84
86
|
|
85
87
|
module ClassMethods
|
86
88
|
def valid_keys
|
87
|
-
[:type, :association_name, :class_name]
|
89
|
+
[:type, :association_name, :class_name, :key_column, :value_column, :belongs_to]
|
88
90
|
end
|
89
91
|
|
90
92
|
# @!group Backend Configuration
|
@@ -99,6 +101,9 @@ other backends on model (otherwise one will overwrite the other).
|
|
99
101
|
options[:type] &&= options[:type].to_sym
|
100
102
|
options[:association_name] &&= options[:association_name].to_sym
|
101
103
|
options[:class_name] &&= Util.constantize(options[:class_name])
|
104
|
+
options[:key_column] ||= :key
|
105
|
+
options[:value_column] ||= :value
|
106
|
+
options[:belongs_to] ||= :translatable
|
102
107
|
if !(options[:type] || (options[:class_name] && options[:association_name]))
|
103
108
|
raise ArgumentError, "KeyValue backend requires an explicit type option, either text or string."
|
104
109
|
end
|
@@ -110,7 +115,7 @@ other backends on model (otherwise one will overwrite the other).
|
|
110
115
|
end
|
111
116
|
|
112
117
|
def table_alias(attr, locale)
|
113
|
-
|
118
|
+
"#{model_class}_#{attr}_#{Mobility.normalize_locale(locale)}_#{options[:association_name]}"
|
114
119
|
end
|
115
120
|
end
|
116
121
|
|
@@ -32,14 +32,13 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
32
32
|
options[:association_name] ||= :"#{options[:type]}_translations"
|
33
33
|
options[:class_name] ||= const_get("#{type.capitalize}Translation")
|
34
34
|
end
|
35
|
-
options[:table_alias_affix] = "#{model_class}_%s_#{options[:association_name]}"
|
36
35
|
rescue NameError
|
37
36
|
raise ArgumentError, "You must define a Mobility::Sequel::#{type.capitalize}Translation class."
|
38
37
|
end
|
39
38
|
# @!endgroup
|
40
39
|
|
41
40
|
def build_op(attr, locale)
|
42
|
-
QualifiedIdentifier.new(table_alias(attr, locale),
|
41
|
+
QualifiedIdentifier.new(table_alias(attr, locale), value_column, locale, self, attr)
|
43
42
|
end
|
44
43
|
|
45
44
|
# @param [Sequel::Dataset] dataset Dataset to prepare
|
@@ -58,10 +57,10 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
58
57
|
dataset.join_table(join_type,
|
59
58
|
class_name.table_name,
|
60
59
|
{
|
61
|
-
|
62
|
-
locale
|
63
|
-
|
64
|
-
|
60
|
+
key_column => attr.to_s,
|
61
|
+
:locale => locale.to_s,
|
62
|
+
:"#{belongs_to}_type" => model_class.name,
|
63
|
+
:"#{belongs_to}_id" => ::Sequel[:"#{model_class.table_name}"][:id]
|
65
64
|
},
|
66
65
|
table_alias: table_alias(attr, locale))
|
67
66
|
end
|
@@ -127,27 +126,37 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
127
126
|
backend = self
|
128
127
|
|
129
128
|
setup do |attributes, options|
|
130
|
-
association_name
|
131
|
-
|
132
|
-
|
129
|
+
association_name = options[:association_name]
|
130
|
+
translation_class = options[:class_name]
|
131
|
+
key_column = options[:key_column]
|
132
|
+
value_column = options[:value_column]
|
133
|
+
belongs_to = options[:belongs_to]
|
134
|
+
belongs_to_id = :"#{belongs_to}_id"
|
135
|
+
belongs_to_type = :"#{belongs_to}_type"
|
136
|
+
|
137
|
+
# Track all attributes for this association, so that we can limit the scope
|
138
|
+
# of keys for the association to only these attributes. We need to track the
|
139
|
+
# attributes assigned to the association in case this setup code is called
|
140
|
+
# multiple times, so we don't "forget" earlier attributes.
|
141
|
+
#
|
133
142
|
attrs_method_name = :"#{association_name}_attributes"
|
134
143
|
association_attributes = (instance_variable_get(:"@#{attrs_method_name}") || []) + attributes
|
135
144
|
instance_variable_set(:"@#{attrs_method_name}", association_attributes)
|
136
145
|
|
137
146
|
one_to_many association_name,
|
138
|
-
reciprocal:
|
139
|
-
key:
|
147
|
+
reciprocal: belongs_to,
|
148
|
+
key: belongs_to_id,
|
140
149
|
reciprocal_type: :one_to_many,
|
141
|
-
conditions: {
|
142
|
-
adder: proc { |translation| translation.update(
|
143
|
-
remover: proc { |translation| translation.update(
|
144
|
-
clearer: proc {
|
145
|
-
class:
|
150
|
+
conditions: { belongs_to_type => self.to_s, key_column => association_attributes },
|
151
|
+
adder: proc { |translation| translation.update(belongs_to_id => pk, belongs_to_type => self.class.to_s) },
|
152
|
+
remover: proc { |translation| translation.update(belongs_to_id => nil, belongs_to_type => nil) },
|
153
|
+
clearer: proc { send_(:"#{association_name}_dataset").update(belongs_to_id => nil, belongs_to_type => nil) },
|
154
|
+
class: translation_class
|
146
155
|
|
147
156
|
callback_methods = Module.new do
|
148
157
|
define_method :before_save do
|
149
158
|
super()
|
150
|
-
send(association_name).select { |t| attributes.include?(t.
|
159
|
+
send(association_name).select { |t| attributes.include?(t.__send__(key_column)) && Util.blank?(t.__send__(value_column)) }.each(&:destroy)
|
151
160
|
end
|
152
161
|
define_method :after_save do
|
153
162
|
super()
|
@@ -156,7 +165,17 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
156
165
|
end
|
157
166
|
include callback_methods
|
158
167
|
|
159
|
-
|
168
|
+
# Clean up *all* leftover translations of this model, only once.
|
169
|
+
translation_classes = [translation_class, *Mobility::Backends::Sequel::KeyValue::Translation.descendants].uniq
|
170
|
+
define_method :after_destroy do
|
171
|
+
super()
|
172
|
+
|
173
|
+
@mobility_after_destroy_translation_classes = [] unless defined?(@mobility_after_destroy_translation_classes)
|
174
|
+
(translation_classes - @mobility_after_destroy_translation_classes).each do |klass|
|
175
|
+
klass.where(belongs_to_id => id, belongs_to_type => self.class.name).destroy
|
176
|
+
end
|
177
|
+
@mobility_after_destroy_translation_classes += translation_classes
|
178
|
+
end
|
160
179
|
include(mod = Module.new)
|
161
180
|
backend.define_column_changes(mod, attributes)
|
162
181
|
end
|
@@ -165,30 +184,19 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
165
184
|
# @param [Symbol] locale
|
166
185
|
# @return [Mobility::Backends::Sequel::KeyValue::TextTranslation,Mobility::Backends::Sequel::KeyValue::StringTranslation]
|
167
186
|
def translation_for(locale, **)
|
168
|
-
translation = model.send(association_name).find { |t| t.
|
169
|
-
translation ||= class_name.new(locale: locale,
|
187
|
+
translation = model.send(association_name).find { |t| t.__send__(key_column) == attribute && t.locale == locale.to_s }
|
188
|
+
translation ||= class_name.new(locale: locale, key_column => attribute)
|
170
189
|
translation
|
171
190
|
end
|
172
191
|
|
173
192
|
# Saves translation which have been built and which have non-blank values.
|
174
193
|
def save_translations
|
175
194
|
cache.each_value do |translation|
|
176
|
-
next unless present?(translation.
|
195
|
+
next unless present?(translation.__send__ value_column)
|
177
196
|
translation.id ? translation.save : model.send("add_#{singularize(association_name)}", translation)
|
178
197
|
end
|
179
198
|
end
|
180
199
|
|
181
|
-
# Clean up *all* leftover translations of this model, only once.
|
182
|
-
module DestroyKeyValueTranslations
|
183
|
-
def after_destroy
|
184
|
-
super
|
185
|
-
[:string, :text].freeze.each do |type|
|
186
|
-
Mobility::Backends::Sequel::KeyValue.const_get("#{type.capitalize}Translation").
|
187
|
-
where(translatable_id: id, translatable_type: self.class.name).destroy
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
200
|
class CacheRequired < ::StandardError; end
|
193
201
|
|
194
202
|
module Cache
|
@@ -212,8 +220,32 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
212
220
|
end
|
213
221
|
end
|
214
222
|
|
215
|
-
|
216
|
-
|
223
|
+
class Translatable < Module
|
224
|
+
attr_reader :key_column, :value_column, :belongs_to, :id_column, :type_column
|
225
|
+
|
226
|
+
def initialize(key_column, value_column, belongs_to)
|
227
|
+
@key_column = key_column
|
228
|
+
@value_column = value_column
|
229
|
+
@belongs_to = belongs_to
|
230
|
+
@id_column = :"#{belongs_to}_id"
|
231
|
+
@type_column = :"#{belongs_to}_type"
|
232
|
+
end
|
233
|
+
|
234
|
+
# Strictly these are not "descendants", but to keep terminology
|
235
|
+
# consistent with ActiveRecord KeyValue backend.
|
236
|
+
def descendants
|
237
|
+
@descendants ||= Set.new
|
238
|
+
end
|
239
|
+
|
240
|
+
def included(base)
|
241
|
+
@descendants ||= Set.new
|
242
|
+
@descendants << base
|
243
|
+
|
244
|
+
mod = self
|
245
|
+
key_column = mod.key_column
|
246
|
+
id_column = mod.id_column
|
247
|
+
type_column = mod.type_column
|
248
|
+
|
217
249
|
base.class_eval do
|
218
250
|
plugin :validation_helpers
|
219
251
|
|
@@ -221,16 +253,16 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
221
253
|
#
|
222
254
|
model = underscore(self.to_s)
|
223
255
|
plural_model = pluralize(model)
|
224
|
-
many_to_one
|
256
|
+
many_to_one mod.belongs_to,
|
225
257
|
reciprocal: plural_model.to_sym,
|
226
258
|
reciprocal_type: :many_to_one,
|
227
259
|
setter: (proc do |able_instance|
|
228
|
-
self[
|
229
|
-
self[
|
260
|
+
self[id_column] = (able_instance.pk if able_instance)
|
261
|
+
self[type_column] = (able_instance.class.name if able_instance)
|
230
262
|
end),
|
231
263
|
dataset: (proc do
|
232
|
-
translatable_type = send
|
233
|
-
translatable_id = send
|
264
|
+
translatable_type = send type_column
|
265
|
+
translatable_id = send id_column
|
234
266
|
return if translatable_type.nil? || translatable_id.nil?
|
235
267
|
klass = self.class.send(:constantize, translatable_type)
|
236
268
|
klass.where(klass.primary_key => translatable_id)
|
@@ -238,29 +270,30 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
238
270
|
eager_loader: (proc do |eo|
|
239
271
|
id_map = {}
|
240
272
|
eo[:rows].each do |model|
|
241
|
-
model_able_type = model.send
|
242
|
-
model_able_id = model.send
|
243
|
-
model.associations[
|
273
|
+
model_able_type = model.send type_column
|
274
|
+
model_able_id = model.send id_column
|
275
|
+
model.associations[belongs_to] = nil
|
244
276
|
((id_map[model_able_type] ||= {})[model_able_id] ||= []) << model if !model_able_type.nil? && !model_able_id.nil?
|
245
277
|
end
|
246
278
|
id_map.each do |klass_name, id_map|
|
247
279
|
klass = constantize(camelize(klass_name))
|
248
280
|
klass.where(klass.primary_key=>id_map.keys).all do |related_obj|
|
249
281
|
id_map[related_obj.pk].each do |model|
|
250
|
-
model.associations[
|
282
|
+
model.associations[belongs_to] = related_obj
|
251
283
|
end
|
252
284
|
end
|
253
285
|
end
|
254
286
|
end)
|
255
287
|
|
256
|
-
|
257
|
-
super
|
258
|
-
validates_presence [:locale,
|
259
|
-
validates_unique [:locale,
|
288
|
+
define_method :validate do
|
289
|
+
super()
|
290
|
+
validates_presence [:locale, key_column, id_column, type_column]
|
291
|
+
validates_unique [:locale, key_column, id_column, type_column]
|
260
292
|
end
|
261
293
|
end
|
262
294
|
end
|
263
295
|
end
|
296
|
+
Translation = Translatable.new(:key, :value, :translatable)
|
264
297
|
|
265
298
|
class TextTranslation < ::Sequel::Model(:mobility_text_translations)
|
266
299
|
include Translation
|
@@ -8,7 +8,9 @@ require_relative "./active_record/uniqueness_validation"
|
|
8
8
|
module Mobility
|
9
9
|
=begin
|
10
10
|
|
11
|
-
Plugin for ActiveRecord models.
|
11
|
+
Plugin for ActiveRecord models. This plugin automatically requires activerecord
|
12
|
+
related plugins, which are not actually "active" unless their base plugin (e.g.
|
13
|
+
dirty for active_record_dirty) is also enabled.
|
12
14
|
|
13
15
|
=end
|
14
16
|
module Plugins
|
@@ -29,7 +29,9 @@ enabled for any one attribute on the model.
|
|
29
29
|
klass.class_eval do
|
30
30
|
extend QueryMethod
|
31
31
|
extend FindByMethods.new(*plugin.names)
|
32
|
-
singleton_class.
|
32
|
+
singleton_class.define_method(plugin.query_method) do |locale: Mobility.locale, &block|
|
33
|
+
Query.build_query(self, locale, &block)
|
34
|
+
end
|
33
35
|
end
|
34
36
|
backend_class.include BackendMethods
|
35
37
|
end
|
@@ -39,6 +41,14 @@ enabled for any one attribute on the model.
|
|
39
41
|
def attribute_alias(attribute, locale = Mobility.locale)
|
40
42
|
"__mobility_%s_%s__" % [attribute, ::Mobility.normalize_locale(locale)]
|
41
43
|
end
|
44
|
+
|
45
|
+
def build_query(klass, locale = Mobility.locale, &block)
|
46
|
+
if block_given?
|
47
|
+
VirtualRow.build_query(klass, locale, &block)
|
48
|
+
else
|
49
|
+
klass.all.extending(QueryExtension)
|
50
|
+
end
|
51
|
+
end
|
42
52
|
end
|
43
53
|
|
44
54
|
module BackendMethods
|
@@ -57,13 +67,9 @@ enabled for any one attribute on the model.
|
|
57
67
|
end
|
58
68
|
|
59
69
|
module QueryMethod
|
60
|
-
# This is required for UniquenessValidator.
|
61
70
|
def __mobility_query_scope__(locale: Mobility.locale, &block)
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
all.extending(QueryExtension)
|
66
|
-
end
|
71
|
+
warn '__mobility_query_scope__ is an internal method and will be deprecated in the next release.'
|
72
|
+
Query.build_query(self, locale, &block)
|
67
73
|
end
|
68
74
|
end
|
69
75
|
|
@@ -71,18 +77,21 @@ enabled for any one attribute on the model.
|
|
71
77
|
# an instance-eval'ed block. Inspired by Sequel's (much more
|
72
78
|
# sophisticated) virtual rows.
|
73
79
|
class VirtualRow < BasicObject
|
74
|
-
attr_reader :
|
80
|
+
attr_reader :backends, :locales
|
75
81
|
|
76
|
-
def initialize(
|
77
|
-
@
|
82
|
+
def initialize(klass, global_locale)
|
83
|
+
@klass, @global_locale, @locales, @backends = klass, global_locale, [], []
|
78
84
|
end
|
79
85
|
|
80
|
-
def method_missing(m, *)
|
81
|
-
if @
|
82
|
-
@
|
83
|
-
|
84
|
-
|
85
|
-
@
|
86
|
+
def method_missing(m, *args)
|
87
|
+
if @klass.mobility_attribute?(m)
|
88
|
+
@backends |= [@klass.mobility_backend_class(m)]
|
89
|
+
::Mobility.validate_locale!(args[0]) if args[0]
|
90
|
+
locale = args[0] || @global_locale
|
91
|
+
@locales |= [locale]
|
92
|
+
@klass.mobility_backend_class(m).build_node(m, locale)
|
93
|
+
elsif @klass.column_names.include?(m.to_s)
|
94
|
+
@klass.arel_table[m]
|
86
95
|
else
|
87
96
|
super
|
88
97
|
end
|
@@ -90,21 +99,27 @@ enabled for any one attribute on the model.
|
|
90
99
|
|
91
100
|
class << self
|
92
101
|
def build_query(klass, locale, &block)
|
102
|
+
::Mobility.validate_locale!(locale)
|
103
|
+
|
93
104
|
row = new(klass, locale)
|
94
105
|
query = block.arity.zero? ? row.instance_eval(&block) : block.call(row)
|
95
106
|
|
96
107
|
if ::ActiveRecord::Relation === query
|
97
108
|
predicates = query.arel.constraints
|
98
|
-
apply_scopes(klass.all, row.
|
109
|
+
apply_scopes(klass.all, row.backends, row.locales, predicates).merge(query)
|
99
110
|
else
|
100
|
-
apply_scopes(klass.all, row.
|
111
|
+
apply_scopes(klass.all, row.backends, row.locales, query).where(query)
|
101
112
|
end
|
102
113
|
end
|
103
114
|
|
104
115
|
private
|
105
116
|
|
106
|
-
def apply_scopes(scope, backends,
|
107
|
-
backends.inject(scope)
|
117
|
+
def apply_scopes(scope, backends, locales, predicates)
|
118
|
+
backends.inject(scope) do |scope_, b|
|
119
|
+
locales.inject(scope_) do |r, locale|
|
120
|
+
b.apply_scope(r, predicates, locale)
|
121
|
+
end
|
122
|
+
end
|
108
123
|
end
|
109
124
|
end
|
110
125
|
end
|
@@ -27,7 +27,7 @@ module Mobility
|
|
27
27
|
|
28
28
|
if ([*options[:scope]] + [attribute]).any? { |name| klass.mobility_attribute?(name) }
|
29
29
|
return unless value.present?
|
30
|
-
relation = klass.unscoped.
|
30
|
+
relation = Plugins::ActiveRecord::Query.build_query(klass.unscoped, Mobility.locale) do |m|
|
31
31
|
node = m.__send__(attribute)
|
32
32
|
options[:case_sensitive] == false ? node.lower.eq(value.downcase) : node.eq(value)
|
33
33
|
end
|
@@ -50,7 +50,7 @@ module Mobility
|
|
50
50
|
|
51
51
|
def mobility_scope_relation(record, relation)
|
52
52
|
[*options[:scope]].inject(relation) do |scoped_relation, scope_item|
|
53
|
-
scoped_relation.
|
53
|
+
Plugins::ActiveRecord::Query.build_query(scoped_relation, Mobility.locale) do |m|
|
54
54
|
m.__send__(scope_item).eq(record.send(scope_item))
|
55
55
|
end
|
56
56
|
end
|
@@ -19,12 +19,6 @@ for aggregating attributes.
|
|
19
19
|
@names = names.map(&:to_s).freeze
|
20
20
|
end
|
21
21
|
|
22
|
-
# Yield each attribute name to block
|
23
|
-
# @yieldparam [String] Attribute
|
24
|
-
def each &block
|
25
|
-
names.each(&block)
|
26
|
-
end
|
27
|
-
|
28
22
|
# Show useful information about this module.
|
29
23
|
# @return [String]
|
30
24
|
def inspect
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require "sequel"
|
2
|
-
raise VersionNotSupportedError, "Mobility is only compatible with Sequel 4.0 and greater" if ::Sequel::MAJOR < 4
|
3
2
|
require "sequel/plugins/mobility"
|
4
3
|
unless defined?(ActiveSupport::Inflector)
|
5
4
|
# TODO: avoid automatically including the inflector extension
|
@@ -13,6 +12,13 @@ require_relative "./sequel/query"
|
|
13
12
|
|
14
13
|
module Mobility
|
15
14
|
module Plugins
|
15
|
+
=begin
|
16
|
+
|
17
|
+
Plugin for Sequel models. This plugin automatically requires sequel related
|
18
|
+
plugins, which are not actually "active" unless their base plugin (e.g. dirty
|
19
|
+
for sequel_dirty) is also enabled.
|
20
|
+
|
21
|
+
=end
|
16
22
|
module Sequel
|
17
23
|
extend Plugin
|
18
24
|
|
@@ -3,7 +3,8 @@ module Mobility
|
|
3
3
|
module Plugins
|
4
4
|
=begin
|
5
5
|
|
6
|
-
|
6
|
+
Supports querying on Sequel model translated attributes. Similar API to the
|
7
|
+
ActiveRecord query plugin.
|
7
8
|
|
8
9
|
=end
|
9
10
|
module Sequel
|
@@ -19,35 +20,47 @@ See ActiveRecord::Query plugin.
|
|
19
20
|
|
20
21
|
klass.class_eval do
|
21
22
|
extend QueryMethod
|
22
|
-
singleton_class.
|
23
|
+
singleton_class.define_method(plugin.query_method) do |locale: Mobility.locale, &block|
|
24
|
+
Query.build_query(self, locale, &block)
|
25
|
+
end
|
23
26
|
end
|
24
27
|
end
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
|
-
def
|
30
|
+
class << self
|
31
|
+
def build_query(klass, locale = Mobility.locale, &block)
|
29
32
|
if block_given?
|
30
|
-
VirtualRow.build_query(
|
33
|
+
VirtualRow.build_query(klass, locale, &block)
|
31
34
|
else
|
32
|
-
dataset.with_extend(QueryExtension)
|
35
|
+
klass.dataset.with_extend(QueryExtension)
|
33
36
|
end
|
34
37
|
end
|
35
38
|
end
|
36
39
|
|
40
|
+
module QueryMethod
|
41
|
+
def __mobility_query_dataset__(locale: Mobility.locale, &block)
|
42
|
+
warn '__mobility_query_dataset__ is an internal method and will be deprecated in the next release.'
|
43
|
+
Query.build_query(self, locale, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
37
47
|
# Internal class to create a "clean room" for manipulating translated
|
38
48
|
# attribute nodes in an instance-eval'ed block. Inspired by Sequel's
|
39
49
|
# (much more sophisticated) virtual rows.
|
40
50
|
class VirtualRow < BasicObject
|
41
|
-
attr_reader :
|
51
|
+
attr_reader :backends, :locales
|
42
52
|
|
43
|
-
def initialize(model_class,
|
44
|
-
@model_class, @
|
53
|
+
def initialize(model_class, global_locale)
|
54
|
+
@model_class, @global_locale, @backends, @locales = model_class, global_locale, [], []
|
45
55
|
end
|
46
56
|
|
47
|
-
def method_missing(m, *)
|
57
|
+
def method_missing(m, *args)
|
48
58
|
if @model_class.mobility_attribute?(m)
|
49
|
-
@
|
50
|
-
|
59
|
+
@backends |= [@model_class.mobility_backend_class(m)]
|
60
|
+
::Mobility.validate_locale!(args[0]) if args[0]
|
61
|
+
locale = args[0] || @global_locale
|
62
|
+
@locales |= [locale]
|
63
|
+
@model_class.mobility_backend_class(m).build_op(m.to_s, locale)
|
51
64
|
elsif @model_class.columns.include?(m.to_s)
|
52
65
|
::Sequel::SQL::QualifiedIdentifier.new(@model_class.table_name, m)
|
53
66
|
else
|
@@ -57,21 +70,27 @@ See ActiveRecord::Query plugin.
|
|
57
70
|
|
58
71
|
class << self
|
59
72
|
def build_query(klass, locale, &block)
|
73
|
+
::Mobility.validate_locale!(locale)
|
74
|
+
|
60
75
|
row = new(klass, locale)
|
61
76
|
query = block.arity.zero? ? row.instance_eval(&block) : block.call(row)
|
62
77
|
|
63
78
|
if ::Sequel::Dataset === query
|
64
79
|
predicates = query.opts[:where]
|
65
|
-
prepare_datasets(query, row.
|
80
|
+
prepare_datasets(query, row.backends, row.locales, predicates)
|
66
81
|
else
|
67
|
-
prepare_datasets(klass.dataset, row.
|
82
|
+
prepare_datasets(klass.dataset, row.backends, row.locales, query).where(query)
|
68
83
|
end
|
69
84
|
end
|
70
85
|
|
71
86
|
private
|
72
87
|
|
73
|
-
def prepare_datasets(dataset, backends,
|
74
|
-
backends.inject(dataset)
|
88
|
+
def prepare_datasets(dataset, backends, locales, predicates)
|
89
|
+
backends.inject(dataset) do |dataset_, b|
|
90
|
+
locales.inject(dataset_) do |ds, locale|
|
91
|
+
b.prepare_dataset(ds, predicates, locale)
|
92
|
+
end
|
93
|
+
end
|
75
94
|
end
|
76
95
|
end
|
77
96
|
end
|
data/lib/mobility/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mobility
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Salzberg
|
@@ -34,7 +34,7 @@ cert_chain:
|
|
34
34
|
gSQml7TqcC6dZRsZRwYqzD9kUwdAJoCqno2CBUKs2l0yQAjFT36lRrVJznb7uWwa
|
35
35
|
xpPFnsrtyaZW6Dty8TSG3qzmeGpmpIotA8x1VA==
|
36
36
|
-----END CERTIFICATE-----
|
37
|
-
date: 2021-02-
|
37
|
+
date: 2021-02-06 00:00:00.000000000 Z
|
38
38
|
dependencies:
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
40
|
name: request_store
|
metadata.gz.sig
CHANGED
Binary file
|