mongoid 8.0.8 → 8.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +65 -41
  3. data/lib/mongoid/association/accessors.rb +5 -1
  4. data/lib/mongoid/association/eager_loadable.rb +3 -0
  5. data/lib/mongoid/association/referenced/has_many/enumerable.rb +21 -4
  6. data/lib/mongoid/config.rb +10 -0
  7. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +15 -1
  8. data/lib/mongoid/document.rb +8 -1
  9. data/lib/mongoid/fields.rb +11 -6
  10. data/lib/mongoid/interceptable.rb +10 -8
  11. data/lib/mongoid/timestamps/created.rb +8 -1
  12. data/lib/mongoid/traversable.rb +12 -0
  13. data/lib/mongoid/validatable/associated.rb +6 -3
  14. data/lib/mongoid/version.rb +1 -1
  15. data/spec/integration/app_spec.rb +4 -0
  16. data/spec/integration/callbacks_models.rb +37 -0
  17. data/spec/integration/callbacks_spec.rb +27 -0
  18. data/spec/mongoid/association/eager_spec.rb +24 -2
  19. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +4 -0
  20. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +28 -37
  21. data/spec/mongoid/association_spec.rb +60 -0
  22. data/spec/mongoid/document_spec.rb +27 -0
  23. data/spec/mongoid/interceptable_spec.rb +100 -0
  24. data/spec/mongoid/interceptable_spec_models.rb +51 -111
  25. data/spec/mongoid/serializable_spec.rb +14 -14
  26. data/spec/mongoid/timestamps/created_spec.rb +23 -0
  27. data/spec/mongoid/validatable/associated_spec.rb +14 -4
  28. data/spec/support/models/book.rb +1 -0
  29. data/spec/support/models/cover.rb +10 -0
  30. metadata +6 -80
  31. checksums.yaml.gz.sig +0 -0
  32. data/spec/shared/LICENSE +0 -20
  33. data/spec/shared/bin/get-mongodb-download-url +0 -17
  34. data/spec/shared/bin/s3-copy +0 -45
  35. data/spec/shared/bin/s3-upload +0 -69
  36. data/spec/shared/lib/mrss/child_process_helper.rb +0 -80
  37. data/spec/shared/lib/mrss/cluster_config.rb +0 -231
  38. data/spec/shared/lib/mrss/constraints.rb +0 -378
  39. data/spec/shared/lib/mrss/docker_runner.rb +0 -298
  40. data/spec/shared/lib/mrss/eg_config_utils.rb +0 -51
  41. data/spec/shared/lib/mrss/event_subscriber.rb +0 -210
  42. data/spec/shared/lib/mrss/lite_constraints.rb +0 -238
  43. data/spec/shared/lib/mrss/server_version_registry.rb +0 -113
  44. data/spec/shared/lib/mrss/session_registry.rb +0 -69
  45. data/spec/shared/lib/mrss/session_registry_legacy.rb +0 -60
  46. data/spec/shared/lib/mrss/spec_organizer.rb +0 -179
  47. data/spec/shared/lib/mrss/utils.rb +0 -37
  48. data/spec/shared/share/Dockerfile.erb +0 -321
  49. data/spec/shared/share/haproxy-1.conf +0 -16
  50. data/spec/shared/share/haproxy-2.conf +0 -17
  51. data/spec/shared/shlib/config.sh +0 -27
  52. data/spec/shared/shlib/distro.sh +0 -74
  53. data/spec/shared/shlib/server.sh +0 -416
  54. data/spec/shared/shlib/set_env.sh +0 -169
  55. data.tar.gz.sig +0 -0
  56. metadata.gz.sig +0 -0
@@ -100,6 +100,66 @@ describe Mongoid::Association do
100
100
  expect(name).to_not be_an_embedded_many
101
101
  end
102
102
  end
103
+
104
+ context "when validation depends on association" do
105
+ before(:all) do
106
+ class Author
107
+ include Mongoid::Document
108
+ embeds_many :books, cascade_callbacks: true
109
+ field :condition, type: Boolean
110
+ end
111
+
112
+ class Book
113
+ include Mongoid::Document
114
+ embedded_in :author
115
+ validate :parent_condition_is_not_true
116
+
117
+ def parent_condition_is_not_true
118
+ return unless author&.condition
119
+ errors.add :base, "Author condition is true."
120
+ end
121
+ end
122
+
123
+ Author.delete_all
124
+ Book.delete_all
125
+ end
126
+
127
+ let(:author) { Author.new }
128
+ let(:book) { Book.new }
129
+
130
+ context "when author is not persisted" do
131
+ it "is valid without books" do
132
+ expect(author.valid?).to be true
133
+ end
134
+
135
+ it "is valid with a book" do
136
+ author.books << book
137
+ expect(author.valid?).to be true
138
+ end
139
+
140
+ it "is not valid when condition is true with a book" do
141
+ author.condition = true
142
+ author.books << book
143
+ expect(author.valid?).to be false
144
+ end
145
+ end
146
+
147
+ context "when author is persisted" do
148
+ before do
149
+ author.books << book
150
+ author.save
151
+ end
152
+
153
+ it "remains valid initially" do
154
+ expect(author.valid?).to be true
155
+ end
156
+
157
+ it "becomes invalid when condition is set to true" do
158
+ author.update_attributes(condition: true)
159
+ expect(author.valid?).to be false
160
+ end
161
+ end
162
+ end
103
163
  end
104
164
 
105
165
  describe "#embedded_one?" do
@@ -591,6 +591,33 @@ describe Mongoid::Document do
591
591
  expect(person.as_document["addresses"].first).to have_key(:locations)
592
592
  end
593
593
 
594
+ context 'when modifying the returned object' do
595
+ let(:record) do
596
+ RootCategory.create(categories: [{ name: 'tests' }]).reload
597
+ end
598
+
599
+ shared_examples_for 'an object with protected internal state' do
600
+ it 'does not expose internal state' do
601
+ before_change = record.as_document.dup
602
+ record.categories.first.name = 'things'
603
+ after_change = record.as_document
604
+ expect(before_change['categories'].first['name']).not_to eq('things')
605
+ end
606
+ end
607
+
608
+ context 'when legacy_attributes is true' do
609
+ config_override :legacy_attributes, true
610
+
611
+ it_behaves_like 'an object with protected internal state'
612
+ end
613
+
614
+ context 'when legacy_attributes is false' do
615
+ config_override :legacy_attributes, false
616
+
617
+ it_behaves_like 'an object with protected internal state'
618
+ end
619
+ end
620
+
594
621
  context "with relation define store_as option in embeded_many" do
595
622
 
596
623
  let!(:phone) do
@@ -388,6 +388,86 @@ describe Mongoid::Interceptable do
388
388
  end
389
389
  end
390
390
  end
391
+
392
+ context 'with embedded grandchildren' do
393
+ IS = InterceptableSpec
394
+
395
+ config_override :prevent_multiple_calls_of_embedded_callbacks, true
396
+
397
+ context 'when creating' do
398
+ let(:registry) { IS::CallbackRegistry.new(only: %i[ before_save ]) }
399
+
400
+ let(:expected_calls) do
401
+ [
402
+ # the parent
403
+ [ IS::CbParent, :before_save ],
404
+
405
+ # the immediate child of the parent
406
+ [ IS::CbCascadedNode, :before_save ],
407
+
408
+ # the grandchild of the parent
409
+ [ IS::CbCascadedNode, :before_save ],
410
+ ]
411
+ end
412
+
413
+ let!(:parent) do
414
+ parent = IS::CbParent.new(registry)
415
+ child = IS::CbCascadedNode.new(registry)
416
+ grandchild = IS::CbCascadedNode.new(registry)
417
+
418
+ child.cb_cascaded_nodes = [ grandchild ]
419
+ parent.cb_cascaded_nodes = [ child ]
420
+
421
+ parent.tap(&:save)
422
+ end
423
+
424
+ it 'should cascade callbacks to grandchildren' do
425
+ expect(registry.calls).to be == expected_calls
426
+ end
427
+ end
428
+
429
+ context 'when updating' do
430
+ let(:registry) { IS::CallbackRegistry.new(only: %i[ before_update ]) }
431
+
432
+ let(:expected_calls) do
433
+ [
434
+ # the parent
435
+ [ IS::CbParent, :before_update ],
436
+
437
+ # the immediate child of the parent
438
+ [ IS::CbCascadedNode, :before_update ],
439
+
440
+ # the grandchild of the parent
441
+ [ IS::CbCascadedNode, :before_update ],
442
+ ]
443
+ end
444
+
445
+ let!(:parent) do
446
+ parent = IS::CbParent.new(nil)
447
+ child = IS::CbCascadedNode.new(nil)
448
+ grandchild = IS::CbCascadedNode.new(nil)
449
+
450
+ child.cb_cascaded_nodes = [ grandchild ]
451
+ parent.cb_cascaded_nodes = [ child ]
452
+
453
+ parent.save
454
+
455
+ parent.callback_registry = registry
456
+ child.callback_registry = registry
457
+ grandchild.callback_registry = registry
458
+
459
+ parent.name = 'updated'
460
+ child.name = 'updated'
461
+ grandchild.name = 'updated'
462
+
463
+ parent.tap(&:save)
464
+ end
465
+
466
+ it 'should cascade callbacks to grandchildren' do
467
+ expect(registry.calls).to be == expected_calls
468
+ end
469
+ end
470
+ end
391
471
  end
392
472
 
393
473
  describe ".before_destroy" do
@@ -578,6 +658,26 @@ describe Mongoid::Interceptable do
578
658
  band.notes.push(note)
579
659
  record.notes.push(note)
580
660
  end
661
+
662
+ context "when saving the root" do
663
+ context 'with prevent_multiple_calls_of_embedded_callbacks enabled' do
664
+ config_override :prevent_multiple_calls_of_embedded_callbacks, true
665
+
666
+ it "executes the callbacks only once for each document" do
667
+ expect(note).to receive(:update_saved).once
668
+ band.save!
669
+ end
670
+ end
671
+
672
+ context 'with prevent_multiple_calls_of_embedded_callbacks disabled' do
673
+ config_override :prevent_multiple_calls_of_embedded_callbacks, false
674
+
675
+ it "executes the callbacks once for each ember" do
676
+ expect(note).to receive(:update_saved).twice
677
+ band.save!
678
+ end
679
+ end
680
+ end
581
681
  end
582
682
  end
583
683
 
@@ -1,13 +1,19 @@
1
1
  module InterceptableSpec
2
2
  class CallbackRegistry
3
- def initialize
3
+ def initialize(only: [])
4
4
  @calls = []
5
+ @only = only
5
6
  end
6
7
 
7
8
  def record_call(cls, cb)
9
+ return unless @only.empty? || @only.any? { |pat| pat == cb }
8
10
  @calls << [cls, cb]
9
11
  end
10
12
 
13
+ def reset!
14
+ @calls.clear
15
+ end
16
+
11
17
  attr_reader :calls
12
18
  end
13
19
 
@@ -15,6 +21,8 @@ module InterceptableSpec
15
21
  extend ActiveSupport::Concern
16
22
 
17
23
  included do
24
+ field :name, type: String
25
+
18
26
  %i(
19
27
  validation save create update
20
28
  ).each do |what|
@@ -34,196 +42,128 @@ module InterceptableSpec
34
42
  end
35
43
  end
36
44
  end
37
- end
38
45
 
39
- class CbHasOneParent
40
- include Mongoid::Document
41
-
42
- has_one :child, autosave: true, class_name: "CbHasOneChild", inverse_of: :parent
46
+ attr_accessor :callback_registry
43
47
 
44
- def initialize(callback_registry)
48
+ def initialize(callback_registry, *args, **kwargs)
45
49
  @callback_registry = callback_registry
46
- super()
50
+ super(*args, **kwargs)
47
51
  end
52
+ end
48
53
 
49
- attr_accessor :callback_registry
50
-
54
+ module RootInsertable
51
55
  def insert_as_root
52
56
  @callback_registry&.record_call(self.class, :insert_into_database)
53
57
  super
54
58
  end
59
+ end
55
60
 
61
+ class CbHasOneParent
62
+ include Mongoid::Document
56
63
  include CallbackTracking
64
+ include RootInsertable
65
+
66
+ has_one :child, autosave: true, class_name: "CbHasOneChild", inverse_of: :parent
57
67
  end
58
68
 
59
69
  class CbHasOneChild
60
70
  include Mongoid::Document
71
+ include CallbackTracking
61
72
 
62
73
  belongs_to :parent, class_name: "CbHasOneParent", inverse_of: :child
63
-
64
- def initialize(callback_registry)
65
- @callback_registry = callback_registry
66
- super()
67
- end
68
-
69
- attr_accessor :callback_registry
70
-
71
- include CallbackTracking
72
74
  end
73
75
 
74
76
  class CbHasManyParent
75
77
  include Mongoid::Document
78
+ include CallbackTracking
79
+ include RootInsertable
76
80
 
77
81
  has_many :children, autosave: true, class_name: "CbHasManyChild", inverse_of: :parent
78
-
79
- def initialize(callback_registry)
80
- @callback_registry = callback_registry
81
- super()
82
- end
83
-
84
- attr_accessor :callback_registry
85
-
86
- def insert_as_root
87
- @callback_registry&.record_call(self.class, :insert_into_database)
88
- super
89
- end
90
-
91
- include CallbackTracking
92
82
  end
93
83
 
94
84
  class CbHasManyChild
95
85
  include Mongoid::Document
86
+ include CallbackTracking
96
87
 
97
88
  belongs_to :parent, class_name: "CbHasManyParent", inverse_of: :children
98
-
99
- def initialize(callback_registry)
100
- @callback_registry = callback_registry
101
- super()
102
- end
103
-
104
- attr_accessor :callback_registry
105
-
106
- include CallbackTracking
107
89
  end
108
90
 
109
91
  class CbEmbedsOneParent
110
92
  include Mongoid::Document
93
+ include CallbackTracking
94
+ include RootInsertable
111
95
 
112
96
  field :name
113
97
 
114
98
  embeds_one :child, cascade_callbacks: true, class_name: "CbEmbedsOneChild", inverse_of: :parent
115
-
116
- def initialize(callback_registry)
117
- @callback_registry = callback_registry
118
- super()
119
- end
120
-
121
- attr_accessor :callback_registry
122
-
123
- def insert_as_root
124
- @callback_registry&.record_call(self.class, :insert_into_database)
125
- super
126
- end
127
-
128
- include CallbackTracking
129
99
  end
130
100
 
131
101
  class CbEmbedsOneChild
132
102
  include Mongoid::Document
103
+ include CallbackTracking
133
104
 
134
105
  field :age
135
106
 
136
107
  embedded_in :parent, class_name: "CbEmbedsOneParent", inverse_of: :child
137
-
138
- def initialize(callback_registry)
139
- @callback_registry = callback_registry
140
- super()
141
- end
142
-
143
- attr_accessor :callback_registry
144
-
145
- include CallbackTracking
146
108
  end
147
109
 
148
110
  class CbEmbedsManyParent
149
111
  include Mongoid::Document
112
+ include CallbackTracking
113
+ include RootInsertable
150
114
 
151
115
  embeds_many :children, cascade_callbacks: true, class_name: "CbEmbedsManyChild", inverse_of: :parent
152
-
153
- def initialize(callback_registry)
154
- @callback_registry = callback_registry
155
- super()
156
- end
157
-
158
- attr_accessor :callback_registry
159
-
160
- def insert_as_root
161
- @callback_registry&.record_call(self.class, :insert_into_database)
162
- super
163
- end
164
-
165
- include CallbackTracking
166
116
  end
167
117
 
168
118
  class CbEmbedsManyChild
169
119
  include Mongoid::Document
120
+ include CallbackTracking
170
121
 
171
122
  embedded_in :parent, class_name: "CbEmbedsManyParent", inverse_of: :children
172
-
173
- def initialize(callback_registry)
174
- @callback_registry = callback_registry
175
- super()
176
- end
177
-
178
- attr_accessor :callback_registry
179
-
180
- include CallbackTracking
181
123
  end
182
124
 
183
125
  class CbParent
184
126
  include Mongoid::Document
185
-
186
- def initialize(callback_registry)
187
- @callback_registry = callback_registry
188
- super()
189
- end
190
-
191
- attr_accessor :callback_registry
127
+ include CallbackTracking
192
128
 
193
129
  embeds_many :cb_children
194
130
  embeds_many :cb_cascaded_children, cascade_callbacks: true
195
-
196
- include CallbackTracking
131
+ embeds_many :cb_cascaded_nodes, cascade_callbacks: true, as: :parent
197
132
  end
198
133
 
199
134
  class CbChild
200
135
  include Mongoid::Document
136
+ include CallbackTracking
201
137
 
202
138
  embedded_in :cb_parent
203
-
204
- def initialize(callback_registry, options)
205
- @callback_registry = callback_registry
206
- super(options)
207
- end
208
-
209
- attr_accessor :callback_registry
210
-
211
- include CallbackTracking
212
139
  end
213
140
 
214
141
  class CbCascadedChild
215
142
  include Mongoid::Document
143
+ include CallbackTracking
216
144
 
217
145
  embedded_in :cb_parent
218
146
 
219
- def initialize(callback_registry, options)
220
- @callback_registry = callback_registry
221
- super(options)
222
- end
147
+ before_save :test_mongoid_state
223
148
 
224
- attr_accessor :callback_registry
149
+ private
150
+
151
+ # Helps test that cascading child callbacks have access to the Mongoid
152
+ # state objects; if the implementation uses fiber-local (instead of truly
153
+ # thread-local) variables, the related tests will fail because the
154
+ # cascading child callbacks use fibers to linearize the recursion.
155
+ def test_mongoid_state
156
+ Mongoid::Threaded.stack('interceptable').push(self)
157
+ end
158
+ end
225
159
 
160
+ class CbCascadedNode
161
+ include Mongoid::Document
226
162
  include CallbackTracking
163
+
164
+ embedded_in :parent, polymorphic: true
165
+
166
+ embeds_many :cb_cascaded_nodes, cascade_callbacks: true, as: :parent
227
167
  end
228
168
  end
229
169
 
@@ -528,13 +528,13 @@ describe Mongoid::Serializable do
528
528
  end
529
529
 
530
530
  it "includes the first relation" do
531
- expect(relation_hash[0]).to include
532
- { "_id" => "kudamm", "street" => "Kudamm" }
531
+ expect(relation_hash[0]).to include(
532
+ { "_id" => "kudamm", "street" => "Kudamm" })
533
533
  end
534
534
 
535
535
  it "includes the second relation" do
536
- expect(relation_hash[1]).to include
537
- { "_id" => "tauentzienstr", "street" => "Tauentzienstr" }
536
+ expect(relation_hash[1]).to include(
537
+ { "_id" => "tauentzienstr", "street" => "Tauentzienstr" })
538
538
  end
539
539
  end
540
540
 
@@ -545,13 +545,13 @@ describe Mongoid::Serializable do
545
545
  end
546
546
 
547
547
  it "includes the first relation" do
548
- expect(relation_hash[0]).to include
549
- { "_id" => "kudamm", "street" => "Kudamm" }
548
+ expect(relation_hash[0]).to include(
549
+ { "_id" => "kudamm", "street" => "Kudamm" })
550
550
  end
551
551
 
552
552
  it "includes the second relation" do
553
- expect(relation_hash[1]).to include
554
- { "_id" => "tauentzienstr", "street" => "Tauentzienstr" }
553
+ expect(relation_hash[1]).to include(
554
+ { "_id" => "tauentzienstr", "street" => "Tauentzienstr" })
555
555
  end
556
556
  end
557
557
 
@@ -670,8 +670,8 @@ describe Mongoid::Serializable do
670
670
  end
671
671
 
672
672
  it "includes the specified relation" do
673
- expect(relation_hash).to include
674
- { "_id" => "leo-marvin", "first_name" => "Leo", "last_name" => "Marvin" }
673
+ expect(relation_hash).to include(
674
+ { "_id" => "Leo-Marvin", "first_name" => "Leo", "last_name" => "Marvin" })
675
675
  end
676
676
  end
677
677
 
@@ -682,8 +682,8 @@ describe Mongoid::Serializable do
682
682
  end
683
683
 
684
684
  it "includes the specified relation" do
685
- expect(relation_hash).to include
686
- { "_id" => "leo-marvin", "first_name" => "Leo", "last_name" => "Marvin" }
685
+ expect(relation_hash).to include(
686
+ { "_id" => "Leo-Marvin", "first_name" => "Leo", "last_name" => "Marvin" })
687
687
  end
688
688
  end
689
689
 
@@ -694,8 +694,8 @@ describe Mongoid::Serializable do
694
694
  end
695
695
 
696
696
  it "includes the specified relation sans exceptions" do
697
- expect(relation_hash).to include
698
- { "first_name" => "Leo", "last_name" => "Marvin" }
697
+ expect(relation_hash).to include(
698
+ { "first_name" => "Leo", "last_name" => "Marvin" })
699
699
  end
700
700
  end
701
701
  end
@@ -43,4 +43,27 @@ describe Mongoid::Timestamps::Created do
43
43
  expect(quiz.created_at).to be_within(10).of(Time.now.utc)
44
44
  end
45
45
  end
46
+
47
+ context "when the document is destroyed" do
48
+ let(:book) do
49
+ Book.create!
50
+ end
51
+
52
+ before do
53
+ Cover.before_save do
54
+ destroy if title == "delete me"
55
+ end
56
+ end
57
+
58
+ after do
59
+ Cover.reset_callbacks(:save)
60
+ end
61
+
62
+ it "does not set the created_at timestamp" do
63
+ book.covers << Cover.new(title: "delete me")
64
+ expect {
65
+ book.save
66
+ }.not_to raise_error
67
+ end
68
+ end
46
69
  end
@@ -37,12 +37,18 @@ describe Mongoid::Validatable::AssociatedValidator do
37
37
  User.new(name: "test")
38
38
  end
39
39
 
40
- let(:description) do
40
+ let(:description1) do
41
+ Description.new
42
+ end
43
+
44
+ let(:description2) do
41
45
  Description.new
42
46
  end
43
47
 
44
48
  before do
45
- user.descriptions << description
49
+ user.descriptions << description1
50
+ user.descriptions << description2
51
+ user.valid?
46
52
  end
47
53
 
48
54
  it "only validates the parent once" do
@@ -50,12 +56,16 @@ describe Mongoid::Validatable::AssociatedValidator do
50
56
  end
51
57
 
52
58
  it "adds the errors from the relation" do
53
- user.valid?
54
59
  expect(user.errors[:descriptions]).to_not be_nil
55
60
  end
56
61
 
62
+ it 'reports all failed validations' do
63
+ errors = user.descriptions.flat_map { |d| d.errors[:details] }
64
+ expect(errors.length).to be == 2
65
+ end
66
+
57
67
  it "only validates the child once" do
58
- expect(description).to_not be_valid
68
+ expect(description1).to_not be_valid
59
69
  end
60
70
  end
61
71
 
@@ -15,4 +15,5 @@ class Book
15
15
  end
16
16
 
17
17
  embeds_many :pages, cascade_callbacks: true
18
+ embeds_many :covers, cascade_callbacks: true
18
19
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Cover
4
+ include Mongoid::Document
5
+ include Mongoid::Timestamps
6
+
7
+ field :title, type: String
8
+
9
+ embedded_in :book
10
+ end