mongoid 8.0.7 → 8.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +65 -41
- data/lib/mongoid/association/accessors.rb +5 -1
- data/lib/mongoid/association/eager_loadable.rb +3 -0
- data/lib/mongoid/atomic.rb +9 -7
- data/lib/mongoid/config.rb +10 -0
- data/lib/mongoid/criteria/queryable/extensions/numeric.rb +15 -1
- data/lib/mongoid/document.rb +8 -1
- data/lib/mongoid/fields.rb +11 -6
- data/lib/mongoid/interceptable.rb +10 -8
- data/lib/mongoid/timestamps/created.rb +8 -1
- data/lib/mongoid/traversable.rb +12 -0
- data/lib/mongoid/validatable/associated.rb +98 -17
- data/lib/mongoid/validatable.rb +8 -0
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +40 -0
- data/spec/integration/callbacks_models.rb +37 -0
- data/spec/integration/callbacks_spec.rb +27 -0
- data/spec/mongoid/association/eager_spec.rb +24 -2
- data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +4 -0
- data/spec/mongoid/association_spec.rb +60 -0
- data/spec/mongoid/document_spec.rb +27 -0
- data/spec/mongoid/interceptable_spec.rb +100 -0
- data/spec/mongoid/interceptable_spec_models.rb +51 -111
- data/spec/mongoid/serializable_spec.rb +14 -14
- data/spec/mongoid/timestamps/created_spec.rb +23 -0
- data/spec/mongoid/validatable/associated_spec.rb +27 -34
- data/spec/support/models/name.rb +10 -0
- metadata +4 -80
- checksums.yaml.gz.sig +0 -0
- data/spec/shared/LICENSE +0 -20
- data/spec/shared/bin/get-mongodb-download-url +0 -17
- data/spec/shared/bin/s3-copy +0 -45
- data/spec/shared/bin/s3-upload +0 -69
- data/spec/shared/lib/mrss/child_process_helper.rb +0 -80
- data/spec/shared/lib/mrss/cluster_config.rb +0 -231
- data/spec/shared/lib/mrss/constraints.rb +0 -378
- data/spec/shared/lib/mrss/docker_runner.rb +0 -298
- data/spec/shared/lib/mrss/eg_config_utils.rb +0 -51
- data/spec/shared/lib/mrss/event_subscriber.rb +0 -210
- data/spec/shared/lib/mrss/lite_constraints.rb +0 -238
- data/spec/shared/lib/mrss/server_version_registry.rb +0 -113
- data/spec/shared/lib/mrss/session_registry.rb +0 -69
- data/spec/shared/lib/mrss/session_registry_legacy.rb +0 -60
- data/spec/shared/lib/mrss/spec_organizer.rb +0 -179
- data/spec/shared/lib/mrss/utils.rb +0 -37
- data/spec/shared/share/Dockerfile.erb +0 -321
- data/spec/shared/share/haproxy-1.conf +0 -16
- data/spec/shared/share/haproxy-2.conf +0 -17
- data/spec/shared/shlib/config.sh +0 -27
- data/spec/shared/shlib/distro.sh +0 -74
- data/spec/shared/shlib/server.sh +0 -416
- data/spec/shared/shlib/set_env.sh +0 -169
- data.tar.gz.sig +0 -4
- 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: 49662a14c4c7135e3403cab365f7e46928baf2efca9ede4e673269d39c5a7292
|
4
|
+
data.tar.gz: a69c22af01dbba7be588e2b4a9a63a1f58323ddd3395771969652323b2d7ecaf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cdde64cee0c2f085b6f250aae4e0f31340a9b79d04177e69f9c287170d8bb2eaba435873d680f77181587624244c1ba5885eff2d3964f3ebbe5f0924a70b1347
|
7
|
+
data.tar.gz: 2d065d480c992dc3a2e772b7c33a0b50805d981c4f150f06b93f0ad9c89fd6b9c138c64354f8353c1a9849d9ddef45eae382bfc3ca1e4bb6aead94e60c5e6324
|
data/Rakefile
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bundler"
|
4
|
-
require "bundler/gem_tasks"
|
5
4
|
Bundler.setup
|
6
5
|
|
7
6
|
ROOT = File.expand_path(File.join(File.dirname(__FILE__)))
|
@@ -10,34 +9,53 @@ $: << File.join(ROOT, 'spec/shared/lib')
|
|
10
9
|
|
11
10
|
require "rake"
|
12
11
|
require "rspec/core/rake_task"
|
13
|
-
require 'mrss/spec_organizer'
|
14
|
-
require 'rubygems/package'
|
15
|
-
require 'rubygems/security/policies'
|
16
|
-
|
17
|
-
def signed_gem?(path_to_gem)
|
18
|
-
Gem::Package.new(path_to_gem, Gem::Security::HighSecurity).verify
|
19
|
-
true
|
20
|
-
rescue Gem::Security::Exception => e
|
21
|
-
false
|
22
|
-
end
|
23
|
-
|
24
|
-
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
25
|
-
require "mongoid/version"
|
26
|
-
|
27
|
-
tasks = Rake.application.instance_variable_get('@tasks')
|
28
|
-
tasks['release:do'] = tasks.delete('release')
|
29
12
|
|
30
|
-
task
|
13
|
+
# stands in for the Bundler-provided `build` task, which builds the
|
14
|
+
# gem for this project. Our release process builds the gems in a
|
15
|
+
# particular way, in a GitHub action. This task is just to help remind
|
16
|
+
# developers of that fact.
|
31
17
|
task :build do
|
32
|
-
|
18
|
+
abort <<~WARNING
|
19
|
+
`rake build` does nothing in this project. The gem must be built via
|
20
|
+
the `Mongoid Release` action on GitHub, which is triggered manually when
|
21
|
+
a new release is ready.
|
22
|
+
WARNING
|
33
23
|
end
|
34
24
|
|
35
|
-
|
36
|
-
|
25
|
+
# `rake version` is used by the deployment system so get the release version
|
26
|
+
# of the product beng deployed. It must do nothing more than just print the
|
27
|
+
# product version number.
|
28
|
+
#
|
29
|
+
# See the mongodb-labs/driver-github-tools/ruby/publish Github action.
|
30
|
+
desc "Print the current value of Mongoid::VERSION"
|
31
|
+
task :version do
|
32
|
+
require 'mongoid/version'
|
33
|
+
|
34
|
+
puts Mongoid::VERSION
|
37
35
|
end
|
38
36
|
|
37
|
+
# overrides the default Bundler-provided `release` task, which also
|
38
|
+
# builds the gem. Our release process assumes the gem has already
|
39
|
+
# been built (and signed via GPG), so we just need `rake release` to
|
40
|
+
# push the gem to rubygems.
|
39
41
|
task :release do
|
40
|
-
|
42
|
+
require 'mongoid/version'
|
43
|
+
|
44
|
+
if ENV['GITHUB_ACTION'].nil?
|
45
|
+
abort <<~WARNING
|
46
|
+
`rake release` must be invoked from the `Mongoid Release` GitHub action,
|
47
|
+
and must not be invoked locally. This ensures the gem is properly signed
|
48
|
+
and distributed by the appropriate user.
|
49
|
+
|
50
|
+
Note that it is the `rubygems/release-gem@v1` step in the `Mongoid Release`
|
51
|
+
action that invokes this task. Do not rename or remove this task, or the
|
52
|
+
release-gem step will fail. Reimplement this task with caution.
|
53
|
+
|
54
|
+
mongoid-#{Mongoid::VERSION}.gem was NOT pushed to RubyGems.
|
55
|
+
WARNING
|
56
|
+
end
|
57
|
+
|
58
|
+
system 'gem', 'push', "mongoid-#{Mongoid::VERSION}.gem"
|
41
59
|
end
|
42
60
|
|
43
61
|
RSpec::Core::RakeTask.new("spec") do |spec|
|
@@ -64,6 +82,8 @@ RUN_PRIORITY = %i(
|
|
64
82
|
)
|
65
83
|
|
66
84
|
def spec_organizer
|
85
|
+
require 'mrss/spec_organizer'
|
86
|
+
|
67
87
|
Mrss::SpecOrganizer.new(
|
68
88
|
root: ROOT,
|
69
89
|
classifiers: CLASSIFIERS,
|
@@ -99,32 +119,36 @@ task :docs => 'docs:yard'
|
|
99
119
|
namespace :docs do
|
100
120
|
desc "Generate yard documention"
|
101
121
|
task :yard do
|
122
|
+
require "mongoid/version"
|
123
|
+
|
102
124
|
out = File.join('yard-docs', Mongoid::VERSION)
|
103
125
|
FileUtils.rm_rf(out)
|
104
126
|
system "yardoc -o #{out} --title mongoid-#{Mongoid::VERSION}"
|
105
127
|
end
|
106
128
|
end
|
107
129
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
130
|
+
desc 'Build and validate the evergreen config'
|
131
|
+
task eg: %w[ eg:build eg:validate ]
|
132
|
+
|
133
|
+
# 'eg' == 'evergreen', but evergreen is too many letters for convenience
|
134
|
+
namespace :eg do
|
135
|
+
desc 'Builds the .evergreen/config.yml file from the templates'
|
136
|
+
task :build do
|
137
|
+
ruby '.evergreen/update-evergreen-configs'
|
113
138
|
end
|
114
|
-
end
|
115
139
|
|
116
|
-
desc '
|
117
|
-
task :
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
140
|
+
desc 'Validates the .evergreen/config.yml file'
|
141
|
+
task :validate do
|
142
|
+
system 'evergreen validate --project mongoid .evergreen/config.yml'
|
143
|
+
end
|
144
|
+
|
145
|
+
desc 'Updates the evergreen executable to the latest available version'
|
146
|
+
task :update do
|
147
|
+
system 'evergreen get-update --install'
|
148
|
+
end
|
149
|
+
|
150
|
+
desc 'Runs the current branch as an evergreen patch'
|
151
|
+
task :patch do
|
152
|
+
system 'evergreen patch --uncommitted --project mongoid --browse --auto-description --yes'
|
129
153
|
end
|
130
154
|
end
|
@@ -115,7 +115,11 @@ module Mongoid
|
|
115
115
|
# during binding or when cascading callbacks. Whenever we retrieve
|
116
116
|
# associations within the codebase, we use without_autobuild.
|
117
117
|
if !without_autobuild? && association.embedded? && attribute_missing?(field_name)
|
118
|
-
|
118
|
+
# We always allow accessing the parent document of an embedded one.
|
119
|
+
try_get_parent = association.is_a?(
|
120
|
+
Mongoid::Association::Embedded::EmbeddedIn
|
121
|
+
) && field_name == association.key
|
122
|
+
raise ActiveModel::MissingAttributeError, "Missing attribute: '#{field_name}'" unless try_get_parent
|
119
123
|
end
|
120
124
|
|
121
125
|
if !reload && (value = ivar(name)) != false
|
@@ -31,6 +31,9 @@ module Mongoid
|
|
31
31
|
docs_map = {}
|
32
32
|
queue = [ klass.to_s ]
|
33
33
|
|
34
|
+
# account for single-collection inheritance
|
35
|
+
queue.push(klass.root_class.to_s) if klass != klass.root_class
|
36
|
+
|
34
37
|
while klass = queue.shift
|
35
38
|
if as = assoc_map.delete(klass)
|
36
39
|
as.each do |assoc|
|
data/lib/mongoid/atomic.rb
CHANGED
@@ -178,13 +178,15 @@ module Mongoid
|
|
178
178
|
#
|
179
179
|
# @return [ Object ] The associated path.
|
180
180
|
def atomic_paths
|
181
|
-
@atomic_paths
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
181
|
+
return @atomic_paths if @atomic_paths
|
182
|
+
|
183
|
+
paths = if _association
|
184
|
+
_association.path(self)
|
185
|
+
else
|
186
|
+
Atomic::Paths::Root.new(self)
|
187
|
+
end
|
188
|
+
|
189
|
+
paths.tap { @atomic_paths = paths unless new_record? }
|
188
190
|
end
|
189
191
|
|
190
192
|
# Get all the attributes that need to be pulled.
|
data/lib/mongoid/config.rb
CHANGED
@@ -142,6 +142,16 @@ module Mongoid
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
|
+
# When this flag is true, callbacks for every embedded document will be
|
146
|
+
# called only once, even if the embedded document is embedded in multiple
|
147
|
+
# documents in the root document's dependencies graph.
|
148
|
+
# This will be the default in 9.0. Setting this flag to false restores the
|
149
|
+
# pre-9.0 behavior, where callbacks are called for every occurrence of an
|
150
|
+
# embedded document. The pre-9.0 behavior leads to a problem that for multi
|
151
|
+
# level nested documents callbacks are called multiple times.
|
152
|
+
# See https://jira.mongodb.org/browse/MONGOID-5542
|
153
|
+
option :prevent_multiple_calls_of_embedded_callbacks, default: false
|
154
|
+
|
145
155
|
# When this flag is true, callbacks for embedded documents will not be
|
146
156
|
# called. This is the default in 8.x, but will be changed to false in 9.0.
|
147
157
|
#
|
@@ -43,7 +43,21 @@ module Mongoid
|
|
43
43
|
#
|
44
44
|
# @return [ Object ] The converted number.
|
45
45
|
def __numeric__(object)
|
46
|
-
|
46
|
+
str = object.to_s
|
47
|
+
raise ArgumentError if str.empty?
|
48
|
+
|
49
|
+
# These requirements seem a bit odd, but they're explicitly specified in the tests,
|
50
|
+
# so we're obligated to keep them, for now. (This code was rewritten from a one-line
|
51
|
+
# regex, due to security concerns with a polynomial regex being used on uncontrolled
|
52
|
+
# data).
|
53
|
+
|
54
|
+
str = str.chop if str.end_with?('.')
|
55
|
+
return 0 if str.empty?
|
56
|
+
|
57
|
+
result = Integer(str) rescue Float(object)
|
58
|
+
|
59
|
+
integer = result.to_i
|
60
|
+
integer == result ? integer : result
|
47
61
|
end
|
48
62
|
|
49
63
|
# Evolve the object to an integer.
|
data/lib/mongoid/document.rb
CHANGED
@@ -133,7 +133,14 @@ module Mongoid
|
|
133
133
|
#
|
134
134
|
# @return [ Hash ] A hash of all attributes in the hierarchy.
|
135
135
|
def as_document
|
136
|
-
|
136
|
+
attrs = as_attributes
|
137
|
+
|
138
|
+
# legacy attributes have a tendency to leak internal state via
|
139
|
+
# `as_document`; we have to deep_dup the attributes here to prevent
|
140
|
+
# that.
|
141
|
+
attrs = attrs.deep_dup if Mongoid.legacy_attributes
|
142
|
+
|
143
|
+
BSON::Document.new(attrs)
|
137
144
|
end
|
138
145
|
|
139
146
|
# Calls #as_json on the document with additional, Mongoid-specific options.
|
data/lib/mongoid/fields.rb
CHANGED
@@ -47,6 +47,11 @@ module Mongoid
|
|
47
47
|
# @api private
|
48
48
|
INVALID_BSON_CLASSES = [ BSON::Decimal128, BSON::Int32, BSON::Int64 ].freeze
|
49
49
|
|
50
|
+
# The suffix for generated translated fields.
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
TRANSLATIONS_SFX = '_translations'
|
54
|
+
|
50
55
|
module ClassMethods
|
51
56
|
# Returns the list of id fields for this model class, as both strings
|
52
57
|
# and symbols.
|
@@ -99,8 +104,8 @@ module Mongoid
|
|
99
104
|
ar.each_with_index do |fn, i|
|
100
105
|
key = fn
|
101
106
|
unless klass.fields.key?(fn) || klass.relations.key?(fn)
|
102
|
-
if
|
103
|
-
key =
|
107
|
+
if fn.end_with?(TRANSLATIONS_SFX)
|
108
|
+
key = fn.delete_suffix(TRANSLATIONS_SFX)
|
104
109
|
else
|
105
110
|
key = fn
|
106
111
|
end
|
@@ -708,11 +713,11 @@ module Mongoid
|
|
708
713
|
# @param [ String ] meth The name of the method.
|
709
714
|
def create_translations_getter(name, meth)
|
710
715
|
generated_methods.module_eval do
|
711
|
-
re_define_method("#{meth}
|
716
|
+
re_define_method("#{meth}#{TRANSLATIONS_SFX}") do
|
712
717
|
attributes[name] ||= {}
|
713
718
|
attributes[name].with_indifferent_access
|
714
719
|
end
|
715
|
-
alias_method :"#{meth}_t", :"#{meth}
|
720
|
+
alias_method :"#{meth}_t", :"#{meth}#{TRANSLATIONS_SFX}"
|
716
721
|
end
|
717
722
|
end
|
718
723
|
|
@@ -726,14 +731,14 @@ module Mongoid
|
|
726
731
|
# @param [ Field ] field The field.
|
727
732
|
def create_translations_setter(name, meth, field)
|
728
733
|
generated_methods.module_eval do
|
729
|
-
re_define_method("#{meth}
|
734
|
+
re_define_method("#{meth}#{TRANSLATIONS_SFX}=") do |value|
|
730
735
|
attribute_will_change!(name)
|
731
736
|
value&.transform_values! do |_value|
|
732
737
|
field.type.mongoize(_value)
|
733
738
|
end
|
734
739
|
attributes[name] = value
|
735
740
|
end
|
736
|
-
alias_method :"#{meth}_t=", :"#{meth}
|
741
|
+
alias_method :"#{meth}_t=", :"#{meth}#{TRANSLATIONS_SFX}="
|
737
742
|
end
|
738
743
|
end
|
739
744
|
|
@@ -141,9 +141,13 @@ module Mongoid
|
|
141
141
|
# @api private
|
142
142
|
def _mongoid_run_child_callbacks(kind, children: nil, &block)
|
143
143
|
if Mongoid::Config.around_callbacks_for_embeds
|
144
|
-
_mongoid_run_child_callbacks_with_around(kind,
|
144
|
+
_mongoid_run_child_callbacks_with_around(kind,
|
145
|
+
children: children,
|
146
|
+
&block)
|
145
147
|
else
|
146
|
-
_mongoid_run_child_callbacks_without_around(kind,
|
148
|
+
_mongoid_run_child_callbacks_without_around(kind,
|
149
|
+
children: children,
|
150
|
+
&block)
|
147
151
|
end
|
148
152
|
end
|
149
153
|
|
@@ -163,13 +167,14 @@ module Mongoid
|
|
163
167
|
# @api private
|
164
168
|
def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block)
|
165
169
|
child, *tail = (children || cascadable_children(kind))
|
170
|
+
with_children = !Mongoid::Config.prevent_multiple_calls_of_embedded_callbacks
|
166
171
|
if child.nil?
|
167
172
|
block&.call
|
168
173
|
elsif tail.empty?
|
169
|
-
child.run_callbacks(child_callback_type(kind, child), &block)
|
174
|
+
child.run_callbacks(child_callback_type(kind, child), with_children: with_children, &block)
|
170
175
|
else
|
171
|
-
child.run_callbacks(child_callback_type(kind, child)) do
|
172
|
-
|
176
|
+
child.run_callbacks(child_callback_type(kind, child), with_children: with_children) do
|
177
|
+
_mongoid_run_child_callbacks(kind, children: tail, &block)
|
173
178
|
end
|
174
179
|
end
|
175
180
|
end
|
@@ -218,9 +223,6 @@ module Mongoid
|
|
218
223
|
return false if env.halted
|
219
224
|
env.value = !env.halted
|
220
225
|
callback_list << [next_sequence, env]
|
221
|
-
if (grandchildren = child.send(:cascadable_children, kind))
|
222
|
-
_mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
|
223
|
-
end
|
224
226
|
end
|
225
227
|
callback_list
|
226
228
|
end
|
@@ -22,13 +22,20 @@ module Mongoid
|
|
22
22
|
# @example Set the created at time.
|
23
23
|
# person.set_created_at
|
24
24
|
def set_created_at
|
25
|
-
if
|
25
|
+
if able_to_set_created_at?
|
26
26
|
time = Time.configured.now
|
27
27
|
self.updated_at = time if is_a?(Updated) && !updated_at_changed?
|
28
28
|
self.created_at = time
|
29
29
|
end
|
30
30
|
clear_timeless_option
|
31
31
|
end
|
32
|
+
|
33
|
+
# Is the created timestamp able to be set?
|
34
|
+
#
|
35
|
+
# @return [ true, false ] If the timestamp can be set.
|
36
|
+
def able_to_set_created_at?
|
37
|
+
!frozen? && !timeless? && !created_at
|
38
|
+
end
|
32
39
|
end
|
33
40
|
end
|
34
41
|
end
|
data/lib/mongoid/traversable.rb
CHANGED
@@ -300,6 +300,18 @@ module Mongoid
|
|
300
300
|
!!(Mongoid::Document > superclass)
|
301
301
|
end
|
302
302
|
|
303
|
+
# Returns the root class of the STI tree that the current
|
304
|
+
# class participates in. If the class is not an STI subclass, this
|
305
|
+
# returns the class itself.
|
306
|
+
#
|
307
|
+
# @return [ Mongoid::Document ] the root of the STI tree
|
308
|
+
def root_class
|
309
|
+
root = self
|
310
|
+
root = root.superclass while root.hereditary?
|
311
|
+
|
312
|
+
root
|
313
|
+
end
|
314
|
+
|
303
315
|
# When inheriting, we want to copy the fields from the parent class and
|
304
316
|
# set the on the child to start, mimicking the behavior of the old
|
305
317
|
# class_inheritable_accessor that was deprecated in Rails edge.
|
@@ -15,32 +15,113 @@ module Mongoid
|
|
15
15
|
#
|
16
16
|
# validates_associated :name, :addresses
|
17
17
|
# end
|
18
|
-
class AssociatedValidator < ActiveModel::
|
18
|
+
class AssociatedValidator < ActiveModel::Validator
|
19
|
+
# Required by `validates_with` so that the validator
|
20
|
+
# gets added to the correct attributes.
|
21
|
+
def attributes
|
22
|
+
options[:attributes]
|
23
|
+
end
|
19
24
|
|
20
|
-
#
|
21
|
-
# valid.
|
22
|
-
# the
|
25
|
+
# Checks that the named associations of the given record
|
26
|
+
# (`attributes`) are valid. This does NOT load the associations
|
27
|
+
# from the database, and will only validate records that are dirty
|
28
|
+
# or unpersisted.
|
23
29
|
#
|
24
|
-
#
|
25
|
-
#
|
30
|
+
# If anything is not valid, appropriate errors will be added to
|
31
|
+
# the `document` parameter.
|
32
|
+
#
|
33
|
+
# @param [ Mongoid::Document ] document the document with the
|
34
|
+
# associations to validate.
|
35
|
+
def validate(document)
|
36
|
+
options[:attributes].each do |attr_name|
|
37
|
+
validate_association(document, attr_name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Validates that the given association provided is either nil,
|
44
|
+
# persisted and unchanged, or invalid. Otherwise, the appropriate errors
|
45
|
+
# will be added to the parent document.
|
26
46
|
#
|
27
47
|
# @param [ Document ] document The document to validate.
|
28
48
|
# @param [ Symbol ] attribute The association to validate.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
49
|
+
def validate_association(document, attribute)
|
50
|
+
# grab the proxy from the instance variable directly; we don't want
|
51
|
+
# any loading logic to run; we just want to see if it's already
|
52
|
+
# been loaded.
|
53
|
+
proxy = document.ivar(attribute)
|
54
|
+
return unless proxy
|
55
|
+
|
56
|
+
# if the variable exists, now we see if it is a proxy, or an actual
|
57
|
+
# document. It might be a literal document instead of a proxy if this
|
58
|
+
# document was created with a Document instance as a provided attribute,
|
59
|
+
# e.g. "Post.new(message: Message.new)".
|
60
|
+
target = proxy.respond_to?(:_target) ? proxy._target : proxy
|
61
|
+
|
62
|
+
# Now, fetch the list of documents from the target. Target may be a
|
63
|
+
# single value, or a list of values, and in the case of HasMany,
|
64
|
+
# might be a rather complex collection. We need to do this without
|
65
|
+
# triggering a load, so it's a bit of a delicate dance.
|
66
|
+
list = get_target_documents(target)
|
67
|
+
|
68
|
+
valid = document.validating do
|
69
|
+
# Now, treating the target as an array, look at each element
|
70
|
+
# and see if it is valid, but only if it has already been
|
71
|
+
# persisted, or changed, and hasn't been flagged for destroy.
|
72
|
+
#
|
73
|
+
# use map.all? instead of just all?, because all? will do short-circuit
|
74
|
+
# evaluation and terminate on the first failed validation.
|
75
|
+
list.map do |value|
|
76
|
+
if value && !value.flagged_for_destroy?
|
77
|
+
value.validated? ? true : value.valid?
|
36
78
|
else
|
37
|
-
|
79
|
+
true
|
38
80
|
end
|
39
81
|
end.all?
|
40
|
-
ensure
|
41
|
-
document.exit_validate
|
42
82
|
end
|
43
|
-
|
83
|
+
|
84
|
+
document.errors.add(attribute, :invalid) unless valid
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Examine the given target object and return an array of
|
90
|
+
# documents (possibly empty) that the target represents.
|
91
|
+
#
|
92
|
+
# @param [ Array | Mongoid::Document | Mongoid::Association::Proxy | HasMany::Enumerable ] target
|
93
|
+
# the target object to examine.
|
94
|
+
#
|
95
|
+
# @return [ Array<Mongoid::Document> ] the list of documents
|
96
|
+
def get_target_documents(target)
|
97
|
+
if target.respond_to?(:_loaded?)
|
98
|
+
get_target_documents_for_has_many(target)
|
99
|
+
else
|
100
|
+
get_target_documents_for_other(target)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns the list of all currently in-memory values held by
|
105
|
+
# the target. The target will not be loaded.
|
106
|
+
#
|
107
|
+
# @param [ HasMany::Enumerable ] target the target that will
|
108
|
+
# be examined for in-memory documents.
|
109
|
+
#
|
110
|
+
# @return [ Array<Mongoid::Document> ] the in-memory documents
|
111
|
+
# held by the target.
|
112
|
+
def get_target_documents_for_has_many(target)
|
113
|
+
[ *target._loaded.values, *target._added.values ]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the target as an array. If the target represents a single
|
117
|
+
# value, it is wrapped in an array.
|
118
|
+
#
|
119
|
+
# @param [ Array | Mongoid::Document | Mongoid::Association::Proxy ] target
|
120
|
+
# the target to return.
|
121
|
+
#
|
122
|
+
# @return [ Array<Mongoid::Document> ] the target, as an array.
|
123
|
+
def get_target_documents_for_other(target)
|
124
|
+
Array.wrap(target)
|
44
125
|
end
|
45
126
|
end
|
46
127
|
end
|
data/lib/mongoid/validatable.rb
CHANGED
@@ -37,6 +37,14 @@ module Mongoid
|
|
37
37
|
Threaded.exit_validate(self)
|
38
38
|
end
|
39
39
|
|
40
|
+
# Perform a validation within the associated block.
|
41
|
+
def validating
|
42
|
+
begin_validate
|
43
|
+
yield
|
44
|
+
ensure
|
45
|
+
exit_validate
|
46
|
+
end
|
47
|
+
|
40
48
|
# Given the provided options, are we performing validations?
|
41
49
|
#
|
42
50
|
# @example Are we performing validations?
|
data/lib/mongoid/version.rb
CHANGED
@@ -2,6 +2,28 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
+
module HabtmSpec
|
6
|
+
class Page
|
7
|
+
include Mongoid::Document
|
8
|
+
embeds_many :blocks, class_name: 'HabtmSpec::Block'
|
9
|
+
end
|
10
|
+
|
11
|
+
class Block
|
12
|
+
include Mongoid::Document
|
13
|
+
embedded_in :page, class_name: 'HabtmSpec::Page'
|
14
|
+
end
|
15
|
+
|
16
|
+
class ImageBlock < Block
|
17
|
+
has_and_belongs_to_many :attachments, inverse_of: nil, class_name: 'HabtmSpec::Attachment'
|
18
|
+
accepts_nested_attributes_for :attachments
|
19
|
+
end
|
20
|
+
|
21
|
+
class Attachment
|
22
|
+
include Mongoid::Document
|
23
|
+
field :file, type: String
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
5
27
|
describe 'has_and_belongs_to_many associations' do
|
6
28
|
|
7
29
|
context 'when an anonymous class defines a has_and_belongs_to_many association' do
|
@@ -18,4 +40,22 @@ describe 'has_and_belongs_to_many associations' do
|
|
18
40
|
expect(klass.new.movies.build).to be_a Movie
|
19
41
|
end
|
20
42
|
end
|
43
|
+
|
44
|
+
context 'when an embedded has habtm relation' do
|
45
|
+
let(:attachment) { HabtmSpec::Attachment.create!(file: 'foo.jpg') }
|
46
|
+
|
47
|
+
let(:page) { HabtmSpec::Page.create! }
|
48
|
+
|
49
|
+
let(:image_block) do
|
50
|
+
image_block = page.blocks.build({
|
51
|
+
_type: 'HabtmSpec::ImageBlock',
|
52
|
+
attachment_ids: [ attachment.id.to_s ],
|
53
|
+
attachments_attributes: { '1234' => { file: 'bar.jpg', id: attachment.id.to_s } }
|
54
|
+
})
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'does not raise on save' do
|
58
|
+
expect { image_block.save! }.not_to raise_error
|
59
|
+
end
|
60
|
+
end
|
21
61
|
end
|
@@ -153,3 +153,40 @@ class Building
|
|
153
153
|
|
154
154
|
has_and_belongs_to_many :architects, dependent: :nullify
|
155
155
|
end
|
156
|
+
|
157
|
+
class Root
|
158
|
+
include Mongoid::Document
|
159
|
+
embeds_many :embedded_once, cascade_callbacks: true
|
160
|
+
after_save :trace
|
161
|
+
|
162
|
+
attr_accessor :logger
|
163
|
+
|
164
|
+
def trace
|
165
|
+
logger << :root
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class EmbeddedOnce
|
170
|
+
include Mongoid::Document
|
171
|
+
embeds_many :embedded_twice, cascade_callbacks: true
|
172
|
+
embedded_in :root
|
173
|
+
after_save :trace
|
174
|
+
|
175
|
+
attr_accessor :logger
|
176
|
+
|
177
|
+
def trace
|
178
|
+
logger << :embedded_once
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class EmbeddedTwice
|
183
|
+
include Mongoid::Document
|
184
|
+
embedded_in :embedded_once
|
185
|
+
after_save :trace
|
186
|
+
|
187
|
+
attr_accessor :logger
|
188
|
+
|
189
|
+
def trace
|
190
|
+
logger << :embedded_twice
|
191
|
+
end
|
192
|
+
end
|