active_force 0.15.0 → 0.16.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 +11 -1
- data/README.md +15 -0
- data/lib/active_force/active_query.rb +7 -25
- data/lib/active_force/association/belongs_to_association.rb +5 -1
- data/lib/active_force/association/eager_load_builder_for_nested_includes.rb +84 -0
- data/lib/active_force/association/eager_load_projection_builder.rb +15 -8
- data/lib/active_force/association/relation_model_builder.rb +10 -8
- data/lib/active_force/association.rb +1 -0
- data/lib/active_force/sobject.rb +11 -8
- 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 +325 -0
- data/spec/active_force/sobject_spec.rb +14 -65
- data/spec/support/sobjects.rb +41 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7691f9f6f82d893a6aafdf29cfc6228ab2fe31988fafcf5be95070347a0286de
|
|
4
|
+
data.tar.gz: 4d0da25e5e12f9014407dba1871953a0b05f58227188d53130eba27dd537aa4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e8cf96c34eb81e3e4236455538bc462de07d02f7149ab064125d4c86f52a96c7e61e98ff91b9fe48e3b5b6d79d88ded4bb47966800f021e153a970adac802b4
|
|
7
|
+
data.tar.gz: 6b7edaa7cf4161921b1d42c3a26fe896781bd3198ce981b2693852f3fe3ff6f1690e27e61ec781761fbe06d10634fc720013f1b2f3a266d3de6a189dd37af2a5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
## Not released
|
|
4
|
+
|
|
5
|
+
## 0.16.0
|
|
6
|
+
|
|
7
|
+
- Fix `default` in models when default value is overridden by the same value, it is still sent to salesforce (https://github.com/Beyond-Finance/active_force/pull/61)
|
|
8
|
+
- Support to fetch multi-level associations during eager load (https://github.com/Beyond-Finance/active_force/pull/62)
|
|
9
|
+
|
|
10
|
+
## 0.15.1
|
|
11
|
+
|
|
12
|
+
- Revert new `pluck` implementation due to compatibility issues (https://github.com/Beyond-Finance/active_force/pull/60)
|
|
13
|
+
|
|
4
14
|
## 0.15.0
|
|
15
|
+
|
|
5
16
|
- Fix model defaults so data is persisted in Salesforce (https://github.com/Beyond-Finance/active_force/pull/55)
|
|
6
17
|
- Add `pluck` query method (https://github.com/Beyond-Finance/active_force/pull/51)
|
|
7
18
|
- Add `#order` method to active query that accepts arguments in several formats ( symbol, string that has raw soql) (https://github.com/Beyond-Finance/active_force/pull/58)
|
|
8
19
|
|
|
9
|
-
|
|
10
20
|
## 0.14.0
|
|
11
21
|
|
|
12
22
|
- Add `scoped_as` option to `has_one` association (https://github.com/Beyond-Finance/active_force/pull/50)
|
data/README.md
CHANGED
|
@@ -192,6 +192,21 @@ It is also possible to eager load associations:
|
|
|
192
192
|
Comment.includes(:post)
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
It is possible to eager load multi level associations
|
|
196
|
+
|
|
197
|
+
In order to utilize multi level eager loads, the API version should be set to 58.0 or higher when instantiating a Restforce client
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
Restforce.new({api_version: '58.0'})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Examples:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
Comment.includes(post: :owner)
|
|
207
|
+
Comment.includes({post: {owner: :account}})
|
|
208
|
+
```
|
|
209
|
+
|
|
195
210
|
### Aggregates
|
|
196
211
|
|
|
197
212
|
Summing the values of a column:
|
|
@@ -4,7 +4,6 @@ require 'forwardable'
|
|
|
4
4
|
|
|
5
5
|
module ActiveForce
|
|
6
6
|
class PreparedStatementInvalid < ArgumentError; end
|
|
7
|
-
|
|
8
7
|
class RecordNotFound < StandardError
|
|
9
8
|
attr_reader :table_name, :conditions
|
|
10
9
|
|
|
@@ -19,15 +18,16 @@ module ActiveForce
|
|
|
19
18
|
class ActiveQuery < Query
|
|
20
19
|
extend Forwardable
|
|
21
20
|
|
|
22
|
-
attr_reader :sobject, :association_mapping
|
|
21
|
+
attr_reader :sobject, :association_mapping, :belongs_to_association_mapping
|
|
23
22
|
|
|
24
23
|
def_delegators :sobject, :sfdc_client, :build, :table_name, :mappings
|
|
25
24
|
def_delegators :to_a, :each, :map, :inspect, :pluck, :each_with_object
|
|
26
25
|
|
|
27
|
-
def initialize sobject
|
|
26
|
+
def initialize (sobject, custom_table_name = nil)
|
|
28
27
|
@sobject = sobject
|
|
29
28
|
@association_mapping = {}
|
|
30
|
-
|
|
29
|
+
@belongs_to_association_mapping = {}
|
|
30
|
+
super custom_table_name || table_name
|
|
31
31
|
fields sobject.fields
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -51,15 +51,6 @@ module ActiveForce
|
|
|
51
51
|
sfdc_client.query(to_s).first.expr0
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
def pluck(*fields)
|
|
55
|
-
fields = mappings.keys if fields.blank?
|
|
56
|
-
|
|
57
|
-
sfdc_client.query(select(*fields).to_s).map do |record|
|
|
58
|
-
values = fields.map { |field| cast_value(field, record) }
|
|
59
|
-
values.length == 1 ? values.first : values
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
54
|
def limit limit
|
|
64
55
|
super
|
|
65
56
|
limit == 1 ? to_a.first : self
|
|
@@ -102,12 +93,9 @@ module ActiveForce
|
|
|
102
93
|
end
|
|
103
94
|
|
|
104
95
|
def includes(*relations)
|
|
105
|
-
relations
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
# downcase the key and downcase when we do the comparison so we don't do any more crazy string manipulation
|
|
109
|
-
association_mapping[association.sfdc_association_field.downcase] = association.relation_name
|
|
110
|
-
end
|
|
96
|
+
includes_query = Association::EagerLoadBuilderForNestedIncludes.build(relations, sobject)
|
|
97
|
+
fields includes_query[:fields]
|
|
98
|
+
association_mapping.merge!(includes_query[:association_mapping])
|
|
111
99
|
self
|
|
112
100
|
end
|
|
113
101
|
|
|
@@ -217,12 +205,6 @@ module ActiveForce
|
|
|
217
205
|
sfdc_client.query(self.to_s)
|
|
218
206
|
end
|
|
219
207
|
|
|
220
|
-
def cast_value(field, object)
|
|
221
|
-
attribute_type = sobject.attribute_types[field.to_s]
|
|
222
|
-
value = object[mappings[field]]
|
|
223
|
-
attribute_type&.cast(value) || value
|
|
224
|
-
end
|
|
225
|
-
|
|
226
208
|
def clone_self_and_clear_cache
|
|
227
209
|
new_query = self.clone
|
|
228
210
|
new_query.instance_variable_set(:@decorated_records, nil)
|
|
@@ -16,7 +16,11 @@ module ActiveForce
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def default_relationship_name
|
|
19
|
-
parent.
|
|
19
|
+
if !parent.custom_table? && !relation_model.custom_table?
|
|
20
|
+
relation_model.table_name
|
|
21
|
+
else
|
|
22
|
+
parent.mappings[foreign_key].gsub(/__c\z/, '__r')
|
|
23
|
+
end
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
def default_foreign_key
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module ActiveForce
|
|
2
|
+
|
|
3
|
+
module Association
|
|
4
|
+
class InvalidAssociationError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class EagerLoadBuilderForNestedIncludes
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def build(relations, current_sobject, parent_association_field = nil)
|
|
10
|
+
new(relations, current_sobject, parent_association_field).projections
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :relations, :current_sobject, :association_mapping, :parent_association_field, :fields
|
|
15
|
+
|
|
16
|
+
def initialize(relations, current_sobject, parent_association_field = nil)
|
|
17
|
+
@relations = [relations].flatten
|
|
18
|
+
@current_sobject = current_sobject
|
|
19
|
+
@association_mapping = {}
|
|
20
|
+
@parent_association_field = parent_association_field
|
|
21
|
+
@fields = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def projections
|
|
26
|
+
relations.each do |relation|
|
|
27
|
+
case relation
|
|
28
|
+
when Symbol
|
|
29
|
+
association = current_sobject.associations[relation]
|
|
30
|
+
raise InvalidAssociationError, "Association named #{relation} was not found on #{current_sobject}" if association.nil?
|
|
31
|
+
build_includes(association)
|
|
32
|
+
when Hash
|
|
33
|
+
build_hash_includes(relation)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
{ fields: fields, association_mapping: association_mapping }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_includes(association)
|
|
40
|
+
fields.concat(EagerLoadProjectionBuilder.build(association, parent_association_field))
|
|
41
|
+
association_mapping[association.sfdc_association_field.downcase] = association.relation_name
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_hash_includes(relation, model = current_sobject, parent_association_field = nil)
|
|
45
|
+
relation.each do |key, value|
|
|
46
|
+
association = model.associations[key]
|
|
47
|
+
raise InvalidAssociationError, "Association named #{key} was not found on #{model}" if association.nil?
|
|
48
|
+
case association
|
|
49
|
+
when ActiveForce::Association::BelongsToAssociation
|
|
50
|
+
build_includes(association)
|
|
51
|
+
nested_query = build_relation_for_belongs_to(association, value)
|
|
52
|
+
fields.concat(nested_query[:fields])
|
|
53
|
+
association_mapping.merge!(nested_query[:association_mapping])
|
|
54
|
+
else
|
|
55
|
+
nested_query = build_relation(association, value)
|
|
56
|
+
fields.concat(nested_query[:fields])
|
|
57
|
+
association_mapping.merge!(nested_query[:association_mapping])
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def build_relation(association, nested_includes)
|
|
65
|
+
sub_query = Query.new(association.sfdc_association_field)
|
|
66
|
+
sub_query.fields association.relation_model.fields
|
|
67
|
+
association_mapping[association.sfdc_association_field.downcase] = association.relation_name
|
|
68
|
+
nested_includes_query = self.class.build(nested_includes, association.relation_model)
|
|
69
|
+
sub_query.fields nested_includes_query[:fields]
|
|
70
|
+
{ fields: ["(#{sub_query})"], association_mapping: nested_includes_query[:association_mapping] }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def build_relation_for_belongs_to(association, nested_includes)
|
|
75
|
+
if parent_association_field.present?
|
|
76
|
+
current_parent_association_field = "#{parent_association_field}.#{association.sfdc_association_field}"
|
|
77
|
+
else
|
|
78
|
+
current_parent_association_field = association.sfdc_association_field
|
|
79
|
+
end
|
|
80
|
+
self.class.build(nested_includes, association.relation_model, current_parent_association_field)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -2,31 +2,33 @@ module ActiveForce
|
|
|
2
2
|
module Association
|
|
3
3
|
class EagerLoadProjectionBuilder
|
|
4
4
|
class << self
|
|
5
|
-
def build(association)
|
|
6
|
-
new(association).projections
|
|
5
|
+
def build(association, parent_association_field = nil)
|
|
6
|
+
new(association, parent_association_field).projections
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
attr_reader :association
|
|
10
|
+
attr_reader :association, :parent_association_field
|
|
11
11
|
|
|
12
|
-
def initialize(association)
|
|
12
|
+
def initialize(association, parent_association_field = nil)
|
|
13
13
|
@association = association
|
|
14
|
+
@parent_association_field = parent_association_field
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def projections
|
|
17
18
|
klass = association.class.name.split('::').last
|
|
18
19
|
builder_class = ActiveForce::Association.const_get "#{klass}ProjectionBuilder"
|
|
19
|
-
builder_class.new(association).projections
|
|
20
|
+
builder_class.new(association, parent_association_field).projections
|
|
20
21
|
rescue NameError
|
|
21
22
|
raise "Don't know how to build projections for #{klass}"
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
class AbstractProjectionBuilder
|
|
26
|
-
attr_reader :association
|
|
27
|
+
attr_reader :association, :parent_association_field
|
|
27
28
|
|
|
28
|
-
def initialize(association)
|
|
29
|
+
def initialize(association, parent_association_field = nil)
|
|
29
30
|
@association = association
|
|
31
|
+
@parent_association_field = parent_association_field
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def projections
|
|
@@ -57,8 +59,13 @@ module ActiveForce
|
|
|
57
59
|
|
|
58
60
|
class BelongsToAssociationProjectionBuilder < AbstractProjectionBuilder
|
|
59
61
|
def projections
|
|
62
|
+
association_field = if parent_association_field.present?
|
|
63
|
+
"#{ parent_association_field }.#{ association.sfdc_association_field }"
|
|
64
|
+
else
|
|
65
|
+
association.sfdc_association_field
|
|
66
|
+
end
|
|
60
67
|
association.relation_model.fields.map do |field|
|
|
61
|
-
"#{
|
|
68
|
+
"#{ association_field }.#{ field }"
|
|
62
69
|
end
|
|
63
70
|
end
|
|
64
71
|
end
|
|
@@ -2,19 +2,20 @@ module ActiveForce
|
|
|
2
2
|
module Association
|
|
3
3
|
class RelationModelBuilder
|
|
4
4
|
class << self
|
|
5
|
-
def build(association, value)
|
|
6
|
-
new(association, value).build_relation_model
|
|
5
|
+
def build(association, value, association_mapping = {})
|
|
6
|
+
new(association, value, association_mapping).build_relation_model
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def initialize(association, value)
|
|
10
|
+
def initialize(association, value, association_mapping = {})
|
|
11
11
|
@association = association
|
|
12
12
|
@value = value
|
|
13
|
+
@association_mapping = association_mapping
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def build_relation_model
|
|
16
17
|
klass = resolve_class
|
|
17
|
-
klass.new(@association, @value).call
|
|
18
|
+
klass.new(@association, @value, @association_mapping).call
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
private
|
|
@@ -28,11 +29,12 @@ module ActiveForce
|
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
class AbstractBuildFrom
|
|
31
|
-
attr_reader :association, :value
|
|
32
|
+
attr_reader :association, :value, :association_mapping
|
|
32
33
|
|
|
33
|
-
def initialize(association, value)
|
|
34
|
+
def initialize(association, value, association_mapping = {})
|
|
34
35
|
@association = association
|
|
35
36
|
@value = value
|
|
37
|
+
@association_mapping = association_mapping
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def call
|
|
@@ -42,13 +44,13 @@ module ActiveForce
|
|
|
42
44
|
|
|
43
45
|
class BuildFromHash < AbstractBuildFrom
|
|
44
46
|
def call
|
|
45
|
-
association.build
|
|
47
|
+
association.build(value, association_mapping)
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
class BuildFromArray < AbstractBuildFrom
|
|
50
52
|
def call
|
|
51
|
-
value.map { |mash| association.build
|
|
53
|
+
value.map { |mash| association.build(mash, association_mapping) }
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
|
|
@@ -4,6 +4,7 @@ require 'active_force/association/relation_model_builder'
|
|
|
4
4
|
require 'active_force/association/has_many_association'
|
|
5
5
|
require 'active_force/association/has_one_association'
|
|
6
6
|
require 'active_force/association/belongs_to_association'
|
|
7
|
+
require 'active_force/association/eager_load_builder_for_nested_includes'
|
|
7
8
|
|
|
8
9
|
module ActiveForce
|
|
9
10
|
module Association
|
data/lib/active_force/sobject.rb
CHANGED
|
@@ -29,8 +29,7 @@ module ActiveForce
|
|
|
29
29
|
|
|
30
30
|
class << self
|
|
31
31
|
extend Forwardable
|
|
32
|
-
def_delegators :query, :not, :or, :where, :first, :last, :all, :find, :find!, :find_by, :find_by!, :sum, :count,
|
|
33
|
-
:includes, :limit, :order, :select, :none, :pluck
|
|
32
|
+
def_delegators :query, :not, :or, :where, :first, :last, :all, :find, :find!, :find_by, :find_by!, :sum, :count, :includes, :limit, :order, :select, :none
|
|
34
33
|
def_delegators :mapping, :table, :table_name, :custom_table?, :mappings
|
|
35
34
|
|
|
36
35
|
private
|
|
@@ -69,7 +68,7 @@ module ActiveForce
|
|
|
69
68
|
if association_mapping.has_key?(column.downcase)
|
|
70
69
|
column = association_mapping[column.downcase]
|
|
71
70
|
end
|
|
72
|
-
sobject.write_value column, value
|
|
71
|
+
sobject.write_value column, value, association_mapping
|
|
73
72
|
end
|
|
74
73
|
end
|
|
75
74
|
sobject.clear_changes_information
|
|
@@ -174,10 +173,10 @@ module ActiveForce
|
|
|
174
173
|
self
|
|
175
174
|
end
|
|
176
175
|
|
|
177
|
-
def write_value key, value
|
|
176
|
+
def write_value key, value, association_mapping = {}
|
|
178
177
|
if association = self.class.find_association(key.to_sym)
|
|
179
178
|
field = association.relation_name
|
|
180
|
-
value = Association::RelationModelBuilder.build(association, value)
|
|
179
|
+
value = Association::RelationModelBuilder.build(association, value, association_mapping)
|
|
181
180
|
elsif key.to_sym.in?(mappings.keys)
|
|
182
181
|
# key is a field name
|
|
183
182
|
field = key
|
|
@@ -228,9 +227,13 @@ module ActiveForce
|
|
|
228
227
|
end
|
|
229
228
|
|
|
230
229
|
def attributes_for_create
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
default_attributes.concat(changed)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def default_attributes
|
|
234
|
+
@attributes.each_value.select do |value|
|
|
235
|
+
value.is_a?(ActiveModel::Attribute::UserProvidedDefault) || value.instance_values["original_attribute"].is_a?(ActiveModel::Attribute::UserProvidedDefault)
|
|
236
|
+
end.map(&:name)
|
|
234
237
|
end
|
|
235
238
|
|
|
236
239
|
def attributes_for_update
|
data/lib/active_force/version.rb
CHANGED
|
@@ -376,7 +376,7 @@ describe ActiveForce::SObject do
|
|
|
376
376
|
it 'allows passing a foreign key' do
|
|
377
377
|
Comment.belongs_to :post, foreign_key: :fancy_post_id
|
|
378
378
|
allow(comment).to receive(:fancy_post_id).and_return "2"
|
|
379
|
-
expect(client).to receive(:query).with("SELECT Id, Title__c FROM Post__c WHERE (Id = '2') LIMIT 1")
|
|
379
|
+
expect(client).to receive(:query).with("SELECT Id, Title__c, BlogId FROM Post__c WHERE (Id = '2') LIMIT 1")
|
|
380
380
|
comment.post
|
|
381
381
|
Comment.belongs_to :post # reset association to original value
|
|
382
382
|
end
|
|
@@ -285,6 +285,331 @@ module ActiveForce
|
|
|
285
285
|
expect(account.owner.id).to eq '321'
|
|
286
286
|
end
|
|
287
287
|
end
|
|
288
|
+
|
|
289
|
+
context 'when invalid associations are passed' do
|
|
290
|
+
it 'raises an error' do
|
|
291
|
+
expect { Quota.includes(:invalid).find('123') }.to raise_error(ActiveForce::Association::InvalidAssociationError, 'Association named invalid was not found on Quota')
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
describe '.includes with nested associations' do
|
|
297
|
+
|
|
298
|
+
context 'with custom objects' do
|
|
299
|
+
it 'formulates the correct SOQL query' do
|
|
300
|
+
soql = Quota.includes(prez_clubs: :club_members).where(id: '123').to_s
|
|
301
|
+
expect(soql).to eq <<-SOQL.squish
|
|
302
|
+
SELECT Id, Bar_Id__c,
|
|
303
|
+
(SELECT Id, QuotaId,
|
|
304
|
+
(SELECT Id, Name, Email FROM ClubMembers__r)
|
|
305
|
+
FROM PrezClubs__r)
|
|
306
|
+
FROM Quota__c
|
|
307
|
+
WHERE (Bar_Id__c = '123')
|
|
308
|
+
SOQL
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it 'builds the associated objects and caches them' do
|
|
312
|
+
response = [build_restforce_sobject({
|
|
313
|
+
'Id' => '123',
|
|
314
|
+
'PrezClubs__r' => build_restforce_collection([
|
|
315
|
+
{'Id' => '213', 'QuotaId' => '123', 'ClubMembers__r' => build_restforce_collection([
|
|
316
|
+
{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com'},
|
|
317
|
+
{'Id' => '214', 'Name' => 'def', 'Email' => 'def@af.com'}
|
|
318
|
+
])},
|
|
319
|
+
{'Id' => '214', 'QuotaId' => '123', 'ClubMembers__r' => build_restforce_collection([
|
|
320
|
+
{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com'},
|
|
321
|
+
{'Id' => '214', 'Name' => 'def', 'Email' => 'def@af.com'}
|
|
322
|
+
])}
|
|
323
|
+
])
|
|
324
|
+
})]
|
|
325
|
+
allow(client).to receive(:query).once.and_return response
|
|
326
|
+
account = Quota.includes(prez_clubs: :club_members).find '123'
|
|
327
|
+
expect(account.prez_clubs).to be_an Array
|
|
328
|
+
expect(account.prez_clubs.all? { |o| o.is_a? PrezClub }).to eq true
|
|
329
|
+
expect(account.prez_clubs.first.club_members).to be_an Array
|
|
330
|
+
expect(account.prez_clubs.first.club_members.all? { |o| o.is_a? ClubMember }).to eq true
|
|
331
|
+
expect(account.prez_clubs.first.club_members.first.id).to eq '213'
|
|
332
|
+
expect(account.prez_clubs.first.club_members.first.name).to eq 'abc'
|
|
333
|
+
expect(account.prez_clubs.first.club_members.first.email).to eq 'abc@af.com'
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
context 'with namespaced sobjects' do
|
|
338
|
+
it 'formulates the correct SOQL query' do
|
|
339
|
+
soql = Salesforce::Account.includes({partner_opportunities: :owner}).where(id: '123').to_s
|
|
340
|
+
expect(soql).to eq <<-SOQL.squish
|
|
341
|
+
SELECT Id, Business_Partner__c,
|
|
342
|
+
(SELECT Id, OwnerId, AccountId, Business_Partner__c, Owner.Id
|
|
343
|
+
FROM Opportunities)
|
|
344
|
+
FROM Account
|
|
345
|
+
WHERE (Id = '123')
|
|
346
|
+
SOQL
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
it 'builds the associated objects and caches them' do
|
|
350
|
+
response = [build_restforce_sobject({
|
|
351
|
+
'Id' => '123',
|
|
352
|
+
'opportunities' => build_restforce_collection([
|
|
353
|
+
{'Id' => '213', 'AccountId' => '123', 'OwnerId' => '321', 'Business_Partner__c' => '123', 'Owner' => {'Id' => '321'}},
|
|
354
|
+
{'Id' => '214', 'AccountId' => '123', 'OwnerId' => '321', 'Business_Partner__c' => '123', 'Owner' => {'Id' => '321'}} ])
|
|
355
|
+
})]
|
|
356
|
+
allow(client).to receive(:query).once.and_return response
|
|
357
|
+
account = Salesforce::Account.includes({partner_opportunities: :owner}).find '123'
|
|
358
|
+
expect(account.partner_opportunities).to be_an Array
|
|
359
|
+
expect(account.partner_opportunities.all? { |o| o.is_a? Salesforce::Opportunity }).to eq true
|
|
360
|
+
expect(account.partner_opportunities.first.owner).to be_a Salesforce::User
|
|
361
|
+
expect(account.partner_opportunities.first.owner.id).to eq '321'
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
context 'an array association nested within a hash association' do
|
|
366
|
+
it 'formulates the correct SOQL query' do
|
|
367
|
+
soql = Club.includes(book_clubs: [:club_members, :books]).where(id: '123').to_s
|
|
368
|
+
expect(soql).to eq <<-SOQL.squish
|
|
369
|
+
SELECT Id,
|
|
370
|
+
(SELECT Id, Name, Location,
|
|
371
|
+
(SELECT Id, Name, Email FROM ClubMembers__r),
|
|
372
|
+
(SELECT Id, Title, Author FROM Books__r)
|
|
373
|
+
FROM BookClubs__r)
|
|
374
|
+
FROM Club__c
|
|
375
|
+
WHERE (Id = '123')
|
|
376
|
+
SOQL
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
it 'builds the associated objects and caches them' do
|
|
380
|
+
response = [build_restforce_sobject({
|
|
381
|
+
'Id' => '123',
|
|
382
|
+
'BookClubs__r' => build_restforce_collection([
|
|
383
|
+
{
|
|
384
|
+
'Id' => '213',
|
|
385
|
+
'Name' => 'abc',
|
|
386
|
+
'Location' => 'abc_location',
|
|
387
|
+
'ClubMembers__r' => build_restforce_collection([{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com'},{'Id' => '214', 'Name' => 'def', 'Email' => 'def@af.com'}]),
|
|
388
|
+
'Books__r' => build_restforce_collection([{'Id' => '213', 'Title' => 'Foo', 'Author' => 'author1'},{'Id' => '214', 'Title' => 'Bar', 'Author' => 'author2'}]),
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
'Id' => '214',
|
|
392
|
+
'Name' => 'def',
|
|
393
|
+
'Location' => 'def_location',
|
|
394
|
+
'ClubMembers__r' => build_restforce_collection([{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com'},{'Id' => '214', 'Name' => 'def', 'Email' => 'def@af.com'}]),
|
|
395
|
+
'Books__r' => build_restforce_collection([{'Id' => '213', 'Title' => 'Foo', 'Author' => 'author1'},{'Id' => '214', 'Title' => 'Bar', 'Author' => 'author2'}]),
|
|
396
|
+
}
|
|
397
|
+
])
|
|
398
|
+
})]
|
|
399
|
+
allow(client).to receive(:query).once.and_return response
|
|
400
|
+
club = Club.includes(book_clubs: [:club_members, :books]).find(id: '123')
|
|
401
|
+
expect(club.book_clubs).to be_an Array
|
|
402
|
+
expect(club.book_clubs.all? { |o| o.is_a? BookClub }).to eq true
|
|
403
|
+
expect(club.book_clubs.first.name).to eq 'abc'
|
|
404
|
+
expect(club.book_clubs.first.location).to eq 'abc_location'
|
|
405
|
+
expect(club.book_clubs.first.club_members).to be_an Array
|
|
406
|
+
expect(club.book_clubs.first.club_members.all? { |o| o.is_a? ClubMember }).to eq true
|
|
407
|
+
expect(club.book_clubs.first.club_members.first.id).to eq '213'
|
|
408
|
+
expect(club.book_clubs.first.club_members.first.name).to eq 'abc'
|
|
409
|
+
expect(club.book_clubs.first.club_members.first.email).to eq 'abc@af.com'
|
|
410
|
+
expect(club.book_clubs.first.books).to be_an Array
|
|
411
|
+
expect(club.book_clubs.first.books.all? { |o| o.is_a? Book }).to eq true
|
|
412
|
+
expect(club.book_clubs.first.books.first.id).to eq '213'
|
|
413
|
+
expect(club.book_clubs.first.books.first.title).to eq 'Foo'
|
|
414
|
+
expect(club.book_clubs.first.books.first.author).to eq 'author1'
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
context 'a hash association nested within a hash association' do
|
|
419
|
+
it 'formulates the correct SOQL query' do
|
|
420
|
+
soql = Club.includes(book_clubs: {club_members: :membership}).where(id: '123').to_s
|
|
421
|
+
expect(soql).to eq <<-SOQL.squish
|
|
422
|
+
SELECT Id,
|
|
423
|
+
(SELECT Id, Name, Location,
|
|
424
|
+
(SELECT Id, Name, Email,
|
|
425
|
+
(SELECT Id, Type, Club_Member_Id__c FROM Membership__r)
|
|
426
|
+
FROM ClubMembers__r)
|
|
427
|
+
FROM BookClubs__r)
|
|
428
|
+
FROM Club__c
|
|
429
|
+
WHERE (Id = '123')
|
|
430
|
+
SOQL
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it 'builds the associated objects and caches them' do
|
|
434
|
+
response = [build_restforce_sobject({
|
|
435
|
+
'Id' => '123',
|
|
436
|
+
'BookClubs__r' => build_restforce_collection([
|
|
437
|
+
{
|
|
438
|
+
'Id' => '213',
|
|
439
|
+
'Name' => 'abc',
|
|
440
|
+
'Location' => 'abc_location',
|
|
441
|
+
'ClubMembers__r' => build_restforce_collection([
|
|
442
|
+
{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com', 'Membership__r' => build_restforce_collection([{'Id' => '111', 'Type' => 'Gold'}])},
|
|
443
|
+
{'Id' => '214', 'Name' => 'abc', 'Email' => 'abc@af.com', 'Membership__r' => build_restforce_collection([{'Id' => '222', 'Type' => 'Silver'}])},
|
|
444
|
+
]),
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
'Id' => '214',
|
|
448
|
+
'Name' => 'def',
|
|
449
|
+
'Location' => 'def_location',
|
|
450
|
+
'ClubMembers__r' => build_restforce_collection([
|
|
451
|
+
{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com', 'Membership__r' => build_restforce_collection([{'Id' => '111', 'Type' => 'Gold'}])},
|
|
452
|
+
{'Id' => '214', 'Name' => 'abc', 'Email' => 'abc@af.com', 'Membership__r' => build_restforce_collection([{'Id' => '222', 'Type' => 'Silver'}])},
|
|
453
|
+
]),
|
|
454
|
+
}
|
|
455
|
+
])
|
|
456
|
+
})]
|
|
457
|
+
allow(client).to receive(:query).once.and_return response
|
|
458
|
+
club = Club.includes(book_clubs: {club_members: :membership}).find(id: '123')
|
|
459
|
+
expect(club.book_clubs).to be_an Array
|
|
460
|
+
expect(club.book_clubs.all? { |o| o.is_a? BookClub }).to eq true
|
|
461
|
+
expect(club.book_clubs.first.id).to eq '213'
|
|
462
|
+
expect(club.book_clubs.first.name).to eq 'abc'
|
|
463
|
+
expect(club.book_clubs.first.location).to eq 'abc_location'
|
|
464
|
+
expect(club.book_clubs.first.club_members).to be_an Array
|
|
465
|
+
expect(club.book_clubs.first.club_members.all? { |o| o.is_a? ClubMember }).to eq true
|
|
466
|
+
expect(club.book_clubs.first.club_members.first.id).to eq '213'
|
|
467
|
+
expect(club.book_clubs.first.club_members.first.name).to eq 'abc'
|
|
468
|
+
expect(club.book_clubs.first.club_members.first.email).to eq 'abc@af.com'
|
|
469
|
+
expect(club.book_clubs.first.club_members.first.membership).to be_a Membership
|
|
470
|
+
expect(club.book_clubs.first.club_members.first.membership.id).to eq '111'
|
|
471
|
+
expect(club.book_clubs.first.club_members.first.membership.type).to eq 'Gold'
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
context 'mulitple nested associations' do
|
|
476
|
+
it 'formulates the correct SOQL query' do
|
|
477
|
+
soql = Club.includes({prez_clubs: {club_members: :membership}}, {book_clubs: [:club_members, :books]}).where(id: '123').to_s
|
|
478
|
+
expect(soql).to eq <<-SOQL.squish
|
|
479
|
+
SELECT Id,
|
|
480
|
+
(SELECT Id, QuotaId,
|
|
481
|
+
(SELECT Id, Name, Email,
|
|
482
|
+
(SELECT Id, Type, Club_Member_Id__c FROM Membership__r)
|
|
483
|
+
FROM ClubMembers__r)
|
|
484
|
+
FROM PrezClubs__r),
|
|
485
|
+
(SELECT Id, Name, Location,
|
|
486
|
+
(SELECT Id, Name, Email FROM ClubMembers__r),
|
|
487
|
+
(SELECT Id, Title, Author FROM Books__r)
|
|
488
|
+
FROM BookClubs__r)
|
|
489
|
+
FROM Club__c
|
|
490
|
+
WHERE (Id = '123')
|
|
491
|
+
SOQL
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
it 'builds the associated objects and caches them' do
|
|
495
|
+
response = [build_restforce_sobject({
|
|
496
|
+
'Id' => '123',
|
|
497
|
+
'PrezClubs__r' => build_restforce_collection([
|
|
498
|
+
{'Id' => '213', 'QuotaId' => '123', 'ClubMembers__r' => build_restforce_collection([
|
|
499
|
+
{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com', 'Membership__r' => build_restforce_collection([{'Id' => '111', 'Type' => 'Gold'}])},
|
|
500
|
+
{'Id' => '214', 'Name' => 'def', 'Email' => 'def@af.com', 'Membership__r' => build_restforce_collection([{'Id' => '222', 'Type' => 'Silver'}])},
|
|
501
|
+
])},
|
|
502
|
+
{'Id' => '214', 'QuotaId' => '123', 'ClubMembers__r' => build_restforce_collection([
|
|
503
|
+
{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com', 'Membership__r' => build_restforce_collection([{'Id' => '111', 'Type' => 'Gold'}])},
|
|
504
|
+
{'Id' => '214', 'Name' => 'def', 'Email' => 'def@af.com', 'Membership__r' => build_restforce_collection([{'Id' => '222', 'Type' => 'Silver'}])},
|
|
505
|
+
])}
|
|
506
|
+
]),
|
|
507
|
+
'BookClubs__r' => build_restforce_collection([
|
|
508
|
+
{
|
|
509
|
+
'Id' => '213',
|
|
510
|
+
'Name' => 'abc',
|
|
511
|
+
'Location' => 'abc_location',
|
|
512
|
+
'ClubMembers__r' => build_restforce_collection([
|
|
513
|
+
{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com'},
|
|
514
|
+
{'Id' => '214', 'Name' => 'abc', 'Email' => 'abc@af.com'},
|
|
515
|
+
]),
|
|
516
|
+
'Books__r' => build_restforce_collection([
|
|
517
|
+
{'Id' => '213', 'Title' => 'Foo', 'Author' => 'author1'},
|
|
518
|
+
{'Id' => '214', 'Title' => 'Bar', 'Author' => 'author2'},
|
|
519
|
+
])
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
'Id' => '214',
|
|
523
|
+
'Name' => 'def',
|
|
524
|
+
'Location' => 'def_location',
|
|
525
|
+
'ClubMembers__r' => build_restforce_collection([
|
|
526
|
+
{'Id' => '213', 'Name' => 'abc', 'Email' => 'abc@af.com'},
|
|
527
|
+
{'Id' => '214', 'Name' => 'abc', 'Email' => 'abc@af.com'},
|
|
528
|
+
]),
|
|
529
|
+
'Books__r' => build_restforce_collection([
|
|
530
|
+
{'Id' => '213', 'Title' => 'Foo', 'Author' => 'author1'},
|
|
531
|
+
{'Id' => '214', 'Title' => 'Bar', 'Author' => 'author2'},
|
|
532
|
+
])
|
|
533
|
+
}
|
|
534
|
+
])
|
|
535
|
+
})]
|
|
536
|
+
allow(client).to receive(:query).once.and_return response
|
|
537
|
+
club = Club.includes({prez_clubs: {club_members: :membership}}, {book_clubs: [:club_members, :books]}).find(id: '123')
|
|
538
|
+
expect(club.prez_clubs).to be_an Array
|
|
539
|
+
expect(club.prez_clubs.all? { |o| o.is_a? PrezClub }).to eq true
|
|
540
|
+
expect(club.prez_clubs.first.club_members).to be_an Array
|
|
541
|
+
expect(club.prez_clubs.first.club_members.all? { |o| o.is_a? ClubMember }).to eq true
|
|
542
|
+
expect(club.prez_clubs.first.club_members.first.id).to eq '213'
|
|
543
|
+
expect(club.prez_clubs.first.club_members.first.name).to eq 'abc'
|
|
544
|
+
expect(club.prez_clubs.first.club_members.first.email).to eq 'abc@af.com'
|
|
545
|
+
expect(club.prez_clubs.first.club_members.first.membership).to be_a Membership
|
|
546
|
+
expect(club.prez_clubs.first.club_members.first.membership.id).to eq '111'
|
|
547
|
+
expect(club.prez_clubs.first.club_members.first.membership.type).to eq 'Gold'
|
|
548
|
+
expect(club.book_clubs).to be_an Array
|
|
549
|
+
expect(club.book_clubs.all? { |o| o.is_a? BookClub }).to eq true
|
|
550
|
+
expect(club.book_clubs.first.id).to eq '213'
|
|
551
|
+
expect(club.book_clubs.first.name).to eq 'abc'
|
|
552
|
+
expect(club.book_clubs.first.location).to eq 'abc_location'
|
|
553
|
+
expect(club.book_clubs.first.club_members).to be_an Array
|
|
554
|
+
expect(club.book_clubs.first.club_members.all? { |o| o.is_a? ClubMember }).to eq true
|
|
555
|
+
expect(club.book_clubs.first.club_members.first.id).to eq '213'
|
|
556
|
+
expect(club.book_clubs.first.club_members.first.name).to eq 'abc'
|
|
557
|
+
expect(club.book_clubs.first.club_members.first.email).to eq 'abc@af.com'
|
|
558
|
+
expect(club.book_clubs.first.books).to be_an Array
|
|
559
|
+
expect(club.book_clubs.first.books.all? { |o| o.is_a? Book }).to eq true
|
|
560
|
+
expect(club.book_clubs.first.books.first.id).to eq '213'
|
|
561
|
+
expect(club.book_clubs.first.books.first.title).to eq 'Foo'
|
|
562
|
+
expect(club.book_clubs.first.books.first.author).to eq 'author1'
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
context 'nested belongs-to association' do
|
|
567
|
+
it 'formulates the correct SOQL query' do
|
|
568
|
+
soql = Comment.includes(post: :blog).where(id: '123').to_s
|
|
569
|
+
expect(soql).to eq <<-SOQL.squish
|
|
570
|
+
SELECT Id, PostId, PosterId__c, FancyPostId, Body__c,
|
|
571
|
+
PostId.Id, PostId.Title__c, PostId.BlogId,
|
|
572
|
+
PostId.BlogId.Id, PostId.BlogId.Name, PostId.BlogId.Link__c
|
|
573
|
+
FROM Comment__c
|
|
574
|
+
WHERE (Id = '123')
|
|
575
|
+
SOQL
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
it 'builds the associated objects and caches them' do
|
|
579
|
+
response = [build_restforce_sobject({
|
|
580
|
+
'Id' => '123',
|
|
581
|
+
'PostId' => '321',
|
|
582
|
+
'PosterId__c' => '432',
|
|
583
|
+
'FancyPostId' => '543',
|
|
584
|
+
'Body__c' => 'body',
|
|
585
|
+
'PostId' => {
|
|
586
|
+
'Id' => '321',
|
|
587
|
+
'Title__c' => 'title',
|
|
588
|
+
'BlogId' => '432',
|
|
589
|
+
'BlogId' => {
|
|
590
|
+
'Id' => '432',
|
|
591
|
+
'Name' => 'name',
|
|
592
|
+
'Link__c' => 'link'
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
})]
|
|
596
|
+
allow(client).to receive(:query).once.and_return response
|
|
597
|
+
comment = Comment.includes(post: :blog).find '123'
|
|
598
|
+
expect(comment.post).to be_a Post
|
|
599
|
+
expect(comment.post.id).to eq '321'
|
|
600
|
+
expect(comment.post.title).to eq 'title'
|
|
601
|
+
expect(comment.post.blog).to be_a Blog
|
|
602
|
+
expect(comment.post.blog.id).to eq '432'
|
|
603
|
+
expect(comment.post.blog.name).to eq 'name'
|
|
604
|
+
expect(comment.post.blog.link).to eq 'link'
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
context 'when invalid nested associations are passed' do
|
|
609
|
+
it 'raises an error' do
|
|
610
|
+
expect { Quota.includes(prez_clubs: :invalid).find('123') }.to raise_error(ActiveForce::Association::InvalidAssociationError, 'Association named invalid was not found on PrezClub')
|
|
611
|
+
end
|
|
612
|
+
end
|
|
288
613
|
end
|
|
289
614
|
end
|
|
290
615
|
end
|
|
@@ -96,6 +96,20 @@ describe ActiveForce::SObject do
|
|
|
96
96
|
it 'sets percent field upon object instantiation' do
|
|
97
97
|
expect(subject.new(**instantiation_attributes)[:percent]).to eq(percent)
|
|
98
98
|
end
|
|
99
|
+
|
|
100
|
+
context 'when the override to default value is the same as the default value' do
|
|
101
|
+
let(:percent) { 50.0 }
|
|
102
|
+
|
|
103
|
+
it 'sends percent to salesforce' do
|
|
104
|
+
expect(client).to receive(:create!)
|
|
105
|
+
.with(anything, hash_including('Percent_Label' => percent))
|
|
106
|
+
subject.create(**instantiation_attributes)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'sets percent field upon object instantiation' do
|
|
110
|
+
expect(subject.new(**instantiation_attributes)[:percent]).to eq(percent)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
99
113
|
end
|
|
100
114
|
end
|
|
101
115
|
|
|
@@ -354,71 +368,6 @@ describe ActiveForce::SObject do
|
|
|
354
368
|
end
|
|
355
369
|
end
|
|
356
370
|
|
|
357
|
-
describe '#pluck' do
|
|
358
|
-
let(:record_attributes) { [] }
|
|
359
|
-
|
|
360
|
-
before do
|
|
361
|
-
collection = build_restforce_collection(record_attributes.map { |a| build_restforce_sobject(a) })
|
|
362
|
-
allow(client).to receive(:query).and_return(collection)
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
it 'returns empty array if no records returned from query' do
|
|
366
|
-
expect(Whizbang.pluck).to eq([])
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
it 'works with another query' do
|
|
370
|
-
expected = 'SELECT Id, Text_Label FROM Whizbang__c WHERE (Checkbox_Label = true)'
|
|
371
|
-
Whizbang.where(checkbox: true).pluck(:id, :text)
|
|
372
|
-
expect(client).to have_received(:query).with(expected)
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
context 'when given no fields' do
|
|
376
|
-
it 'queries for all fields' do
|
|
377
|
-
expected = "SELECT #{Whizbang.fields.join ', '} FROM Whizbang__c"
|
|
378
|
-
Whizbang.pluck
|
|
379
|
-
expect(client).to have_received(:query).with(expected)
|
|
380
|
-
end
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
context 'when given one field' do
|
|
384
|
-
let(:ids) { ['a', 'b', 1] }
|
|
385
|
-
let(:record_attributes) { ids.map { |v| { 'Id' => v } } }
|
|
386
|
-
|
|
387
|
-
it 'queries for only that field' do
|
|
388
|
-
expected = 'SELECT Id FROM Whizbang__c'
|
|
389
|
-
Whizbang.pluck(:id)
|
|
390
|
-
expect(client).to have_received(:query).with(expected)
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
it 'returns an array of casted values for that field' do
|
|
394
|
-
expect(Whizbang.pluck(:id)).to match_array(ids.map(&:to_s))
|
|
395
|
-
end
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
context 'when given more than one field' do
|
|
399
|
-
let(:record_attributes) do
|
|
400
|
-
[
|
|
401
|
-
{ 'Text_Label' => 'test1', 'Date_Label' => '2023-01-01' },
|
|
402
|
-
{ 'Text_Label' => 'test2', 'Date_Label' => '2023-01-02' }
|
|
403
|
-
]
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
it 'queries for only those fields' do
|
|
407
|
-
expected = 'SELECT Text_Label, Date_Label FROM Whizbang__c'
|
|
408
|
-
Whizbang.pluck(:text, :date)
|
|
409
|
-
expect(client).to have_received(:query).with(expected)
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
it 'returns an array of arrays of casted values in the correct order' do
|
|
413
|
-
expected = [
|
|
414
|
-
['test1', Date.new(2023, 1, 1)],
|
|
415
|
-
['test2', Date.new(2023, 1, 2)]
|
|
416
|
-
]
|
|
417
|
-
expect(Whizbang.pluck(:text, :date)).to match_array(expected)
|
|
418
|
-
end
|
|
419
|
-
end
|
|
420
|
-
end
|
|
421
|
-
|
|
422
371
|
describe '#reload' do
|
|
423
372
|
let(:client) do
|
|
424
373
|
double("sfdc_client", query: [Restforce::Mash.new(Id: 1, Name: 'Jeff')])
|
data/spec/support/sobjects.rb
CHANGED
|
@@ -10,6 +10,7 @@ end
|
|
|
10
10
|
class Post < ActiveForce::SObject
|
|
11
11
|
self.table_name = "Post__c"
|
|
12
12
|
field :title
|
|
13
|
+
field :blog_id, from: "BlogId"
|
|
13
14
|
has_many :comments
|
|
14
15
|
has_many :impossible_comments, model: Comment, scoped_as: ->{ where('1 = 0') }
|
|
15
16
|
has_many :reply_comments, model: Comment, scoped_as: ->(post){ where(body: "RE: #{post.title}").order('CreationDate DESC') }
|
|
@@ -17,6 +18,13 @@ class Post < ActiveForce::SObject
|
|
|
17
18
|
has_many :poster_comments, { foreign_key: :poster_id, model: Comment }
|
|
18
19
|
has_one :last_comment, model: Comment, scoped_as: -> { where.not(body: nil).order('CreatedDate DESC') }
|
|
19
20
|
has_one :repeat_comment, model: Comment, scoped_as: ->(post) { where(body: post.title) }
|
|
21
|
+
belongs_to :blog
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Blog < ActiveForce::SObject
|
|
25
|
+
field :name, from: 'Name'
|
|
26
|
+
field :link, from: 'Link__c'
|
|
27
|
+
has_many :posts
|
|
20
28
|
end
|
|
21
29
|
class Territory < ActiveForce::SObject
|
|
22
30
|
field :quota_id, from: "Quota__c"
|
|
@@ -26,7 +34,40 @@ end
|
|
|
26
34
|
class PrezClub < ActiveForce::SObject
|
|
27
35
|
field :quota_id, from: 'QuotaId'
|
|
28
36
|
belongs_to :quota
|
|
37
|
+
has_many :club_members
|
|
38
|
+
has_one :club_owner
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Club < ActiveForce::SObject
|
|
42
|
+
has_many :book_clubs
|
|
43
|
+
has_many :prez_clubs
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class BookClub < ActiveForce::SObject
|
|
47
|
+
field :name, from: 'Name'
|
|
48
|
+
field :location, from: 'Location'
|
|
49
|
+
has_many :club_members
|
|
50
|
+
has_many :books
|
|
29
51
|
end
|
|
52
|
+
class ClubMember < ActiveForce::SObject
|
|
53
|
+
field :name, from: 'Name'
|
|
54
|
+
field :email, from: 'Email'
|
|
55
|
+
has_one :membership
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Book < ActiveForce::SObject
|
|
59
|
+
field :title, from: 'Title'
|
|
60
|
+
field :author, from: 'Author'
|
|
61
|
+
end
|
|
62
|
+
class Membership < ActiveForce::SObject
|
|
63
|
+
field :type, from: 'Type'
|
|
64
|
+
field :club_member_id, from: 'Club_Member_Id__c'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class ClubOwner < ActiveForce::SObject
|
|
68
|
+
field :name, from: 'Name'
|
|
69
|
+
end
|
|
70
|
+
|
|
30
71
|
class Quota < ActiveForce::SObject
|
|
31
72
|
field :id, from: 'Bar_Id__c'
|
|
32
73
|
has_many :prez_clubs
|
metadata
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_force
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eloy Espinaco
|
|
8
8
|
- Pablo Oldani
|
|
9
9
|
- Armando Andini
|
|
10
10
|
- José Piccioni
|
|
11
|
-
autorequire:
|
|
11
|
+
autorequire:
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date: 2023-
|
|
14
|
+
date: 2023-07-31 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: activemodel
|
|
@@ -150,6 +150,7 @@ files:
|
|
|
150
150
|
- lib/active_force/association.rb
|
|
151
151
|
- lib/active_force/association/association.rb
|
|
152
152
|
- lib/active_force/association/belongs_to_association.rb
|
|
153
|
+
- lib/active_force/association/eager_load_builder_for_nested_includes.rb
|
|
153
154
|
- lib/active_force/association/eager_load_projection_builder.rb
|
|
154
155
|
- lib/active_force/association/has_many_association.rb
|
|
155
156
|
- lib/active_force/association/has_one_association.rb
|
|
@@ -192,7 +193,7 @@ metadata:
|
|
|
192
193
|
bug_tracker_uri: https://github.com/Beyond-Finance/active_force/issues
|
|
193
194
|
changelog_uri: https://github.com/Beyond-Finance/active_force/blob/main/CHANGELOG.md
|
|
194
195
|
source_code_uri: https://github.com/Beyond-Finance/active_force
|
|
195
|
-
post_install_message:
|
|
196
|
+
post_install_message:
|
|
196
197
|
rdoc_options: []
|
|
197
198
|
require_paths:
|
|
198
199
|
- lib
|
|
@@ -207,8 +208,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
207
208
|
- !ruby/object:Gem::Version
|
|
208
209
|
version: '0'
|
|
209
210
|
requirements: []
|
|
210
|
-
rubygems_version: 3.
|
|
211
|
-
signing_key:
|
|
211
|
+
rubygems_version: 3.1.6
|
|
212
|
+
signing_key:
|
|
212
213
|
specification_version: 4
|
|
213
214
|
summary: Help you implement models persisting on Sales Force within Rails using RESTForce
|
|
214
215
|
test_files:
|