mongoid 9.0.6 → 9.0.8
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/Rakefile +9 -9
- data/lib/mongoid/association/embedded/batchable.rb +11 -10
- data/lib/mongoid/association/embedded/embeds_many/proxy.rb +61 -0
- data/lib/mongoid/association/embedded/embeds_one/proxy.rb +1 -1
- data/lib/mongoid/association/nested/many.rb +2 -0
- data/lib/mongoid/association/nested/one.rb +1 -1
- data/lib/mongoid/association/referenced/has_many/proxy.rb +0 -4
- data/lib/mongoid/changeable.rb +10 -1
- data/lib/mongoid/clients/sessions.rb +3 -4
- data/lib/mongoid/config.rb +1 -1
- data/lib/mongoid/contextual/aggregable/mongo.rb +6 -1
- data/lib/mongoid/contextual/mongo.rb +1 -1
- data/lib/mongoid/railties/bson_object_id_serializer.rb +7 -0
- data/lib/mongoid/reloadable.rb +6 -0
- data/lib/mongoid/validatable/associated.rb +1 -1
- data/lib/mongoid/validatable/macros.rb +15 -0
- data/lib/mongoid/validatable/numericality.rb +19 -0
- data/lib/mongoid/validatable.rb +1 -0
- data/lib/mongoid/version.rb +5 -2
- data/spec/integration/app_spec.rb +6 -0
- data/spec/integration/associations/embeds_many_spec.rb +110 -0
- data/spec/integration/associations/embeds_one_spec.rb +25 -6
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +81 -0
- data/spec/integration/associations/has_many_spec.rb +56 -0
- data/spec/integration/associations/has_one_spec.rb +55 -3
- data/spec/mongoid/association/referenced/has_many_models.rb +24 -0
- data/spec/mongoid/association/referenced/has_one_models.rb +10 -2
- data/spec/mongoid/association_spec.rb +0 -60
- data/spec/mongoid/clients/transactions_spec.rb +162 -1
- data/spec/mongoid/clients/transactions_spec_models.rb +93 -0
- data/spec/mongoid/contextual/aggregable/mongo_spec.rb +33 -0
- data/spec/mongoid/contextual/mongo_spec.rb +6 -0
- data/spec/mongoid/reloadable_spec.rb +24 -0
- data/spec/mongoid/validatable/numericality_spec.rb +16 -0
- data/spec/shared/LICENSE +20 -0
- data/spec/shared/bin/get-mongodb-download-url +17 -0
- data/spec/shared/bin/s3-copy +45 -0
- data/spec/shared/bin/s3-upload +69 -0
- data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
- data/spec/shared/lib/mrss/cluster_config.rb +231 -0
- data/spec/shared/lib/mrss/constraints.rb +378 -0
- data/spec/shared/lib/mrss/docker_runner.rb +298 -0
- data/spec/shared/lib/mrss/eg_config_utils.rb +51 -0
- data/spec/shared/lib/mrss/event_subscriber.rb +210 -0
- data/spec/shared/lib/mrss/lite_constraints.rb +238 -0
- data/spec/shared/lib/mrss/release/candidate.rb +281 -0
- data/spec/shared/lib/mrss/release/product_data.rb +144 -0
- data/spec/shared/lib/mrss/server_version_registry.rb +113 -0
- data/spec/shared/lib/mrss/session_registry.rb +69 -0
- data/spec/shared/lib/mrss/session_registry_legacy.rb +60 -0
- data/spec/shared/lib/mrss/spec_organizer.rb +179 -0
- data/spec/shared/lib/mrss/utils.rb +37 -0
- data/spec/shared/lib/tasks/candidate.rake +64 -0
- data/spec/shared/share/Dockerfile.erb +251 -0
- data/spec/shared/share/haproxy-1.conf +16 -0
- data/spec/shared/share/haproxy-2.conf +17 -0
- data/spec/shared/shlib/config.sh +27 -0
- data/spec/shared/shlib/distro.sh +84 -0
- data/spec/shared/shlib/server.sh +423 -0
- data/spec/shared/shlib/set_env.sh +110 -0
- data/spec/support/expectations.rb +20 -18
- metadata +59 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '032258516f15736f4121de82ee4cdcdb625515f653f85a66b0df613665236e4d'
|
4
|
+
data.tar.gz: 443ec24d9a54bb10c87023cd6304081aa48bf92bf5654044cc935f79ca954b9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c875bf631d019da208fa59221a74e3468931a802664e43ce2e8ed669e990722a499d8976a8acafb8078dbeff8c02a76df96b9d76f8e33c0c979658f40748fd9
|
7
|
+
data.tar.gz: 46943ea7b081db4784159bacc2056b75b6511f8c8892910dc2c2bfc7ee1229a6ddf0d9141f52137a12a04eea841c24e16b4eae7fb8fc15bd26e2dec7d0273ecc
|
data/Rakefile
CHANGED
@@ -11,16 +11,16 @@ $: << File.join(ROOT, 'spec/shared/lib')
|
|
11
11
|
require "rake"
|
12
12
|
require "rspec/core/rake_task"
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
if File.exist?('./spec/shared/lib/tasks/candidate.rake')
|
15
|
+
load 'spec/shared/lib/tasks/candidate.rake'
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Build the gem'
|
18
19
|
task :build do
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
WARNING
|
20
|
+
command = %w[ gem build ]
|
21
|
+
command << "--output=#{ENV['GEM_FILE_NAME']}" if ENV['GEM_FILE_NAME']
|
22
|
+
command << (ENV['GEMSPEC'] || 'mongoid.gemspec')
|
23
|
+
system(*command)
|
24
24
|
end
|
25
25
|
|
26
26
|
# `rake version` is used by the deployment system so get the release version
|
@@ -313,18 +313,19 @@ module Mongoid
|
|
313
313
|
#
|
314
314
|
# @return [ Array<Hash> ] The documents as an array of hashes.
|
315
315
|
def pre_process_batch_insert(docs)
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
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
|
325
325
|
end
|
326
|
+
|
327
|
+
results << doc.send(:as_attributes)
|
326
328
|
end
|
327
|
-
doc.send(:as_attributes)
|
328
329
|
end
|
329
330
|
end
|
330
331
|
|
@@ -443,6 +443,67 @@ module Mongoid
|
|
443
443
|
execute_callback :after_add, document
|
444
444
|
end
|
445
445
|
|
446
|
+
# Returns a unique id for the document, which is either
|
447
|
+
# its _id or its object_id.
|
448
|
+
def id_of(doc)
|
449
|
+
doc._id || doc.object_id
|
450
|
+
end
|
451
|
+
|
452
|
+
# Optimized version of #append that handles multiple documents
|
453
|
+
# in a more efficient way.
|
454
|
+
#
|
455
|
+
# @param [ Array<Document> ] documents The documents to append.
|
456
|
+
#
|
457
|
+
# @return [ EmbedsMany::Proxy ] This proxy instance.
|
458
|
+
def append_many(documents, &block)
|
459
|
+
unique_set = process_incoming_docs(documents, &block)
|
460
|
+
|
461
|
+
_unscoped.concat(unique_set)
|
462
|
+
_target.push(*scope(unique_set))
|
463
|
+
update_attributes_hash
|
464
|
+
|
465
|
+
unique_set.each { |doc| execute_callback :after_add, doc }
|
466
|
+
|
467
|
+
self
|
468
|
+
end
|
469
|
+
|
470
|
+
# Processes the list of documents, building a list of those
|
471
|
+
# that are not already in the association, and preparing
|
472
|
+
# each unique document to be integrated into the association.
|
473
|
+
#
|
474
|
+
# The :before_add callback is executed for each unique document
|
475
|
+
# as part of this step.
|
476
|
+
#
|
477
|
+
# @param [ Array<Document> ] documents The incoming documents to
|
478
|
+
# process.
|
479
|
+
#
|
480
|
+
# @yield [ Document ] Optional block to call for each unique
|
481
|
+
# document.
|
482
|
+
#
|
483
|
+
# @return [ Array<Document> ] The list of unique documents that
|
484
|
+
# do not yet exist in the association.
|
485
|
+
def process_incoming_docs(documents, &block)
|
486
|
+
visited_docs = Set.new(_target.map { |doc| id_of(doc) })
|
487
|
+
next_index = _unscoped.size
|
488
|
+
|
489
|
+
documents.select do |doc|
|
490
|
+
next unless doc
|
491
|
+
|
492
|
+
id = id_of(doc)
|
493
|
+
next if visited_docs.include?(id)
|
494
|
+
|
495
|
+
execute_callback :before_add, doc
|
496
|
+
|
497
|
+
visited_docs.add(id)
|
498
|
+
integrate(doc)
|
499
|
+
|
500
|
+
doc._index = next_index
|
501
|
+
next_index += 1
|
502
|
+
|
503
|
+
block&.call(doc) || true
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
446
507
|
# Instantiate the binding associated with this association.
|
447
508
|
#
|
448
509
|
# @example Create the binding.
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# TODO: consider refactoring this Proxy class, to satisfy the following
|
4
|
-
# cops...
|
5
|
-
# rubocop:disable Metrics/ClassLength
|
6
3
|
module Mongoid
|
7
4
|
module Association
|
8
5
|
module Referenced
|
@@ -588,4 +585,3 @@ module Mongoid
|
|
588
585
|
end
|
589
586
|
end
|
590
587
|
end
|
591
|
-
# rubocop:enable Metrics/ClassLength
|
data/lib/mongoid/changeable.rb
CHANGED
@@ -15,6 +15,14 @@ module Mongoid
|
|
15
15
|
changed_attributes.keys.select { |attr| attribute_change(attr) }
|
16
16
|
end
|
17
17
|
|
18
|
+
# Indicates that the children of this document may have changed, and
|
19
|
+
# ought to be checked when the document is validated.
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def children_may_have_changed!
|
23
|
+
@children_may_have_changed = true
|
24
|
+
end
|
25
|
+
|
18
26
|
# Has the document changed?
|
19
27
|
#
|
20
28
|
# @example Has the document changed?
|
@@ -31,7 +39,7 @@ module Mongoid
|
|
31
39
|
#
|
32
40
|
# @return [ true | false ] If any children have changed.
|
33
41
|
def children_changed?
|
34
|
-
_children.any?(&:changed?)
|
42
|
+
@children_may_have_changed || _children.any?(&:changed?)
|
35
43
|
end
|
36
44
|
|
37
45
|
# Get the attribute changes.
|
@@ -69,6 +77,7 @@ module Mongoid
|
|
69
77
|
@previous_changes = changes
|
70
78
|
@attributes_before_last_save = @previous_attributes
|
71
79
|
@previous_attributes = attributes.dup
|
80
|
+
@children_may_have_changed = false
|
72
81
|
reset_atomic_updates!
|
73
82
|
changed_attributes.clear
|
74
83
|
end
|
@@ -92,8 +92,7 @@ module Mongoid
|
|
92
92
|
begin
|
93
93
|
session.with_transaction(options) do
|
94
94
|
yield
|
95
|
-
end
|
96
|
-
run_commit_callbacks(session)
|
95
|
+
end.tap { run_commit_callbacks(session) }
|
97
96
|
rescue *transactions_not_supported_exceptions
|
98
97
|
raise Mongoid::Errors::TransactionsNotSupported
|
99
98
|
rescue Mongoid::Errors::Rollback
|
@@ -213,8 +212,8 @@ module Mongoid
|
|
213
212
|
|
214
213
|
# Transforms custom options for after_commit and after_rollback callbacks
|
215
214
|
# into options for +set_callback+.
|
216
|
-
def set_options_for_callbacks!(args)
|
217
|
-
options = args.extract_options
|
215
|
+
def set_options_for_callbacks!(args, enforced_options = {})
|
216
|
+
options = args.extract_options!.merge(enforced_options)
|
218
217
|
args << options
|
219
218
|
|
220
219
|
if options[:on]
|
data/lib/mongoid/config.rb
CHANGED
@@ -102,7 +102,7 @@ module Mongoid
|
|
102
102
|
#
|
103
103
|
# - :immediate - Initializes a single +Concurrent::ImmediateExecutor+
|
104
104
|
# - :global_thread_pool - Initializes a single +Concurrent::ThreadPoolExecutor+
|
105
|
-
# that uses the +
|
105
|
+
# that uses the +global_executor_concurrency+ for the +max_threads+ value.
|
106
106
|
option :async_query_executor, default: :immediate
|
107
107
|
|
108
108
|
# Defines how many asynchronous queries can be executed concurrently.
|
@@ -27,7 +27,12 @@ module Mongoid
|
|
27
27
|
# If no documents are found, then returned Hash will have
|
28
28
|
# count, sum of 0 and max, min, avg of nil.
|
29
29
|
def aggregates(field)
|
30
|
-
result = collection.aggregate(
|
30
|
+
result = collection.aggregate(
|
31
|
+
pipeline(field),
|
32
|
+
session: _session,
|
33
|
+
hint: view.hint
|
34
|
+
).to_a
|
35
|
+
|
31
36
|
if result.empty?
|
32
37
|
Aggregable::EMPTY_RESULT.dup
|
33
38
|
else
|
@@ -1063,7 +1063,7 @@ module Mongoid
|
|
1063
1063
|
end
|
1064
1064
|
|
1065
1065
|
def retrieve_nth_to_last_with_limit(n, limit)
|
1066
|
-
v = view.sort(inverse_sorting).
|
1066
|
+
v = view.sort(inverse_sorting).limit(limit || 1)
|
1067
1067
|
v = v.skip(n) if n > 0
|
1068
1068
|
raw_docs = v.to_a.reverse
|
1069
1069
|
process_raw_docs(raw_docs, limit)
|
@@ -33,6 +33,13 @@ module Mongoid
|
|
33
33
|
def deserialize(string)
|
34
34
|
BSON::ObjectId.from_string(string)
|
35
35
|
end
|
36
|
+
|
37
|
+
# Returns the klass this serializer handles.
|
38
|
+
#
|
39
|
+
# @return [ BSON::ObjectId ] The class this serializer handles.
|
40
|
+
def klass
|
41
|
+
BSON::ObjectId
|
42
|
+
end
|
36
43
|
end
|
37
44
|
end
|
38
45
|
end
|
data/lib/mongoid/reloadable.rb
CHANGED
@@ -17,6 +17,12 @@ module Mongoid
|
|
17
17
|
reloaded = _reload
|
18
18
|
check_for_deleted_document!(reloaded)
|
19
19
|
|
20
|
+
# In an instance where we create a new document, but set the ID to an existing one,
|
21
|
+
# when the document is reloaded, we want to set new_record to false.
|
22
|
+
# This is necessary otherwise saving will fail, as it will try to insert the document,
|
23
|
+
# instead of attempting to update the existing document.
|
24
|
+
@new_record = false unless reloaded.nil? || reloaded.empty?
|
25
|
+
|
20
26
|
reset_object!(reloaded)
|
21
27
|
|
22
28
|
run_callbacks(:find) unless _find_callbacks.empty?
|
@@ -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? && (!value.persisted? || value.changed?)
|
78
78
|
value.validated? ? true : value.valid?
|
79
79
|
else
|
80
80
|
true
|
@@ -89,6 +89,21 @@ module Mongoid
|
|
89
89
|
def validates_presence_of(*args)
|
90
90
|
validates_with(PresenceValidator, _merge_attributes(args))
|
91
91
|
end
|
92
|
+
|
93
|
+
# Validates whether or not a field contains a numeric value.
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
# class Person
|
97
|
+
# include Mongoid::Document
|
98
|
+
# field :cost
|
99
|
+
#
|
100
|
+
# validates_numericality_of :cost
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# @param [ Object... ] *args The names of the field(s) to validate.
|
104
|
+
def validates_numericality_of(*args)
|
105
|
+
validates_with(NumericalityValidator, _merge_attributes(args))
|
106
|
+
end
|
92
107
|
end
|
93
108
|
end
|
94
109
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Validatable
|
5
|
+
# A specialization of the ActiveModel numericality validator, which adds
|
6
|
+
# logic to recognize and accept BSON::Decimal128 as a number.
|
7
|
+
class NumericalityValidator < ActiveModel::Validations::NumericalityValidator
|
8
|
+
private
|
9
|
+
|
10
|
+
# Ensure that BSON::Decimal128 is treated as a BigDecimal during the
|
11
|
+
# validation step.
|
12
|
+
def prepare_value_for_validation(value, record, attr_name)
|
13
|
+
result = super
|
14
|
+
|
15
|
+
result.is_a?(BSON::Decimal128) ? result.to_big_decimal : result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/mongoid/validatable.rb
CHANGED
@@ -6,6 +6,7 @@ require "mongoid/validatable/localizable"
|
|
6
6
|
require "mongoid/validatable/associated"
|
7
7
|
require "mongoid/validatable/format"
|
8
8
|
require "mongoid/validatable/length"
|
9
|
+
require "mongoid/validatable/numericality"
|
9
10
|
require "mongoid/validatable/queryable"
|
10
11
|
require "mongoid/validatable/presence"
|
11
12
|
require "mongoid/validatable/uniqueness"
|
data/lib/mongoid/version.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# rubocop:todo all
|
3
2
|
|
4
3
|
module Mongoid
|
5
|
-
|
4
|
+
# The current version of Mongoid
|
5
|
+
#
|
6
|
+
# Note that this file is automatically updated via `rake candidate:create`.
|
7
|
+
# Manual changes to this file will be overwritten by that rake task.
|
8
|
+
VERSION = '9.0.8'
|
6
9
|
end
|
@@ -127,6 +127,12 @@ describe 'Mongoid application tests' do
|
|
127
127
|
end
|
128
128
|
|
129
129
|
context 'new application - rails' do
|
130
|
+
before(:all) do
|
131
|
+
if SpecConfig.instance.rails_version < '7.1'
|
132
|
+
skip '`rails new` with rails < 7.1 fails because modern concurrent-ruby removed logger dependency'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
130
136
|
it 'creates' do
|
131
137
|
prepare_new_rails_app 'mongoid-test' do
|
132
138
|
check_call(%w(rails g model post), env: clean_env)
|
@@ -3,6 +3,24 @@
|
|
3
3
|
|
4
4
|
require 'spec_helper'
|
5
5
|
|
6
|
+
module EmbedsManySpec
|
7
|
+
class Post
|
8
|
+
include Mongoid::Document
|
9
|
+
field :title, type: String
|
10
|
+
embeds_many :comments, class_name: 'EmbedsManySpec::Comment', as: :container
|
11
|
+
accepts_nested_attributes_for :comments
|
12
|
+
end
|
13
|
+
|
14
|
+
class Comment
|
15
|
+
include Mongoid::Document
|
16
|
+
field :content, type: String
|
17
|
+
validates :content, presence: true
|
18
|
+
embedded_in :container, polymorphic: true
|
19
|
+
embeds_many :comments, class_name: 'EmbedsManySpec::Comment', as: :container
|
20
|
+
accepts_nested_attributes_for :comments
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
6
24
|
describe 'embeds_many associations' do
|
7
25
|
|
8
26
|
context 're-associating the same object' do
|
@@ -201,6 +219,47 @@ describe 'embeds_many associations' do
|
|
201
219
|
include_examples 'persists correctly'
|
202
220
|
end
|
203
221
|
end
|
222
|
+
|
223
|
+
context 'including duplicates in the assignment' do
|
224
|
+
let(:canvas) do
|
225
|
+
Canvas.create!(shapes: [Shape.new])
|
226
|
+
end
|
227
|
+
|
228
|
+
shared_examples 'persists correctly' do
|
229
|
+
it 'persists correctly' do
|
230
|
+
canvas.shapes.length.should eq 2
|
231
|
+
_canvas = Canvas.find(canvas.id)
|
232
|
+
_canvas.shapes.length.should eq 2
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
context 'via assignment operator' do
|
237
|
+
before do
|
238
|
+
canvas.shapes = [ canvas.shapes.first, Shape.new, canvas.shapes.first ]
|
239
|
+
canvas.save!
|
240
|
+
end
|
241
|
+
|
242
|
+
include_examples 'persists correctly'
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'via attributes=' do
|
246
|
+
before do
|
247
|
+
canvas.attributes = { shapes: [ canvas.shapes.first, Shape.new, canvas.shapes.first ] }
|
248
|
+
canvas.save!
|
249
|
+
end
|
250
|
+
|
251
|
+
include_examples 'persists correctly'
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'via assign_attributes' do
|
255
|
+
before do
|
256
|
+
canvas.assign_attributes(shapes: [ canvas.shapes.first, Shape.new, canvas.shapes.first ])
|
257
|
+
canvas.save!
|
258
|
+
end
|
259
|
+
|
260
|
+
include_examples 'persists correctly'
|
261
|
+
end
|
262
|
+
end
|
204
263
|
end
|
205
264
|
|
206
265
|
context 'when an anonymous class defines an embeds_many association' do
|
@@ -217,4 +276,55 @@ describe 'embeds_many associations' do
|
|
217
276
|
expect(klass.new.addresses.build).to be_a Address
|
218
277
|
end
|
219
278
|
end
|
279
|
+
|
280
|
+
context 'with deeply nested trees' do
|
281
|
+
let(:post) { EmbedsManySpec::Post.create!(title: 'Post') }
|
282
|
+
let(:child) { post.comments.create!(content: 'Child') }
|
283
|
+
|
284
|
+
# creating grandchild will cascade to create the other documents
|
285
|
+
let!(:grandchild) { child.comments.create!(content: 'Grandchild') }
|
286
|
+
|
287
|
+
let(:updated_parent_title) { 'Post Updated' }
|
288
|
+
let(:updated_grandchild_content) { 'Grandchild Updated' }
|
289
|
+
|
290
|
+
context 'with nested attributes' do
|
291
|
+
let(:attributes) do
|
292
|
+
{
|
293
|
+
title: updated_parent_title,
|
294
|
+
comments_attributes: [
|
295
|
+
{
|
296
|
+
# no change for comment1
|
297
|
+
_id: child.id,
|
298
|
+
comments_attributes: [
|
299
|
+
{
|
300
|
+
_id: grandchild.id,
|
301
|
+
content: updated_grandchild_content,
|
302
|
+
}
|
303
|
+
]
|
304
|
+
}
|
305
|
+
]
|
306
|
+
}
|
307
|
+
end
|
308
|
+
|
309
|
+
context 'when the grandchild is invalid' do
|
310
|
+
let(:updated_grandchild_content) { '' } # invalid value
|
311
|
+
|
312
|
+
it 'will not save the parent' do
|
313
|
+
expect(post.update(attributes)).to be_falsey
|
314
|
+
expect(post.errors).not_to be_empty
|
315
|
+
expect(post.reload.title).not_to eq(updated_parent_title)
|
316
|
+
expect(grandchild.reload.content).not_to eq(updated_grandchild_content)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
context 'when the grandchild is valid' do
|
321
|
+
it 'will save the parent' do
|
322
|
+
expect(post.update(attributes)).to be_truthy
|
323
|
+
expect(post.errors).to be_empty
|
324
|
+
expect(post.reload.title).to eq(updated_parent_title)
|
325
|
+
expect(grandchild.reload.content).to eq(updated_grandchild_content)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
220
330
|
end
|
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# rubocop:todo all
|
3
2
|
|
4
3
|
require 'spec_helper'
|
5
4
|
|
6
5
|
describe 'embeds_one associations' do
|
7
|
-
|
8
|
-
context 're-associating the same object' do
|
6
|
+
context 'when re-associating the same object' do
|
9
7
|
context 'with dependent: destroy' do
|
10
8
|
let(:canvas) do
|
11
9
|
Canvas.create!(palette: Palette.new)
|
@@ -17,7 +15,7 @@ describe 'embeds_one associations' do
|
|
17
15
|
canvas.palette = canvas.palette
|
18
16
|
canvas.save!
|
19
17
|
canvas.reload
|
20
|
-
canvas.palette.
|
18
|
+
expect(canvas.palette).to eq palette
|
21
19
|
end
|
22
20
|
end
|
23
21
|
end
|
@@ -31,12 +29,33 @@ describe 'embeds_one associations' do
|
|
31
29
|
end
|
32
30
|
|
33
31
|
it 'loads the association correctly' do
|
34
|
-
expect { klass }.
|
35
|
-
expect { klass.new.address }.
|
32
|
+
expect { klass }.not_to raise_error
|
33
|
+
expect { klass.new.address }.not_to raise_error
|
36
34
|
instance = klass.new
|
37
35
|
address = Address.new
|
38
36
|
instance.address = address
|
39
37
|
expect(instance.address).to eq address
|
40
38
|
end
|
41
39
|
end
|
40
|
+
|
41
|
+
context 'when parent is persisted' do
|
42
|
+
let!(:person) do
|
43
|
+
Person.create!
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when assigning the new child' do
|
47
|
+
context 'when assigning an attribute to the child' do
|
48
|
+
before do
|
49
|
+
# person.reload
|
50
|
+
person.name = Name.new
|
51
|
+
person.name.first_name = 'Dmitry'
|
52
|
+
person.save!
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'persists the child' do
|
56
|
+
expect(person.reload.name.first_name).to eq 'Dmitry'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
42
61
|
end
|
@@ -23,6 +23,38 @@ module HabtmSpec
|
|
23
23
|
include Mongoid::Document
|
24
24
|
field :file, type: String
|
25
25
|
end
|
26
|
+
|
27
|
+
class Item
|
28
|
+
include Mongoid::Document
|
29
|
+
|
30
|
+
field :title, type: String
|
31
|
+
|
32
|
+
has_and_belongs_to_many :colors, class_name: 'HabtmSpec::Color', inverse_of: :items
|
33
|
+
|
34
|
+
accepts_nested_attributes_for :colors
|
35
|
+
end
|
36
|
+
|
37
|
+
class Beam
|
38
|
+
include Mongoid::Document
|
39
|
+
|
40
|
+
field :name, type: String
|
41
|
+
validates :name, presence: true
|
42
|
+
|
43
|
+
has_and_belongs_to_many :colors, class_name: 'HabtmSpec::Color', inverse_of: :beams
|
44
|
+
|
45
|
+
accepts_nested_attributes_for :colors
|
46
|
+
end
|
47
|
+
|
48
|
+
class Color
|
49
|
+
include Mongoid::Document
|
50
|
+
|
51
|
+
field :name, type: String
|
52
|
+
|
53
|
+
has_and_belongs_to_many :items, class_name: 'HabtmSpec::Item', inverse_of: :colors
|
54
|
+
has_and_belongs_to_many :beams, class_name: 'HabtmSpec::Beam', inverse_of: :colors
|
55
|
+
|
56
|
+
accepts_nested_attributes_for :items, :beams
|
57
|
+
end
|
26
58
|
end
|
27
59
|
|
28
60
|
describe 'has_and_belongs_to_many associations' do
|
@@ -59,4 +91,53 @@ describe 'has_and_belongs_to_many associations' do
|
|
59
91
|
expect { image_block.save! }.not_to raise_error
|
60
92
|
end
|
61
93
|
end
|
94
|
+
|
95
|
+
context 'with deeply nested trees' do
|
96
|
+
let(:item) { HabtmSpec::Item.create!(title: 'Item') }
|
97
|
+
let(:beam) { HabtmSpec::Beam.create!(name: 'Beam') }
|
98
|
+
let!(:color) { HabtmSpec::Color.create!(name: 'Red', items: [ item ], beams: [ beam ]) }
|
99
|
+
|
100
|
+
let(:updated_item_title) { 'Item Updated' }
|
101
|
+
let(:updated_beam_name) { 'Beam Updated' }
|
102
|
+
|
103
|
+
context 'with nested attributes' do
|
104
|
+
let(:attributes) do
|
105
|
+
{
|
106
|
+
title: updated_item_title,
|
107
|
+
colors_attributes: [
|
108
|
+
{
|
109
|
+
# no change for color
|
110
|
+
_id: color.id,
|
111
|
+
beams_attributes: [
|
112
|
+
{
|
113
|
+
_id: beam.id,
|
114
|
+
name: updated_beam_name,
|
115
|
+
}
|
116
|
+
]
|
117
|
+
}
|
118
|
+
]
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'when the beam is invalid' do
|
123
|
+
let(:updated_beam_name) { '' } # invalid value
|
124
|
+
|
125
|
+
it 'will not save the parent' do
|
126
|
+
expect(item.update(attributes)).to be_falsey
|
127
|
+
expect(item.errors).not_to be_empty
|
128
|
+
expect(item.reload.title).not_to eq(updated_item_title)
|
129
|
+
expect(beam.reload.name).not_to eq(updated_beam_name)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'when the beam is valid' do
|
134
|
+
it 'will save the parent' do
|
135
|
+
expect(item.update(attributes)).to be_truthy
|
136
|
+
expect(item.errors).to be_empty
|
137
|
+
expect(item.reload.title).to eq(updated_item_title)
|
138
|
+
expect(beam.reload.name).to eq(updated_beam_name)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
62
143
|
end
|