mobility 0.8.8 → 1.0.0.alpha

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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +56 -0
  5. data/Gemfile +52 -16
  6. data/Gemfile.lock +113 -52
  7. data/Guardfile +23 -1
  8. data/README.md +184 -92
  9. data/Rakefile +6 -4
  10. data/lib/mobility.rb +40 -166
  11. data/lib/mobility/active_record/translation.rb +1 -1
  12. data/lib/mobility/arel/nodes/pg_ops.rb +1 -1
  13. data/lib/mobility/backend.rb +19 -41
  14. data/lib/mobility/backends.rb +20 -0
  15. data/lib/mobility/backends/active_record.rb +4 -0
  16. data/lib/mobility/backends/active_record/column.rb +2 -0
  17. data/lib/mobility/backends/active_record/container.rb +4 -2
  18. data/lib/mobility/backends/active_record/hstore.rb +2 -0
  19. data/lib/mobility/backends/active_record/json.rb +2 -0
  20. data/lib/mobility/backends/active_record/jsonb.rb +2 -0
  21. data/lib/mobility/backends/active_record/key_value.rb +5 -3
  22. data/lib/mobility/backends/active_record/pg_hash.rb +1 -1
  23. data/lib/mobility/backends/active_record/serialized.rb +2 -0
  24. data/lib/mobility/backends/active_record/table.rb +5 -3
  25. data/lib/mobility/backends/column.rb +0 -6
  26. data/lib/mobility/backends/container.rb +2 -1
  27. data/lib/mobility/backends/hash.rb +39 -0
  28. data/lib/mobility/backends/hstore.rb +0 -1
  29. data/lib/mobility/backends/json.rb +0 -1
  30. data/lib/mobility/backends/jsonb.rb +0 -1
  31. data/lib/mobility/backends/key_value.rb +22 -14
  32. data/lib/mobility/backends/null.rb +2 -0
  33. data/lib/mobility/backends/sequel.rb +3 -0
  34. data/lib/mobility/backends/sequel/column.rb +2 -0
  35. data/lib/mobility/backends/sequel/container.rb +3 -1
  36. data/lib/mobility/backends/sequel/hstore.rb +2 -0
  37. data/lib/mobility/backends/sequel/json.rb +2 -0
  38. data/lib/mobility/backends/sequel/jsonb.rb +3 -1
  39. data/lib/mobility/backends/sequel/key_value.rb +8 -6
  40. data/lib/mobility/backends/sequel/serialized.rb +2 -0
  41. data/lib/mobility/backends/sequel/table.rb +5 -2
  42. data/lib/mobility/backends/serialized.rb +1 -3
  43. data/lib/mobility/backends/table.rb +14 -6
  44. data/lib/mobility/pluggable.rb +36 -0
  45. data/lib/mobility/plugin.rb +260 -0
  46. data/lib/mobility/plugins.rb +26 -25
  47. data/lib/mobility/plugins/active_model.rb +17 -0
  48. data/lib/mobility/plugins/active_model/cache.rb +26 -0
  49. data/lib/mobility/plugins/active_model/dirty.rb +310 -54
  50. data/lib/mobility/plugins/active_record.rb +34 -0
  51. data/lib/mobility/plugins/active_record/backend.rb +25 -0
  52. data/lib/mobility/plugins/active_record/cache.rb +28 -0
  53. data/lib/mobility/plugins/active_record/dirty.rb +72 -101
  54. data/lib/mobility/plugins/active_record/query.rb +48 -34
  55. data/lib/mobility/plugins/active_record/uniqueness_validation.rb +60 -0
  56. data/lib/mobility/plugins/attribute_methods.rb +28 -20
  57. data/lib/mobility/plugins/attributes.rb +70 -0
  58. data/lib/mobility/plugins/backend.rb +138 -0
  59. data/lib/mobility/plugins/backend_reader.rb +34 -0
  60. data/lib/mobility/plugins/cache.rb +59 -24
  61. data/lib/mobility/plugins/default.rb +22 -17
  62. data/lib/mobility/plugins/dirty.rb +12 -33
  63. data/lib/mobility/plugins/fallbacks.rb +51 -43
  64. data/lib/mobility/plugins/fallthrough_accessors.rb +26 -25
  65. data/lib/mobility/plugins/locale_accessors.rb +25 -35
  66. data/lib/mobility/plugins/presence.rb +28 -21
  67. data/lib/mobility/plugins/query.rb +8 -17
  68. data/lib/mobility/plugins/reader.rb +50 -0
  69. data/lib/mobility/plugins/sequel.rb +34 -0
  70. data/lib/mobility/plugins/sequel/backend.rb +25 -0
  71. data/lib/mobility/plugins/sequel/cache.rb +24 -0
  72. data/lib/mobility/plugins/sequel/dirty.rb +45 -32
  73. data/lib/mobility/plugins/sequel/query.rb +21 -6
  74. data/lib/mobility/plugins/writer.rb +44 -0
  75. data/lib/mobility/translations.rb +95 -0
  76. data/lib/mobility/version.rb +12 -1
  77. data/lib/rails/generators/mobility/templates/initializer.rb +95 -77
  78. metadata +51 -51
  79. metadata.gz.sig +0 -0
  80. data/lib/mobility/active_model.rb +0 -4
  81. data/lib/mobility/active_model/backend_resetter.rb +0 -26
  82. data/lib/mobility/active_record.rb +0 -23
  83. data/lib/mobility/active_record/backend_resetter.rb +0 -26
  84. data/lib/mobility/active_record/uniqueness_validator.rb +0 -60
  85. data/lib/mobility/attributes.rb +0 -324
  86. data/lib/mobility/backend/orm_delegator.rb +0 -44
  87. data/lib/mobility/backend_resetter.rb +0 -50
  88. data/lib/mobility/configuration.rb +0 -138
  89. data/lib/mobility/fallbacks.rb +0 -28
  90. data/lib/mobility/interface.rb +0 -0
  91. data/lib/mobility/loaded.rb +0 -4
  92. data/lib/mobility/plugins/active_record/attribute_methods.rb +0 -38
  93. data/lib/mobility/plugins/cache/translation_cacher.rb +0 -40
  94. data/lib/mobility/sequel.rb +0 -9
  95. data/lib/mobility/sequel/backend_resetter.rb +0 -23
  96. data/lib/mobility/translates.rb +0 -73
@@ -0,0 +1,25 @@
1
+ module Mobility
2
+ module Plugins
3
+ module ActiveRecord
4
+ module Backend
5
+ extend Plugin
6
+
7
+ requires :backend, include: :before
8
+
9
+ def load_backend(backend)
10
+ if Symbol === backend
11
+ require "mobility/backends/active_record/#{backend}"
12
+ Backends.load_backend("active_record_#{backend}".to_sym)
13
+ else
14
+ super
15
+ end
16
+ rescue LoadError => e
17
+ raise unless e.message =~ /active_record\/#{backend}/
18
+ super
19
+ end
20
+ end
21
+ end
22
+
23
+ register_plugin(:active_record_backend, ActiveRecord::Backend)
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # frozen-string-literal: true
2
+ require "mobility/plugins/active_model/cache"
3
+
4
+ module Mobility
5
+ module Plugins
6
+ module ActiveRecord
7
+ =begin
8
+
9
+ Resets cache on calls to +reload+, in addition to other AM dirty reset
10
+ methods.
11
+
12
+ =end
13
+ module Cache
14
+ extend Plugin
15
+
16
+ requires :cache, include: false
17
+
18
+ included_hook do |klass, _|
19
+ if options[:cache]
20
+ define_cache_hooks(klass, :changes_applied, :clear_changes_information, :reload)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ register_plugin(:active_record_cache, ActiveRecord::Cache)
27
+ end
28
+ end
@@ -12,7 +12,6 @@ details on usage.
12
12
  In addition to methods added by {Mobility::Plugins::ActiveModel::Dirty}, the
13
13
  AR::Dirty plugin adds support for the following persistence-specific methods
14
14
  (for a model with a translated attribute +title+):
15
- - +saved_changes+
16
15
  - +saved_change_to_title?+
17
16
  - +saved_change_to_title+
18
17
  - +title_before_last_save+
@@ -20,129 +19,101 @@ AR::Dirty plugin adds support for the following persistence-specific methods
20
19
  - +title_change_to_be_saved+
21
20
  - +title_in_database+
22
21
 
22
+ The following methods are also patched to include translated attribute changes:
23
+ - +saved_changes+
24
+ - +has_changes_to_save?+
25
+ - +changes_to_save+
26
+ - +changed_attribute_names_to_save+
27
+ - +attributes_in_database+
28
+
29
+ In addition, the following ActiveModel attribute handler methods are also
30
+ patched to work with translated attributes:
31
+ - +saved_change_to_attribute?+
32
+ - +saved_change_to_attribute+
33
+ - +attribute_before_last_save+
34
+ - +will_save_change_to_attribute?+
35
+ - +attribute_change_to_be_saved+
36
+ - +attribute_in_database+
37
+
38
+ (When using these methods, you must pass the attribute name along with its
39
+ locale suffix, so +title_en+, +title_pt_br+, etc.)
40
+
23
41
  =end
24
42
  module Dirty
25
- include ActiveModel::Dirty
26
-
27
- # Builds module which patches a few AR methods to handle changes to
28
- # translated attributes just like normal attributes.
29
- class MethodsBuilder < ActiveModel::Dirty::MethodsBuilder
30
- def initialize(*attribute_names)
31
- super
32
- @attribute_names = attribute_names
33
- define_method_overrides if ::ActiveRecord::VERSION::STRING < '5.2'
34
- define_attribute_methods if ::ActiveRecord::VERSION::STRING >= '5.1'
35
- end
43
+ extend Plugin
36
44
 
37
- # Overrides +ActiveRecord::AttributeMethods::ClassMethods#has_attribute+ (in AR 5.1) and
38
- # +ActiveModel::AttributeMethods#_read_attribute+ (in AR >= 5.2) to
39
- # ensure that fallthrough attribute methods are treated like "real"
40
- # attribute methods.
41
- #
42
- # @note Patching +has_attribute?+ is necessary in AR 5.1 due to this commit[https://github.com/rails/rails/commit/4fed08fa787a316fa51f14baca9eae11913f5050].
43
- # @param [Attributes] attributes
44
- def included(model_class)
45
- super
46
-
47
- if ::ActiveRecord::VERSION::MAJOR == 5 && ::ActiveRecord::VERSION::MINOR == 1
48
- names = @attribute_names
49
- method_name_regex = /\A(#{names.join('|')})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
50
- has_attribute = Module.new do
51
- define_method :has_attribute? do |attr_name|
52
- super(attr_name) || (String === attr_name && !!method_name_regex.match(attr_name))
53
- end
54
- end
55
- model_class.extend has_attribute
56
- elsif ::ActiveRecord::VERSION::STRING >= '5.2'
57
- model_class.include ReadAttribute
58
- end
45
+ requires :dirty, include: false
46
+ requires :active_model_dirty, include: :before
47
+
48
+ initialize_hook do
49
+ if options[:dirty]
50
+ include InstanceMethods
59
51
  end
52
+ end
60
53
 
61
- private
54
+ included_hook do |_, backend_class|
55
+ if options[:dirty]
56
+ backend_class.include BackendMethods
57
+ end
58
+ end
62
59
 
63
- # @note These method overrides are needed for AR versions < 5.2,
64
- # since AR::Dirty overrides AM::Dirty, disabling previous_changes
65
- # from being set. Here we "undo" that.
66
- def define_method_overrides
67
- changes_applied_method = ::ActiveRecord::VERSION::STRING < '5.1' ? :changes_applied : :changes_internally_applied
68
- define_method changes_applied_method do
69
- @previously_changed = changes
70
- super()
71
- end
60
+ private
72
61
 
73
- define_method :clear_changes_information do
74
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
75
- super()
76
- end
62
+ def dirty_handler_methods
63
+ HandlerMethods
64
+ end
77
65
 
78
- define_method :previous_changes do
79
- (@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new).merge(super())
80
- end
66
+ # Module which defines generic ActiveRecord::Dirty handler methods like
67
+ # +attribute_before_last_save+ that are patched to work with translated
68
+ # attributes.
69
+ HandlerMethods = ActiveModel::Dirty::HandlerMethodsBuilder.new(
70
+ Class.new do
71
+ # In earlier versions of Rails, these are needed to avoid an
72
+ # exception when including the AR Dirty module outside of an
73
+ # AR::Base class. Eventually we should be able to drop them.
74
+ def self.after_create; end
75
+ def self.after_update; end
76
+
77
+ include ::ActiveRecord::AttributeMethods::Dirty
81
78
  end
79
+ )
82
80
 
83
- # For AR >= 5.1 only
84
- def define_attribute_methods
85
- define_method :saved_changes do
86
- (@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new).merge(super())
81
+ module InstanceMethods
82
+ if ::ActiveRecord::VERSION::STRING >= '5.1' # define patterns added in 5.1
83
+ def saved_changes
84
+ super.merge(mutations_from_mobility.previous_changes)
87
85
  end
88
86
 
89
- @attribute_names.each do |name|
90
- define_method :"saved_change_to_#{name}?" do
91
- previous_changes.include?(Mobility.normalize_locale_accessor(name))
92
- end
93
-
94
- define_method :"saved_change_to_#{name}" do
95
- previous_changes[Mobility.normalize_locale_accessor(name)]
96
- end
87
+ def changes_to_save
88
+ super.merge(mutations_from_mobility.changes)
89
+ end
97
90
 
98
- define_method :"#{name}_before_last_save" do
99
- previous_changes[Mobility.normalize_locale_accessor(name)].first
100
- end
91
+ def changed_attribute_names_to_save
92
+ super + mutations_from_mobility.changed
93
+ end
101
94
 
102
- alias_method :"will_save_change_to_#{name}?", :"#{name}_changed?"
103
- alias_method :"#{name}_change_to_be_saved", :"#{name}_change"
104
- alias_method :"#{name}_in_database", :"#{name}_was"
95
+ def attributes_in_database
96
+ super.merge(mutations_from_mobility.changed_attributes)
105
97
  end
106
- end
107
98
 
108
- # Overrides _read_attribute to correctly dispatch reads on translated
109
- # attributes to their respective setters, rather than to
110
- # +@attributes+, which would otherwise return +nil+.
111
- #
112
- # For background on why this is necessary, see:
113
- # https://github.com/shioyama/mobility/issues/115
114
- module ReadAttribute
115
- # @note We first check if attributes has the key +attr+ to avoid
116
- # doing any extra work in case this is a "normal"
117
- # (non-translated) attribute.
118
- def _read_attribute(attr, *args)
119
- if @attributes.key?(attr)
120
- super
121
- else
122
- mobility_changed_attributes.include?(attr) ? __send__(attr) : super
99
+ if ::ActiveRecord::VERSION::STRING >= '6.0'
100
+ def has_changes_to_save?
101
+ super || mutations_from_mobility.changed?
123
102
  end
124
103
  end
104
+ end
125
105
 
126
- # @note This is necessary due to a performance fix in e12607
127
- # which skips setting @previously_changed if @attributes is
128
- # defined. For Mobility models using the Dirty plugin, there will
129
- # be cases where @attributes has been set, but there are *other*
130
- # changes on virtual translated attributes which need to also be
131
- # assigned. In this case, we use the presence of such changed
132
- # virtual attributes as an alternative trigger to set this variable.
133
- #
134
- # See:
135
- # - https://github.com/rails/rails/commit/e126078a0e013acfe0a397a8dad33b2c9de78732
136
- # - https://github.com/shioyama/mobility/pull/166
137
- def changes_applied
138
- if defined?(@attributes) && mobility_changed_attributes.any?
139
- @previously_changed = changes
140
- end
141
- super
106
+ def reload(*)
107
+ super.tap do
108
+ @mutations_from_mobility = nil
142
109
  end
143
110
  end
144
111
  end
112
+
113
+ BackendMethods = ActiveModel::Dirty::BackendMethods
145
114
  end
146
115
  end
116
+
117
+ register_plugin(:active_record_dirty, ActiveRecord::Dirty)
147
118
  end
148
119
  end
@@ -1,4 +1,6 @@
1
1
  # frozen-string-literal: true
2
+ require "active_record/relation"
3
+
2
4
  module Mobility
3
5
  module Plugins
4
6
  =begin
@@ -15,45 +17,47 @@ enabled for any one attribute on the model.
15
17
  =end
16
18
  module ActiveRecord
17
19
  module Query
18
- class << self
19
- def apply(attributes)
20
- attributes.model_class.class_eval do
20
+ extend Plugin
21
+
22
+ requires :query, include: false
23
+
24
+ included_hook do |klass, backend_class|
25
+ plugin = self
26
+ if options[:query]
27
+ raise MissingBackend, "backend required for Query plugin" unless backend_class
28
+
29
+ klass.class_eval do
21
30
  extend QueryMethod
22
- extend FindByMethods.new(*attributes.names)
23
- singleton_class.send :alias_method, Mobility.query_method, :__mobility_query_scope__
31
+ extend FindByMethods.new(*plugin.names)
32
+ singleton_class.send :alias_method, plugin.query_method, :__mobility_query_scope__
24
33
  end
25
- attributes.backend_class.include self
34
+ backend_class.include BackendMethods
26
35
  end
36
+ end
27
37
 
38
+ class << self
28
39
  def attribute_alias(attribute, locale = Mobility.locale)
29
40
  "__mobility_%s_%s__" % [attribute, ::Mobility.normalize_locale(locale)]
30
41
  end
31
42
  end
32
43
 
33
- # @note We use +instance_variable_get+ here to get the +AttributeSet+
34
- # rather than the hash of attributes. Getting the full hash of
35
- # attributes is a performance hit and better to avoid if unnecessary.
36
- # TODO: Improve this.
37
- def read(locale, **)
38
- if (model_attributes_defined? &&
39
- model_attributes.key?(alias_ = Query.attribute_alias(attribute, locale)))
40
- model_attributes[alias_].value
41
- else
42
- super
44
+ module BackendMethods
45
+ # @note We use +instance_variable_get+ here to get the +AttributeSet+
46
+ # rather than the hash of attributes. Getting the full hash of
47
+ # attributes is a performance hit and better to avoid if unnecessary.
48
+ # TODO: Improve this.
49
+ def read(locale, **)
50
+ if model.instance_variable_defined?(:@attributes) &&
51
+ (model_attributes = model.instance_variable_get(:@attributes)).key?(alias_ = Query.attribute_alias(attribute, locale))
52
+ model_attributes[alias_].value
53
+ else
54
+ super
55
+ end
43
56
  end
44
57
  end
45
58
 
46
- private
47
-
48
- def model_attributes_defined?
49
- model.instance_variable_defined?(:@attributes)
50
- end
51
-
52
- def model_attributes
53
- model.instance_variable_get(:@attributes)
54
- end
55
-
56
59
  module QueryMethod
60
+ # This is required for UniquenessValidator.
57
61
  def __mobility_query_scope__(locale: Mobility.locale, &block)
58
62
  if block_given?
59
63
  VirtualRow.build_query(self, locale, &block)
@@ -121,7 +125,7 @@ enabled for any one attribute on the model.
121
125
  case opts
122
126
  when Symbol, String
123
127
  @klass.mobility_attribute?(opts) ? order({ opts => :asc }, *rest) : super
124
- when Hash
128
+ when ::Hash
125
129
  i18n_keys, keys = opts.keys.partition(&@klass.method(:mobility_attribute?))
126
130
  return super if i18n_keys.empty?
127
131
 
@@ -139,8 +143,10 @@ enabled for any one attribute on the model.
139
143
 
140
144
  if ::ActiveRecord::VERSION::STRING >= '5.0'
141
145
  %w[pluck group select].each do |method_name|
142
- define_method method_name do |*attrs|
143
- return super(*attrs) unless attrs.any?(&@klass.method(:mobility_attribute?))
146
+ define_method method_name do |*attrs, &block|
147
+ return super(*attrs, &block) if (method_name == 'select' && block.present?)
148
+
149
+ return super(*attrs, &block) unless attrs.any?(&@klass.method(:mobility_attribute?))
144
150
 
145
151
  keys = attrs.dup
146
152
 
@@ -154,7 +160,7 @@ enabled for any one attribute on the model.
154
160
  @klass.mobility_backend_class(key).apply_scope(query, backend_node(key))
155
161
  end
156
162
 
157
- base.public_send(method_name, *keys)
163
+ base.public_send(method_name, *keys, &block)
158
164
  end
159
165
  end
160
166
  end
@@ -180,7 +186,7 @@ enabled for any one attribute on the model.
180
186
 
181
187
  class << self
182
188
  def build(scope, where_opts, invert: false, &block)
183
- return yield unless Hash === where_opts
189
+ return yield unless ::Hash === where_opts
184
190
 
185
191
  opts = where_opts.with_indifferent_access
186
192
  locale = opts.delete(:locale) || Mobility.locale
@@ -193,11 +199,11 @@ enabled for any one attribute on the model.
193
199
  # Builds a translated relation for a given opts hash and optional
194
200
  # invert boolean.
195
201
  def _build(scope, opts, locale, invert)
196
- return yield unless scope.respond_to?(:mobility_modules)
202
+ return yield if (mods = attribute_modules(scope)).empty?
197
203
 
198
204
  keys, predicates = opts.keys.map(&:to_s), []
199
205
 
200
- query_map = scope.mobility_modules.inject(IDENTITY) do |qm, mod|
206
+ query_map = mods.inject(IDENTITY) do |qm, mod|
201
207
  i18n_keys = mod.names & keys
202
208
  next qm if i18n_keys.empty?
203
209
 
@@ -213,7 +219,11 @@ enabled for any one attribute on the model.
213
219
  return yield if query_map == IDENTITY
214
220
 
215
221
  relation = opts.empty? ? scope : yield(opts)
216
- query_map[relation.where(predicates.inject(&:and))]
222
+ query_map[relation.where(predicates.inject(:and))]
223
+ end
224
+
225
+ def attribute_modules(scope)
226
+ scope.model.ancestors.grep(::Mobility::Translations)
217
227
  end
218
228
 
219
229
  def build_predicate(node, values)
@@ -265,6 +275,10 @@ enabled for any one attribute on the model.
265
275
 
266
276
  private_constant :QueryExtension, :FindByMethods
267
277
  end
278
+
279
+ class MissingBackend < Mobility::Error; end
268
280
  end
281
+
282
+ register_plugin(:active_record_query, ActiveRecord::Query)
269
283
  end
270
284
  end
@@ -0,0 +1,60 @@
1
+ module Mobility
2
+ module Plugins
3
+ module ActiveRecord
4
+ module UniquenessValidation
5
+ extend Plugin
6
+
7
+ requires :query, include: false
8
+
9
+ included_hook do |klass|
10
+ klass.class_eval do
11
+ unless const_defined?(:UniquenessValidator, false)
12
+ self.const_set(:UniquenessValidator, Class.new(UniquenessValidator))
13
+ end
14
+ end
15
+ end
16
+
17
+ class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
18
+ # @param [ActiveRecord::Base] record Translated model
19
+ # @param [String] attribute Name of attribute
20
+ # @param [Object] value Attribute value
21
+ def validate_each(record, attribute, value)
22
+ klass = record.class
23
+
24
+ if ([*options[:scope]] + [attribute]).any? { |name| klass.mobility_attribute?(name) }
25
+ return unless value.present?
26
+ relation = klass.unscoped.__mobility_query_scope__ do |m|
27
+ node = m.__send__(attribute)
28
+ options[:case_sensitive] == false ? node.lower.eq(value.downcase) : node.eq(value)
29
+ end
30
+ relation = relation.where.not(klass.primary_key => record.id) if record.persisted?
31
+ relation = mobility_scope_relation(record, relation)
32
+ relation = relation.merge(options[:conditions]) if options[:conditions]
33
+
34
+ if relation.exists?
35
+ error_options = options.except(:case_sensitive, :scope, :conditions)
36
+ error_options[:value] = value
37
+
38
+ record.errors.add(attribute, :taken, error_options)
39
+ end
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def mobility_scope_relation(record, relation)
48
+ [*options[:scope]].inject(relation) do |scoped_relation, scope_item|
49
+ scoped_relation.__mobility_query_scope__ do |m|
50
+ m.__send__(scope_item).eq(record.send(scope_item))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ register_plugin(:active_record_uniqueness_validation, ActiveRecord::UniquenessValidation)
59
+ end
60
+ end