diff --git a/lib/mongoid/association/eager_loadable.rb b/lib/mongoid/association/eager_loadable.rb index 5ae3b67bfd..84cfd14996 100644 --- a/lib/mongoid/association/eager_loadable.rb +++ b/lib/mongoid/association/eager_loadable.rb @@ -31,6 +31,9 @@ def preload(associations, docs) docs_map = {} queue = [ klass.to_s ] + # account for single-collection inheritance + queue.push(klass.root_class.to_s) if klass != klass.root_class + while klass = queue.shift if as = assoc_map.delete(klass) as.each do |assoc| diff --git a/lib/mongoid/traversable.rb b/lib/mongoid/traversable.rb index 0098d4c4d2..79db072606 100644 --- a/lib/mongoid/traversable.rb +++ b/lib/mongoid/traversable.rb @@ -323,6 +323,18 @@ def hereditary? !!(Mongoid::Document > superclass) end + # Returns the root class of the STI tree that the current + # class participates in. If the class is not an STI subclass, this + # returns the class itself. + # + # @return [ Mongoid::Document ] the root of the STI tree + def root_class + root = self + root = root.superclass while root.hereditary? + + root + end + # When inheriting, we want to copy the fields from the parent class and # set the on the child to start, mimicking the behavior of the old # class_inheritable_accessor that was deprecated in Rails edge. diff --git a/lib/mongoid/validatable/associated.rb b/lib/mongoid/validatable/associated.rb index e8559c09fa..f729fb09d0 100644 --- a/lib/mongoid/validatable/associated.rb +++ b/lib/mongoid/validatable/associated.rb @@ -73,7 +73,7 @@ def validate_association(document, attribute) # use map.all? instead of just all?, because all? will do short-circuit # evaluation and terminate on the first failed validation. list.map do |value| - if value && !value.flagged_for_destroy? && (!value.persisted? || value.changed?) + if value && !value.flagged_for_destroy? value.validated? ? true : value.valid? else true diff --git a/spec/mongoid/association/eager_spec.rb b/spec/mongoid/association/eager_spec.rb index a194fc7411..2171f63040 100644 --- a/spec/mongoid/association/eager_spec.rb +++ b/spec/mongoid/association/eager_spec.rb @@ -14,14 +14,36 @@ Mongoid::Contextual::Mongo.new(criteria) end + let(:association_host) { Account } + let(:inclusions) do includes.map do |key| - Account.reflect_on_association(key) + association_host.reflect_on_association(key) end end let(:doc) { criteria.first } + context 'when root is an STI subclass' do + # Driver has_one Vehicle + # Vehicle belongs_to Driver + # Truck is a Vehicle + + before do + Driver.create!(vehicle: Truck.new) + end + + let(:criteria) { Truck.all } + let(:includes) { %i[ driver ] } + let(:association_host) { Truck } + + it 'preloads the driver' do + expect(doc.ivar(:driver)).to be false + context.preload(inclusions, [ doc ]) + expect(doc.ivar(:driver)).to be == Driver.first + end + end + context "when belongs_to" do let!(:account) do @@ -42,7 +64,7 @@ it "preloads the parent" do expect(doc.ivar(:person)).to be false context.preload(inclusions, [doc]) - expect(doc.ivar(:person)).to eq(doc.person) + expect(doc.ivar(:person)).to be == person end end diff --git a/spec/mongoid/association_spec.rb b/spec/mongoid/association_spec.rb index 043b451865..81b630c4f8 100644 --- a/spec/mongoid/association_spec.rb +++ b/spec/mongoid/association_spec.rb @@ -100,6 +100,66 @@ expect(name).to_not be_an_embedded_many end end + + context "when validation depends on association" do + before(:all) do + class Author + include Mongoid::Document + embeds_many :books, cascade_callbacks: true + field :condition, type: Boolean + end + + class Book + include Mongoid::Document + embedded_in :author + validate :parent_condition_is_not_true + + def parent_condition_is_not_true + return unless author&.condition + errors.add :base, "Author condition is true." + end + end + + Author.delete_all + Book.delete_all + end + + let(:author) { Author.new } + let(:book) { Book.new } + + context "when author is not persisted" do + it "is valid without books" do + expect(author.valid?).to be true + end + + it "is valid with a book" do + author.books << book + expect(author.valid?).to be true + end + + it "is not valid when condition is true with a book" do + author.condition = true + author.books << book + expect(author.valid?).to be false + end + end + + context "when author is persisted" do + before do + author.books << book + author.save + end + + it "remains valid initially" do + expect(author.valid?).to be true + end + + it "becomes invalid when condition is set to true" do + author.update_attributes(condition: true) + expect(author.valid?).to be false + end + end + end end describe "#embedded_one?" do