active_force 0.23.0 → 0.24.0
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/README.md +11 -0
- data/lib/active_force/active_query.rb +9 -4
- data/lib/active_force/association/eager_load_builder_for_nested_includes.rb +16 -8
- data/lib/active_force/association/eager_load_projection_builder.rb +13 -11
- data/lib/active_force/query.rb +1 -1
- data/lib/active_force/select_builder.rb +41 -0
- data/lib/active_force/version.rb +1 -1
- data/spec/active_force/sobject/includes_spec.rb +40 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e6bf4ca3441ea8c1d0df00f6a8925f021fa53a3a0ce2e19beda69ecab4b5881
|
4
|
+
data.tar.gz: 940b3627880eb3a67d8387c744246a8bb29cc816ccf828130dd375e1e33b1791
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7ab1dc49814c642b34b61391a2d8ab99fc2b0854f3113b5c4d5715a67ec681d3061fa2e3e53eea3ec9780525aeba3804c7043901b7652baa67113d2659bd8dc
|
7
|
+
data.tar.gz: d69e76e37be28552a80a9a3e2872a48ff15e04df4178f5ce2b670426be23570a7ca11256e41ed757ba04fc20270bff875730d8aea2927eee7e0129124c5f994e
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
## Not released
|
4
4
|
|
5
|
+
## 0.24.0
|
6
|
+
- Add support for nested select statements that are used in conjuction with nested includes (https://github.com/Beyond-Finance/active_force/pull/102)
|
7
|
+
|
5
8
|
## 0.23.0
|
6
9
|
- Partially addresses #90. `#select` accepts a block and returns an array of filtered SObjects. (https://github.com/Beyond-Finance/active_force/pull/99)
|
7
10
|
|
data/README.md
CHANGED
@@ -218,6 +218,17 @@ Comment.includes(post: :owner)
|
|
218
218
|
Comment.includes({post: {owner: :account}})
|
219
219
|
```
|
220
220
|
|
221
|
+
You can also use #select with a multi level #includes.
|
222
|
+
|
223
|
+
Examples:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
Comment.select(:body, post: [:title, :is_active]).includes(post: :owner)
|
227
|
+
Comment.select(:body, account: :owner_id).includes({post: {owner: :account}})
|
228
|
+
```
|
229
|
+
|
230
|
+
The Sobject name in the #select must match the Sobject in the #includes for the fields to be filtered.
|
231
|
+
|
221
232
|
### Aggregates
|
222
233
|
|
223
234
|
Summing the values of a column:
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'active_support/all'
|
2
2
|
require 'active_force/query'
|
3
|
+
require 'active_force/select_builder'
|
3
4
|
require 'forwardable'
|
4
5
|
|
5
6
|
module ActiveForce
|
@@ -25,7 +26,7 @@ module ActiveForce
|
|
25
26
|
class ActiveQuery < Query
|
26
27
|
extend Forwardable
|
27
28
|
|
28
|
-
attr_reader :sobject, :association_mapping, :belongs_to_association_mapping
|
29
|
+
attr_reader :sobject, :association_mapping, :belongs_to_association_mapping, :nested_query_fields
|
29
30
|
|
30
31
|
def_delegators :sobject, :sfdc_client, :build, :table_name, :mappings
|
31
32
|
def_delegators :to_a, :blank?, :present?, :any?, :each, :map, :inspect, :pluck, :each_with_object
|
@@ -36,6 +37,7 @@ module ActiveForce
|
|
36
37
|
@belongs_to_association_mapping = {}
|
37
38
|
super custom_table_name || table_name
|
38
39
|
fields sobject.fields
|
40
|
+
@nested_query_fields = []
|
39
41
|
end
|
40
42
|
|
41
43
|
def to_a
|
@@ -86,8 +88,11 @@ module ActiveForce
|
|
86
88
|
end
|
87
89
|
result
|
88
90
|
else
|
89
|
-
|
90
|
-
|
91
|
+
fields_collection = ActiveForce::SelectBuilder.new(selected_fields, self).parse
|
92
|
+
nested_query_fields.concat(fields_collection[:nested_query_fields]) if fields_collection[:nested_query_fields]
|
93
|
+
return self if fields_collection[:non_nested_query_fields].blank?
|
94
|
+
|
95
|
+
super *fields_collection[:non_nested_query_fields]
|
91
96
|
end
|
92
97
|
end
|
93
98
|
|
@@ -114,7 +119,7 @@ module ActiveForce
|
|
114
119
|
end
|
115
120
|
|
116
121
|
def includes(*relations)
|
117
|
-
includes_query = Association::EagerLoadBuilderForNestedIncludes.build(relations, sobject)
|
122
|
+
includes_query = Association::EagerLoadBuilderForNestedIncludes.build(relations, sobject, nil, nested_query_fields)
|
118
123
|
fields includes_query[:fields]
|
119
124
|
association_mapping.merge!(includes_query[:association_mapping])
|
120
125
|
self
|
@@ -6,18 +6,19 @@ module ActiveForce
|
|
6
6
|
class EagerLoadBuilderForNestedIncludes
|
7
7
|
|
8
8
|
class << self
|
9
|
-
def build(relations, current_sobject, parent_association_field = nil)
|
10
|
-
new(relations, current_sobject, parent_association_field).projections
|
9
|
+
def build(relations, current_sobject, parent_association_field = nil, query_fields = nil)
|
10
|
+
new(relations, current_sobject, parent_association_field, query_fields).projections
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
attr_reader :relations, :current_sobject, :association_mapping, :parent_association_field, :fields
|
14
|
+
attr_reader :relations, :current_sobject, :association_mapping, :parent_association_field, :fields, :query_fields
|
15
15
|
|
16
|
-
def initialize(relations, current_sobject, parent_association_field = nil)
|
16
|
+
def initialize(relations, current_sobject, parent_association_field = nil, query_fields = nil)
|
17
17
|
@relations = [relations].flatten
|
18
18
|
@current_sobject = current_sobject
|
19
19
|
@association_mapping = {}
|
20
20
|
@parent_association_field = parent_association_field
|
21
|
+
@query_fields = query_fields
|
21
22
|
@fields = []
|
22
23
|
end
|
23
24
|
|
@@ -37,10 +38,17 @@ module ActiveForce
|
|
37
38
|
end
|
38
39
|
|
39
40
|
def build_includes(association)
|
40
|
-
fields.concat(EagerLoadProjectionBuilder.build(association, parent_association_field))
|
41
|
+
fields.concat(EagerLoadProjectionBuilder.build(association, parent_association_field, query_fields_for(association)))
|
41
42
|
association_mapping[association.sfdc_association_field.downcase] = association.relation_name
|
42
43
|
end
|
43
44
|
|
45
|
+
def query_fields_for(association)
|
46
|
+
return nil if query_fields.blank?
|
47
|
+
query_fields_with_association = query_fields.find { |nested_field| nested_field[association.relation_name].present? }
|
48
|
+
return nil if query_fields_with_association.blank?
|
49
|
+
query_fields_with_association[association.relation_name].map { |field| association.relation_model.mappings[field] }
|
50
|
+
end
|
51
|
+
|
44
52
|
def build_hash_includes(relation, model = current_sobject, parent_association_field = nil)
|
45
53
|
relation.each do |key, value|
|
46
54
|
association = model.associations[key]
|
@@ -63,10 +71,10 @@ module ActiveForce
|
|
63
71
|
|
64
72
|
def build_relation(association, nested_includes)
|
65
73
|
builder_class = ActiveForce::Association::EagerLoadProjectionBuilder.projection_builder_class(association)
|
66
|
-
projection_builder = builder_class.new(association)
|
74
|
+
projection_builder = builder_class.new(association, nil, query_fields_for(association))
|
67
75
|
sub_query = projection_builder.query_with_association_fields
|
68
76
|
association_mapping[association.sfdc_association_field.downcase] = association.relation_name
|
69
|
-
nested_includes_query = self.class.build(nested_includes, association.relation_model)
|
77
|
+
nested_includes_query = self.class.build(nested_includes, association.relation_model, nil, query_fields)
|
70
78
|
sub_query.fields nested_includes_query[:fields]
|
71
79
|
{ fields: ["(#{sub_query})"], association_mapping: nested_includes_query[:association_mapping] }
|
72
80
|
end
|
@@ -78,7 +86,7 @@ module ActiveForce
|
|
78
86
|
else
|
79
87
|
current_parent_association_field = association.sfdc_association_field
|
80
88
|
end
|
81
|
-
self.class.build(nested_includes, association.relation_model, current_parent_association_field)
|
89
|
+
self.class.build(nested_includes, association.relation_model, current_parent_association_field, query_fields)
|
82
90
|
end
|
83
91
|
end
|
84
92
|
end
|
@@ -3,8 +3,8 @@ module ActiveForce
|
|
3
3
|
class InvalidEagerLoadAssociation < StandardError; end
|
4
4
|
class EagerLoadProjectionBuilder
|
5
5
|
class << self
|
6
|
-
def build(association, parent_association_field = nil)
|
7
|
-
new(association, parent_association_field).projections
|
6
|
+
def build(association, parent_association_field = nil, query_fields = nil)
|
7
|
+
new(association, parent_association_field, query_fields).projections
|
8
8
|
end
|
9
9
|
|
10
10
|
def projection_builder_class(association)
|
@@ -15,26 +15,27 @@ module ActiveForce
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
attr_reader :association, :parent_association_field
|
18
|
+
attr_reader :association, :parent_association_field, :query_fields
|
19
19
|
|
20
|
-
def initialize(association, parent_association_field = nil)
|
20
|
+
def initialize(association, parent_association_field = nil, query_fields = nil)
|
21
21
|
@association = association
|
22
22
|
@parent_association_field = parent_association_field
|
23
|
+
@query_fields = query_fields
|
23
24
|
end
|
24
25
|
|
25
26
|
def projections
|
26
27
|
builder_class = self.class.projection_builder_class(association)
|
27
|
-
builder_class.new(association, parent_association_field).projections
|
28
|
+
builder_class.new(association, parent_association_field, query_fields).projections
|
28
29
|
end
|
29
|
-
|
30
30
|
end
|
31
31
|
|
32
32
|
class AbstractProjectionBuilder
|
33
|
-
attr_reader :association, :parent_association_field
|
33
|
+
attr_reader :association, :parent_association_field, :query_fields
|
34
34
|
|
35
|
-
def initialize(association, parent_association_field = nil)
|
35
|
+
def initialize(association, parent_association_field = nil, query_fields = nil)
|
36
36
|
@association = association
|
37
37
|
@parent_association_field = parent_association_field
|
38
|
+
@query_fields = query_fields
|
38
39
|
end
|
39
40
|
|
40
41
|
def projections
|
@@ -54,8 +55,8 @@ module ActiveForce
|
|
54
55
|
# to be pluralized
|
55
56
|
def query_with_association_fields
|
56
57
|
relationship_name = association.sfdc_association_field
|
57
|
-
|
58
|
-
query.
|
58
|
+
selected_fields = query_fields || association.relation_model.fields
|
59
|
+
query = ActiveQuery.new(association.relation_model, relationship_name).select(*selected_fields)
|
59
60
|
apply_association_scope(query)
|
60
61
|
end
|
61
62
|
end
|
@@ -79,7 +80,8 @@ module ActiveForce
|
|
79
80
|
else
|
80
81
|
association.sfdc_association_field
|
81
82
|
end
|
82
|
-
association.relation_model.fields
|
83
|
+
selected_fields = query_fields || association.relation_model.fields
|
84
|
+
selected_fields.map do |field|
|
83
85
|
"#{ association_field }.#{ field }"
|
84
86
|
end
|
85
87
|
end
|
data/lib/active_force/query.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActiveForce
|
2
|
+
class SelectBuilder
|
3
|
+
|
4
|
+
attr_reader :selected_fields, :nested_query_fields, :non_nested_query_fields, :query
|
5
|
+
|
6
|
+
def initialize(selected_fields, query)
|
7
|
+
@query = query
|
8
|
+
@selected_fields = selected_fields
|
9
|
+
@non_nested_query_fields = []
|
10
|
+
@nested_query_fields = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
selected_fields.each do |field|
|
15
|
+
case field
|
16
|
+
when Symbol
|
17
|
+
non_nested_query_fields << query.mappings[field]
|
18
|
+
when Hash
|
19
|
+
populate_nested_query_fields(field)
|
20
|
+
when String
|
21
|
+
non_nested_query_fields << field
|
22
|
+
end
|
23
|
+
end
|
24
|
+
{non_nested_query_fields: non_nested_query_fields, nested_query_fields: nested_query_fields}
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def populate_nested_query_fields(field)
|
30
|
+
field.each do |key, value|
|
31
|
+
case value
|
32
|
+
when Symbol
|
33
|
+
field[key] = [value]
|
34
|
+
when Hash
|
35
|
+
raise ArgumentError, 'Nested Hash is not supported in select statement, you may wish to use an Array'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
nested_query_fields << field
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/active_force/version.rb
CHANGED
@@ -43,6 +43,25 @@ module ActiveForce
|
|
43
43
|
expect(territory.quota.id).to eq "321"
|
44
44
|
end
|
45
45
|
|
46
|
+
context 'when nested select statement' do
|
47
|
+
it 'formulates the correct SOQL query' do
|
48
|
+
soql = Salesforce::Territory.select(:id, :quota_id, quota: :id).includes(:quota).where(id: '123').to_s
|
49
|
+
expect(soql).to eq "SELECT Id, QuotaId, QuotaId.Id FROM Territory WHERE (Id = '123')"
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'errors when correct format is not followed' do
|
53
|
+
expect{Salesforce::Territory.select(:id, :quota_id, quota: {id: :quote}).includes(:quota).where(id: '123').to_s}.to raise_error ArgumentError
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when nested includes statement' do
|
57
|
+
it 'formulates the correct SOQL query' do
|
58
|
+
soql = Comment.select(:post_id, :body, post: [:title, :is_active], blog: :name).includes(post: :blog).to_s
|
59
|
+
|
60
|
+
expect(soql).to eq "SELECT PostId, Body__c, PostId.Title__c, PostId.IsActive, PostId.BlogId.Name FROM Comment__c"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
46
65
|
context 'with namespaced SObjects' do
|
47
66
|
it 'queries the API for the associated record' do
|
48
67
|
soql = Salesforce::Territory.includes(:quota).where(id: '123').to_s
|
@@ -156,6 +175,20 @@ module ActiveForce
|
|
156
175
|
end
|
157
176
|
|
158
177
|
context 'has_many' do
|
178
|
+
context 'when nested select statement' do
|
179
|
+
it 'formulates the correct SOQL query' do
|
180
|
+
soql = Account.select(opportunities: :id).includes(:opportunities).where(id: '123').to_s
|
181
|
+
expect(soql).to eq "SELECT Id, OwnerId, (SELECT Id FROM Opportunities) FROM Account WHERE (Id = '123')"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'when normal select with nested includes' do
|
186
|
+
it 'formulates the correct SOQL query' do
|
187
|
+
soql = Blog.select(:id, :link).includes(posts: :comments).to_s
|
188
|
+
expect(soql).to eq "SELECT Id, Link__c, (SELECT Id, Title__c, BlogId, IsActive, (SELECT Id, PostId, PosterId__c, FancyPostId, Body__c FROM Comments__r) FROM Posts__r) FROM Blog__c"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
159
192
|
context 'with standard objects' do
|
160
193
|
it 'formulates the correct SOQL query' do
|
161
194
|
soql = Account.includes(:opportunities).where(id: '123').to_s
|
@@ -294,6 +327,13 @@ module ActiveForce
|
|
294
327
|
end
|
295
328
|
|
296
329
|
context 'has_one' do
|
330
|
+
context 'when nested select statement is present' do
|
331
|
+
it 'formulates the correct SOQL query' do
|
332
|
+
soql = ClubMember.select(:name, :email, membership: :type).includes(:membership).to_s
|
333
|
+
expect(soql).to eq "SELECT Name, Email, (SELECT Type FROM Membership__r) FROM ClubMember__c"
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
297
337
|
context 'when assocation has a scope' do
|
298
338
|
it 'formulates the correct SOQL query with the scope applied' do
|
299
339
|
soql = Post.includes(:last_comment).where(id: '1234').to_s
|
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.
|
4
|
+
version: 0.24.0
|
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-10-01 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activemodel
|
@@ -163,6 +163,7 @@ files:
|
|
163
163
|
- lib/active_force/field.rb
|
164
164
|
- lib/active_force/mapping.rb
|
165
165
|
- lib/active_force/query.rb
|
166
|
+
- lib/active_force/select_builder.rb
|
166
167
|
- lib/active_force/sobject.rb
|
167
168
|
- lib/active_force/standard_types.rb
|
168
169
|
- lib/active_force/table.rb
|
@@ -219,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
219
220
|
- !ruby/object:Gem::Version
|
220
221
|
version: '0'
|
221
222
|
requirements: []
|
222
|
-
rubygems_version: 3.
|
223
|
+
rubygems_version: 3.4.10
|
223
224
|
signing_key:
|
224
225
|
specification_version: 4
|
225
226
|
summary: Help you implement models persisting on Sales Force within Rails using RESTForce
|