From 5c2620227a5b2b24eab57ed1f34f104486e7a558 Mon Sep 17 00:00:00 2001 From: Sean Edge Date: Fri, 29 Sep 2023 11:28:12 -0400 Subject: [PATCH] =?UTF-8?q?Fix=20eager=20loading=20of=20scoped=20associati?= =?UTF-8?q?ons.=20Previously=20the=20scope=20was=20no=E2=80=A6=20(#67)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix eager loading of scoped associations. Previously the scope was not applied. Scopes that accept arguments cannot be used as they are expecting an instance to be passed to them and if we are eager loading there is no instance to reference. * Update CHANGELOG. --- CHANGELOG.md | 1 + lib/active_force/association/association.rb | 8 ++++++ .../eager_load_projection_builder.rb | 16 ++++++++--- spec/active_force/sobject/includes_spec.rb | 28 +++++++++++++++++-- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87499e6..b7cf3bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Not released +- Fix eager loading of scoped associations. (https://github.com/Beyond-Finance/active_force/pull/67) - Adding `.blank?`, `.present?`, and `.any?` delegators to `ActiveQuery`. (https://github.com/Beyond-Finance/active_force/pull/68) - Adding `update` and `update!` class methods on `SObject`. (https://github.com/Beyond-Finance/active_force/pull/66) diff --git a/lib/active_force/association/association.rb b/lib/active_force/association/association.rb index 8bb1428..0d7cdd5 100644 --- a/lib/active_force/association/association.rb +++ b/lib/active_force/association/association.rb @@ -26,6 +26,14 @@ def relationship_name options[:relationship_name] || relation_model.to_s.constantize.table_name end + def scoped_as + options[:scoped_as] || nil + end + + def scoped? + options[:scoped_as].present? + end + ### # Does this association's relation_model represent # +sfdc_table_name+? Examples of +sfdc_table_name+ diff --git a/lib/active_force/association/eager_load_projection_builder.rb b/lib/active_force/association/eager_load_projection_builder.rb index 4c827cf..70dfe74 100644 --- a/lib/active_force/association/eager_load_projection_builder.rb +++ b/lib/active_force/association/eager_load_projection_builder.rb @@ -1,5 +1,6 @@ module ActiveForce module Association + class InvalidEagerLoadAssociation < StandardError; end class EagerLoadProjectionBuilder class << self def build(association, parent_association_field = nil) @@ -34,6 +35,13 @@ def initialize(association, parent_association_field = nil) def projections raise "Must define #{self.class.name}#projections" end + + def apply_association_scope(query) + return query unless association.scoped? + raise InvalidEagerLoadAssociation, "Cannot use scopes that expect arguments: #{association.relation_name}" if association.scoped_as.arity.positive? + + query.instance_exec(&association.scoped_as) + end end class HasManyAssociationProjectionBuilder < AbstractProjectionBuilder @@ -43,17 +51,17 @@ class HasManyAssociationProjectionBuilder < AbstractProjectionBuilder # to be pluralized def projections relationship_name = association.sfdc_association_field - query = Query.new relationship_name + query = ActiveQuery.new(association.relation_model, relationship_name) query.fields association.relation_model.fields - ["(#{query.to_s})"] + ["(#{apply_association_scope(query).to_s})"] end end class HasOneAssociationProjectionBuilder < AbstractProjectionBuilder def projections - query = Query.new association.sfdc_association_field + query = ActiveQuery.new(association.relation_model, association.sfdc_association_field) query.fields association.relation_model.fields - ["(#{query.to_s})"] + ["(#{apply_association_scope(query).to_s})"] end end diff --git a/spec/active_force/sobject/includes_spec.rb b/spec/active_force/sobject/includes_spec.rb index 21a7a2f..b4ab9c9 100644 --- a/spec/active_force/sobject/includes_spec.rb +++ b/spec/active_force/sobject/includes_spec.rb @@ -225,6 +225,13 @@ module ActiveForce end end + context 'when assocation has a scope' do + it 'formulates the correct SOQL query with the scope applied' do + soql = Post.includes(:impossible_comments).where(id: '1234').to_s + expect(soql).to eq "SELECT Id, Title__c, BlogId, (SELECT Id, PostId, PosterId__c, FancyPostId, Body__c FROM Comments__r WHERE (1 = 0)) FROM Post__c WHERE (Id = '1234')" + end + end + context 'with namespaced SObjects' do it 'formulates the correct SOQL query' do soql = Salesforce::Quota.includes(:prez_clubs).where(id: '123').to_s @@ -286,9 +293,26 @@ module ActiveForce end end + context 'has_one' do + context 'when assocation has a scope' do + it 'formulates the correct SOQL query with the scope applied' do + soql = Post.includes(:last_comment).where(id: '1234').to_s + expect(soql).to eq "SELECT Id, Title__c, BlogId, (SELECT Id, PostId, PosterId__c, FancyPostId, Body__c FROM Comment__r WHERE (NOT ((Body__c = NULL))) ORDER BY CreatedDate DESC) FROM Post__c WHERE (Id = '1234')" + end + end + + end + context 'when invalid associations are passed' do - it 'raises an error' do - expect { Quota.includes(:invalid).find('123') }.to raise_error(ActiveForce::Association::InvalidAssociationError, 'Association named invalid was not found on Quota') + context 'when the association is not defined' do + it 'raises an error' do + expect { Quota.includes(:invalid).find('123') }.to raise_error(ActiveForce::Association::InvalidAssociationError, 'Association named invalid was not found on Quota') + end + end + context 'when the association is scoped and accepts an argument' do + it 'raises and error' do + expect { Post.includes(:reply_comments).find('1234')}.to raise_error(ActiveForce::Association::InvalidEagerLoadAssociation) + end end end end