Skip to content

Commit f52a4a7

Browse files
committed
MONGOID-4889 Optimize batch assignment of embedded documents
1 parent fe9397e commit f52a4a7

File tree

3 files changed

+82
-10
lines changed

3 files changed

+82
-10
lines changed

lib/mongoid/association/embedded/batchable.rb

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -313,18 +313,19 @@ def selector
313313
#
314314
# @return [ Array<Hash> ] The documents as an array of hashes.
315315
def pre_process_batch_insert(docs)
316-
docs.map do |doc|
317-
next unless doc
318-
append(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
316+
[].tap do |results|
317+
append_many(docs) do |doc|
318+
if persistable? && !_assigning?
319+
self.path = doc.atomic_path unless path
320+
if doc.valid?(:create)
321+
doc.run_before_callbacks(:save, :create)
322+
else
323+
self.inserts_valid = false
324+
end
325325
end
326+
327+
results << doc.send(:as_attributes)
326328
end
327-
doc.send(:as_attributes)
328329
end
329330
end
330331

lib/mongoid/association/embedded/embeds_many/proxy.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,36 @@ def append(document)
443443
execute_callback :after_add, document
444444
end
445445

446+
# Optimized version of #append that handles multiple documents
447+
# in a more efficient way.
448+
def append_many(documents, &block)
449+
id_of = ->(doc){ doc._id || doc.object_id }
450+
451+
visited_docs = Set.new(_target.map(&id_of))
452+
next_index = _unscoped.size
453+
454+
unique_set = documents.select do |doc|
455+
next unless doc
456+
next if visited_docs.include?(id_of[doc])
457+
458+
execute_callback :before_add, doc
459+
460+
visited_docs.add(id_of[doc])
461+
integrate(doc)
462+
463+
doc._index = next_index
464+
next_index += 1
465+
466+
block.call(doc) if block
467+
end
468+
469+
_unscoped.concat(unique_set)
470+
_target.push(*scope(unique_set))
471+
update_attributes_hash
472+
473+
unique_set.each { |doc| execute_callback :after_add, doc }
474+
end
475+
446476
# Instantiate the binding associated with this association.
447477
#
448478
# @example Create the binding.

spec/integration/associations/embeds_many_spec.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,47 @@
201201
include_examples 'persists correctly'
202202
end
203203
end
204+
205+
context 'including duplicates in the assignment' do
206+
let(:canvas) do
207+
Canvas.create!(shapes: [Shape.new])
208+
end
209+
210+
shared_examples 'persists correctly' do
211+
it 'persists correctly' do
212+
canvas.shapes.length.should eq 2
213+
_canvas = Canvas.find(canvas.id)
214+
_canvas.shapes.length.should eq 2
215+
end
216+
end
217+
218+
context 'via assignment operator' do
219+
before do
220+
canvas.shapes = [ canvas.shapes.first, Shape.new, canvas.shapes.first ]
221+
canvas.save!
222+
end
223+
224+
include_examples 'persists correctly'
225+
end
226+
227+
context 'via attributes=' do
228+
before do
229+
canvas.attributes = { shapes: [ canvas.shapes.first, Shape.new, canvas.shapes.first ] }
230+
canvas.save!
231+
end
232+
233+
include_examples 'persists correctly'
234+
end
235+
236+
context 'via assign_attributes' do
237+
before do
238+
canvas.assign_attributes(shapes: [ canvas.shapes.first, Shape.new, canvas.shapes.first ])
239+
canvas.save!
240+
end
241+
242+
include_examples 'persists correctly'
243+
end
244+
end
204245
end
205246

206247
context 'when an anonymous class defines an embeds_many association' do

0 commit comments

Comments
 (0)