mongoid 9.0.3 → 9.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|