mongoid 8.1.11 → 8.1.12
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/embedded/batchable.rb +11 -10
- data/lib/mongoid/association/embedded/embeds_many/proxy.rb +61 -0
- data/lib/mongoid/clients/factory.rb +4 -0
- data/lib/mongoid/extensions/hash.rb +27 -1
- data/lib/mongoid/touchable.rb +1 -1
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/associations/embeds_many_spec.rb +41 -0
- data/spec/mongoid/clients/factory_spec.rb +30 -0
- data/spec/mongoid/extensions/hash_spec.rb +236 -0
- data/spec/mongoid/touchable_spec.rb +12 -0
- data/spec/shared/CANDIDATE.md +28 -0
- data/spec/shared/lib/mrss/spec_organizer.rb +32 -3
- data/spec/shared/shlib/server.sh +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 211cdf23f6449f62e5e4e07abd0cae37e9802d2214f92bb898237087442bd8c2
|
|
4
|
+
data.tar.gz: 8b8a00a6eb944ae5a94144b50a5ebf72654624e75a12d6202e91a59b785dcd50
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a4c4577005e47f2c07274a95a4d1d1f5639bee2d14224721172a5180608341fbeb41b0ee034be892d45a6e19ce339f4e1f19efa0287487b727bf2bf8c382317
|
|
7
|
+
data.tar.gz: 88c40b52d6f222f4c1d081ba4260ff83530b9a8daea85d0b2a28ca9850530688ebb5d8c90d10906d5e66d8a66e54ed8eb12d14c5c65a12299445a9bd89e9172f
|
|
@@ -314,18 +314,19 @@ module Mongoid
|
|
|
314
314
|
#
|
|
315
315
|
# @return [ Array<Hash> ] The documents as an array of hashes.
|
|
316
316
|
def pre_process_batch_insert(docs)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
317
|
+
[].tap do |results|
|
|
318
|
+
append_many(docs) do |doc|
|
|
319
|
+
if persistable? && !_assigning?
|
|
320
|
+
self.path = doc.atomic_path unless path
|
|
321
|
+
if doc.valid?(:create)
|
|
322
|
+
doc.run_before_callbacks(:save, :create)
|
|
323
|
+
else
|
|
324
|
+
self.inserts_valid = false
|
|
325
|
+
end
|
|
326
326
|
end
|
|
327
|
+
|
|
328
|
+
results << doc.send(:as_attributes)
|
|
327
329
|
end
|
|
328
|
-
doc.send(:as_attributes)
|
|
329
330
|
end
|
|
330
331
|
end
|
|
331
332
|
|
|
@@ -411,6 +411,67 @@ module Mongoid
|
|
|
411
411
|
execute_callback :after_add, document
|
|
412
412
|
end
|
|
413
413
|
|
|
414
|
+
# Returns a unique id for the document, which is either
|
|
415
|
+
# its _id or its object_id.
|
|
416
|
+
def id_of(doc)
|
|
417
|
+
doc._id || doc.object_id
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Optimized version of #append that handles multiple documents
|
|
421
|
+
# in a more efficient way.
|
|
422
|
+
#
|
|
423
|
+
# @param [ Array<Document> ] documents The documents to append.
|
|
424
|
+
#
|
|
425
|
+
# @return [ EmbedsMany::Proxy ] This proxy instance.
|
|
426
|
+
def append_many(documents, &block)
|
|
427
|
+
unique_set = process_incoming_docs(documents, &block)
|
|
428
|
+
|
|
429
|
+
_unscoped.concat(unique_set)
|
|
430
|
+
_target.push(*scope(unique_set))
|
|
431
|
+
update_attributes_hash
|
|
432
|
+
|
|
433
|
+
unique_set.each { |doc| execute_callback :after_add, doc }
|
|
434
|
+
|
|
435
|
+
self
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# Processes the list of documents, building a list of those
|
|
439
|
+
# that are not already in the association, and preparing
|
|
440
|
+
# each unique document to be integrated into the association.
|
|
441
|
+
#
|
|
442
|
+
# The :before_add callback is executed for each unique document
|
|
443
|
+
# as part of this step.
|
|
444
|
+
#
|
|
445
|
+
# @param [ Array<Document> ] documents The incoming documents to
|
|
446
|
+
# process.
|
|
447
|
+
#
|
|
448
|
+
# @yield [ Document ] Optional block to call for each unique
|
|
449
|
+
# document.
|
|
450
|
+
#
|
|
451
|
+
# @return [ Array<Document> ] The list of unique documents that
|
|
452
|
+
# do not yet exist in the association.
|
|
453
|
+
def process_incoming_docs(documents, &block)
|
|
454
|
+
visited_docs = Set.new(_target.map { |doc| id_of(doc) })
|
|
455
|
+
next_index = _unscoped.size
|
|
456
|
+
|
|
457
|
+
documents.select do |doc|
|
|
458
|
+
next unless doc
|
|
459
|
+
|
|
460
|
+
id = id_of(doc)
|
|
461
|
+
next if visited_docs.include?(id)
|
|
462
|
+
|
|
463
|
+
execute_callback :before_add, doc
|
|
464
|
+
|
|
465
|
+
visited_docs.add(id)
|
|
466
|
+
integrate(doc)
|
|
467
|
+
|
|
468
|
+
doc._index = next_index
|
|
469
|
+
next_index += 1
|
|
470
|
+
|
|
471
|
+
block&.call(doc) || true
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
414
475
|
# Instantiate the binding associated with this association.
|
|
415
476
|
#
|
|
416
477
|
# @example Create the binding.
|
|
@@ -95,6 +95,10 @@ module Mongoid
|
|
|
95
95
|
[MONGOID_WRAPPING_LIBRARY] + options[:wrapping_libraries]
|
|
96
96
|
else
|
|
97
97
|
[MONGOID_WRAPPING_LIBRARY]
|
|
98
|
+
end.tap do |wrap|
|
|
99
|
+
if defined?(::Rails) && ::Rails.respond_to?(:version)
|
|
100
|
+
wrap << { name: 'Rails', version: ::Rails.version }
|
|
101
|
+
end
|
|
98
102
|
end
|
|
99
103
|
options[:wrapping_libraries] = wrap_lib
|
|
100
104
|
end
|
|
@@ -167,6 +167,28 @@ module Mongoid
|
|
|
167
167
|
true
|
|
168
168
|
end
|
|
169
169
|
|
|
170
|
+
ALLOWED_TO_CRITERIA_METHODS = %i[
|
|
171
|
+
all all_in all_of and any_in any_of asc ascending
|
|
172
|
+
batch_size between
|
|
173
|
+
collation comment cursor_type
|
|
174
|
+
desc descending
|
|
175
|
+
elem_match eq exists extras
|
|
176
|
+
geo_spatial group gt gte
|
|
177
|
+
hint
|
|
178
|
+
in includes
|
|
179
|
+
limit lt lte
|
|
180
|
+
max_distance max_scan max_time_ms merge mod
|
|
181
|
+
ne near near_sphere nin no_timeout none none_of nor not not_in
|
|
182
|
+
offset only or order order_by
|
|
183
|
+
project
|
|
184
|
+
raw read reorder
|
|
185
|
+
scoped skip slice snapshot
|
|
186
|
+
text_search type
|
|
187
|
+
unscoped unwind
|
|
188
|
+
where with_size with_type without
|
|
189
|
+
].freeze
|
|
190
|
+
private_constant :ALLOWED_TO_CRITERIA_METHODS
|
|
191
|
+
|
|
170
192
|
# Convert this hash to a criteria. Will iterate over each keys in the
|
|
171
193
|
# hash which must correspond to method on a criteria object. The hash
|
|
172
194
|
# must also include a "klass" key.
|
|
@@ -178,7 +200,11 @@ module Mongoid
|
|
|
178
200
|
def to_criteria
|
|
179
201
|
criteria = Criteria.new(delete(:klass) || delete("klass"))
|
|
180
202
|
each_pair do |method, args|
|
|
181
|
-
|
|
203
|
+
method_sym = method.to_sym
|
|
204
|
+
unless ALLOWED_TO_CRITERIA_METHODS.include?(method_sym)
|
|
205
|
+
raise ArgumentError, "Method '#{method}' is not allowed in to_criteria"
|
|
206
|
+
end
|
|
207
|
+
criteria = criteria.public_send(method_sym, args)
|
|
182
208
|
end
|
|
183
209
|
criteria
|
|
184
210
|
end
|
data/lib/mongoid/touchable.rb
CHANGED
|
@@ -39,7 +39,7 @@ module Mongoid
|
|
|
39
39
|
# seems to not always/ever be set here. See MONGOID-5014.
|
|
40
40
|
_parent.touch
|
|
41
41
|
|
|
42
|
-
if field
|
|
42
|
+
if field.present?
|
|
43
43
|
# If we are told to also touch a field, perform a separate write
|
|
44
44
|
# for that field. See MONGOID-5136.
|
|
45
45
|
# In theory we should combine the writes, which would require
|
data/lib/mongoid/version.rb
CHANGED
|
@@ -200,6 +200,47 @@ describe 'embeds_many associations' do
|
|
|
200
200
|
include_examples 'persists correctly'
|
|
201
201
|
end
|
|
202
202
|
end
|
|
203
|
+
|
|
204
|
+
context 'including duplicates in the assignment' do
|
|
205
|
+
let(:canvas) do
|
|
206
|
+
Canvas.create!(shapes: [Shape.new])
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
shared_examples 'persists correctly' do
|
|
210
|
+
it 'persists correctly' do
|
|
211
|
+
canvas.shapes.length.should eq 2
|
|
212
|
+
_canvas = Canvas.find(canvas.id)
|
|
213
|
+
_canvas.shapes.length.should eq 2
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
context 'via assignment operator' do
|
|
218
|
+
before do
|
|
219
|
+
canvas.shapes = [ canvas.shapes.first, Shape.new, canvas.shapes.first ]
|
|
220
|
+
canvas.save!
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
include_examples 'persists correctly'
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
context 'via attributes=' do
|
|
227
|
+
before do
|
|
228
|
+
canvas.attributes = { shapes: [ canvas.shapes.first, Shape.new, canvas.shapes.first ] }
|
|
229
|
+
canvas.save!
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
include_examples 'persists correctly'
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
context 'via assign_attributes' do
|
|
236
|
+
before do
|
|
237
|
+
canvas.assign_attributes(shapes: [ canvas.shapes.first, Shape.new, canvas.shapes.first ])
|
|
238
|
+
canvas.save!
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
include_examples 'persists correctly'
|
|
242
|
+
end
|
|
243
|
+
end
|
|
203
244
|
end
|
|
204
245
|
|
|
205
246
|
context 'when an anonymous class defines an embeds_many association' do
|
|
@@ -29,6 +29,32 @@ describe Mongoid::Clients::Factory do
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
shared_examples_for 'includes rails wrapping library' do
|
|
33
|
+
context 'when Rails is available' do
|
|
34
|
+
around do |example|
|
|
35
|
+
original_rails_constant = defined?(::Rails) ? ::Rails : nil
|
|
36
|
+
Object.send(:remove_const, :Rails) if original_rails_constant
|
|
37
|
+
|
|
38
|
+
module ::Rails
|
|
39
|
+
def self.version
|
|
40
|
+
'6.1.0'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
example.run
|
|
45
|
+
|
|
46
|
+
Object.send(:remove_const, :Rails) if defined?(::Rails)
|
|
47
|
+
Object.const_set(:Rails, original_rails_constant)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'adds Rails as another wrapping library' do
|
|
51
|
+
expect(client.options[:wrapping_libraries]).to include(
|
|
52
|
+
{'name' => 'Rails', 'version' => '6.1.0'},
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
32
58
|
describe ".create" do
|
|
33
59
|
|
|
34
60
|
context "when provided a name" do
|
|
@@ -88,6 +114,8 @@ describe Mongoid::Clients::Factory do
|
|
|
88
114
|
Mongoid::Clients::Factory::MONGOID_WRAPPING_LIBRARY)]
|
|
89
115
|
end
|
|
90
116
|
|
|
117
|
+
it_behaves_like 'includes rails wrapping library'
|
|
118
|
+
|
|
91
119
|
context 'when configuration specifies a wrapping library' do
|
|
92
120
|
|
|
93
121
|
let(:config) do
|
|
@@ -109,6 +137,8 @@ describe Mongoid::Clients::Factory do
|
|
|
109
137
|
{'name' => 'Foo'},
|
|
110
138
|
]
|
|
111
139
|
end
|
|
140
|
+
|
|
141
|
+
it_behaves_like 'includes rails wrapping library'
|
|
112
142
|
end
|
|
113
143
|
end
|
|
114
144
|
|
|
@@ -497,4 +497,240 @@ describe Mongoid::Extensions::Hash do
|
|
|
497
497
|
|
|
498
498
|
it_behaves_like 'unsatisfiable criteria method'
|
|
499
499
|
end
|
|
500
|
+
|
|
501
|
+
describe '#to_criteria' do
|
|
502
|
+
subject(:criteria) { hash.to_criteria }
|
|
503
|
+
|
|
504
|
+
context 'when klass is specified' do
|
|
505
|
+
let(:hash) do
|
|
506
|
+
{ klass: Band, where: { name: 'Songs Ohia' } }
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
it 'returns a criteria' do
|
|
510
|
+
expect(criteria).to be_a(Mongoid::Criteria)
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
it 'sets the klass' do
|
|
514
|
+
expect(criteria.klass).to eq(Band)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
it 'sets the selector' do
|
|
518
|
+
expect(criteria.selector).to eq({ 'name' => 'Songs Ohia' })
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
context 'when klass is missing' do
|
|
523
|
+
let(:hash) do
|
|
524
|
+
{ where: { name: 'Songs Ohia' } }
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
it 'returns a criteria' do
|
|
528
|
+
expect(criteria).to be_a(Mongoid::Criteria)
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
it 'has klass nil' do
|
|
532
|
+
expect(criteria.klass).to be_nil
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
it 'sets the selector' do
|
|
536
|
+
expect(criteria.selector).to eq({ 'name' => 'Songs Ohia' })
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
context 'with allowed methods' do
|
|
541
|
+
context 'when using multiple query methods' do
|
|
542
|
+
let(:hash) do
|
|
543
|
+
{
|
|
544
|
+
klass: Band,
|
|
545
|
+
where: { active: true },
|
|
546
|
+
limit: 10,
|
|
547
|
+
skip: 5,
|
|
548
|
+
order_by: { name: 1 }
|
|
549
|
+
}
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
it 'applies all methods successfully' do
|
|
553
|
+
expect(criteria.selector).to eq({ 'active' => true })
|
|
554
|
+
expect(criteria.options[:limit]).to eq(10)
|
|
555
|
+
expect(criteria.options[:skip]).to eq(5)
|
|
556
|
+
expect(criteria.options[:sort]).to eq({ 'name' => 1 })
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
context 'when using query selector methods' do
|
|
561
|
+
let(:hash) do
|
|
562
|
+
{
|
|
563
|
+
klass: Band,
|
|
564
|
+
gt: { members: 2 },
|
|
565
|
+
in: { genre: ['rock', 'metal'] }
|
|
566
|
+
}
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
it 'applies selector methods' do
|
|
570
|
+
expect(criteria.selector['members']).to eq({ '$gt' => 2 })
|
|
571
|
+
expect(criteria.selector['genre']).to eq({ '$in' => ['rock', 'metal'] })
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
context 'when using aggregation methods' do
|
|
576
|
+
let(:hash) do
|
|
577
|
+
{
|
|
578
|
+
klass: Band,
|
|
579
|
+
project: { name: 1, members: 1 }
|
|
580
|
+
}
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
it 'applies aggregation methods' do
|
|
584
|
+
expect { criteria }.not_to raise_error
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
context 'with disallowed methods' do
|
|
590
|
+
context 'when attempting to call create' do
|
|
591
|
+
let(:hash) do
|
|
592
|
+
{ klass: Band, create: { name: 'Malicious' } }
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
it 'raises ArgumentError' do
|
|
596
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'create' is not allowed in to_criteria")
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
context 'when attempting to call create!' do
|
|
601
|
+
let(:hash) do
|
|
602
|
+
{ klass: Band, 'create!': { name: 'Malicious' } }
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
it 'raises ArgumentError' do
|
|
606
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'create!' is not allowed in to_criteria")
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
context 'when attempting to call build' do
|
|
611
|
+
let(:hash) do
|
|
612
|
+
{ klass: Band, build: { name: 'Malicious' } }
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
it 'raises ArgumentError' do
|
|
616
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'build' is not allowed in to_criteria")
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
context 'when attempting to call find' do
|
|
621
|
+
let(:hash) do
|
|
622
|
+
{ klass: Band, find: 'some_id' }
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
it 'raises ArgumentError' do
|
|
626
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'find' is not allowed in to_criteria")
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
context 'when attempting to call execute_or_raise' do
|
|
631
|
+
let(:hash) do
|
|
632
|
+
{ klass: Band, execute_or_raise: ['id1', 'id2'] }
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
it 'raises ArgumentError' do
|
|
636
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'execute_or_raise' is not allowed in to_criteria")
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
context 'when attempting to call new' do
|
|
641
|
+
let(:hash) do
|
|
642
|
+
{ klass: Band, new: { name: 'Test' } }
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
it 'raises ArgumentError' do
|
|
646
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'new' is not allowed in to_criteria")
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
context 'when allowed method is combined with disallowed method' do
|
|
651
|
+
let(:hash) do
|
|
652
|
+
{
|
|
653
|
+
klass: Band,
|
|
654
|
+
where: { active: true },
|
|
655
|
+
create: { name: 'Malicious' }
|
|
656
|
+
}
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
it 'raises ArgumentError before executing any methods' do
|
|
660
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'create' is not allowed in to_criteria")
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
context 'security validation' do
|
|
666
|
+
# This test ensures that ALL public methods not in the allowlist are blocked
|
|
667
|
+
it 'blocks all dangerous public methods' do
|
|
668
|
+
dangerous_methods = %i[
|
|
669
|
+
build create create! new
|
|
670
|
+
find find_or_create_by find_or_create_by! find_or_initialize_by
|
|
671
|
+
first_or_create first_or_create! first_or_initialize
|
|
672
|
+
execute_or_raise multiple_from_db for_ids
|
|
673
|
+
documents= inclusions= scoping_options=
|
|
674
|
+
initialize freeze as_json
|
|
675
|
+
]
|
|
676
|
+
|
|
677
|
+
dangerous_methods.each do |method|
|
|
678
|
+
hash = { klass: Band, method => 'arg' }
|
|
679
|
+
expect { hash.to_criteria }.to raise_error(
|
|
680
|
+
ArgumentError,
|
|
681
|
+
"Method '#{method}' is not allowed in to_criteria"
|
|
682
|
+
), "Expected method '#{method}' to be blocked but it was allowed"
|
|
683
|
+
end
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
it 'blocks dangerous inherited methods from Object' do
|
|
687
|
+
# Critical security test: block send, instance_eval, etc.
|
|
688
|
+
inherited_dangerous = %i[
|
|
689
|
+
send __send__ instance_eval instance_exec
|
|
690
|
+
instance_variable_set method
|
|
691
|
+
]
|
|
692
|
+
|
|
693
|
+
inherited_dangerous.each do |method|
|
|
694
|
+
hash = { klass: Band, method => 'arg' }
|
|
695
|
+
expect { hash.to_criteria }.to raise_error(
|
|
696
|
+
ArgumentError,
|
|
697
|
+
"Method '#{method}' is not allowed in to_criteria"
|
|
698
|
+
), "Expected inherited method '#{method}' to be blocked"
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
it 'blocks Enumerable execution methods' do
|
|
703
|
+
# to_criteria should build queries, not execute them
|
|
704
|
+
enumerable_methods = %i[each map select count sum]
|
|
705
|
+
|
|
706
|
+
enumerable_methods.each do |method|
|
|
707
|
+
hash = { klass: Band, method => 'arg' }
|
|
708
|
+
expect { hash.to_criteria }.to raise_error(
|
|
709
|
+
ArgumentError,
|
|
710
|
+
"Method '#{method}' is not allowed in to_criteria"
|
|
711
|
+
), "Expected Enumerable method '#{method}' to be blocked"
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
it 'allows all whitelisted methods' do
|
|
716
|
+
# Sample of allowed methods from each category
|
|
717
|
+
allowed_sample = {
|
|
718
|
+
where: { name: 'Test' }, # Query selector
|
|
719
|
+
limit: 10, # Query option
|
|
720
|
+
skip: 5, # Query option
|
|
721
|
+
gt: { age: 18 }, # Query selector
|
|
722
|
+
in: { status: ['active'] }, # Query selector
|
|
723
|
+
ascending: :name, # Sorting
|
|
724
|
+
includes: :notes, # Eager loading
|
|
725
|
+
merge: { klass: Band }, # Merge
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
allowed_sample.each do |method, args|
|
|
729
|
+
hash = { klass: Band, method => args }
|
|
730
|
+
expect { hash.to_criteria }.not_to raise_error,
|
|
731
|
+
"Expected method '#{method}' to be allowed but it was blocked"
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
end
|
|
735
|
+
end
|
|
500
736
|
end
|
|
@@ -149,6 +149,18 @@ describe Mongoid::Touchable do
|
|
|
149
149
|
building.updated_at.should == update_time
|
|
150
150
|
end
|
|
151
151
|
end
|
|
152
|
+
|
|
153
|
+
context 'when not updating an additional field' do
|
|
154
|
+
it 'does not call set_field_atomic_updates' do
|
|
155
|
+
# Regression test for MONGOID-5833
|
|
156
|
+
entrance
|
|
157
|
+
update_time
|
|
158
|
+
expect(entrance).not_to receive(:set_field_atomic_updates)
|
|
159
|
+
entrance.touch
|
|
160
|
+
|
|
161
|
+
entrance.updated_at.should == update_time
|
|
162
|
+
end
|
|
163
|
+
end
|
|
152
164
|
end
|
|
153
165
|
|
|
154
166
|
context "when the document is referenced" do
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Candidate Tasks
|
|
2
|
+
|
|
3
|
+
When using the `candidate` rake tasks, you must make sure:
|
|
4
|
+
|
|
5
|
+
1. You are using at least `git` version 2.49.0.
|
|
6
|
+
2. You have the `gh` CLI tool installed.
|
|
7
|
+
3. You are logged into `gh` with an account that has collaborator access to the repository.
|
|
8
|
+
4. You have run `gh repo set-default` from the root of your local checkout to set the default repository to the canonical MongoDB repo.
|
|
9
|
+
5. The `origin` remote for your local checkout is set to your own fork.
|
|
10
|
+
6. The `upstream` remote for your local checkout is set to the canonical
|
|
11
|
+
MongoDB repo.
|
|
12
|
+
|
|
13
|
+
Once configured, you can use the following commands:
|
|
14
|
+
|
|
15
|
+
1. `rake candidate:prs` - This will list all pull requests that will be included in the next release. Any with `[?]` are unlabelled (or are not labelled with a recognized label). Otherwise, `[b]` means `bug`, `[f]` means `feature`, and `[x]` means `bcbreak`.
|
|
16
|
+
2. `rake candidate:preview` - This will generate and display the release notes for the next release, based on the associated pull requests.
|
|
17
|
+
3. `rake candidate:create` - This will create a new PR against the default repository, using the generated release notes as the description. The new PR will be given the `release-candidate` label.
|
|
18
|
+
|
|
19
|
+
Then, after the release candidate PR is approved and merged, the release process will automatically bundle, sign, and release the new version.
|
|
20
|
+
|
|
21
|
+
Once you've merged the PR, you can switch to the "Actions" tab for the repository on GitHub and look for the "Release" workflow (might be named differently), which should have triggered automatically. You can monitor the progress of the release there. If there are any problems, the workflow is generally safe to re-run after you've addressed them.
|
|
22
|
+
|
|
23
|
+
Things to do after the release succeeds:
|
|
24
|
+
|
|
25
|
+
1. Copy the release notes from the PR and create a new release announcement on the forums (https://www.mongodb.com/community/forums/c/announcements/driver-releases/110).
|
|
26
|
+
2. If the release was not automatically announced in #ruby, copy a link to the GitHub release or MongoDB forum post there.
|
|
27
|
+
3. Close the release in Jira.
|
|
28
|
+
|
|
@@ -25,19 +25,19 @@ module Mrss
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def initialize(root: nil, classifiers:, priority_order:,
|
|
28
|
-
spec_root: nil, rspec_json_path: nil, rspec_all_json_path: nil,
|
|
29
|
-
randomize: false
|
|
28
|
+
spec_root: nil, rspec_json_path: nil, rspec_all_json_path: nil, rspec_xml_path: nil, randomize: false
|
|
30
29
|
)
|
|
31
30
|
@spec_root = spec_root || File.join(root, 'spec')
|
|
32
31
|
@classifiers = classifiers
|
|
33
32
|
@priority_order = priority_order
|
|
34
33
|
@rspec_json_path = rspec_json_path || File.join(root, 'tmp/rspec.json')
|
|
35
34
|
@rspec_all_json_path = rspec_all_json_path || File.join(root, 'tmp/rspec-all.json')
|
|
35
|
+
@rspec_xml_path = rspec_xml_path || File.join(root, 'tmp/rspec.xml')
|
|
36
36
|
@randomize = !!randomize
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
attr_reader :spec_root, :classifiers, :priority_order
|
|
40
|
-
attr_reader :rspec_json_path, :rspec_all_json_path
|
|
40
|
+
attr_reader :rspec_json_path, :rspec_all_json_path, :rspec_xml_path
|
|
41
41
|
|
|
42
42
|
def randomize?
|
|
43
43
|
@randomize
|
|
@@ -47,6 +47,25 @@ module Mrss
|
|
|
47
47
|
@seed ||= (rand * 100_000).to_i
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
# Remove all XML files from tmp directory before running tests
|
|
51
|
+
def cleanup_xml_files
|
|
52
|
+
xml_pattern = File.join(File.dirname(rspec_xml_path), '*.xml')
|
|
53
|
+
Dir.glob(xml_pattern).each do |xml_file|
|
|
54
|
+
FileUtils.rm_f(xml_file)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Move the XML file to a timestamped version for evergreen upload
|
|
59
|
+
def archive_xml_file(category)
|
|
60
|
+
return unless File.exist?(rspec_xml_path)
|
|
61
|
+
|
|
62
|
+
timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%3N')
|
|
63
|
+
archived_path = rspec_xml_path.sub(/\.xml$/, "-#{category}-#{timestamp}.xml")
|
|
64
|
+
|
|
65
|
+
FileUtils.mv(rspec_xml_path, archived_path)
|
|
66
|
+
puts "Archived XML results to #{archived_path}"
|
|
67
|
+
end
|
|
68
|
+
|
|
50
69
|
def buckets
|
|
51
70
|
@buckets ||= {}.tap do |buckets|
|
|
52
71
|
Find.find(spec_root) do |path|
|
|
@@ -96,6 +115,8 @@ module Mrss
|
|
|
96
115
|
|
|
97
116
|
def run_buckets(*buckets)
|
|
98
117
|
FileUtils.rm_f(rspec_all_json_path)
|
|
118
|
+
# Clean up all XML files before starting test runs
|
|
119
|
+
cleanup_xml_files
|
|
99
120
|
|
|
100
121
|
buckets.each do |bucket|
|
|
101
122
|
if bucket && !self.buckets[bucket]
|
|
@@ -131,7 +152,12 @@ module Mrss
|
|
|
131
152
|
def run_files(category, paths)
|
|
132
153
|
puts "Running #{category.to_s.gsub('_', ' ')} tests"
|
|
133
154
|
FileUtils.rm_f(rspec_json_path)
|
|
155
|
+
FileUtils.rm_f(rspec_xml_path) # Clean up XML file before running this bucket
|
|
156
|
+
|
|
134
157
|
cmd = %w(rspec) + paths
|
|
158
|
+
# Add junit formatter for XML output
|
|
159
|
+
cmd += ['--format', 'Rfc::Riff', '--format', 'RspecJunitFormatter', '--out', rspec_xml_path]
|
|
160
|
+
|
|
135
161
|
if randomize?
|
|
136
162
|
cmd += %W(--order rand:#{seed})
|
|
137
163
|
end
|
|
@@ -147,6 +173,9 @@ module Mrss
|
|
|
147
173
|
FileUtils.cp(rspec_json_path, rspec_all_json_path)
|
|
148
174
|
end
|
|
149
175
|
end
|
|
176
|
+
|
|
177
|
+
# Archive XML file after running this bucket
|
|
178
|
+
archive_xml_file(category)
|
|
150
179
|
end
|
|
151
180
|
|
|
152
181
|
true
|
data/spec/shared/shlib/server.sh
CHANGED
|
@@ -78,7 +78,7 @@ install_mlaunch_venv() {
|
|
|
78
78
|
# Debian11/Ubuntu2204 have venv installed, but it is nonfunctional unless
|
|
79
79
|
# the python3-venv package is also installed (it lacks the ensurepip
|
|
80
80
|
# module).
|
|
81
|
-
sudo apt-get install --yes python3-venv
|
|
81
|
+
sudo apt-get update && sudo apt-get install --yes python3-venv
|
|
82
82
|
fi
|
|
83
83
|
if test "$USE_SYSTEM_PYTHON_PACKAGES" = 1 &&
|
|
84
84
|
python3 -m pip list |grep mtools
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mongoid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.1.
|
|
4
|
+
version: 8.1.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The MongoDB Ruby Team
|
|
@@ -862,6 +862,7 @@ files:
|
|
|
862
862
|
- spec/mongoid_spec.rb
|
|
863
863
|
- spec/rails/controller_extension/controller_runtime_spec.rb
|
|
864
864
|
- spec/rails/mongoid_spec.rb
|
|
865
|
+
- spec/shared/CANDIDATE.md
|
|
865
866
|
- spec/shared/LICENSE
|
|
866
867
|
- spec/shared/bin/get-mongodb-download-url
|
|
867
868
|
- spec/shared/bin/s3-copy
|
|
@@ -1186,7 +1187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
1186
1187
|
- !ruby/object:Gem::Version
|
|
1187
1188
|
version: 1.3.6
|
|
1188
1189
|
requirements: []
|
|
1189
|
-
rubygems_version:
|
|
1190
|
+
rubygems_version: 4.0.4
|
|
1190
1191
|
specification_version: 4
|
|
1191
1192
|
summary: Elegant Persistence in Ruby for MongoDB.
|
|
1192
1193
|
test_files:
|
|
@@ -1581,6 +1582,7 @@ test_files:
|
|
|
1581
1582
|
- spec/mongoid_spec.rb
|
|
1582
1583
|
- spec/rails/controller_extension/controller_runtime_spec.rb
|
|
1583
1584
|
- spec/rails/mongoid_spec.rb
|
|
1585
|
+
- spec/shared/CANDIDATE.md
|
|
1584
1586
|
- spec/shared/LICENSE
|
|
1585
1587
|
- spec/shared/bin/get-mongodb-download-url
|
|
1586
1588
|
- spec/shared/bin/s3-copy
|