openstax_active_force 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.mailmap +3 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +3 -0
  6. data/CHANGELOG.md +98 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +174 -0
  10. data/Rakefile +6 -0
  11. data/active_force.gemspec +30 -0
  12. data/lib/active_attr/dirty.rb +33 -0
  13. data/lib/active_force/active_query.rb +155 -0
  14. data/lib/active_force/association/association.rb +50 -0
  15. data/lib/active_force/association/belongs_to_association.rb +26 -0
  16. data/lib/active_force/association/eager_load_projection_builder.rb +60 -0
  17. data/lib/active_force/association/has_many_association.rb +33 -0
  18. data/lib/active_force/association/relation_model_builder.rb +70 -0
  19. data/lib/active_force/association.rb +28 -0
  20. data/lib/active_force/attribute.rb +30 -0
  21. data/lib/active_force/mapping.rb +78 -0
  22. data/lib/active_force/query.rb +110 -0
  23. data/lib/active_force/sobject.rb +210 -0
  24. data/lib/active_force/standard_types.rb +357 -0
  25. data/lib/active_force/table.rb +37 -0
  26. data/lib/active_force/version.rb +3 -0
  27. data/lib/active_force.rb +13 -0
  28. data/lib/generators/active_force/model/USAGE +8 -0
  29. data/lib/generators/active_force/model/model_generator.rb +62 -0
  30. data/lib/generators/active_force/model/templates/model.rb.erb +5 -0
  31. data/spec/active_force/active_query_spec.rb +178 -0
  32. data/spec/active_force/association/relation_model_builder_spec.rb +62 -0
  33. data/spec/active_force/association_spec.rb +157 -0
  34. data/spec/active_force/attribute_spec.rb +27 -0
  35. data/spec/active_force/callbacks_spec.rb +20 -0
  36. data/spec/active_force/mapping_spec.rb +18 -0
  37. data/spec/active_force/query_spec.rb +126 -0
  38. data/spec/active_force/sobject/includes_spec.rb +290 -0
  39. data/spec/active_force/sobject/table_name_spec.rb +27 -0
  40. data/spec/active_force/sobject_spec.rb +398 -0
  41. data/spec/active_force/table_spec.rb +25 -0
  42. data/spec/active_force_spec.rb +7 -0
  43. data/spec/fixtures/sobject/single_sobject_hash.yml +26 -0
  44. data/spec/spec_helper.rb +16 -0
  45. data/spec/support/fixture_helpers.rb +45 -0
  46. data/spec/support/restforce_factories.rb +9 -0
  47. data/spec/support/sobjects.rb +97 -0
  48. data/spec/support/whizbang.rb +30 -0
  49. metadata +196 -0
@@ -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
+ ActiveForce.sfdc_client = 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
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveForce::SObject do
4
+ describe '#table_name' do
5
+ it 'Use the class name adding "__c"' do
6
+ expect(Custom.table_name).to eq('Custom__c')
7
+ end
8
+
9
+ it 'with standard SObject types it does not add the "__c"' do
10
+ expect(Account.table_name).to eq('Account')
11
+ end
12
+
13
+ it 'can be enforced in the class definition' do
14
+ expect(EnforcedTableName.table_name).to eq('Forced__c')
15
+ end
16
+
17
+ context 'with a namespace' do
18
+ it "the namespace is not included" do
19
+ expect(Foo::Bar.table_name).to eq('Bar__c')
20
+ end
21
+
22
+ it 'standard types are inferred correctly' do
23
+ expect(Foo::Account.table_name).to eq('Account')
24
+ end
25
+ end
26
+ end
27
+ end