active_force 0.21.0 → 0.21.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|