mongoid 9.0.3 → 9.0.5
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
- data/lib/mongoid/association/eager_loadable.rb +3 -0
- data/lib/mongoid/attributes/readonly.rb +8 -3
- data/lib/mongoid/criteria/queryable/selectable.rb +1 -1
- data/lib/mongoid/equality.rb +1 -0
- data/lib/mongoid/interceptable.rb +6 -5
- data/lib/mongoid/loadable.rb +72 -8
- data/lib/mongoid/persistence_context.rb +1 -1
- data/lib/mongoid/timestamps/created.rb +8 -1
- data/lib/mongoid/traversable.rb +36 -5
- data/lib/mongoid/validatable/associated.rb +1 -1
- data/lib/mongoid/version.rb +1 -1
- data/spec/mongoid/association/eager_spec.rb +24 -2
- data/spec/mongoid/association_spec.rb +60 -0
- data/spec/mongoid/attributes/readonly_spec.rb +19 -0
- data/spec/mongoid/criteria/queryable/selectable_spec.rb +29 -0
- data/spec/mongoid/equality_spec.rb +6 -0
- data/spec/mongoid/interceptable_spec.rb +78 -0
- data/spec/mongoid/interceptable_spec_models.rb +39 -115
- data/spec/mongoid/loadable_spec.rb +86 -0
- data/spec/mongoid/persistence_context_spec.rb +8 -0
- data/spec/mongoid/timestamps/created_spec.rb +23 -0
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb0499892233035ab6e0b07f3df4d08dfe7129c1b32830288e9453478b8aba9d
|
4
|
+
data.tar.gz: 5b02bd092e46dfb2aadfa580f3c8b3b4548b82d2e354eba4768ee6c1772eed98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96c1585ccba204b4c316920662aa5e750f3e0dd46a7849ca142d8b42d8b94ba8516c16ae9a194a30dd648937b94ec9247371035ddf951d93ec251eeb06fa15cd
|
7
|
+
data.tar.gz: 057f7cf03935fe694f385f186d74e3468d928f44dac47695ab58319e050ae86c25039083c1bf3debebdf8fdea9025347365b0e341040898cfdf2e09acbf72b31
|
@@ -42,6 +42,9 @@ module Mongoid
|
|
42
42
|
docs_map = {}
|
43
43
|
queue = [ klass.to_s ]
|
44
44
|
|
45
|
+
# account for single-collection inheritance
|
46
|
+
queue.push(klass.root_class.to_s) if klass != klass.root_class
|
47
|
+
|
45
48
|
while klass = queue.shift
|
46
49
|
if as = assoc_map.delete(klass)
|
47
50
|
as.each do |assoc|
|
@@ -23,7 +23,7 @@ module Mongoid
|
|
23
23
|
# @return [ true | false ] If the document is new, or if the field is not
|
24
24
|
# readonly.
|
25
25
|
def attribute_writable?(name)
|
26
|
-
new_record? || (!readonly_attributes.include?(name) && _loaded?(name))
|
26
|
+
new_record? || (!self.class.readonly_attributes.include?(name) && _loaded?(name))
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
@@ -63,12 +63,17 @@ module Mongoid
|
|
63
63
|
# end
|
64
64
|
#
|
65
65
|
# @param [ Symbol... ] *names The names of the fields.
|
66
|
+
# @note When a parent class contains readonly attributes and is then
|
67
|
+
# inherited by a child class, the child class will inherit the
|
68
|
+
# parent's readonly attributes at the time of its creation.
|
69
|
+
# Updating the parent does not propagate down to child classes after wards.
|
66
70
|
def attr_readonly(*names)
|
71
|
+
self.readonly_attributes = self.readonly_attributes.dup
|
67
72
|
names.each do |name|
|
68
|
-
readonly_attributes << database_field_name(name)
|
73
|
+
self.readonly_attributes << database_field_name(name)
|
69
74
|
end
|
70
75
|
end
|
71
76
|
end
|
72
77
|
end
|
73
78
|
end
|
74
|
-
end
|
79
|
+
end
|
@@ -553,7 +553,7 @@ module Mongoid
|
|
553
553
|
# @return [ Selectable ] The new selectable.
|
554
554
|
def not(*criteria)
|
555
555
|
if criteria.empty?
|
556
|
-
dup.tap { |query| query.negating =
|
556
|
+
dup.tap { |query| query.negating = !query.negating }
|
557
557
|
else
|
558
558
|
criteria.compact.inject(self.clone) do |c, new_s|
|
559
559
|
if new_s.is_a?(Selectable)
|
data/lib/mongoid/equality.rb
CHANGED
@@ -152,9 +152,13 @@ module Mongoid
|
|
152
152
|
# @api private
|
153
153
|
def _mongoid_run_child_callbacks(kind, children: nil, &block)
|
154
154
|
if Mongoid::Config.around_callbacks_for_embeds
|
155
|
-
_mongoid_run_child_callbacks_with_around(kind,
|
155
|
+
_mongoid_run_child_callbacks_with_around(kind,
|
156
|
+
children: children,
|
157
|
+
&block)
|
156
158
|
else
|
157
|
-
_mongoid_run_child_callbacks_without_around(kind,
|
159
|
+
_mongoid_run_child_callbacks_without_around(kind,
|
160
|
+
children: children,
|
161
|
+
&block)
|
158
162
|
end
|
159
163
|
end
|
160
164
|
|
@@ -235,9 +239,6 @@ module Mongoid
|
|
235
239
|
return false if env.halted
|
236
240
|
env.value = !env.halted
|
237
241
|
callback_list << [next_sequence, env]
|
238
|
-
if (grandchildren = child.send(:cascadable_children, kind))
|
239
|
-
_mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
|
240
|
-
end
|
241
242
|
end
|
242
243
|
callback_list
|
243
244
|
end
|
data/lib/mongoid/loadable.rb
CHANGED
@@ -10,6 +10,13 @@ module Mongoid
|
|
10
10
|
# (See #model_paths.)
|
11
11
|
DEFAULT_MODEL_PATHS = %w( ./app/models ./lib/models ).freeze
|
12
12
|
|
13
|
+
# The default list of glob patterns that match paths to ignore when loading
|
14
|
+
# models. Defaults to '*/models/concerns/*', which Rails uses for extensions
|
15
|
+
# to models (and which cause errors when loaded out of order).
|
16
|
+
#
|
17
|
+
# See #ignore_patterns.
|
18
|
+
DEFAULT_IGNORE_PATTERNS = %w( */models/concerns/* ).freeze
|
19
|
+
|
13
20
|
# Search a list of model paths to get every model and require it, so
|
14
21
|
# that indexing and inheritance work in both development and production
|
15
22
|
# with the same results.
|
@@ -24,17 +31,47 @@ module Mongoid
|
|
24
31
|
# for model files. These must either be absolute paths, or relative to
|
25
32
|
# the current working directory.
|
26
33
|
def load_models(paths = model_paths)
|
27
|
-
|
28
|
-
|
29
|
-
|
34
|
+
files = files_under_paths(paths)
|
35
|
+
|
36
|
+
files.sort.each do |file|
|
37
|
+
load_model(file)
|
38
|
+
end
|
39
|
+
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# Given a list of paths, return all ruby files under that path (or, if
|
44
|
+
# `preload_models` is a list of model names, returns only the files for
|
45
|
+
# those named models).
|
46
|
+
#
|
47
|
+
# @param [ Array<String> ] paths the list of paths to search
|
48
|
+
#
|
49
|
+
# @return [ Array<String> ] the normalized file names, suitable for loading
|
50
|
+
# via `require_dependency` or `require`.
|
51
|
+
def files_under_paths(paths)
|
52
|
+
paths.flat_map { |path| files_under_path(path) }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given a single path, returns all ruby files under that path (or, if
|
56
|
+
# `preload_models` is a list of model names, returns only the files for
|
57
|
+
# those named models).
|
58
|
+
#
|
59
|
+
# @param [ String ] path the path to search
|
60
|
+
#
|
61
|
+
# @return [ Array<String> ] the normalized file names, suitable for loading
|
62
|
+
# via `require_dependency` or `require`.
|
63
|
+
def files_under_path(path)
|
64
|
+
files = if preload_models.resizable?
|
65
|
+
preload_models.
|
66
|
+
map { |model| "#{path}/#{model.underscore}.rb" }.
|
67
|
+
select { |file_name| File.exists?(file_name) }
|
30
68
|
else
|
31
|
-
|
69
|
+
Dir.glob("#{path}/**/*.rb").
|
70
|
+
reject { |file_name| ignored?(file_name) }
|
32
71
|
end
|
33
72
|
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
73
|
+
# strip the path and the suffix from each entry
|
74
|
+
files.map { |file| file.gsub(/^#{path}\// , "").gsub(/\.rb$/, "") }
|
38
75
|
end
|
39
76
|
|
40
77
|
# A convenience method for loading a model's file. If Rails'
|
@@ -71,6 +108,14 @@ module Mongoid
|
|
71
108
|
DEFAULT_MODEL_PATHS
|
72
109
|
end
|
73
110
|
|
111
|
+
# Returns the array of glob patterns that determine whether a given
|
112
|
+
# path should be ignored by the model loader.
|
113
|
+
#
|
114
|
+
# @return [ Array<String> ] the array of ignore patterns
|
115
|
+
def ignore_patterns
|
116
|
+
@ignore_patterns ||= DEFAULT_IGNORE_PATTERNS.dup
|
117
|
+
end
|
118
|
+
|
74
119
|
# Sets the model paths to the given array of paths. These are the paths
|
75
120
|
# where the application's model definitions are located.
|
76
121
|
#
|
@@ -78,6 +123,25 @@ module Mongoid
|
|
78
123
|
def model_paths=(paths)
|
79
124
|
@model_paths = paths
|
80
125
|
end
|
126
|
+
|
127
|
+
# Sets the ignore patterns to the given array of patterns. These are glob
|
128
|
+
# patterns that determine whether a given path should be ignored by the
|
129
|
+
# model loader or not.
|
130
|
+
#
|
131
|
+
# @param [ Array<String> ] patterns The list of glob patterns
|
132
|
+
def ignore_patterns=(patterns)
|
133
|
+
@ignore_patterns = patterns
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns true if the given file path matches any of the ignore patterns.
|
137
|
+
#
|
138
|
+
# @param [ String ] file_path The file path to consider
|
139
|
+
#
|
140
|
+
# @return [ true | false ] whether or not the given file path should be
|
141
|
+
# ignored.
|
142
|
+
def ignored?(file_path)
|
143
|
+
ignore_patterns.any? { |pattern| File.fnmatch?(pattern, file_path) }
|
144
|
+
end
|
81
145
|
end
|
82
146
|
|
83
147
|
end
|
@@ -138,7 +138,7 @@ module Mongoid
|
|
138
138
|
# @return [ Symbol ] The client name for this persistence
|
139
139
|
# context.
|
140
140
|
def client_name
|
141
|
-
@client_name ||= options[:client] ||
|
141
|
+
@client_name ||= __evaluate__(options[:client]) ||
|
142
142
|
Threaded.client_override ||
|
143
143
|
__evaluate__(storage_options[:client])
|
144
144
|
end
|
@@ -23,13 +23,20 @@ module Mongoid
|
|
23
23
|
# @example Set the created at time.
|
24
24
|
# person.set_created_at
|
25
25
|
def set_created_at
|
26
|
-
if
|
26
|
+
if able_to_set_created_at?
|
27
27
|
now = Time.current
|
28
28
|
self.updated_at = now if is_a?(Updated) && !updated_at_changed?
|
29
29
|
self.created_at = now
|
30
30
|
end
|
31
31
|
clear_timeless_option
|
32
32
|
end
|
33
|
+
|
34
|
+
# Is the created timestamp able to be set?
|
35
|
+
#
|
36
|
+
# @return [ true, false ] If the timestamp can be set.
|
37
|
+
def able_to_set_created_at?
|
38
|
+
!frozen? && !timeless? && !created_at
|
39
|
+
end
|
33
40
|
end
|
34
41
|
end
|
35
42
|
end
|
data/lib/mongoid/traversable.rb
CHANGED
@@ -8,6 +8,29 @@ module Mongoid
|
|
8
8
|
# around traversing the document graph.
|
9
9
|
module Traversable
|
10
10
|
extend ActiveSupport::Concern
|
11
|
+
# This code is extracted from ActiveSupport so that we do not depend on
|
12
|
+
# their private API that may change at any time.
|
13
|
+
# This code should be reviewed and maybe removed when implementing
|
14
|
+
# https://jira.mongodb.org/browse/MONGOID-5832
|
15
|
+
class << self
|
16
|
+
# @api private
|
17
|
+
def __redefine(owner, name, value)
|
18
|
+
if owner.singleton_class?
|
19
|
+
owner.redefine_method(name) { value }
|
20
|
+
owner.send(:public, name)
|
21
|
+
end
|
22
|
+
owner.redefine_singleton_method(name) { value }
|
23
|
+
owner.singleton_class.send(:public, name)
|
24
|
+
owner.redefine_singleton_method("#{name}=") do |new_value|
|
25
|
+
if owner.equal?(self)
|
26
|
+
value = new_value
|
27
|
+
else
|
28
|
+
::Mongoid::Traversable.redefine(self, name, new_value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
owner.singleton_class.send(:public, "#{name}=")
|
32
|
+
end
|
33
|
+
end
|
11
34
|
|
12
35
|
# Class-level methods for the Traversable behavior.
|
13
36
|
module ClassMethods
|
@@ -21,6 +44,18 @@ module Mongoid
|
|
21
44
|
!!(superclass < Mongoid::Document)
|
22
45
|
end
|
23
46
|
|
47
|
+
# Returns the root class of the STI tree that the current
|
48
|
+
# class participates in. If the class is not an STI subclass, this
|
49
|
+
# returns the class itself.
|
50
|
+
#
|
51
|
+
# @return [ Mongoid::Document ] the root of the STI tree
|
52
|
+
def root_class
|
53
|
+
root = self
|
54
|
+
root = root.superclass while root.hereditary?
|
55
|
+
|
56
|
+
root
|
57
|
+
end
|
58
|
+
|
24
59
|
# When inheriting, we want to copy the fields from the parent class and
|
25
60
|
# set the on the child to start, mimicking the behavior of the old
|
26
61
|
# class_inheritable_accessor that was deprecated in Rails edge.
|
@@ -105,11 +140,7 @@ module Mongoid
|
|
105
140
|
if value
|
106
141
|
Mongoid::Fields::Validators::Macro.validate_field_name(self, value)
|
107
142
|
value = value.to_s
|
108
|
-
|
109
|
-
::ActiveSupport::ClassAttribute.redefine(self, 'discriminator_key', value)
|
110
|
-
else
|
111
|
-
super
|
112
|
-
end
|
143
|
+
::Mongoid::Traversable.__redefine(self, 'discriminator_key', value)
|
113
144
|
else
|
114
145
|
# When discriminator key is set to nil, replace the class's definition
|
115
146
|
# of the discriminator key reader (provided by class_attribute earlier)
|
@@ -74,7 +74,7 @@ module Mongoid
|
|
74
74
|
# use map.all? instead of just all?, because all? will do short-circuit
|
75
75
|
# evaluation and terminate on the first failed validation.
|
76
76
|
list.map do |value|
|
77
|
-
if value && !value.flagged_for_destroy?
|
77
|
+
if value && !value.flagged_for_destroy?
|
78
78
|
value.validated? ? true : value.valid?
|
79
79
|
else
|
80
80
|
true
|
data/lib/mongoid/version.rb
CHANGED
@@ -15,14 +15,36 @@ describe Mongoid::Association::EagerLoadable do
|
|
15
15
|
Mongoid::Contextual::Mongo.new(criteria)
|
16
16
|
end
|
17
17
|
|
18
|
+
let(:association_host) { Account }
|
19
|
+
|
18
20
|
let(:inclusions) do
|
19
21
|
includes.map do |key|
|
20
|
-
|
22
|
+
association_host.reflect_on_association(key)
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
let(:doc) { criteria.first }
|
25
27
|
|
28
|
+
context 'when root is an STI subclass' do
|
29
|
+
# Driver has_one Vehicle
|
30
|
+
# Vehicle belongs_to Driver
|
31
|
+
# Truck is a Vehicle
|
32
|
+
|
33
|
+
before do
|
34
|
+
Driver.create!(vehicle: Truck.new)
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:criteria) { Truck.all }
|
38
|
+
let(:includes) { %i[ driver ] }
|
39
|
+
let(:association_host) { Truck }
|
40
|
+
|
41
|
+
it 'preloads the driver' do
|
42
|
+
expect(doc.ivar(:driver)).to be false
|
43
|
+
context.preload(inclusions, [ doc ])
|
44
|
+
expect(doc.ivar(:driver)).to be == Driver.first
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
26
48
|
context "when belongs_to" do
|
27
49
|
|
28
50
|
let!(:account) do
|
@@ -43,7 +65,7 @@ describe Mongoid::Association::EagerLoadable do
|
|
43
65
|
it "preloads the parent" do
|
44
66
|
expect(doc.ivar(:person)).to be false
|
45
67
|
context.preload(inclusions, [doc])
|
46
|
-
expect(doc.ivar(:person)).to
|
68
|
+
expect(doc.ivar(:person)).to be == person
|
47
69
|
end
|
48
70
|
end
|
49
71
|
|
@@ -115,6 +115,66 @@ describe Mongoid::Association do
|
|
115
115
|
expect(name).to_not be_an_embedded_many
|
116
116
|
end
|
117
117
|
end
|
118
|
+
|
119
|
+
context "when validation depends on association" do
|
120
|
+
before(:all) do
|
121
|
+
class Author
|
122
|
+
include Mongoid::Document
|
123
|
+
embeds_many :books, cascade_callbacks: true
|
124
|
+
field :condition, type: Boolean
|
125
|
+
end
|
126
|
+
|
127
|
+
class Book
|
128
|
+
include Mongoid::Document
|
129
|
+
embedded_in :author
|
130
|
+
validate :parent_condition_is_not_true
|
131
|
+
|
132
|
+
def parent_condition_is_not_true
|
133
|
+
return unless author&.condition
|
134
|
+
errors.add :base, "Author condition is true."
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
Author.delete_all
|
139
|
+
Book.delete_all
|
140
|
+
end
|
141
|
+
|
142
|
+
let(:author) { Author.new }
|
143
|
+
let(:book) { Book.new }
|
144
|
+
|
145
|
+
context "when author is not persisted" do
|
146
|
+
it "is valid without books" do
|
147
|
+
expect(author.valid?).to be true
|
148
|
+
end
|
149
|
+
|
150
|
+
it "is valid with a book" do
|
151
|
+
author.books << book
|
152
|
+
expect(author.valid?).to be true
|
153
|
+
end
|
154
|
+
|
155
|
+
it "is not valid when condition is true with a book" do
|
156
|
+
author.condition = true
|
157
|
+
author.books << book
|
158
|
+
expect(author.valid?).to be false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "when author is persisted" do
|
163
|
+
before do
|
164
|
+
author.books << book
|
165
|
+
author.save
|
166
|
+
end
|
167
|
+
|
168
|
+
it "remains valid initially" do
|
169
|
+
expect(author.valid?).to be true
|
170
|
+
end
|
171
|
+
|
172
|
+
it "becomes invalid when condition is set to true" do
|
173
|
+
author.update_attributes(condition: true)
|
174
|
+
expect(author.valid?).to be false
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
118
178
|
end
|
119
179
|
|
120
180
|
describe "#embedded_one?" do
|
@@ -266,7 +266,26 @@ describe Mongoid::Attributes::Readonly do
|
|
266
266
|
expect(child.mother).to be_nil
|
267
267
|
end
|
268
268
|
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context "when a subclass inherits readonly fields" do
|
272
|
+
let(:attributes) do
|
273
|
+
[:title, :terms]
|
274
|
+
end
|
275
|
+
|
276
|
+
before do
|
277
|
+
class OldPerson < Person
|
278
|
+
attr_readonly :age
|
279
|
+
end
|
280
|
+
end
|
269
281
|
|
282
|
+
it "ensures subclass inherits the readonly attributes from parent" do
|
283
|
+
expect(OldPerson.readonly_attributes.to_a).to include("title","terms")
|
284
|
+
end
|
285
|
+
|
286
|
+
it "ensures subclass does not modify parent's readonly attributes" do
|
287
|
+
expect(Person.readonly_attributes.to_a).not_to include("age")
|
288
|
+
end
|
270
289
|
end
|
271
290
|
end
|
272
291
|
end
|
@@ -1939,6 +1939,35 @@ describe Mongoid::Criteria::Queryable::Selectable do
|
|
1939
1939
|
end
|
1940
1940
|
end
|
1941
1941
|
|
1942
|
+
describe "#not" do
|
1943
|
+
context "when negating a criterion" do
|
1944
|
+
let(:selection) do
|
1945
|
+
query.not(field: /value/)
|
1946
|
+
end
|
1947
|
+
|
1948
|
+
it "adds the $not selector" do
|
1949
|
+
expect(selection.selector).to eq({
|
1950
|
+
"field" => { "$not" => /value/ }
|
1951
|
+
})
|
1952
|
+
end
|
1953
|
+
|
1954
|
+
it "returns a cloned query" do
|
1955
|
+
expect(selection).to_not equal(query)
|
1956
|
+
end
|
1957
|
+
|
1958
|
+
context "when toggling negation state" do
|
1959
|
+
it "negates the negating value" do
|
1960
|
+
expect(query.negating).to be_nil
|
1961
|
+
negated_query = query.not
|
1962
|
+
expect(negated_query.negating).to be true
|
1963
|
+
double_negated_query = negated_query.not
|
1964
|
+
expect(double_negated_query.negating).to be false
|
1965
|
+
end
|
1966
|
+
end
|
1967
|
+
end
|
1968
|
+
end
|
1969
|
+
|
1970
|
+
|
1942
1971
|
describe Symbol do
|
1943
1972
|
|
1944
1973
|
describe "#all" do
|
@@ -189,6 +189,12 @@ describe Mongoid::Equality do
|
|
189
189
|
it "compares based on the document id" do
|
190
190
|
expect(first <=> second).to eq(-1)
|
191
191
|
end
|
192
|
+
|
193
|
+
it "doesn't break if one isn't a document" do
|
194
|
+
expect do
|
195
|
+
first <=> "Foo"
|
196
|
+
end.to_not raise_error
|
197
|
+
end
|
192
198
|
end
|
193
199
|
|
194
200
|
describe "#eql?" do
|
@@ -389,6 +389,84 @@ describe Mongoid::Interceptable do
|
|
389
389
|
end
|
390
390
|
end
|
391
391
|
end
|
392
|
+
|
393
|
+
context 'with embedded grandchildren' do
|
394
|
+
IS = InterceptableSpec
|
395
|
+
|
396
|
+
context 'when creating' do
|
397
|
+
let(:registry) { IS::CallbackRegistry.new(only: %i[ before_save ]) }
|
398
|
+
|
399
|
+
let(:expected_calls) do
|
400
|
+
[
|
401
|
+
# the parent
|
402
|
+
[ IS::CbParent, :before_save ],
|
403
|
+
|
404
|
+
# the immediate child of the parent
|
405
|
+
[ IS::CbCascadedNode, :before_save ],
|
406
|
+
|
407
|
+
# the grandchild of the parent
|
408
|
+
[ IS::CbCascadedNode, :before_save ],
|
409
|
+
]
|
410
|
+
end
|
411
|
+
|
412
|
+
let!(:parent) do
|
413
|
+
parent = IS::CbParent.new(registry)
|
414
|
+
child = IS::CbCascadedNode.new(registry)
|
415
|
+
grandchild = IS::CbCascadedNode.new(registry)
|
416
|
+
|
417
|
+
child.cb_cascaded_nodes = [ grandchild ]
|
418
|
+
parent.cb_cascaded_nodes = [ child ]
|
419
|
+
|
420
|
+
parent.tap(&:save)
|
421
|
+
end
|
422
|
+
|
423
|
+
it 'should cascade callbacks to grandchildren' do
|
424
|
+
expect(registry.calls).to be == expected_calls
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
context 'when updating' do
|
429
|
+
let(:registry) { IS::CallbackRegistry.new(only: %i[ before_update ]) }
|
430
|
+
|
431
|
+
let(:expected_calls) do
|
432
|
+
[
|
433
|
+
# the parent
|
434
|
+
[ IS::CbParent, :before_update ],
|
435
|
+
|
436
|
+
# the immediate child of the parent
|
437
|
+
[ IS::CbCascadedNode, :before_update ],
|
438
|
+
|
439
|
+
# the grandchild of the parent
|
440
|
+
[ IS::CbCascadedNode, :before_update ],
|
441
|
+
]
|
442
|
+
end
|
443
|
+
|
444
|
+
let!(:parent) do
|
445
|
+
parent = IS::CbParent.new(nil)
|
446
|
+
child = IS::CbCascadedNode.new(nil)
|
447
|
+
grandchild = IS::CbCascadedNode.new(nil)
|
448
|
+
|
449
|
+
child.cb_cascaded_nodes = [ grandchild ]
|
450
|
+
parent.cb_cascaded_nodes = [ child ]
|
451
|
+
|
452
|
+
parent.save
|
453
|
+
|
454
|
+
parent.callback_registry = registry
|
455
|
+
child.callback_registry = registry
|
456
|
+
grandchild.callback_registry = registry
|
457
|
+
|
458
|
+
parent.name = 'updated'
|
459
|
+
child.name = 'updated'
|
460
|
+
grandchild.name = 'updated'
|
461
|
+
|
462
|
+
parent.tap(&:save)
|
463
|
+
end
|
464
|
+
|
465
|
+
it 'should cascade callbacks to grandchildren' do
|
466
|
+
expect(registry.calls).to be == expected_calls
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
392
470
|
end
|
393
471
|
|
394
472
|
describe ".before_destroy" do
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# rubocop:todo all
|
2
2
|
module InterceptableSpec
|
3
3
|
class CallbackRegistry
|
4
|
-
def initialize
|
4
|
+
def initialize(only: [])
|
5
5
|
@calls = []
|
6
|
+
@only = only
|
6
7
|
end
|
7
8
|
|
8
9
|
def record_call(cls, cb)
|
10
|
+
return unless @only.empty? || @only.any? { |pat| pat == cb }
|
9
11
|
@calls << [cls, cb]
|
10
12
|
end
|
11
13
|
|
@@ -16,6 +18,8 @@ module InterceptableSpec
|
|
16
18
|
extend ActiveSupport::Concern
|
17
19
|
|
18
20
|
included do
|
21
|
+
field :name, type: String
|
22
|
+
|
19
23
|
%i(
|
20
24
|
validation save create update
|
21
25
|
).each do |what|
|
@@ -35,199 +39,110 @@ module InterceptableSpec
|
|
35
39
|
end
|
36
40
|
end
|
37
41
|
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class CbHasOneParent
|
41
|
-
include Mongoid::Document
|
42
42
|
|
43
|
-
|
43
|
+
attr_accessor :callback_registry
|
44
44
|
|
45
|
-
def initialize(callback_registry)
|
45
|
+
def initialize(callback_registry, *args, **kwargs)
|
46
46
|
@callback_registry = callback_registry
|
47
|
-
super()
|
47
|
+
super(*args, **kwargs)
|
48
48
|
end
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
51
|
+
module RootInsertable
|
52
52
|
def insert_as_root
|
53
53
|
@callback_registry&.record_call(self.class, :insert_into_database)
|
54
54
|
super
|
55
55
|
end
|
56
|
+
end
|
56
57
|
|
58
|
+
class CbHasOneParent
|
59
|
+
include Mongoid::Document
|
57
60
|
include CallbackTracking
|
61
|
+
include RootInsertable
|
62
|
+
|
63
|
+
has_one :child, autosave: true, class_name: "CbHasOneChild", inverse_of: :parent
|
58
64
|
end
|
59
65
|
|
60
66
|
class CbHasOneChild
|
61
67
|
include Mongoid::Document
|
68
|
+
include CallbackTracking
|
62
69
|
|
63
70
|
belongs_to :parent, class_name: "CbHasOneParent", inverse_of: :child
|
64
|
-
|
65
|
-
def initialize(callback_registry)
|
66
|
-
@callback_registry = callback_registry
|
67
|
-
super()
|
68
|
-
end
|
69
|
-
|
70
|
-
attr_accessor :callback_registry
|
71
|
-
|
72
|
-
include CallbackTracking
|
73
71
|
end
|
74
72
|
|
75
73
|
class CbHasManyParent
|
76
74
|
include Mongoid::Document
|
75
|
+
include CallbackTracking
|
76
|
+
include RootInsertable
|
77
77
|
|
78
78
|
has_many :children, autosave: true, class_name: "CbHasManyChild", inverse_of: :parent
|
79
|
-
|
80
|
-
def initialize(callback_registry)
|
81
|
-
@callback_registry = callback_registry
|
82
|
-
super()
|
83
|
-
end
|
84
|
-
|
85
|
-
attr_accessor :callback_registry
|
86
|
-
|
87
|
-
def insert_as_root
|
88
|
-
@callback_registry&.record_call(self.class, :insert_into_database)
|
89
|
-
super
|
90
|
-
end
|
91
|
-
|
92
|
-
include CallbackTracking
|
93
79
|
end
|
94
80
|
|
95
81
|
class CbHasManyChild
|
96
82
|
include Mongoid::Document
|
83
|
+
include CallbackTracking
|
97
84
|
|
98
85
|
belongs_to :parent, class_name: "CbHasManyParent", inverse_of: :children
|
99
|
-
|
100
|
-
def initialize(callback_registry)
|
101
|
-
@callback_registry = callback_registry
|
102
|
-
super()
|
103
|
-
end
|
104
|
-
|
105
|
-
attr_accessor :callback_registry
|
106
|
-
|
107
|
-
include CallbackTracking
|
108
86
|
end
|
109
87
|
|
110
88
|
class CbEmbedsOneParent
|
111
89
|
include Mongoid::Document
|
90
|
+
include CallbackTracking
|
91
|
+
include RootInsertable
|
112
92
|
|
113
93
|
field :name
|
114
94
|
|
115
95
|
embeds_one :child, cascade_callbacks: true, class_name: "CbEmbedsOneChild", inverse_of: :parent
|
116
|
-
|
117
|
-
def initialize(callback_registry)
|
118
|
-
@callback_registry = callback_registry
|
119
|
-
super()
|
120
|
-
end
|
121
|
-
|
122
|
-
attr_accessor :callback_registry
|
123
|
-
|
124
|
-
def insert_as_root
|
125
|
-
@callback_registry&.record_call(self.class, :insert_into_database)
|
126
|
-
super
|
127
|
-
end
|
128
|
-
|
129
|
-
include CallbackTracking
|
130
96
|
end
|
131
97
|
|
132
98
|
class CbEmbedsOneChild
|
133
99
|
include Mongoid::Document
|
100
|
+
include CallbackTracking
|
134
101
|
|
135
102
|
field :age
|
136
103
|
|
137
104
|
embedded_in :parent, class_name: "CbEmbedsOneParent", inverse_of: :child
|
138
|
-
|
139
|
-
def initialize(callback_registry)
|
140
|
-
@callback_registry = callback_registry
|
141
|
-
super()
|
142
|
-
end
|
143
|
-
|
144
|
-
attr_accessor :callback_registry
|
145
|
-
|
146
|
-
include CallbackTracking
|
147
105
|
end
|
148
106
|
|
149
107
|
class CbEmbedsManyParent
|
150
108
|
include Mongoid::Document
|
109
|
+
include CallbackTracking
|
110
|
+
include RootInsertable
|
151
111
|
|
152
112
|
embeds_many :children, cascade_callbacks: true, class_name: "CbEmbedsManyChild", inverse_of: :parent
|
153
|
-
|
154
|
-
def initialize(callback_registry)
|
155
|
-
@callback_registry = callback_registry
|
156
|
-
super()
|
157
|
-
end
|
158
|
-
|
159
|
-
attr_accessor :callback_registry
|
160
|
-
|
161
|
-
def insert_as_root
|
162
|
-
@callback_registry&.record_call(self.class, :insert_into_database)
|
163
|
-
super
|
164
|
-
end
|
165
|
-
|
166
|
-
include CallbackTracking
|
167
113
|
end
|
168
114
|
|
169
115
|
class CbEmbedsManyChild
|
170
116
|
include Mongoid::Document
|
117
|
+
include CallbackTracking
|
171
118
|
|
172
119
|
embedded_in :parent, class_name: "CbEmbedsManyParent", inverse_of: :children
|
173
|
-
|
174
|
-
def initialize(callback_registry)
|
175
|
-
@callback_registry = callback_registry
|
176
|
-
super()
|
177
|
-
end
|
178
|
-
|
179
|
-
attr_accessor :callback_registry
|
180
|
-
|
181
|
-
include CallbackTracking
|
182
120
|
end
|
183
121
|
|
184
122
|
class CbParent
|
185
123
|
include Mongoid::Document
|
186
|
-
|
187
|
-
def initialize(callback_registry)
|
188
|
-
@callback_registry = callback_registry
|
189
|
-
super()
|
190
|
-
end
|
191
|
-
|
192
|
-
attr_accessor :callback_registry
|
124
|
+
include CallbackTracking
|
193
125
|
|
194
126
|
embeds_many :cb_children
|
195
127
|
embeds_many :cb_cascaded_children, cascade_callbacks: true
|
196
|
-
|
197
|
-
include CallbackTracking
|
128
|
+
embeds_many :cb_cascaded_nodes, cascade_callbacks: true, as: :parent
|
198
129
|
end
|
199
130
|
|
200
131
|
class CbChild
|
201
132
|
include Mongoid::Document
|
133
|
+
include CallbackTracking
|
202
134
|
|
203
135
|
embedded_in :cb_parent
|
204
|
-
|
205
|
-
def initialize(callback_registry, options)
|
206
|
-
@callback_registry = callback_registry
|
207
|
-
super(options)
|
208
|
-
end
|
209
|
-
|
210
|
-
attr_accessor :callback_registry
|
211
|
-
|
212
|
-
include CallbackTracking
|
213
136
|
end
|
214
137
|
|
215
138
|
class CbCascadedChild
|
216
139
|
include Mongoid::Document
|
140
|
+
include CallbackTracking
|
217
141
|
|
218
142
|
embedded_in :cb_parent
|
219
143
|
|
220
|
-
def initialize(callback_registry, options)
|
221
|
-
@callback_registry = callback_registry
|
222
|
-
super(options)
|
223
|
-
end
|
224
|
-
|
225
|
-
attr_accessor :callback_registry
|
226
|
-
|
227
144
|
before_save :test_mongoid_state
|
228
145
|
|
229
|
-
include CallbackTracking
|
230
|
-
|
231
146
|
private
|
232
147
|
|
233
148
|
# Helps test that cascading child callbacks have access to the Mongoid
|
@@ -238,6 +153,15 @@ module InterceptableSpec
|
|
238
153
|
Mongoid::Threaded.stack('interceptable').push(self)
|
239
154
|
end
|
240
155
|
end
|
156
|
+
|
157
|
+
class CbCascadedNode
|
158
|
+
include Mongoid::Document
|
159
|
+
include CallbackTracking
|
160
|
+
|
161
|
+
embedded_in :parent, polymorphic: true
|
162
|
+
|
163
|
+
embeds_many :cb_cascaded_nodes, cascade_callbacks: true, as: :parent
|
164
|
+
end
|
241
165
|
end
|
242
166
|
|
243
167
|
class InterceptableBand
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Mongoid::Loadable do
|
6
|
+
let(:lib_dir) { Pathname.new('../../lib').realpath(__dir__) }
|
7
|
+
|
8
|
+
shared_context 'with ignore_patterns' do
|
9
|
+
around do |example|
|
10
|
+
saved = Mongoid.ignore_patterns
|
11
|
+
Mongoid.ignore_patterns = ignore_patterns
|
12
|
+
example.run
|
13
|
+
ensure
|
14
|
+
Mongoid.ignore_patterns = saved
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#ignore_patterns' do
|
19
|
+
context 'when not explicitly set' do
|
20
|
+
it 'equals the default list of ignore patterns' do
|
21
|
+
expect(Mongoid.ignore_patterns).to eq Mongoid::Loadable::DEFAULT_IGNORE_PATTERNS
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when explicitly set' do
|
26
|
+
include_context 'with ignore_patterns'
|
27
|
+
|
28
|
+
let(:ignore_patterns) { %w[ pattern1 pattern2 ] }
|
29
|
+
|
30
|
+
it 'equals the list of specified patterns' do
|
31
|
+
expect(Mongoid.ignore_patterns).to eq ignore_patterns
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#files_under_path' do
|
37
|
+
let(:results) { Mongoid.files_under_path(lib_dir) }
|
38
|
+
|
39
|
+
include_context 'with ignore_patterns'
|
40
|
+
|
41
|
+
context 'when ignore_patterns is empty' do
|
42
|
+
let(:ignore_patterns) { [] }
|
43
|
+
|
44
|
+
it 'returns all ruby files' do
|
45
|
+
expect(results.length).to be > 10 # should be a bunch of them
|
46
|
+
expect(results).to include('rails/mongoid')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when ignore_patterns is not empty' do
|
51
|
+
let(:ignore_patterns) { %w[ */rails/* ] }
|
52
|
+
|
53
|
+
it 'omits the ignored paths' do
|
54
|
+
expect(results.length).to be > 10 # should be a bunch of them
|
55
|
+
expect(results).not_to include('rails/mongoid')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#files_under_paths' do
|
61
|
+
let(:paths) { [ lib_dir.join('mongoid'), lib_dir.join('rails') ] }
|
62
|
+
let(:results) { Mongoid.files_under_paths(paths) }
|
63
|
+
|
64
|
+
include_context 'with ignore_patterns'
|
65
|
+
|
66
|
+
context 'when ignore_patterns is empty' do
|
67
|
+
let(:ignore_patterns) { [] }
|
68
|
+
|
69
|
+
it 'returns all ruby files' do
|
70
|
+
expect(results.length).to be > 10 # should be a bunch
|
71
|
+
expect(results).to include('generators/mongoid/model/model_generator')
|
72
|
+
expect(results).to include('fields/encrypted')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when ignore_patterns is not empty' do
|
77
|
+
let(:ignore_patterns) { %w[ */model/* */fields/* ] }
|
78
|
+
|
79
|
+
it 'returns all ruby files' do
|
80
|
+
expect(results.length).to be > 10 # should be a bunch
|
81
|
+
expect(results).not_to include('generators/mongoid/model/model_generator')
|
82
|
+
expect(results).not_to include('fields/encrypted')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -584,6 +584,14 @@ describe Mongoid::PersistenceContext do
|
|
584
584
|
expect(persistence_context.client).to eq(Mongoid::Clients.with_name(:alternative))
|
585
585
|
end
|
586
586
|
|
587
|
+
context 'when the client option is a proc' do
|
588
|
+
let(:options) { { client: -> { :alternative } } }
|
589
|
+
|
590
|
+
it 'evaluates the proc' do
|
591
|
+
expect(persistence_context.client).to eq(Mongoid::Clients.with_name(:alternative))
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
587
595
|
context 'when there is a client override' do
|
588
596
|
persistence_context_override :client, :other
|
589
597
|
|
@@ -44,4 +44,27 @@ describe Mongoid::Timestamps::Created do
|
|
44
44
|
expect(quiz.created_at).to be_within(10).of(Time.now.utc)
|
45
45
|
end
|
46
46
|
end
|
47
|
+
|
48
|
+
context "when the document is destroyed" do
|
49
|
+
let(:book) do
|
50
|
+
Book.create!
|
51
|
+
end
|
52
|
+
|
53
|
+
before do
|
54
|
+
Cover.before_save do
|
55
|
+
destroy if title == "delete me"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
after do
|
60
|
+
Cover.reset_callbacks(:save)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "does not set the created_at timestamp" do
|
64
|
+
book.covers << Cover.new(title: "delete me")
|
65
|
+
expect {
|
66
|
+
book.save
|
67
|
+
}.not_to raise_error
|
68
|
+
end
|
69
|
+
end
|
47
70
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 9.0.
|
4
|
+
version: 9.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- The MongoDB Ruby Team
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-01-30 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activemodel
|
@@ -830,6 +829,7 @@ files:
|
|
830
829
|
- spec/mongoid/inspectable_spec.rb
|
831
830
|
- spec/mongoid/interceptable_spec.rb
|
832
831
|
- spec/mongoid/interceptable_spec_models.rb
|
832
|
+
- spec/mongoid/loadable_spec.rb
|
833
833
|
- spec/mongoid/loading_spec.rb
|
834
834
|
- spec/mongoid/loggable_spec.rb
|
835
835
|
- spec/mongoid/matcher/extract_attribute_data/numeric_keys.yml
|
@@ -1188,7 +1188,6 @@ metadata:
|
|
1188
1188
|
documentation_uri: https://www.mongodb.com/docs/mongoid/
|
1189
1189
|
homepage_uri: https://mongoid.org/
|
1190
1190
|
source_code_uri: https://github.com/mongodb/mongoid
|
1191
|
-
post_install_message:
|
1192
1191
|
rdoc_options: []
|
1193
1192
|
require_paths:
|
1194
1193
|
- lib
|
@@ -1203,8 +1202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
1203
1202
|
- !ruby/object:Gem::Version
|
1204
1203
|
version: 1.3.6
|
1205
1204
|
requirements: []
|
1206
|
-
rubygems_version: 3.
|
1207
|
-
signing_key:
|
1205
|
+
rubygems_version: 3.6.3
|
1208
1206
|
specification_version: 4
|
1209
1207
|
summary: Elegant Persistence in Ruby for MongoDB.
|
1210
1208
|
test_files:
|
@@ -1552,6 +1550,7 @@ test_files:
|
|
1552
1550
|
- spec/mongoid/inspectable_spec.rb
|
1553
1551
|
- spec/mongoid/interceptable_spec.rb
|
1554
1552
|
- spec/mongoid/interceptable_spec_models.rb
|
1553
|
+
- spec/mongoid/loadable_spec.rb
|
1555
1554
|
- spec/mongoid/loading_spec.rb
|
1556
1555
|
- spec/mongoid/loggable_spec.rb
|
1557
1556
|
- spec/mongoid/matcher/extract_attribute_data/numeric_keys.yml
|