mobility 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 225a8aa8a3eb8691e9a89d60fdd56509b47f7f36d22d1bb8d0c6f485f0e7cc2d
4
- data.tar.gz: be10ccdea2727e568da790950b4df0633a543094543f7fd5f9c89154d5ea803e
3
+ metadata.gz: c5cedc8fabef42f478f93a4bb4fa1e8764759855dcded4d25f397c018f1811d2
4
+ data.tar.gz: 1fb647fdbcbafa3b8a5a6d47d71500e2481556aa72674c5269ea0dc560d63ca0
5
5
  SHA512:
6
- metadata.gz: 6f350d0a8e5fcf4f1804716f2cdfc0d500abd033ca0243e0c6be663adedf3ee192d785546a1be1274acbd5738533c7b5e3b9f8c48446e22bde39c26de8217819
7
- data.tar.gz: 78fb7729118181d00e21f78c1144166916270e555bb4191c05a10cc660fb57d36b8af034cc7d42bfbd3d66d9509d1686f80554f85538b9a6d0fd488becd12966
6
+ metadata.gz: ee138cb5544fc8942dc8a6dbf7fcfd54bf3829329075950755b8fea3ba676c430b880b14366dcfc09b66651b06577957e02130c5b4f2a62d7db96d02f1a150c8
7
+ data.tar.gz: 980502d8c285bedf8e2958a24cb77c129ee39f1cfb946c44bbd83f7cc89b30009a829bfb17b515aac9e02d3f3a810b1433cab416aabec67c1daab9c6f100683d
checksums.yaml.gz.sig CHANGED
@@ -1 +1,2 @@
1
- ��2Rm;f���"ڲ�-�Dj�� �2`մ�)�J\��g�`����B
1
+ ���X�ӔJ%��g;���xh�}��ڗbm�D&$��W�! lM�j�T��?8�,�!^�iТ�{��M.��MQ�:8Ăy��@U&A:d���\km@����uŔOԃ��}ꨛ���=���lH��uT��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.1.2)
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.8)
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.44.0)
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
@@ -55,7 +55,7 @@ Installation
55
55
  Add this line to your application's Gemfile:
56
56
 
57
57
  ```ruby
58
- gem 'mobility', '~> 1.1.3'
58
+ gem 'mobility', '~> 1.2.0'
59
59
  ```
60
60
 
61
61
  ### ActiveRecord (Rails)
@@ -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 to configure the
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
- @setup_block = lambda do |*args|
140
- class_exec(*args, &setup_block)
141
- class_exec(*args, &block)
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
- Class.new(self) do
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
- # Show useful information about this backend class, if it has no name.
192
- # @return [String]
193
- def inspect
194
- name ? super : "#<#{superclass.name}>"
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, options|
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]
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
- backend = self
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
- backend.define_hash_initializer(mod, [column_name])
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::Expression
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
- backend = self
127
-
128
- setup do |attributes, options|
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
- #
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
- backend.define_column_changes(mod, attributes)
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
- backend = self
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
- backend.define_hash_initializer(mod, columns)
51
- backend.define_column_changes(mod, attributes, column_affix: options[:column_affix])
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::Expression
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
- backend = self
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
- backend.define_column_changes(mod, attributes)
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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mobility
4
+ module Plugins
5
+ module ColumnFallback
6
+ extend Plugin
7
+
8
+ default false
9
+
10
+ requires :backend, include: :before
11
+ end
12
+
13
+ register_plugin(:column_fallback, ColumnFallback)
14
+ end
15
+ end
@@ -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.to_s)
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)
@@ -7,8 +7,8 @@ module Mobility
7
7
 
8
8
  module VERSION
9
9
  MAJOR = 1
10
- MINOR = 1
11
- TINY = 3
10
+ MINOR = 2
11
+ TINY = 0
12
12
  PRE = nil
13
13
 
14
14
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
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.1.3
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-08-06 00:00:00.000000000 Z
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