active_force 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +43 -17
  4. data/lib/active_attr/dirty.rb +3 -0
  5. data/lib/active_force/active_query.rb +32 -2
  6. data/lib/active_force/association.rb +14 -10
  7. data/lib/active_force/association/association.rb +26 -11
  8. data/lib/active_force/association/belongs_to_association.rb +1 -4
  9. data/lib/active_force/association/eager_load_projection_builder.rb +60 -0
  10. data/lib/active_force/association/has_many_association.rb +15 -5
  11. data/lib/active_force/association/relation_model_builder.rb +70 -0
  12. data/lib/active_force/attribute.rb +30 -0
  13. data/lib/active_force/mapping.rb +78 -0
  14. data/lib/active_force/query.rb +2 -8
  15. data/lib/active_force/sobject.rb +79 -95
  16. data/lib/active_force/table.rb +6 -2
  17. data/lib/active_force/version.rb +1 -1
  18. data/lib/generators/active_force/model/model_generator.rb +1 -0
  19. data/lib/generators/active_force/model/templates/model.rb.erb +0 -2
  20. data/spec/active_force/active_query_spec.rb +39 -12
  21. data/spec/active_force/association/relation_model_builder_spec.rb +62 -0
  22. data/spec/active_force/association_spec.rb +53 -88
  23. data/spec/active_force/attribute_spec.rb +27 -0
  24. data/spec/active_force/callbacks_spec.rb +1 -23
  25. data/spec/active_force/mapping_spec.rb +18 -0
  26. data/spec/active_force/query_spec.rb +32 -54
  27. data/spec/active_force/sobject/includes_spec.rb +290 -0
  28. data/spec/active_force/sobject/table_name_spec.rb +0 -21
  29. data/spec/active_force/sobject_spec.rb +212 -29
  30. data/spec/active_force/table_spec.rb +0 -3
  31. data/spec/fixtures/sobject/single_sobject_hash.yml +2 -0
  32. data/spec/spec_helper.rb +10 -4
  33. data/spec/support/restforce_factories.rb +9 -0
  34. data/spec/support/sobjects.rb +97 -0
  35. data/spec/support/whizbang.rb +25 -7
  36. metadata +18 -2
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveForce::Attribute do
4
+ let(:attribute) { ActiveForce::Attribute }
5
+
6
+ describe 'initialize' do
7
+ let(:some_field) { attribute.new(:some_field) }
8
+
9
+ it 'should set "from" and "as" as default' do
10
+ expect(some_field.sfdc_name).to eq 'Some_Field__c'
11
+ expect(some_field.as).to eq :string
12
+ end
13
+
14
+ it 'should take values from the option parameter' do
15
+ other_field = attribute.new(:other_field, sfdc_name: 'OT__c', as: :integer)
16
+ expect(other_field.sfdc_name).to eq 'OT__c'
17
+ expect(other_field.as).to eq :integer
18
+ end
19
+ end
20
+
21
+ describe 'when the attribute is' do
22
+ it 'a multipick should return all values as 1 string separated with ";"' do
23
+ names = attribute.new(:names, as: :multi_picklist)
24
+ expect(names.value_for_hash ['olvap', 'eloy']).to eq 'olvap;eloy'
25
+ end
26
+ end
27
+ end
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'active_force/sobject'
3
2
 
4
3
  describe ActiveForce::SObject do
5
4
  let(:client) { double 'Client', create!: 'id' }
@@ -9,29 +8,8 @@ describe ActiveForce::SObject do
9
8
  end
10
9
 
11
10
  describe "save" do
12
-
13
11
  it 'call action callback when save a record' do
14
- class Whizbanged < ActiveForce::SObject
15
-
16
- field :updated_from
17
- field :dirty_attribute
18
-
19
- before_save :set_as_updated_from_rails
20
- after_save :mark_dirty
21
-
22
- private
23
-
24
- def set_as_updated_from_rails
25
- self.updated_from = 'Rails'
26
- end
27
-
28
- def mark_dirty
29
- self.dirty_attribute = true
30
- end
31
-
32
- end
33
-
34
- whizbanged = Whizbanged.new
12
+ whizbanged = Whizbang.new
35
13
  whizbanged.save
36
14
  expect(whizbanged.updated_from).to eq 'Rails'
37
15
  expect(whizbanged.dirty_attribute).to eq true
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveForce::Mapping do
4
+ let(:mapping){ ActiveForce::Mapping.new 'some_table' }
5
+
6
+ describe 'field' do
7
+ it 'should add a new attribute to the instance' do
8
+ mapping.field :id, from: 'Id'
9
+ expect(mapping.mappings).to eq({ id: 'Id' })
10
+ end
11
+
12
+ it 'sf_names should return all attributes names from salesforce' do
13
+ mapping.field :id, from: 'Id'
14
+ mapping.field :name, from: 'Name'
15
+ expect(mapping.sfdc_names).to eq ['Id', 'Name']
16
+ end
17
+ end
18
+ end
@@ -1,148 +1,126 @@
1
1
  require 'spec_helper'
2
- require 'active_force/query'
3
2
 
4
3
  describe ActiveForce::Query do
5
-
6
- before do
7
- @query = ActiveForce::Query.new 'table_name'
8
- @query.fields ['name', 'etc']
9
- end
10
-
11
- after do
4
+ let(:query) do
5
+ query = ActiveForce::Query.new 'table_name'
6
+ query.fields ['name', 'etc']
7
+ query
12
8
  end
13
9
 
14
10
  describe '.select' do
15
11
  it 'use column sent on the select method' do
16
- expect(@query.select('name').all.to_s).to eq "SELECT name FROM table_name"
12
+ expect(query.select('name').all.to_s).to eq "SELECT name FROM table_name"
17
13
  end
18
14
 
19
15
  it 'use columns sent on the select method' do
20
- expect(@query.select(['id','name']).all.to_s).to eq "SELECT id, name FROM table_name"
16
+ expect(query.select(['id','name']).all.to_s).to eq "SELECT id, name FROM table_name"
21
17
  end
22
18
  end
23
19
 
24
20
  describe ".all" do
25
21
  it "table should return table name" do
26
- expect(@query.all.table).to eq(@query.table)
22
+ expect(query.all.table).to eq(query.table)
27
23
  end
28
24
 
29
25
  it "fields should return fields" do
30
- expect(@query.all.fields).to eq @query.fields
26
+ expect(query.all.fields).to eq query.fields
31
27
  end
32
28
  end
33
29
 
34
30
  describe ".all.to_s" do
35
31
  it "should return a query for all records" do
36
- expect(@query.all.to_s).to eq "SELECT Id, name, etc FROM table_name"
32
+ expect(query.all.to_s).to eq "SELECT Id, name, etc FROM table_name"
37
33
  end
38
34
 
39
35
  it "should ignore dupicated attributes in select statment" do
40
- @query.fields ['Id', 'name', 'etc']
41
- expect(@query.all.to_s).to eq "SELECT Id, name, etc FROM table_name"
36
+ query.fields ['Id', 'name', 'etc']
37
+ expect(query.all.to_s).to eq "SELECT Id, name, etc FROM table_name"
42
38
  end
43
39
  end
44
40
 
45
41
  describe ".where" do
46
42
  it "should add a where condition to a query" do
47
- expect(@query.where("name like '%a%'").to_s).to eq "SELECT Id, name, etc FROM table_name WHERE name like '%a%'"
43
+ expect(query.where("name like '%a%'").to_s).to eq "SELECT Id, name, etc FROM table_name WHERE (name like '%a%')"
48
44
  end
49
45
 
50
- it "should add multiples conditions to a query" do
51
- expect(@query.where("condition1 = 1").where("condition2 = 2").to_s).to eq "SELECT Id, name, etc FROM table_name WHERE condition1 = 1 AND condition2 = 2"
46
+ it "should add multiples conditions to a query with parentheses" do
47
+ expect(query.where("condition1 = 1").where("condition2 = 2 OR condition3 = 3").to_s).to eq "SELECT Id, name, etc FROM table_name WHERE (condition1 = 1) AND (condition2 = 2 OR condition3 = 3)"
52
48
  end
53
49
  end
54
50
 
55
51
  describe ".limit" do
56
52
  it "should add a limit to a query" do
57
- expect(@query.limit("25").to_s).to eq "SELECT Id, name, etc FROM table_name LIMIT 25"
53
+ expect(query.limit("25").to_s).to eq "SELECT Id, name, etc FROM table_name LIMIT 25"
58
54
  end
59
55
  end
60
56
 
61
57
  describe ".limit_value" do
62
58
  it "should return the limit value" do
63
- @query.limit(4)
64
- expect(@query.limit_value).to eq 4
59
+ query.limit(4)
60
+ expect(query.limit_value).to eq 4
65
61
  end
66
62
  end
67
63
 
68
64
  describe ".offset" do
69
65
  it "should add an offset to a query" do
70
- expect(@query.offset(4).to_s).to eq "SELECT Id, name, etc FROM table_name OFFSET 4"
66
+ expect(query.offset(4).to_s).to eq "SELECT Id, name, etc FROM table_name OFFSET 4"
71
67
  end
72
68
  end
73
69
 
74
70
  describe ".offset_value" do
75
71
  it "should return the offset value" do
76
- @query.offset(4)
77
- expect(@query.offset_value).to eq 4
72
+ query.offset(4)
73
+ expect(query.offset_value).to eq 4
78
74
  end
79
75
  end
80
76
 
81
77
  describe ".find.to_s" do
82
78
  it "should return a query for 1 record" do
83
- expect(@query.find(2).to_s).to eq "SELECT Id, name, etc FROM table_name WHERE Id = '2' LIMIT 1"
79
+ expect(query.find(2).to_s).to eq "SELECT Id, name, etc FROM table_name WHERE (Id = '2') LIMIT 1"
84
80
  end
85
81
  end
86
82
 
87
83
  describe ".order" do
88
84
  it "should add a order condition in the statment" do
89
- expect(@query.order("name desc").to_s).to eq "SELECT Id, name, etc FROM table_name ORDER BY name desc"
85
+ expect(query.order("name desc").to_s).to eq "SELECT Id, name, etc FROM table_name ORDER BY name desc"
90
86
  end
91
87
 
92
88
  it "should add a order condition in the statment with WHERE and LIMIT" do
93
- expect(@query.where("condition1 = 1").order("name desc").limit(1).to_s).to eq "SELECT Id, name, etc FROM table_name WHERE condition1 = 1 ORDER BY name desc LIMIT 1"
89
+ expect(query.where("condition1 = 1").order("name desc").limit(1).to_s).to eq "SELECT Id, name, etc FROM table_name WHERE (condition1 = 1) ORDER BY name desc LIMIT 1"
94
90
  end
95
91
  end
96
92
 
97
93
  describe '.join' do
98
-
99
- before do
100
- @join = ActiveForce::Query.new 'join_table_name'
101
- @join.fields ['name', 'etc']
94
+ let(:join_query) do
95
+ join = ActiveForce::Query.new 'join_table_name'
96
+ join.fields ['name', 'etc']
97
+ join
102
98
  end
103
99
 
104
100
  it 'should add another select statment on the current select' do
105
- expect(@query.join(@join).to_s).to eq 'SELECT Id, name, etc, (SELECT Id, name, etc FROM join_table_name) FROM table_name'
101
+ expect(query.join(join_query).to_s).to eq 'SELECT Id, name, etc, (SELECT Id, name, etc FROM join_table_name) FROM table_name'
106
102
  end
107
103
  end
108
104
 
109
105
  describe '.first' do
110
106
  it 'should return the query for the first record' do
111
- expect(@query.first.to_s).to eq 'SELECT Id, name, etc FROM table_name LIMIT 1'
107
+ expect(query.first.to_s).to eq 'SELECT Id, name, etc FROM table_name LIMIT 1'
112
108
  end
113
109
  end
114
110
 
115
111
  describe '.last' do
116
112
  it 'should return the query for the last record' do
117
- expect(@query.last.to_s).to eq 'SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT 1'
113
+ expect(query.last.to_s).to eq 'SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT 1'
118
114
  end
119
115
  end
120
116
 
121
117
  describe ".count" do
122
118
  it "should return the query for getting the row count" do
123
- expect(@query.count.to_s).to eq 'SELECT count(Id) FROM table_name'
119
+ expect(query.count.to_s).to eq 'SELECT count(Id) FROM table_name'
124
120
  end
125
121
 
126
122
  it "should work with a condition" do
127
- expect(@query.where("name = 'cool'").count.to_s).to eq "SELECT count(Id) FROM table_name WHERE name = 'cool'"
128
- end
129
- end
130
-
131
- describe '.options' do
132
- it 'should add a where if the option has a where condition' do
133
- expect(@query.options(where: 'var = 1').to_s).to eq "SELECT Id, name, etc FROM table_name WHERE var = 1"
134
- end
135
-
136
- it 'should add a limit if the option has a limit condition' do
137
- expect(@query.options(limit: 1).to_s).to eq "SELECT Id, name, etc FROM table_name LIMIT 1"
138
- end
139
-
140
- it 'should add a order if the option has a order condition' do
141
- expect(@query.options(order: 'name desc').to_s).to eq "SELECT Id, name, etc FROM table_name ORDER BY name desc"
142
- end
143
-
144
- it 'should work with multiples options' do
145
- expect(@query.options(where: 'var = 1', order: 'name desc', limit: 1).to_s).to eq "SELECT Id, name, etc FROM table_name WHERE var = 1 ORDER BY name desc LIMIT 1"
123
+ expect(query.where("name = 'cool'").count.to_s).to eq "SELECT count(Id) FROM table_name WHERE (name = 'cool')"
146
124
  end
147
125
  end
148
126
  end
@@ -0,0 +1,290 @@
1
+ require 'spec_helper'
2
+
3
+ module ActiveForce
4
+ describe SObject do
5
+ let(:client){ double "client" }
6
+
7
+ before do
8
+ allow(ActiveForce::SObject).to receive(:sfdc_client).and_return client
9
+ end
10
+
11
+ describe '.includes' do
12
+ context 'belongs_to' do
13
+ it 'queries the API for the associated record' do
14
+ soql = Territory.includes(:quota).where(id: '123').to_s
15
+ expect(soql).to eq "SELECT Id, Quota__c, Name, Quota__r.Bar_Id__c FROM Territory WHERE (Id = '123')"
16
+ end
17
+
18
+ it "queries the API once to retrieve the object and its related one" do
19
+ response = [build_restforce_sobject({
20
+ "Id" => "123",
21
+ "Quota__c" => "321",
22
+ "Quota__r" => {
23
+ "Bar_Id__c" => "321"
24
+ }
25
+ })]
26
+ allow(client).to receive(:query).once.and_return response
27
+ territory = Territory.includes(:quota).find "123"
28
+ expect(territory.quota).to be_a Quota
29
+ expect(territory.quota.id).to eq "321"
30
+ end
31
+
32
+ it "queries the API once to retrieve the object and its related one" do
33
+ response = [build_restforce_sobject({
34
+ "Id" => "123",
35
+ "Quota__c" => "321",
36
+ "Quota__r" => {
37
+ "Bar_Id__c" => "321"
38
+ }
39
+ })]
40
+ allow(client).to receive(:query).once.and_return response
41
+ territory = Territory.includes(:quota).find "123"
42
+ expect(territory.quota).to be_a Quota
43
+ expect(territory.quota.id).to eq "321"
44
+ end
45
+
46
+ context 'with namespaced SObjects' do
47
+ it 'queries the API for the associated record' do
48
+ soql = Salesforce::Territory.includes(:quota).where(id: '123').to_s
49
+ expect(soql).to eq "SELECT Id, QuotaId, WidgetId, Quota__r.Id FROM Territory WHERE (Id = '123')"
50
+ end
51
+
52
+ it "queries the API once to retrieve the object and its related one" do
53
+ response = [build_restforce_sobject({
54
+ "Id" => "123",
55
+ "QuotaId" => "321",
56
+ "WidgetId" => "321",
57
+ "Quota__r" => {
58
+ "Id" => "321"
59
+ }
60
+ })]
61
+ allow(client).to receive(:query).once.and_return response
62
+ territory = Salesforce::Territory.includes(:quota).find "123"
63
+ expect(territory.quota).to be_a Salesforce::Quota
64
+ expect(territory.quota.id).to eq "321"
65
+ end
66
+
67
+ context 'when the relationship table name is different from the actual table name' do
68
+ it 'formulates the correct SOQL' do
69
+ soql = Salesforce::Opportunity.includes(:owner).where(id: '123').to_s
70
+ expect(soql).to eq "SELECT Id, OwnerId, AccountId, Business_Partner__c, Owner.Id FROM Opportunity WHERE (Id = '123')"
71
+ end
72
+
73
+ it "queries the API once to retrieve the object and its related one" do
74
+ response = [build_restforce_sobject({
75
+ "Id" => "123",
76
+ "OwnerId" => "321",
77
+ "AccountId" => "432",
78
+ "Owner" => {
79
+ "Id" => "321"
80
+ }
81
+ })]
82
+ allow(client).to receive(:query).once.and_return response
83
+ opportunity = Salesforce::Opportunity.includes(:owner).find "123"
84
+ expect(opportunity.owner).to be_a Salesforce::User
85
+ expect(opportunity.owner.id).to eq "321"
86
+ end
87
+ end
88
+
89
+ context 'when the class name does not match the SFDC entity name' do
90
+ let(:expected_soql) do
91
+ "SELECT Id, QuotaId, WidgetId, Tegdiw__r.Id FROM Territory WHERE (Id = '123')"
92
+ end
93
+
94
+ it 'queries the API for the associated record' do
95
+ soql = Salesforce::Territory.includes(:widget).where(id: '123').to_s
96
+ expect(soql).to eq expected_soql
97
+ end
98
+
99
+ it "queries the API once to retrieve the object and its related one" do
100
+ response = [build_restforce_sobject({
101
+ "Id" => "123",
102
+ "WidgetId" => "321",
103
+ "Tegdiw__r" => {
104
+ "Id" => "321"
105
+ }
106
+ })]
107
+ expected = expected_soql + ' LIMIT 1'
108
+ allow(client).to receive(:query).once.with(expected).and_return response
109
+ territory = Salesforce::Territory.includes(:widget).find "123"
110
+ expect(territory.widget).to be_a Salesforce::Widget
111
+ expect(territory.widget.id).to eq "321"
112
+ end
113
+ end
114
+
115
+ context 'child to several parents' do
116
+ it 'queries the API for associated records' do
117
+ soql = Salesforce::Territory.includes(:quota, :widget).where(id: '123').to_s
118
+ expect(soql).to eq "SELECT Id, QuotaId, WidgetId, Quota__r.Id, Tegdiw__r.Id FROM Territory WHERE (Id = '123')"
119
+ end
120
+
121
+ it "queries the API once to retrieve the object and its assocations" do
122
+ response = [build_restforce_sobject({
123
+ "Id" => "123",
124
+ "QuotaId" => "321",
125
+ "WidgetId" => "321",
126
+ "Quota__r" => {
127
+ "Id" => "321"
128
+ },
129
+ "Tegdiw__r" => {
130
+ "Id" => "321"
131
+ }
132
+ })]
133
+ allow(client).to receive(:query).once.and_return response
134
+ territory = Salesforce::Territory.includes(:quota, :widget).find "123"
135
+ expect(territory.quota).to be_a Salesforce::Quota
136
+ expect(territory.quota.id).to eq "321"
137
+ expect(territory.widget).to be_a Salesforce::Widget
138
+ expect(territory.widget.id).to eq "321"
139
+ end
140
+ end
141
+ end
142
+
143
+ context 'when there is no associated record' do
144
+ it "queries the API once to retrieve the object and its related one" do
145
+ response = [build_restforce_sobject({
146
+ "Id" => "123",
147
+ "Quota__c" => "321",
148
+ "Quota__r" => nil
149
+ })]
150
+ allow(client).to receive(:query).once.and_return response
151
+ territory = Territory.includes(:quota).find "123"
152
+ expect(territory.quota).to be_nil
153
+ expect(territory.quota).to be_nil
154
+ end
155
+ end
156
+ end
157
+
158
+ context 'has_many' do
159
+ context 'with standard objects' do
160
+ it 'formulates the correct SOQL query' do
161
+ soql = Account.includes(:opportunities).where(id: '123').to_s
162
+ expect(soql).to eq "SELECT Id, OwnerId, (SELECT Id, AccountId FROM Opportunities) FROM Account WHERE (Id = '123')"
163
+ end
164
+
165
+ it 'builds the associated objects and caches them' do
166
+ response = [build_restforce_sobject({
167
+ 'Id' => '123',
168
+ 'Opportunities' => build_restforce_collection([
169
+ {'Id' => '213', 'AccountId' => '123'},
170
+ {'Id' => '214', 'AccountId' => '123'}
171
+ ])
172
+ })]
173
+ allow(client).to receive(:query).once.and_return response
174
+ account = Account.includes(:opportunities).find '123'
175
+ expect(account.opportunities).to be_an Array
176
+ expect(account.opportunities.all? { |o| o.is_a? Opportunity }).to eq true
177
+ end
178
+ end
179
+
180
+ context 'with custom objects' do
181
+ it 'formulates the correct SOQL query' do
182
+ soql = Quota.includes(:prez_clubs).where(id: '123').to_s
183
+ expect(soql).to eq "SELECT Id, Bar_Id__c, (SELECT Id, QuotaId FROM PrezClubs__r) FROM Quota__c WHERE (Bar_Id__c = '123')"
184
+ end
185
+
186
+ it 'builds the associated objects and caches them' do
187
+ response = [build_restforce_sobject({
188
+ 'Id' => '123',
189
+ 'PrezClubs__r' => build_restforce_collection([
190
+ {'Id' => '213', 'QuotaId' => '123'},
191
+ {'Id' => '214', 'QuotaId' => '123'}
192
+ ])
193
+ })]
194
+ allow(client).to receive(:query).once.and_return response
195
+ account = Quota.includes(:prez_clubs).find '123'
196
+ expect(account.prez_clubs).to be_an Array
197
+ expect(account.prez_clubs.all? { |o| o.is_a? PrezClub }).to eq true
198
+ end
199
+ end
200
+
201
+ context 'mixing standard and custom objects' do
202
+ it 'formulates the correct SOQL query' do
203
+ soql = Quota.includes(:territories, :prez_clubs).where(id: '123').to_s
204
+ expect(soql).to eq "SELECT Id, Bar_Id__c, (SELECT Id, Quota__c, Name FROM Territories), (SELECT Id, QuotaId FROM PrezClubs__r) FROM Quota__c WHERE (Bar_Id__c = '123')"
205
+ end
206
+
207
+ it 'builds the associated objects and caches them' do
208
+ response = [build_restforce_sobject({
209
+ 'Id' => '123',
210
+ 'PrezClubs__r' => build_restforce_collection([
211
+ {'Id' => '213', 'QuotaId' => '123'},
212
+ {'Id' => '214', 'QuotaId' => '123'}
213
+ ]),
214
+ 'Territories' => build_restforce_collection([
215
+ {'Id' => '213', 'Quota__c' => '123'},
216
+ {'Id' => '214', 'Quota__c' => '123'}
217
+ ])
218
+ })]
219
+ allow(client).to receive(:query).once.and_return response
220
+ account = Quota.includes(:territories, :prez_clubs).find '123'
221
+ expect(account.prez_clubs).to be_an Array
222
+ expect(account.prez_clubs.all? { |o| o.is_a? PrezClub }).to eq true
223
+ expect(account.territories).to be_an Array
224
+ expect(account.territories.all? { |o| o.is_a? Territory }).to eq true
225
+ end
226
+ end
227
+
228
+ context 'with namespaced SObjects' do
229
+ it 'formulates the correct SOQL query' do
230
+ soql = Salesforce::Quota.includes(:prez_clubs).where(id: '123').to_s
231
+ expect(soql).to eq "SELECT Id, (SELECT Id, QuotaId FROM PrezClubs__r) FROM Quota__c WHERE (Id = '123')"
232
+ end
233
+
234
+ it 'builds the associated objects and caches them' do
235
+ response = [build_restforce_sobject({
236
+ 'Id' => '123',
237
+ 'PrezClubs__r' => build_restforce_collection([
238
+ {'Id' => '213', 'QuotaId' => '123'},
239
+ {'Id' => '214', 'QuotaId' => '123'}
240
+ ])
241
+ })]
242
+ allow(client).to receive(:query).once.and_return response
243
+ account = Salesforce::Quota.includes(:prez_clubs).find '123'
244
+ expect(account.prez_clubs).to be_an Array
245
+ expect(account.prez_clubs.all? { |o| o.is_a? Salesforce::PrezClub }).to eq true
246
+ end
247
+ end
248
+
249
+ context 'when there are no associated records returned by the query' do
250
+ it 'caches the response' do
251
+ response = [build_restforce_sobject({
252
+ 'Id' => '123',
253
+ 'Opportunities' => nil
254
+ })]
255
+ allow(client).to receive(:query).once.and_return response
256
+ account = Account.includes(:opportunities).find '123'
257
+ expect(account.opportunities).to eq []
258
+ expect(account.opportunities).to eq []
259
+ end
260
+ end
261
+ end
262
+
263
+ describe 'mixing belongs_to and has_many' do
264
+ it 'formulates the correct SOQL query' do
265
+ soql = Account.includes(:opportunities, :owner).where(id: '123').to_s
266
+ expect(soql).to eq "SELECT Id, OwnerId, (SELECT Id, AccountId FROM Opportunities), Owner__r.Id FROM Account WHERE (Id = '123')"
267
+ end
268
+
269
+ it 'builds the associated objects and caches them' do
270
+ response = [build_restforce_sobject({
271
+ 'Id' => '123',
272
+ 'Opportunities' => build_restforce_collection([
273
+ {'Id' => '213', 'AccountId' => '123'},
274
+ {'Id' => '214', 'AccountId' => '123'}
275
+ ]),
276
+ 'Owner__r' => {
277
+ 'Id' => '321'
278
+ }
279
+ })]
280
+ allow(client).to receive(:query).once.and_return response
281
+ account = Account.includes(:opportunities).find '123'
282
+ expect(account.opportunities).to be_an Array
283
+ expect(account.opportunities.all? { |o| o.is_a? Opportunity }).to eq true
284
+ expect(account.owner).to be_a Owner
285
+ expect(account.owner.id).to eq '321'
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end