openstax_active_force 1.0.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.
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