active_force 0.21.0 → 0.21.1
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/CHANGELOG.md +3 -0
- data/lib/active_force/association/eager_load_builder_for_nested_includes.rb +3 -2
- data/lib/active_force/association/eager_load_projection_builder.rb +18 -11
- data/lib/active_force/version.rb +1 -1
- data/spec/active_force/association_spec.rb +1 -1
- data/spec/active_force/sobject/includes_spec.rb +41 -9
- data/spec/support/sobjects.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a2d59fa533f638635afa0e8654fae4f77540310fba228ae697e05eaaf965525
|
4
|
+
data.tar.gz: 0570306ac71f575994b07aace62ad9579d0103f812eceb59aaa83b1a6ddc1911
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ceccbbfb84f6f80190d11ae8722ba8fbbe423cc4164b0567df2fd912f7e45150ddfb4092da3af528d431f03527676dd897e5565f6d5b18fdf70492d489667896
|
7
|
+
data.tar.gz: 91962473ab612ca13f755f7b94b891d8c6f849677999ef0333a98924b9f6a001566cd602fc3ee3c9c8e1b6cdcef039fbfeb13e4f1a8f2e284877e5d771a84e3c
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
## Not released
|
4
4
|
|
5
|
+
## 0.21.1
|
6
|
+
- Fixes #91. Applies scopes to eager-loaded associations when they are nested. (https://github.com/Beyond-Finance/active_force/pull/92)
|
7
|
+
|
5
8
|
## 0.21.0
|
6
9
|
|
7
10
|
- Uninitialized attributes will ERROR instead of returning as `nil` (https://github.com/Beyond-Finance/active_force/pull/78)
|
@@ -62,8 +62,9 @@ module ActiveForce
|
|
62
62
|
private
|
63
63
|
|
64
64
|
def build_relation(association, nested_includes)
|
65
|
-
|
66
|
-
|
65
|
+
builder_class = ActiveForce::Association::EagerLoadProjectionBuilder.projection_builder_class(association)
|
66
|
+
projection_builder = builder_class.new(association)
|
67
|
+
sub_query = projection_builder.query_with_association_fields
|
67
68
|
association_mapping[association.sfdc_association_field.downcase] = association.relation_name
|
68
69
|
nested_includes_query = self.class.build(nested_includes, association.relation_model)
|
69
70
|
sub_query.fields nested_includes_query[:fields]
|
@@ -6,6 +6,13 @@ module ActiveForce
|
|
6
6
|
def build(association, parent_association_field = nil)
|
7
7
|
new(association, parent_association_field).projections
|
8
8
|
end
|
9
|
+
|
10
|
+
def projection_builder_class(association)
|
11
|
+
klass = association.class.name.demodulize
|
12
|
+
ActiveForce::Association.const_get "#{klass}ProjectionBuilder"
|
13
|
+
rescue NameError
|
14
|
+
raise "No projection builder exists for #{klass}"
|
15
|
+
end
|
9
16
|
end
|
10
17
|
|
11
18
|
attr_reader :association, :parent_association_field
|
@@ -16,12 +23,10 @@ module ActiveForce
|
|
16
23
|
end
|
17
24
|
|
18
25
|
def projections
|
19
|
-
|
20
|
-
builder_class = ActiveForce::Association.const_get "#{klass}ProjectionBuilder"
|
26
|
+
builder_class = self.class.projection_builder_class(association)
|
21
27
|
builder_class.new(association, parent_association_field).projections
|
22
|
-
rescue NameError
|
23
|
-
raise "Don't know how to build projections for #{klass}"
|
24
28
|
end
|
29
|
+
|
25
30
|
end
|
26
31
|
|
27
32
|
class AbstractProjectionBuilder
|
@@ -42,26 +47,28 @@ module ActiveForce
|
|
42
47
|
|
43
48
|
query.instance_exec(&association.scoped_as)
|
44
49
|
end
|
45
|
-
end
|
46
50
|
|
47
|
-
class HasManyAssociationProjectionBuilder < AbstractProjectionBuilder
|
48
51
|
###
|
49
52
|
# Use ActiveForce::Query to build a subquery for the SFDC
|
50
53
|
# relationship name. Per SFDC convention, the name needs
|
51
54
|
# to be pluralized
|
52
|
-
def
|
55
|
+
def query_with_association_fields
|
53
56
|
relationship_name = association.sfdc_association_field
|
54
57
|
query = ActiveQuery.new(association.relation_model, relationship_name)
|
55
58
|
query.fields association.relation_model.fields
|
56
|
-
|
59
|
+
apply_association_scope(query)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class HasManyAssociationProjectionBuilder < AbstractProjectionBuilder
|
64
|
+
def projections
|
65
|
+
["(#{query_with_association_fields.to_s})"]
|
57
66
|
end
|
58
67
|
end
|
59
68
|
|
60
69
|
class HasOneAssociationProjectionBuilder < AbstractProjectionBuilder
|
61
70
|
def projections
|
62
|
-
|
63
|
-
query.fields association.relation_model.fields
|
64
|
-
["(#{apply_association_scope(query).to_s})"]
|
71
|
+
["(#{query_with_association_fields.to_s})"]
|
65
72
|
end
|
66
73
|
end
|
67
74
|
|
data/lib/active_force/version.rb
CHANGED
@@ -386,7 +386,7 @@ describe ActiveForce::SObject do
|
|
386
386
|
it 'allows passing a foreign key' do
|
387
387
|
Comment.belongs_to :post, foreign_key: :fancy_post_id
|
388
388
|
allow(comment).to receive(:fancy_post_id).and_return "2"
|
389
|
-
expect(client).to receive(:query).with("SELECT Id, Title__c, BlogId FROM Post__c WHERE (Id = '2') LIMIT 1")
|
389
|
+
expect(client).to receive(:query).with("SELECT Id, Title__c, BlogId, IsActive FROM Post__c WHERE (Id = '2') LIMIT 1")
|
390
390
|
comment.post
|
391
391
|
Comment.belongs_to :post # reset association to original value
|
392
392
|
end
|
@@ -228,7 +228,7 @@ module ActiveForce
|
|
228
228
|
context 'when assocation has a scope' do
|
229
229
|
it 'formulates the correct SOQL query with the scope applied' do
|
230
230
|
soql = Post.includes(:impossible_comments).where(id: '1234').to_s
|
231
|
-
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')"
|
231
|
+
expect(soql).to eq "SELECT Id, Title__c, BlogId, IsActive, (SELECT Id, PostId, PosterId__c, FancyPostId, Body__c FROM Comments__r WHERE (1 = 0)) FROM Post__c WHERE (Id = '1234')"
|
232
232
|
end
|
233
233
|
end
|
234
234
|
|
@@ -297,7 +297,7 @@ module ActiveForce
|
|
297
297
|
context 'when assocation has a scope' do
|
298
298
|
it 'formulates the correct SOQL query with the scope applied' do
|
299
299
|
soql = Post.includes(:last_comment).where(id: '1234').to_s
|
300
|
-
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')"
|
300
|
+
expect(soql).to eq "SELECT Id, Title__c, BlogId, IsActive, (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')"
|
301
301
|
end
|
302
302
|
end
|
303
303
|
|
@@ -397,9 +397,41 @@ module ActiveForce
|
|
397
397
|
end
|
398
398
|
end
|
399
399
|
|
400
|
+
context 'when the associations have scopes' do
|
401
|
+
it 'generates the correct SOQL query' do
|
402
|
+
soql = Blog.includes(active_posts: :impossible_comments).where(id: '123').to_s
|
403
|
+
expect(soql).to eq <<-SOQL.squish
|
404
|
+
SELECT Id, Name, Link__c,
|
405
|
+
(SELECT Id, Title__c, BlogId, IsActive,
|
406
|
+
(SELECT Id, PostId, PosterId__c, FancyPostId, Body__c
|
407
|
+
FROM Comments__r WHERE (1 = 0))
|
408
|
+
FROM Posts__r
|
409
|
+
WHERE (IsActive = true))
|
410
|
+
FROM Blog__c
|
411
|
+
WHERE (Id = '123')
|
412
|
+
SOQL
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'builds the associated objects and caches them' do
|
416
|
+
response = [build_restforce_sobject({
|
417
|
+
'Id' => '123',
|
418
|
+
'Posts__r' => build_restforce_collection([
|
419
|
+
{'Id' => '213', 'IsActive' => true, 'Comments__r' => [{'Id' => '987'}]},
|
420
|
+
{'Id' => '214', 'IsActive' => true, 'Comments__r' => [{'Id' => '456'}]}
|
421
|
+
])
|
422
|
+
})]
|
423
|
+
allow(client).to receive(:query).once.and_return response
|
424
|
+
blog = Blog.includes(active_posts: :impossible_comments).find '123'
|
425
|
+
expect(blog.active_posts).to be_an Array
|
426
|
+
expect(blog.active_posts.all? { |o| o.is_a? Post }).to eq true
|
427
|
+
expect(blog.active_posts.first.impossible_comments.first).to be_a Comment
|
428
|
+
expect(blog.active_posts.first.impossible_comments.first.id).to eq '987'
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
400
432
|
context 'with namespaced sobjects' do
|
401
433
|
it 'formulates the correct SOQL query' do
|
402
|
-
soql = Salesforce::Account.includes({
|
434
|
+
soql = Salesforce::Account.includes({opportunities: :owner}).where(id: '123').to_s
|
403
435
|
expect(soql).to eq <<-SOQL.squish
|
404
436
|
SELECT Id, Business_Partner__c,
|
405
437
|
(SELECT Id, OwnerId, AccountId, Business_Partner__c, Owner.Id
|
@@ -417,11 +449,11 @@ module ActiveForce
|
|
417
449
|
{'Id' => '214', 'AccountId' => '123', 'OwnerId' => '321', 'Business_Partner__c' => '123', 'Owner' => {'Id' => '321'}} ])
|
418
450
|
})]
|
419
451
|
allow(client).to receive(:query).once.and_return response
|
420
|
-
account = Salesforce::Account.includes({
|
421
|
-
expect(account.
|
422
|
-
expect(account.
|
423
|
-
expect(account.
|
424
|
-
expect(account.
|
452
|
+
account = Salesforce::Account.includes({opportunities: :owner}).find '123'
|
453
|
+
expect(account.opportunities).to be_an Array
|
454
|
+
expect(account.opportunities.all? { |o| o.is_a? Salesforce::Opportunity }).to eq true
|
455
|
+
expect(account.opportunities.first.owner).to be_a Salesforce::User
|
456
|
+
expect(account.opportunities.first.owner.id).to eq '321'
|
425
457
|
end
|
426
458
|
end
|
427
459
|
|
@@ -631,7 +663,7 @@ module ActiveForce
|
|
631
663
|
soql = Comment.includes(post: :blog).where(id: '123').to_s
|
632
664
|
expect(soql).to eq <<-SOQL.squish
|
633
665
|
SELECT Id, PostId, PosterId__c, FancyPostId, Body__c,
|
634
|
-
PostId.Id, PostId.Title__c, PostId.BlogId,
|
666
|
+
PostId.Id, PostId.Title__c, PostId.BlogId, PostId.IsActive,
|
635
667
|
PostId.BlogId.Id, PostId.BlogId.Name, PostId.BlogId.Link__c
|
636
668
|
FROM Comment__c
|
637
669
|
WHERE (Id = '123')
|
data/spec/support/sobjects.rb
CHANGED
@@ -11,6 +11,7 @@ class Post < ActiveForce::SObject
|
|
11
11
|
self.table_name = "Post__c"
|
12
12
|
field :title
|
13
13
|
field :blog_id, from: "BlogId"
|
14
|
+
field :is_active, from: "IsActive", as: :boolean
|
14
15
|
has_many :comments
|
15
16
|
has_many :impossible_comments, model: Comment, scoped_as: ->{ where('1 = 0') }
|
16
17
|
has_many :reply_comments, model: Comment, scoped_as: ->(post){ where(body: "RE: #{post.title}").order('CreationDate DESC') }
|
@@ -25,6 +26,7 @@ class Blog < ActiveForce::SObject
|
|
25
26
|
field :name, from: 'Name'
|
26
27
|
field :link, from: 'Link__c'
|
27
28
|
has_many :posts
|
29
|
+
has_many :active_posts, model: 'Post', scoped_as: -> { where(is_active: true) }
|
28
30
|
end
|
29
31
|
class Territory < ActiveForce::SObject
|
30
32
|
field :quota_id, from: "Quota__c"
|
@@ -149,6 +151,7 @@ module Salesforce
|
|
149
151
|
end
|
150
152
|
class Account < ActiveForce::SObject
|
151
153
|
field :business_partner
|
154
|
+
has_many :opportunities, model: Opportunity
|
152
155
|
has_many :partner_opportunities, model: Opportunity, scoped_as: ->(account){ where(business_partner: account.business_partner).includes(:owner) }
|
153
156
|
end
|
154
157
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_force
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.21.
|
4
|
+
version: 0.21.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eloy Espinaco
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2024-
|
14
|
+
date: 2024-05-17 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activemodel
|