mobility 1.1.3 → 1.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 +2 -1
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +3 -3
- data/README.md +1 -1
- data/lib/mobility/backend.rb +82 -26
- data/lib/mobility/backends/active_record/key_value.rb +68 -49
- data/lib/mobility/backends/sequel/container.rb +2 -4
- data/lib/mobility/backends/sequel/key_value.rb +63 -54
- data/lib/mobility/backends/sequel/pg_hash.rb +3 -5
- data/lib/mobility/backends/sequel/table.rb +3 -5
- data/lib/mobility/plugins/active_record/column_fallback.rb +139 -0
- data/lib/mobility/plugins/active_record.rb +2 -0
- data/lib/mobility/plugins/column_fallback.rb +15 -0
- data/lib/mobility/plugins/sequel/column_fallback.rb +133 -0
- data/lib/mobility/plugins/sequel/query.rb +1 -1
- data/lib/mobility/plugins/sequel.rb +2 -0
- data/lib/mobility/version.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +5 -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: c5cedc8fabef42f478f93a4bb4fa1e8764759855dcded4d25f397c018f1811d2
|
4
|
+
data.tar.gz: 1fb647fdbcbafa3b8a5a6d47d71500e2481556aa72674c5269ea0dc560d63ca0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee138cb5544fc8942dc8a6dbf7fcfd54bf3829329075950755b8fea3ba676c430b880b14366dcfc09b66651b06577957e02130c5b4f2a62d7db96d02f1a150c8
|
7
|
+
data.tar.gz: 980502d8c285bedf8e2958a24cb77c129ee39f1cfb946c44bbd83f7cc89b30009a829bfb17b515aac9e02d3f3a810b1433cab416aabec67c1daab9c6f100683d
|
checksums.yaml.gz.sig
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
��
|
1
|
+
���X�ӔJ%��g;���xh�}��ڗbm�D&$��W�!lM�j�T��?8�,�!^�iТ�{��M.��MQ�:8Ăy��@U&A:d���\km@����uŔOԃ��}ꨛ���=���lH��u�T��bHU�w��
|
2
|
+
@&v�}����'l*�"U�!��Ѣ��TQI�]V���X�Y��Gd�t����o���]T�.�.0B{Y84��v�OB?ĎM}���f�~�(
|
data/CHANGELOG.md
CHANGED
@@ -6,10 +6,24 @@
|
|
6
6
|
|
7
7
|
## 1.1
|
8
8
|
|
9
|
+
## 1.2
|
10
|
+
|
11
|
+
### 1.2.0
|
12
|
+
- Add ColumnFallback plugin
|
13
|
+
([#512](https://github.com/shioyama/mobility/pull/512))
|
14
|
+
- Fix Sequel querying on untranslated attributes in `i18n` block
|
15
|
+
([#529](https://github.com/shioyama/mobility/pull/529))
|
16
|
+
- Allow passing configured backend class as third argument to setup
|
17
|
+
([#528](https://github.com/shioyama/mobility/pull/528))
|
18
|
+
- Clearly distinguish backend classes from their configured subclasses
|
19
|
+
([#527](Clearly distinguish backend classes from their configured subclasses))
|
20
|
+
|
9
21
|
### 1.1.3
|
10
22
|
- Do not swallow keyword args on ruby 3 in fallthrough accessors
|
11
23
|
([#520](https://github.com/shioyama/mobility/pull/520)) thanks
|
12
24
|
[doits](https://github.com/doits)!
|
25
|
+
- Assign blank values in pg hash backends
|
26
|
+
([#516](https://github.com/shioyama/mobility/pull/516))
|
13
27
|
|
14
28
|
### 1.1.2
|
15
29
|
- Check whether class responds to mobility_attribute?
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
mobility (1.
|
4
|
+
mobility (1.2.0.alpha)
|
5
5
|
i18n (>= 0.6.10, < 2)
|
6
6
|
request_store (~> 1.0)
|
7
7
|
|
@@ -11,7 +11,7 @@ GEM
|
|
11
11
|
benchmark-ips (2.8.4)
|
12
12
|
byebug (11.1.3)
|
13
13
|
coderay (1.1.3)
|
14
|
-
concurrent-ruby (1.1.
|
14
|
+
concurrent-ruby (1.1.9)
|
15
15
|
database_cleaner (1.99.0)
|
16
16
|
diff-lcs (1.4.4)
|
17
17
|
ffi (1.15.0)
|
@@ -68,7 +68,7 @@ GEM
|
|
68
68
|
diff-lcs (>= 1.2.0, < 2.0)
|
69
69
|
rspec-support (~> 3.10.0)
|
70
70
|
rspec-support (3.10.2)
|
71
|
-
sequel (5.
|
71
|
+
sequel (5.48.0)
|
72
72
|
shellany (0.0.1)
|
73
73
|
thor (1.1.0)
|
74
74
|
yard (0.9.26)
|
data/README.md
CHANGED
data/lib/mobility/backend.rb
CHANGED
@@ -21,8 +21,8 @@ On top of this, a backend will normally:
|
|
21
21
|
corresponding to valid keys for configuring this backend.
|
22
22
|
- implement a +configure+ class method to apply any normalization to the
|
23
23
|
keys on the options hash included in +valid_keys+
|
24
|
-
- call the +setup+ method yielding attributes and options
|
25
|
-
model class
|
24
|
+
- call the +setup+ method yielding attributes and options (and optionally the
|
25
|
+
configured backend class) to configure the model class
|
26
26
|
|
27
27
|
@example Defining a Backend
|
28
28
|
class MyBackend
|
@@ -47,6 +47,13 @@ On top of this, a backend will normally:
|
|
47
47
|
setup do |attributes, options|
|
48
48
|
# Do something with attributes and options in context of model class.
|
49
49
|
end
|
50
|
+
|
51
|
+
# The block can optionally take the configured backend class as its third
|
52
|
+
# argument:
|
53
|
+
#
|
54
|
+
# setup do |attributes, options, backend_class|
|
55
|
+
# ...
|
56
|
+
# end
|
50
57
|
end
|
51
58
|
|
52
59
|
@see Mobility::Translations
|
@@ -117,7 +124,6 @@ On top of this, a backend will normally:
|
|
117
124
|
# Extend included class with +setup+ method and other class methods
|
118
125
|
def self.included(base)
|
119
126
|
base.extend ClassMethods
|
120
|
-
base.singleton_class.attr_reader :options, :model_class
|
121
127
|
end
|
122
128
|
|
123
129
|
# Defines setup hooks for backend to customize model class.
|
@@ -136,9 +142,11 @@ On top of this, a backend will normally:
|
|
136
142
|
def setup &block
|
137
143
|
if @setup_block
|
138
144
|
setup_block = @setup_block
|
139
|
-
|
140
|
-
|
141
|
-
|
145
|
+
exec_setup_block = method(:exec_setup_block)
|
146
|
+
@setup_block = lambda do |attributes, options, backend_class|
|
147
|
+
[setup_block, block].each do |blk|
|
148
|
+
exec_setup_block.call(self, attributes, options, backend_class, &blk)
|
149
|
+
end
|
142
150
|
end
|
143
151
|
else
|
144
152
|
@setup_block = block
|
@@ -147,17 +155,6 @@ On top of this, a backend will normally:
|
|
147
155
|
|
148
156
|
def inherited(subclass)
|
149
157
|
subclass.instance_variable_set(:@setup_block, @setup_block)
|
150
|
-
subclass.instance_variable_set(:@options, @options)
|
151
|
-
subclass.instance_variable_set(:@model_class, @model_class)
|
152
|
-
end
|
153
|
-
|
154
|
-
# Call setup block on a class with attributes and options.
|
155
|
-
# @param model_class Class to be setup-ed
|
156
|
-
# @param [Array<String>] attribute_names
|
157
|
-
# @param [Hash] options
|
158
|
-
def setup_model(model_class, attribute_names)
|
159
|
-
return unless setup_block = @setup_block
|
160
|
-
model_class.class_exec(attribute_names, options, &setup_block)
|
161
158
|
end
|
162
159
|
|
163
160
|
# Build a subclass of this backend class for a given set of options
|
@@ -167,11 +164,7 @@ On top of this, a backend will normally:
|
|
167
164
|
# @param [Hash] options
|
168
165
|
# @return [Class] backend subclass
|
169
166
|
def build_subclass(model_class, options)
|
170
|
-
|
171
|
-
@model_class = model_class
|
172
|
-
configure(options) if respond_to?(:configure)
|
173
|
-
@options = options.freeze
|
174
|
-
end
|
167
|
+
ConfiguredBackend.build(self, model_class, options)
|
175
168
|
end
|
176
169
|
|
177
170
|
# Create instance and class methods to access value on options hash
|
@@ -188,10 +181,30 @@ On top of this, a backend will normally:
|
|
188
181
|
EOM
|
189
182
|
end
|
190
183
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
184
|
+
def options
|
185
|
+
raise_unconfigured!(:options)
|
186
|
+
end
|
187
|
+
|
188
|
+
def model_class
|
189
|
+
raise_unconfigured!(:model_class)
|
190
|
+
end
|
191
|
+
|
192
|
+
def setup_model(_model_class, _attributes)
|
193
|
+
raise_unconfigured!(:setup_model)
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def raise_unconfigured!(method_name)
|
199
|
+
raise UnconfiguredError, "You are calling #{method_name} on an unconfigured backend class."
|
200
|
+
end
|
201
|
+
|
202
|
+
def exec_setup_block(model_class, *args, &block)
|
203
|
+
if block.arity == 3
|
204
|
+
model_class.class_exec(*args[0..2], &block)
|
205
|
+
else
|
206
|
+
model_class.class_exec(*args[0..1], &block)
|
207
|
+
end
|
195
208
|
end
|
196
209
|
end
|
197
210
|
|
@@ -204,5 +217,48 @@ On top of this, a backend will normally:
|
|
204
217
|
backend.write(locale, value, options)
|
205
218
|
end
|
206
219
|
end
|
220
|
+
|
221
|
+
class ConfiguredError < StandardError; end
|
222
|
+
class UnconfiguredError < StandardError; end
|
223
|
+
=begin
|
224
|
+
|
225
|
+
Module included in configured backend classes, which in addition to methods on
|
226
|
+
the parent backend class also have a +model_class+ and set of +options+.
|
227
|
+
|
228
|
+
=end
|
229
|
+
module ConfiguredBackend
|
230
|
+
def self.build(backend_class, model_class, options)
|
231
|
+
Class.new(backend_class) do
|
232
|
+
extend ConfiguredBackend
|
233
|
+
|
234
|
+
@model_class = model_class
|
235
|
+
configure(options) if respond_to?(:configure)
|
236
|
+
@options = options.freeze
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def self.extended(klass)
|
241
|
+
klass.singleton_class.attr_reader :options, :model_class
|
242
|
+
end
|
243
|
+
|
244
|
+
# Call setup block on a class with attributes and options.
|
245
|
+
# @param model_class Class to be setup-ed
|
246
|
+
# @param [Array<String>] attribute_names
|
247
|
+
# @param [Hash] options
|
248
|
+
def setup_model(model_class, attribute_names)
|
249
|
+
return unless setup_block = @setup_block
|
250
|
+
exec_setup_block(model_class, attribute_names, options, self, &setup_block)
|
251
|
+
end
|
252
|
+
|
253
|
+
def inherited(_)
|
254
|
+
raise ConfiguredError, "Configured backends cannot be subclassed."
|
255
|
+
end
|
256
|
+
|
257
|
+
# Show subclassed backend class name, if it has one.
|
258
|
+
# @return [String]
|
259
|
+
def inspect
|
260
|
+
(name = superclass.name) ? "#<#{name}>" : super
|
261
|
+
end
|
262
|
+
end
|
207
263
|
end
|
208
264
|
end
|
@@ -65,6 +65,69 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
# Called from setup block. Can be overridden to customize behaviour.
|
69
|
+
def define_has_many_association(klass, attributes)
|
70
|
+
# Track all attributes for this association, so that we can limit the scope
|
71
|
+
# of keys for the association to only these attributes. We need to track the
|
72
|
+
# attributes assigned to the association in case this setup code is called
|
73
|
+
# multiple times, so we don't "forget" earlier attributes.
|
74
|
+
#
|
75
|
+
attrs_method_name = :"__#{association_name}_attributes"
|
76
|
+
association_attributes = (klass.instance_variable_get(:"@#{attrs_method_name}") || []) + attributes
|
77
|
+
klass.instance_variable_set(:"@#{attrs_method_name}", association_attributes)
|
78
|
+
|
79
|
+
b = self
|
80
|
+
|
81
|
+
klass.has_many association_name, ->{ where b.key_column => association_attributes },
|
82
|
+
as: belongs_to,
|
83
|
+
class_name: class_name.name,
|
84
|
+
inverse_of: belongs_to,
|
85
|
+
autosave: true
|
86
|
+
end
|
87
|
+
|
88
|
+
# Called from setup block. Can be overridden to customize behaviour.
|
89
|
+
def define_initialize_dup(klass)
|
90
|
+
b = self
|
91
|
+
module_name = "MobilityArKeyValue#{association_name.to_s.camelcase}"
|
92
|
+
unless const_defined?(module_name)
|
93
|
+
callback_methods = Module.new do
|
94
|
+
define_method :initialize_dup do |source|
|
95
|
+
super(source)
|
96
|
+
self.send("#{b.association_name}=", source.send(b.association_name).map(&:dup))
|
97
|
+
# Set inverse on associations
|
98
|
+
send(b.association_name).each do |translation|
|
99
|
+
translation.send(:"#{b.belongs_to}=", self)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
klass.include const_set(module_name, callback_methods)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Called from setup block. Can be overridden to customize behaviour.
|
108
|
+
def define_before_save_callback(klass)
|
109
|
+
b = self
|
110
|
+
klass.before_save do
|
111
|
+
send(b.association_name).select { |t| t.send(b.value_column).blank? }.each do |translation|
|
112
|
+
send(b.association_name).destroy(translation)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Called from setup block. Can be overridden to customize behaviour.
|
118
|
+
def define_after_destroy_callback(klass)
|
119
|
+
# Ensure we only call after destroy hook once per translations class
|
120
|
+
b = self
|
121
|
+
translation_classes = [class_name, *Mobility::Backends::ActiveRecord::KeyValue::Translation.descendants].uniq
|
122
|
+
klass.after_destroy do
|
123
|
+
@mobility_after_destroy_translation_classes = [] unless defined?(@mobility_after_destroy_translation_classes)
|
124
|
+
(translation_classes - @mobility_after_destroy_translation_classes).each do |translation_class|
|
125
|
+
translation_class.where(b.belongs_to => self).destroy_all
|
126
|
+
end
|
127
|
+
@mobility_after_destroy_translation_classes += translation_classes
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
68
131
|
private
|
69
132
|
|
70
133
|
def join_translations(relation, key, locale, join_type)
|
@@ -149,55 +212,11 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
|
|
149
212
|
end
|
150
213
|
end
|
151
214
|
|
152
|
-
setup do |attributes,
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
belongs_to = options[:belongs_to]
|
158
|
-
|
159
|
-
# Track all attributes for this association, so that we can limit the scope
|
160
|
-
# of keys for the association to only these attributes. We need to track the
|
161
|
-
# attributes assigned to the association in case this setup code is called
|
162
|
-
# multiple times, so we don't "forget" earlier attributes.
|
163
|
-
#
|
164
|
-
attrs_method_name = :"__#{association_name}_attributes"
|
165
|
-
association_attributes = (instance_variable_get(:"@#{attrs_method_name}") || []) + attributes
|
166
|
-
instance_variable_set(:"@#{attrs_method_name}", association_attributes)
|
167
|
-
|
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,
|
172
|
-
autosave: true
|
173
|
-
before_save do
|
174
|
-
send(association_name).select { |t| t.send(value_column).blank? }.each do |translation|
|
175
|
-
send(association_name).destroy(translation)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
module_name = "MobilityArKeyValue#{association_name.to_s.camelcase}"
|
180
|
-
unless const_defined?(module_name)
|
181
|
-
callback_methods = Module.new do
|
182
|
-
define_method :initialize_dup do |source|
|
183
|
-
super(source)
|
184
|
-
self.send("#{association_name}=", source.send(association_name).map(&:dup))
|
185
|
-
# Set inverse on associations
|
186
|
-
send(association_name).each do |translation|
|
187
|
-
translation.send(:"#{belongs_to}=", self)
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
include const_set(module_name, callback_methods)
|
192
|
-
end
|
193
|
-
|
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
|
215
|
+
setup do |attributes, _options, backend_class|
|
216
|
+
backend_class.define_has_many_association(self, attributes)
|
217
|
+
backend_class.define_initialize_dup(self)
|
218
|
+
backend_class.define_before_save_callback(self)
|
219
|
+
backend_class.define_after_destroy_callback(self)
|
201
220
|
end
|
202
221
|
|
203
222
|
# Returns translation for a given locale, or builds one if none is present.
|
@@ -57,9 +57,7 @@ Implements the {Mobility::Backends::Container} backend for Sequel models.
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
setup do |attributes, options|
|
60
|
+
setup do |attributes, options, backend_class|
|
63
61
|
column_name = options[:column_name]
|
64
62
|
mod = Module.new do
|
65
63
|
define_method :before_validation do
|
@@ -71,7 +69,7 @@ Implements the {Mobility::Backends::Container} backend for Sequel models.
|
|
71
69
|
end
|
72
70
|
end
|
73
71
|
include mod
|
74
|
-
|
72
|
+
backend_class.define_hash_initializer(mod, [column_name])
|
75
73
|
|
76
74
|
plugin :defaults_setter
|
77
75
|
attributes.each { |attribute| default_values[attribute.to_sym] = {} }
|
@@ -51,6 +51,63 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
# Called from setup block. Can be overridden to customize behaviour.
|
55
|
+
def define_one_to_many_association(klass, attributes)
|
56
|
+
belongs_to_id = :"#{belongs_to}_id"
|
57
|
+
belongs_to_type = :"#{belongs_to}_type"
|
58
|
+
|
59
|
+
# Track all attributes for this association, so that we can limit the scope
|
60
|
+
# of keys for the association to only these attributes. We need to track the
|
61
|
+
# attributes assigned to the association in case this setup code is called
|
62
|
+
# multiple times, so we don't "forget" earlier attributes.
|
63
|
+
#
|
64
|
+
attrs_method_name = :"#{association_name}_attributes"
|
65
|
+
association_attributes = (klass.instance_variable_get(:"@#{attrs_method_name}") || []) + attributes
|
66
|
+
klass.instance_variable_set(:"@#{attrs_method_name}", association_attributes)
|
67
|
+
|
68
|
+
klass.one_to_many association_name,
|
69
|
+
reciprocal: belongs_to,
|
70
|
+
key: belongs_to_id,
|
71
|
+
reciprocal_type: :one_to_many,
|
72
|
+
conditions: { belongs_to_type => klass.to_s, key_column => association_attributes },
|
73
|
+
adder: proc { |translation| translation.update(belongs_to_id => pk, belongs_to_type => self.class.to_s) },
|
74
|
+
remover: proc { |translation| translation.update(belongs_to_id => nil, belongs_to_type => nil) },
|
75
|
+
clearer: proc { send_(:"#{association_name}_dataset").update(belongs_to_id => nil, belongs_to_type => nil) },
|
76
|
+
class: class_name
|
77
|
+
end
|
78
|
+
|
79
|
+
# Called from setup block. Can be overridden to customize behaviour.
|
80
|
+
def define_save_callbacks(klass, attributes)
|
81
|
+
b = self
|
82
|
+
callback_methods = Module.new do
|
83
|
+
define_method :before_save do
|
84
|
+
super()
|
85
|
+
send(b.association_name).select { |t| attributes.include?(t.__send__(b.key_column)) && Util.blank?(t.__send__(b.value_column)) }.each(&:destroy)
|
86
|
+
end
|
87
|
+
define_method :after_save do
|
88
|
+
super()
|
89
|
+
attributes.each { |attribute| mobility_backends[attribute].save_translations }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
klass.include callback_methods
|
93
|
+
end
|
94
|
+
|
95
|
+
# Called from setup block. Can be overridden to customize behaviour.
|
96
|
+
def define_after_destroy_callback(klass)
|
97
|
+
# Clean up *all* leftover translations of this model, only once.
|
98
|
+
b = self
|
99
|
+
translation_classes = [class_name, *Mobility::Backends::Sequel::KeyValue::Translation.descendants].uniq
|
100
|
+
klass.define_method :after_destroy do
|
101
|
+
super()
|
102
|
+
|
103
|
+
@mobility_after_destroy_translation_classes = [] unless defined?(@mobility_after_destroy_translation_classes)
|
104
|
+
(translation_classes - @mobility_after_destroy_translation_classes).each do |translation_class|
|
105
|
+
translation_class.where(:"#{b.belongs_to}_id" => id, :"#{b.belongs_to}_type" => self.class.name).destroy
|
106
|
+
end
|
107
|
+
@mobility_after_destroy_translation_classes += translation_classes
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
54
111
|
private
|
55
112
|
|
56
113
|
def join_translations(dataset, attr, locale, join_type)
|
@@ -74,7 +131,7 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
74
131
|
visit_sql_identifier(predicate, locale)
|
75
132
|
when ::Sequel::SQL::BooleanExpression
|
76
133
|
visit_boolean(predicate, locale)
|
77
|
-
when ::Sequel::SQL::
|
134
|
+
when ::Sequel::SQL::ComplexExpression
|
78
135
|
visit(predicate.args, locale)
|
79
136
|
else
|
80
137
|
{}
|
@@ -123,61 +180,13 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
|
|
123
180
|
end
|
124
181
|
end
|
125
182
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
#
|
142
|
-
attrs_method_name = :"#{association_name}_attributes"
|
143
|
-
association_attributes = (instance_variable_get(:"@#{attrs_method_name}") || []) + attributes
|
144
|
-
instance_variable_set(:"@#{attrs_method_name}", association_attributes)
|
145
|
-
|
146
|
-
one_to_many association_name,
|
147
|
-
reciprocal: belongs_to,
|
148
|
-
key: belongs_to_id,
|
149
|
-
reciprocal_type: :one_to_many,
|
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
|
155
|
-
|
156
|
-
callback_methods = Module.new do
|
157
|
-
define_method :before_save do
|
158
|
-
super()
|
159
|
-
send(association_name).select { |t| attributes.include?(t.__send__(key_column)) && Util.blank?(t.__send__(value_column)) }.each(&:destroy)
|
160
|
-
end
|
161
|
-
define_method :after_save do
|
162
|
-
super()
|
163
|
-
attributes.each { |attribute| mobility_backends[attribute].save_translations }
|
164
|
-
end
|
165
|
-
end
|
166
|
-
include callback_methods
|
167
|
-
|
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()
|
183
|
+
setup do |attributes, _options, backend_class|
|
184
|
+
backend_class.define_one_to_many_association(self, attributes)
|
185
|
+
backend_class.define_save_callbacks(self, attributes)
|
186
|
+
backend_class.define_after_destroy_callback(self)
|
172
187
|
|
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
|
179
188
|
include(mod = Module.new)
|
180
|
-
|
189
|
+
backend_class.define_column_changes(mod, attributes)
|
181
190
|
end
|
182
191
|
|
183
192
|
# Returns translation for a given locale, or initializes one if none is present.
|
@@ -33,9 +33,7 @@ jsonb).
|
|
33
33
|
model[column_name.to_sym]
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
setup do |attributes, options|
|
36
|
+
setup do |attributes, options, backend_class|
|
39
37
|
columns = attributes.map { |attribute| (options[:column_affix] % attribute).to_sym }
|
40
38
|
|
41
39
|
mod = Module.new do
|
@@ -47,8 +45,8 @@ jsonb).
|
|
47
45
|
end
|
48
46
|
end
|
49
47
|
include mod
|
50
|
-
|
51
|
-
|
48
|
+
backend_class.define_hash_initializer(mod, columns)
|
49
|
+
backend_class.define_column_changes(mod, attributes, column_affix: options[:column_affix])
|
52
50
|
|
53
51
|
plugin :defaults_setter
|
54
52
|
columns.each { |column| default_values[column] = {} }
|
@@ -84,7 +84,7 @@ Implements the {Mobility::Backends::Table} backend for Sequel models.
|
|
84
84
|
visit_sql_identifier(predicate, locale)
|
85
85
|
when ::Sequel::SQL::BooleanExpression
|
86
86
|
visit_boolean(predicate, locale)
|
87
|
-
when ::Sequel::SQL::
|
87
|
+
when ::Sequel::SQL::ComplexExpression
|
88
88
|
visit(predicate.args, locale)
|
89
89
|
else
|
90
90
|
nil
|
@@ -116,9 +116,7 @@ Implements the {Mobility::Backends::Table} backend for Sequel models.
|
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
|
-
|
120
|
-
|
121
|
-
setup do |attributes, options|
|
119
|
+
setup do |attributes, options, backend_class|
|
122
120
|
association_name = options[:association_name]
|
123
121
|
subclass_name = options[:subclass_name]
|
124
122
|
|
@@ -155,7 +153,7 @@ Implements the {Mobility::Backends::Table} backend for Sequel models.
|
|
155
153
|
include callback_methods
|
156
154
|
|
157
155
|
include(mod = Module.new)
|
158
|
-
|
156
|
+
backend_class.define_column_changes(mod, attributes)
|
159
157
|
end
|
160
158
|
|
161
159
|
def translation_for(locale, **)
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Mobility
|
4
|
+
module Plugins
|
5
|
+
=begin
|
6
|
+
|
7
|
+
Plugin to use an original column for a given locale, and otherwise use the backend.
|
8
|
+
|
9
|
+
=end
|
10
|
+
module ActiveRecord
|
11
|
+
module ColumnFallback
|
12
|
+
extend Plugin
|
13
|
+
|
14
|
+
requires :column_fallback, include: false
|
15
|
+
|
16
|
+
included_hook do |_, backend_class|
|
17
|
+
case (column_fallback = options[:column_fallback])
|
18
|
+
when TrueClass
|
19
|
+
backend_class.include I18nDefaultLocaleBackend
|
20
|
+
when Array, Proc
|
21
|
+
backend_class.include BackendModule.new(column_fallback)
|
22
|
+
else
|
23
|
+
raise ArgumentError, "column_fallback value must be a boolean, an array of locales or a proc"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module I18nDefaultLocaleBackend
|
28
|
+
def read(locale, **)
|
29
|
+
locale == I18n.default_locale ? model.read_attribute(attribute) : super
|
30
|
+
end
|
31
|
+
|
32
|
+
def write(locale, value, **)
|
33
|
+
locale == I18n.default_locale ? model.send(:write_attribute, attribute, value) : super
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.included(base)
|
37
|
+
base.extend(ClassMethods)
|
38
|
+
end
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
def build_node(attr, locale)
|
42
|
+
if locale == I18n.default_locale
|
43
|
+
model_class.arel_table[attr]
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class BackendModule < Module
|
52
|
+
def initialize(column_fallback)
|
53
|
+
case (@column_fallback = column_fallback)
|
54
|
+
when Array
|
55
|
+
define_array_accessors
|
56
|
+
when Proc
|
57
|
+
define_proc_accessors
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def included(base)
|
62
|
+
base.extend(ClassMethods.new(@column_fallback))
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def define_array_accessors
|
68
|
+
column_fallback = @column_fallback
|
69
|
+
|
70
|
+
module_eval <<-EOM, __FILE__, __LINE__ + 1
|
71
|
+
def read(locale, **)
|
72
|
+
if #{column_fallback}.include?(locale)
|
73
|
+
model.read_attribute(attribute)
|
74
|
+
else
|
75
|
+
super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def write(locale, value, **)
|
80
|
+
if #{column_fallback}.include?(locale)
|
81
|
+
model.send(:write_attribute, attribute, value)
|
82
|
+
else
|
83
|
+
super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
EOM
|
87
|
+
end
|
88
|
+
|
89
|
+
def define_proc_accessors
|
90
|
+
column_fallback = @column_fallback
|
91
|
+
|
92
|
+
define_method :read do |locale, **options|
|
93
|
+
if column_fallback.call(locale)
|
94
|
+
model.read_attribute(attribute)
|
95
|
+
else
|
96
|
+
super(locale, **options)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
define_method :write do |locale, value, **options|
|
101
|
+
if column_fallback.call(locale)
|
102
|
+
model.send(:write_attribute, attribute, value)
|
103
|
+
else
|
104
|
+
super(locale, value, **options)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class ClassMethods < Module
|
110
|
+
def initialize(column_fallback)
|
111
|
+
case column_fallback
|
112
|
+
when Array
|
113
|
+
module_eval <<-EOM, __FILE__, __LINE__ + 1
|
114
|
+
def build_node(attr, locale)
|
115
|
+
if #{column_fallback}.include?(locale)
|
116
|
+
model_class.arel_table[attr]
|
117
|
+
else
|
118
|
+
super
|
119
|
+
end
|
120
|
+
end
|
121
|
+
EOM
|
122
|
+
when Proc
|
123
|
+
define_method(:build_node) do |attr, locale|
|
124
|
+
if column_fallback.call(locale)
|
125
|
+
model_class.arel_table[attr]
|
126
|
+
else
|
127
|
+
super(attr, locale)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
register_plugin(:active_record_column_fallback, ActiveRecord::ColumnFallback)
|
138
|
+
end
|
139
|
+
end
|
@@ -4,6 +4,7 @@ require_relative "./active_record/dirty"
|
|
4
4
|
require_relative "./active_record/cache"
|
5
5
|
require_relative "./active_record/query"
|
6
6
|
require_relative "./active_record/uniqueness_validation"
|
7
|
+
require_relative "./active_record/column_fallback"
|
7
8
|
|
8
9
|
module Mobility
|
9
10
|
=begin
|
@@ -24,6 +25,7 @@ dirty for active_record_dirty) is also enabled.
|
|
24
25
|
requires :active_record_cache
|
25
26
|
requires :active_record_query
|
26
27
|
requires :active_record_uniqueness_validation
|
28
|
+
requires :active_record_column_fallback
|
27
29
|
|
28
30
|
|
29
31
|
included_hook do |klass|
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Mobility
|
4
|
+
=begin
|
5
|
+
|
6
|
+
Plugin to use an original column for a given locale, and otherwise use the backend.
|
7
|
+
|
8
|
+
=end
|
9
|
+
module Plugins
|
10
|
+
module Sequel
|
11
|
+
module ColumnFallback
|
12
|
+
extend Plugin
|
13
|
+
|
14
|
+
requires :column_fallback, include: false
|
15
|
+
|
16
|
+
included_hook do |_, backend_class|
|
17
|
+
case (column_fallback = options[:column_fallback])
|
18
|
+
when TrueClass
|
19
|
+
backend_class.include I18nDefaultLocaleBackend
|
20
|
+
when Array, Proc
|
21
|
+
backend_class.include BackendModule.new(column_fallback)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module I18nDefaultLocaleBackend
|
26
|
+
def read(locale, **)
|
27
|
+
locale == I18n.default_locale ? model[attribute.to_sym] : super
|
28
|
+
end
|
29
|
+
|
30
|
+
def write(locale, value, **)
|
31
|
+
if locale == I18n.default_locale
|
32
|
+
model[attribute.to_sym] = value
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.included(base)
|
39
|
+
base.extend(ClassMethods)
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
def build_op(attr, locale)
|
44
|
+
if locale == I18n.default_locale
|
45
|
+
::Sequel::SQL::QualifiedIdentifier.new(model_class.table_name, attr.to_sym)
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class BackendModule < Module
|
54
|
+
def initialize(column_fallback)
|
55
|
+
case (@column_fallback = column_fallback)
|
56
|
+
when Array
|
57
|
+
define_array_accessors
|
58
|
+
when Proc
|
59
|
+
define_proc_accessors
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def included(base)
|
64
|
+
base.extend(ClassMethods.new(@column_fallback))
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def define_array_accessors
|
70
|
+
column_fallback = @column_fallback
|
71
|
+
|
72
|
+
module_eval <<-EOM, __FILE__, __LINE__ + 1
|
73
|
+
def read(locale, **)
|
74
|
+
#{column_fallback}.include?(locale) ? model[attribute.to_sym] : super
|
75
|
+
end
|
76
|
+
|
77
|
+
def write(locale, value, **)
|
78
|
+
if #{column_fallback}.include?(locale)
|
79
|
+
model[attribute.to_sym] = value
|
80
|
+
else
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
EOM
|
85
|
+
end
|
86
|
+
|
87
|
+
def define_proc_accessors
|
88
|
+
column_fallback = @column_fallback
|
89
|
+
|
90
|
+
define_method :read do |locale, **options|
|
91
|
+
column_fallback.call(locale) ? model[attribute.to_sym] : super(locale, **options)
|
92
|
+
end
|
93
|
+
|
94
|
+
define_method :write do |locale, value, **options|
|
95
|
+
if column_fallback.call(locale)
|
96
|
+
model[attribute.to_sym] = value
|
97
|
+
else
|
98
|
+
super(locale, value, **options)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class ClassMethods < Module
|
104
|
+
def initialize(column_fallback)
|
105
|
+
case column_fallback
|
106
|
+
when Array
|
107
|
+
module_eval <<-EOM, __FILE__, __LINE__ + 1
|
108
|
+
def build_op(attr, locale)
|
109
|
+
if #{column_fallback}.include?(locale)
|
110
|
+
::Sequel::SQL::QualifiedIdentifier.new(model_class.table_name, attr.to_sym)
|
111
|
+
else
|
112
|
+
super
|
113
|
+
end
|
114
|
+
end
|
115
|
+
EOM
|
116
|
+
when Proc
|
117
|
+
define_method(:build_op) do |attr, locale|
|
118
|
+
if column_fallback.call(locale)
|
119
|
+
::Sequel::SQL::QualifiedIdentifier.new(model_class.table_name, attr.to_sym)
|
120
|
+
else
|
121
|
+
super(attr, locale)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
register_plugin(:sequel_column_fallback, Sequel::ColumnFallback)
|
132
|
+
end
|
133
|
+
end
|
@@ -61,7 +61,7 @@ ActiveRecord query plugin.
|
|
61
61
|
locale = args[0] || @global_locale
|
62
62
|
@locales |= [locale]
|
63
63
|
@model_class.mobility_backend_class(m).build_op(m.to_s, locale)
|
64
|
-
elsif @model_class.columns.include?(m
|
64
|
+
elsif @model_class.columns.include?(m)
|
65
65
|
::Sequel::SQL::QualifiedIdentifier.new(@model_class.table_name, m)
|
66
66
|
else
|
67
67
|
super
|
@@ -9,6 +9,7 @@ require_relative "./sequel/backend"
|
|
9
9
|
require_relative "./sequel/dirty"
|
10
10
|
require_relative "./sequel/cache"
|
11
11
|
require_relative "./sequel/query"
|
12
|
+
require_relative "./sequel/column_fallback"
|
12
13
|
|
13
14
|
module Mobility
|
14
15
|
module Plugins
|
@@ -26,6 +27,7 @@ for sequel_dirty) is also enabled.
|
|
26
27
|
requires :sequel_dirty
|
27
28
|
requires :sequel_cache
|
28
29
|
requires :sequel_query
|
30
|
+
requires :sequel_column_fallback
|
29
31
|
|
30
32
|
included_hook do |klass|
|
31
33
|
unless sequel_class?(klass)
|
data/lib/mobility/version.rb
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
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.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Salzberg
|
@@ -34,7 +34,7 @@ cert_chain:
|
|
34
34
|
eBMcZq0d1tbtv1M1UXND9mOfhLZ31YvoSTPkrJiRpljUNgD0+ugelnr1/5X/9k8y
|
35
35
|
J9QOd3C5jpSShf/HMvpJnFuSYFm19cH9GrHjvw==
|
36
36
|
-----END CERTIFICATE-----
|
37
|
-
date: 2021-
|
37
|
+
date: 2021-09-26 00:00:00.000000000 Z
|
38
38
|
dependencies:
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
40
|
name: request_store
|
@@ -198,6 +198,7 @@ files:
|
|
198
198
|
- lib/mobility/plugins/active_record.rb
|
199
199
|
- lib/mobility/plugins/active_record/backend.rb
|
200
200
|
- lib/mobility/plugins/active_record/cache.rb
|
201
|
+
- lib/mobility/plugins/active_record/column_fallback.rb
|
201
202
|
- lib/mobility/plugins/active_record/dirty.rb
|
202
203
|
- lib/mobility/plugins/active_record/query.rb
|
203
204
|
- lib/mobility/plugins/active_record/uniqueness_validation.rb
|
@@ -209,6 +210,7 @@ files:
|
|
209
210
|
- lib/mobility/plugins/backend.rb
|
210
211
|
- lib/mobility/plugins/backend_reader.rb
|
211
212
|
- lib/mobility/plugins/cache.rb
|
213
|
+
- lib/mobility/plugins/column_fallback.rb
|
212
214
|
- lib/mobility/plugins/default.rb
|
213
215
|
- lib/mobility/plugins/dirty.rb
|
214
216
|
- lib/mobility/plugins/fallbacks.rb
|
@@ -220,6 +222,7 @@ files:
|
|
220
222
|
- lib/mobility/plugins/sequel.rb
|
221
223
|
- lib/mobility/plugins/sequel/backend.rb
|
222
224
|
- lib/mobility/plugins/sequel/cache.rb
|
225
|
+
- lib/mobility/plugins/sequel/column_fallback.rb
|
223
226
|
- lib/mobility/plugins/sequel/dirty.rb
|
224
227
|
- lib/mobility/plugins/sequel/query.rb
|
225
228
|
- lib/mobility/plugins/writer.rb
|
metadata.gz.sig
CHANGED
Binary file
|