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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +56 -0
- data/Gemfile +52 -16
- data/Gemfile.lock +113 -52
- data/Guardfile +23 -1
- data/README.md +184 -92
- data/Rakefile +6 -4
- data/lib/mobility.rb +40 -166
- data/lib/mobility/active_record/translation.rb +1 -1
- data/lib/mobility/arel/nodes/pg_ops.rb +1 -1
- data/lib/mobility/backend.rb +19 -41
- data/lib/mobility/backends.rb +20 -0
- data/lib/mobility/backends/active_record.rb +4 -0
- data/lib/mobility/backends/active_record/column.rb +2 -0
- data/lib/mobility/backends/active_record/container.rb +4 -2
- data/lib/mobility/backends/active_record/hstore.rb +2 -0
- data/lib/mobility/backends/active_record/json.rb +2 -0
- data/lib/mobility/backends/active_record/jsonb.rb +2 -0
- data/lib/mobility/backends/active_record/key_value.rb +5 -3
- data/lib/mobility/backends/active_record/pg_hash.rb +1 -1
- data/lib/mobility/backends/active_record/serialized.rb +2 -0
- data/lib/mobility/backends/active_record/table.rb +5 -3
- data/lib/mobility/backends/column.rb +0 -6
- data/lib/mobility/backends/container.rb +2 -1
- data/lib/mobility/backends/hash.rb +39 -0
- data/lib/mobility/backends/hstore.rb +0 -1
- data/lib/mobility/backends/json.rb +0 -1
- data/lib/mobility/backends/jsonb.rb +0 -1
- data/lib/mobility/backends/key_value.rb +22 -14
- data/lib/mobility/backends/null.rb +2 -0
- data/lib/mobility/backends/sequel.rb +3 -0
- data/lib/mobility/backends/sequel/column.rb +2 -0
- data/lib/mobility/backends/sequel/container.rb +3 -1
- data/lib/mobility/backends/sequel/hstore.rb +2 -0
- data/lib/mobility/backends/sequel/json.rb +2 -0
- data/lib/mobility/backends/sequel/jsonb.rb +3 -1
- data/lib/mobility/backends/sequel/key_value.rb +8 -6
- data/lib/mobility/backends/sequel/serialized.rb +2 -0
- data/lib/mobility/backends/sequel/table.rb +5 -2
- data/lib/mobility/backends/serialized.rb +1 -3
- data/lib/mobility/backends/table.rb +14 -6
- data/lib/mobility/pluggable.rb +36 -0
- data/lib/mobility/plugin.rb +260 -0
- data/lib/mobility/plugins.rb +26 -25
- data/lib/mobility/plugins/active_model.rb +17 -0
- data/lib/mobility/plugins/active_model/cache.rb +26 -0
- data/lib/mobility/plugins/active_model/dirty.rb +310 -54
- data/lib/mobility/plugins/active_record.rb +34 -0
- data/lib/mobility/plugins/active_record/backend.rb +25 -0
- data/lib/mobility/plugins/active_record/cache.rb +28 -0
- data/lib/mobility/plugins/active_record/dirty.rb +72 -101
- data/lib/mobility/plugins/active_record/query.rb +48 -34
- data/lib/mobility/plugins/active_record/uniqueness_validation.rb +60 -0
- data/lib/mobility/plugins/attribute_methods.rb +28 -20
- data/lib/mobility/plugins/attributes.rb +70 -0
- data/lib/mobility/plugins/backend.rb +138 -0
- data/lib/mobility/plugins/backend_reader.rb +34 -0
- data/lib/mobility/plugins/cache.rb +59 -24
- data/lib/mobility/plugins/default.rb +22 -17
- data/lib/mobility/plugins/dirty.rb +12 -33
- data/lib/mobility/plugins/fallbacks.rb +51 -43
- data/lib/mobility/plugins/fallthrough_accessors.rb +26 -25
- data/lib/mobility/plugins/locale_accessors.rb +25 -35
- data/lib/mobility/plugins/presence.rb +28 -21
- data/lib/mobility/plugins/query.rb +8 -17
- data/lib/mobility/plugins/reader.rb +50 -0
- data/lib/mobility/plugins/sequel.rb +34 -0
- data/lib/mobility/plugins/sequel/backend.rb +25 -0
- data/lib/mobility/plugins/sequel/cache.rb +24 -0
- data/lib/mobility/plugins/sequel/dirty.rb +45 -32
- data/lib/mobility/plugins/sequel/query.rb +21 -6
- data/lib/mobility/plugins/writer.rb +44 -0
- data/lib/mobility/translations.rb +95 -0
- data/lib/mobility/version.rb +12 -1
- data/lib/rails/generators/mobility/templates/initializer.rb +95 -77
- metadata +51 -51
- metadata.gz.sig +0 -0
- data/lib/mobility/active_model.rb +0 -4
- data/lib/mobility/active_model/backend_resetter.rb +0 -26
- data/lib/mobility/active_record.rb +0 -23
- data/lib/mobility/active_record/backend_resetter.rb +0 -26
- data/lib/mobility/active_record/uniqueness_validator.rb +0 -60
- data/lib/mobility/attributes.rb +0 -324
- data/lib/mobility/backend/orm_delegator.rb +0 -44
- data/lib/mobility/backend_resetter.rb +0 -50
- data/lib/mobility/configuration.rb +0 -138
- data/lib/mobility/fallbacks.rb +0 -28
- data/lib/mobility/interface.rb +0 -0
- data/lib/mobility/loaded.rb +0 -4
- data/lib/mobility/plugins/active_record/attribute_methods.rb +0 -38
- data/lib/mobility/plugins/cache/translation_cacher.rb +0 -40
- data/lib/mobility/sequel.rb +0 -9
- data/lib/mobility/sequel/backend_resetter.rb +0 -23
- data/lib/mobility/translates.rb +0 -73
@@ -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
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
54
|
+
included_hook do |_, backend_class|
|
55
|
+
if options[:dirty]
|
56
|
+
backend_class.include BackendMethods
|
57
|
+
end
|
58
|
+
end
|
62
59
|
|
63
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
62
|
+
def dirty_handler_methods
|
63
|
+
HandlerMethods
|
64
|
+
end
|
77
65
|
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
91
|
+
def changed_attribute_names_to_save
|
92
|
+
super + mutations_from_mobility.changed
|
93
|
+
end
|
101
94
|
|
102
|
-
|
103
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
20
|
+
extend Plugin
|
21
|
+
|
22
|
+
requires :query, include: false
|
23
|
+
|
24
|
+
included_hook do |klass, backend_class|
|
25
|
+
plugin = self
|
26
|
+
if options[:query]
|
27
|
+
raise MissingBackend, "backend required for Query plugin" unless backend_class
|
28
|
+
|
29
|
+
klass.class_eval do
|
21
30
|
extend QueryMethod
|
22
|
-
extend FindByMethods.new(*
|
23
|
-
singleton_class.send :alias_method,
|
31
|
+
extend FindByMethods.new(*plugin.names)
|
32
|
+
singleton_class.send :alias_method, plugin.query_method, :__mobility_query_scope__
|
24
33
|
end
|
25
|
-
|
34
|
+
backend_class.include BackendMethods
|
26
35
|
end
|
36
|
+
end
|
27
37
|
|
38
|
+
class << self
|
28
39
|
def attribute_alias(attribute, locale = Mobility.locale)
|
29
40
|
"__mobility_%s_%s__" % [attribute, ::Mobility.normalize_locale(locale)]
|
30
41
|
end
|
31
42
|
end
|
32
43
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
module BackendMethods
|
45
|
+
# @note We use +instance_variable_get+ here to get the +AttributeSet+
|
46
|
+
# rather than the hash of attributes. Getting the full hash of
|
47
|
+
# attributes is a performance hit and better to avoid if unnecessary.
|
48
|
+
# TODO: Improve this.
|
49
|
+
def read(locale, **)
|
50
|
+
if model.instance_variable_defined?(:@attributes) &&
|
51
|
+
(model_attributes = model.instance_variable_get(:@attributes)).key?(alias_ = Query.attribute_alias(attribute, locale))
|
52
|
+
model_attributes[alias_].value
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
43
56
|
end
|
44
57
|
end
|
45
58
|
|
46
|
-
private
|
47
|
-
|
48
|
-
def model_attributes_defined?
|
49
|
-
model.instance_variable_defined?(:@attributes)
|
50
|
-
end
|
51
|
-
|
52
|
-
def model_attributes
|
53
|
-
model.instance_variable_get(:@attributes)
|
54
|
-
end
|
55
|
-
|
56
59
|
module QueryMethod
|
60
|
+
# This is required for UniquenessValidator.
|
57
61
|
def __mobility_query_scope__(locale: Mobility.locale, &block)
|
58
62
|
if block_given?
|
59
63
|
VirtualRow.build_query(self, locale, &block)
|
@@ -121,7 +125,7 @@ enabled for any one attribute on the model.
|
|
121
125
|
case opts
|
122
126
|
when Symbol, String
|
123
127
|
@klass.mobility_attribute?(opts) ? order({ opts => :asc }, *rest) : super
|
124
|
-
when Hash
|
128
|
+
when ::Hash
|
125
129
|
i18n_keys, keys = opts.keys.partition(&@klass.method(:mobility_attribute?))
|
126
130
|
return super if i18n_keys.empty?
|
127
131
|
|
@@ -139,8 +143,10 @@ enabled for any one attribute on the model.
|
|
139
143
|
|
140
144
|
if ::ActiveRecord::VERSION::STRING >= '5.0'
|
141
145
|
%w[pluck group select].each do |method_name|
|
142
|
-
define_method method_name do |*attrs|
|
143
|
-
return super(*attrs)
|
146
|
+
define_method method_name do |*attrs, &block|
|
147
|
+
return super(*attrs, &block) if (method_name == 'select' && block.present?)
|
148
|
+
|
149
|
+
return super(*attrs, &block) unless attrs.any?(&@klass.method(:mobility_attribute?))
|
144
150
|
|
145
151
|
keys = attrs.dup
|
146
152
|
|
@@ -154,7 +160,7 @@ enabled for any one attribute on the model.
|
|
154
160
|
@klass.mobility_backend_class(key).apply_scope(query, backend_node(key))
|
155
161
|
end
|
156
162
|
|
157
|
-
base.public_send(method_name, *keys)
|
163
|
+
base.public_send(method_name, *keys, &block)
|
158
164
|
end
|
159
165
|
end
|
160
166
|
end
|
@@ -180,7 +186,7 @@ enabled for any one attribute on the model.
|
|
180
186
|
|
181
187
|
class << self
|
182
188
|
def build(scope, where_opts, invert: false, &block)
|
183
|
-
return yield unless Hash === where_opts
|
189
|
+
return yield unless ::Hash === where_opts
|
184
190
|
|
185
191
|
opts = where_opts.with_indifferent_access
|
186
192
|
locale = opts.delete(:locale) || Mobility.locale
|
@@ -193,11 +199,11 @@ enabled for any one attribute on the model.
|
|
193
199
|
# Builds a translated relation for a given opts hash and optional
|
194
200
|
# invert boolean.
|
195
201
|
def _build(scope, opts, locale, invert)
|
196
|
-
return yield
|
202
|
+
return yield if (mods = attribute_modules(scope)).empty?
|
197
203
|
|
198
204
|
keys, predicates = opts.keys.map(&:to_s), []
|
199
205
|
|
200
|
-
query_map =
|
206
|
+
query_map = mods.inject(IDENTITY) do |qm, mod|
|
201
207
|
i18n_keys = mod.names & keys
|
202
208
|
next qm if i18n_keys.empty?
|
203
209
|
|
@@ -213,7 +219,11 @@ enabled for any one attribute on the model.
|
|
213
219
|
return yield if query_map == IDENTITY
|
214
220
|
|
215
221
|
relation = opts.empty? ? scope : yield(opts)
|
216
|
-
query_map[relation.where(predicates.inject(
|
222
|
+
query_map[relation.where(predicates.inject(:and))]
|
223
|
+
end
|
224
|
+
|
225
|
+
def attribute_modules(scope)
|
226
|
+
scope.model.ancestors.grep(::Mobility::Translations)
|
217
227
|
end
|
218
228
|
|
219
229
|
def build_predicate(node, values)
|
@@ -265,6 +275,10 @@ enabled for any one attribute on the model.
|
|
265
275
|
|
266
276
|
private_constant :QueryExtension, :FindByMethods
|
267
277
|
end
|
278
|
+
|
279
|
+
class MissingBackend < Mobility::Error; end
|
268
280
|
end
|
281
|
+
|
282
|
+
register_plugin(:active_record_query, ActiveRecord::Query)
|
269
283
|
end
|
270
284
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Mobility
|
2
|
+
module Plugins
|
3
|
+
module ActiveRecord
|
4
|
+
module UniquenessValidation
|
5
|
+
extend Plugin
|
6
|
+
|
7
|
+
requires :query, include: false
|
8
|
+
|
9
|
+
included_hook do |klass|
|
10
|
+
klass.class_eval do
|
11
|
+
unless const_defined?(:UniquenessValidator, false)
|
12
|
+
self.const_set(:UniquenessValidator, Class.new(UniquenessValidator))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
|
18
|
+
# @param [ActiveRecord::Base] record Translated model
|
19
|
+
# @param [String] attribute Name of attribute
|
20
|
+
# @param [Object] value Attribute value
|
21
|
+
def validate_each(record, attribute, value)
|
22
|
+
klass = record.class
|
23
|
+
|
24
|
+
if ([*options[:scope]] + [attribute]).any? { |name| klass.mobility_attribute?(name) }
|
25
|
+
return unless value.present?
|
26
|
+
relation = klass.unscoped.__mobility_query_scope__ do |m|
|
27
|
+
node = m.__send__(attribute)
|
28
|
+
options[:case_sensitive] == false ? node.lower.eq(value.downcase) : node.eq(value)
|
29
|
+
end
|
30
|
+
relation = relation.where.not(klass.primary_key => record.id) if record.persisted?
|
31
|
+
relation = mobility_scope_relation(record, relation)
|
32
|
+
relation = relation.merge(options[:conditions]) if options[:conditions]
|
33
|
+
|
34
|
+
if relation.exists?
|
35
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
36
|
+
error_options[:value] = value
|
37
|
+
|
38
|
+
record.errors.add(attribute, :taken, error_options)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def mobility_scope_relation(record, relation)
|
48
|
+
[*options[:scope]].inject(relation) do |scoped_relation, scope_item|
|
49
|
+
scoped_relation.__mobility_query_scope__ do |m|
|
50
|
+
m.__send__(scope_item).eq(record.send(scope_item))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
register_plugin(:active_record_uniqueness_validation, ActiveRecord::UniquenessValidation)
|
59
|
+
end
|
60
|
+
end
|