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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.mailmap +3 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +98 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +174 -0
- data/Rakefile +6 -0
- data/active_force.gemspec +30 -0
- data/lib/active_attr/dirty.rb +33 -0
- data/lib/active_force/active_query.rb +155 -0
- data/lib/active_force/association/association.rb +50 -0
- data/lib/active_force/association/belongs_to_association.rb +26 -0
- data/lib/active_force/association/eager_load_projection_builder.rb +60 -0
- data/lib/active_force/association/has_many_association.rb +33 -0
- data/lib/active_force/association/relation_model_builder.rb +70 -0
- data/lib/active_force/association.rb +28 -0
- data/lib/active_force/attribute.rb +30 -0
- data/lib/active_force/mapping.rb +78 -0
- data/lib/active_force/query.rb +110 -0
- data/lib/active_force/sobject.rb +210 -0
- data/lib/active_force/standard_types.rb +357 -0
- data/lib/active_force/table.rb +37 -0
- data/lib/active_force/version.rb +3 -0
- data/lib/active_force.rb +13 -0
- data/lib/generators/active_force/model/USAGE +8 -0
- data/lib/generators/active_force/model/model_generator.rb +62 -0
- data/lib/generators/active_force/model/templates/model.rb.erb +5 -0
- data/spec/active_force/active_query_spec.rb +178 -0
- data/spec/active_force/association/relation_model_builder_spec.rb +62 -0
- data/spec/active_force/association_spec.rb +157 -0
- data/spec/active_force/attribute_spec.rb +27 -0
- data/spec/active_force/callbacks_spec.rb +20 -0
- data/spec/active_force/mapping_spec.rb +18 -0
- data/spec/active_force/query_spec.rb +126 -0
- data/spec/active_force/sobject/includes_spec.rb +290 -0
- data/spec/active_force/sobject/table_name_spec.rb +27 -0
- data/spec/active_force/sobject_spec.rb +398 -0
- data/spec/active_force/table_spec.rb +25 -0
- data/spec/active_force_spec.rb +7 -0
- data/spec/fixtures/sobject/single_sobject_hash.yml +26 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/fixture_helpers.rb +45 -0
- data/spec/support/restforce_factories.rb +9 -0
- data/spec/support/sobjects.rb +97 -0
- data/spec/support/whizbang.rb +30 -0
- 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
|