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,357 @@
|
|
1
|
+
module StandardTypes
|
2
|
+
STANDARD_TYPES = %w[
|
3
|
+
Account
|
4
|
+
Asset
|
5
|
+
AssetFeed
|
6
|
+
AssetTag
|
7
|
+
AssignmentRule
|
8
|
+
AsyncApexJob
|
9
|
+
AttachedContentDocument
|
10
|
+
Attachment
|
11
|
+
AuthSession
|
12
|
+
Bookmark
|
13
|
+
BrandTemplate
|
14
|
+
BusinessHours
|
15
|
+
BusinessProcess
|
16
|
+
CallCenter
|
17
|
+
Campaign
|
18
|
+
CampaignFeed
|
19
|
+
CampaignMember
|
20
|
+
CampaignMemberStatus
|
21
|
+
CampaignOwnerSharingRule
|
22
|
+
Campaign
|
23
|
+
CampaignShare
|
24
|
+
CampaignTag
|
25
|
+
Case
|
26
|
+
CaseArticle
|
27
|
+
CaseComment
|
28
|
+
CaseContactRole
|
29
|
+
CaseFeed
|
30
|
+
CaseHistory
|
31
|
+
CaseMilestone
|
32
|
+
CaseOwnerSharingRule
|
33
|
+
CaseShare
|
34
|
+
CaseSolution
|
35
|
+
Solution
|
36
|
+
CaseStatus
|
37
|
+
CaseTag
|
38
|
+
CaseTeamMember
|
39
|
+
CaseTeamRole
|
40
|
+
CaseTeamTemplate
|
41
|
+
CaseTeamTemplateMember
|
42
|
+
CaseTeamTemplateRecord
|
43
|
+
CategoryData
|
44
|
+
CategoryNode
|
45
|
+
CategoryNodeLocalization
|
46
|
+
ChatterActivity
|
47
|
+
ChatterAnswersActivity
|
48
|
+
ChatterAnswersReputationLevel
|
49
|
+
ChatterAnswersReputationLevel
|
50
|
+
ChatterConversation
|
51
|
+
ChatterConversationMember
|
52
|
+
ChatterMessage
|
53
|
+
CollaborationGroup
|
54
|
+
CollaborationGroupFeed
|
55
|
+
CollaborationGroupMember
|
56
|
+
CollaborationGroupMemberRequest
|
57
|
+
CollaborationInvitation
|
58
|
+
CombinedAttachment
|
59
|
+
Community
|
60
|
+
Contact
|
61
|
+
ContactFeed
|
62
|
+
ContactHistory
|
63
|
+
ContactOwnerSharingRule
|
64
|
+
ContactShare
|
65
|
+
ContactTag
|
66
|
+
ContentDocument
|
67
|
+
ContentDocumentFeed
|
68
|
+
ContentDocumentHistory
|
69
|
+
ContentDocumentLink
|
70
|
+
ContentVersion
|
71
|
+
ContentVersionHistory
|
72
|
+
ContentWorkspace
|
73
|
+
ContentWorkspaceDoc
|
74
|
+
Contract
|
75
|
+
ContractContactRole
|
76
|
+
ContractFeed
|
77
|
+
ContractHistory
|
78
|
+
ContractLineItem
|
79
|
+
ContractLineItemHistory
|
80
|
+
ContractStatus
|
81
|
+
ContractTag
|
82
|
+
CronTrigger
|
83
|
+
CronJobDetail
|
84
|
+
CurrencyType
|
85
|
+
CustomBrand
|
86
|
+
CustomBrandAsset
|
87
|
+
CustomPermission
|
88
|
+
DandBCompany
|
89
|
+
Dashboard
|
90
|
+
DashboardComponent
|
91
|
+
DashboardComponentFeed
|
92
|
+
DashboardFeed
|
93
|
+
DashboardTag
|
94
|
+
DatacloudCompany
|
95
|
+
DatacloudContact
|
96
|
+
DatacloudDandBCompany
|
97
|
+
DatacloudOwnedEntity
|
98
|
+
DatacloudPurchaseUsage
|
99
|
+
DatacloudSocialHandle
|
100
|
+
DatedConversionRate
|
101
|
+
DeclinedEventRelation
|
102
|
+
Division
|
103
|
+
DivisionLocalization
|
104
|
+
Document
|
105
|
+
DocumentAttachmentMap
|
106
|
+
DocumentTag
|
107
|
+
EmailMessage
|
108
|
+
EmailServicesAddress
|
109
|
+
EmailServicesFunction
|
110
|
+
EmailStatus
|
111
|
+
EmailTemplate
|
112
|
+
Entitlement
|
113
|
+
EntitlementContact
|
114
|
+
EntitlementFeed
|
115
|
+
EntitlementHistory
|
116
|
+
EntitlementTemplate
|
117
|
+
EntityHistory
|
118
|
+
EntitySubscription
|
119
|
+
EnvironmentHubMember
|
120
|
+
Event
|
121
|
+
EventFeed
|
122
|
+
EventRelation
|
123
|
+
EventTag
|
124
|
+
EventWhoRelation
|
125
|
+
EventRelation
|
126
|
+
ExternalDataSource
|
127
|
+
ExternalDataUserAuth
|
128
|
+
FeedComment
|
129
|
+
FeedItem
|
130
|
+
FeedLike
|
131
|
+
FeedPollChoice
|
132
|
+
FeedPollVote
|
133
|
+
FeedPost
|
134
|
+
FeedTrackedChange
|
135
|
+
FieldPermissions
|
136
|
+
FiscalYearSettings
|
137
|
+
Folder
|
138
|
+
ForecastingAdjustment
|
139
|
+
ForecastingFact
|
140
|
+
ForecastingItem
|
141
|
+
ForecastingQuota
|
142
|
+
ForecastShare
|
143
|
+
Group
|
144
|
+
GroupMember
|
145
|
+
HashtagDefinition
|
146
|
+
Holiday
|
147
|
+
Idea
|
148
|
+
IdeaComment
|
149
|
+
IdeaTheme
|
150
|
+
KnowledgeableUser
|
151
|
+
KnowledgeArticle
|
152
|
+
KnowledgeArticleVersion
|
153
|
+
KnowledgeArticleVersionHistory
|
154
|
+
KnowledgeArticleViewStat
|
155
|
+
KnowledgeArticleVoteStat
|
156
|
+
Lead
|
157
|
+
LeadFeed
|
158
|
+
LeadHistory
|
159
|
+
LeadOwnerSharingRule
|
160
|
+
LeadShare
|
161
|
+
LeadStatus
|
162
|
+
LeadTag
|
163
|
+
LimitAllocationPerApp
|
164
|
+
LineitemOverride
|
165
|
+
LoginHistory
|
166
|
+
MailmergeTemplate
|
167
|
+
MilestoneType
|
168
|
+
Name
|
169
|
+
Network
|
170
|
+
NetworkActivityAudit
|
171
|
+
NetworkMember
|
172
|
+
NetworkMemberGroup
|
173
|
+
NetworkModeration
|
174
|
+
NewsFeed
|
175
|
+
Note
|
176
|
+
NoteAndAttachment
|
177
|
+
NoteTag
|
178
|
+
ObjectPermissions
|
179
|
+
ObjectTerritory2AssignmentRule
|
180
|
+
ObjectTerritory2AssignmentRuleItem
|
181
|
+
ObjectTerritory2Association
|
182
|
+
OpenActivity
|
183
|
+
Opportunity
|
184
|
+
OpportunityCompetitor
|
185
|
+
OpportunityContactRole
|
186
|
+
OpportunityFeed
|
187
|
+
OpportunityFieldHistory
|
188
|
+
OpportunityHistory
|
189
|
+
OpportunityLineItem
|
190
|
+
OpportunityLineItemSchedule
|
191
|
+
OpportunityOverride
|
192
|
+
OpportunityOwnerSharingRule
|
193
|
+
OpportunityPartner
|
194
|
+
OpportunityShare
|
195
|
+
OpportunitySplit
|
196
|
+
OpportunitySplitType
|
197
|
+
OpportunityStage
|
198
|
+
OpportunityTag
|
199
|
+
OpportunityTeamMember
|
200
|
+
Organization
|
201
|
+
OrgWideEmailAddress
|
202
|
+
OwnedContentDocument
|
203
|
+
PackageLicense
|
204
|
+
Partner
|
205
|
+
PartnerNetworkConnection
|
206
|
+
PartnerNetworkRecordConnection
|
207
|
+
PartnerRole
|
208
|
+
Period
|
209
|
+
PermissionSet
|
210
|
+
PermissionSetAssignment
|
211
|
+
Pricebook2
|
212
|
+
Pricebook2History
|
213
|
+
PricebookEntry
|
214
|
+
ProcessDefinition
|
215
|
+
ProcessInstance
|
216
|
+
ProcessInstanceHistory
|
217
|
+
ProcessInstanceNode
|
218
|
+
ProcessInstanceStep
|
219
|
+
ProcessInstanceWorkitem
|
220
|
+
ProcessNode
|
221
|
+
Product2
|
222
|
+
Product2Feed
|
223
|
+
ProductEntitlementTemplate
|
224
|
+
Profile
|
225
|
+
ProfileSkill
|
226
|
+
ProfileSkillEndorsement
|
227
|
+
ProfileSkillEndorsementHistory
|
228
|
+
ProfileSkillFeed
|
229
|
+
ProfileSkillHistory
|
230
|
+
ProfileSkillShare
|
231
|
+
ProfileSkillUser
|
232
|
+
ProfileSkillUserHistory
|
233
|
+
PushTopic
|
234
|
+
QuantityForecast
|
235
|
+
QuantityForecastHistory
|
236
|
+
Question
|
237
|
+
QuestionDataCategorySelection
|
238
|
+
QuestionReportAbuse
|
239
|
+
QuestionSubscription
|
240
|
+
QueueSobject
|
241
|
+
Quote
|
242
|
+
QuoteDocument
|
243
|
+
QuoteLineItem
|
244
|
+
RecentlyViewed
|
245
|
+
RecordType
|
246
|
+
RecordTypeLocalization
|
247
|
+
Reply
|
248
|
+
ReplyReportAbuse
|
249
|
+
Report
|
250
|
+
ReportFeed
|
251
|
+
ReportTag
|
252
|
+
RevenueForecast
|
253
|
+
RevenueForecastHistory
|
254
|
+
RuleTerritory2Association
|
255
|
+
Scontrol
|
256
|
+
ScontrolLocalization
|
257
|
+
SelfServiceUser
|
258
|
+
ServiceContract
|
259
|
+
ServiceContractFeed
|
260
|
+
ServiceContractHistory
|
261
|
+
ServiceContractOwnerSharingRule
|
262
|
+
ServiceContractShare
|
263
|
+
SetupEntityAccess
|
264
|
+
SignupRequest
|
265
|
+
Site
|
266
|
+
SiteHistory
|
267
|
+
SlaProcess
|
268
|
+
Solution
|
269
|
+
SolutionFeed
|
270
|
+
SolutionHistory
|
271
|
+
SolutionStatus
|
272
|
+
SolutionTag
|
273
|
+
StaticResource
|
274
|
+
StreamingChannel
|
275
|
+
TagDefinition
|
276
|
+
Task
|
277
|
+
TaskFeed
|
278
|
+
TaskPriority
|
279
|
+
TaskRelation
|
280
|
+
TaskStatus
|
281
|
+
TaskTag
|
282
|
+
TaskWhoRelation
|
283
|
+
TaskRelation
|
284
|
+
Territory
|
285
|
+
Territory2
|
286
|
+
Territory2Model
|
287
|
+
Territory2ModelHistory
|
288
|
+
Territory2Type
|
289
|
+
Topic
|
290
|
+
TopicAssignment
|
291
|
+
TopicFeed
|
292
|
+
UndecidedEventRelation
|
293
|
+
User
|
294
|
+
UserAccountTeamMember
|
295
|
+
UserConfigTransferButton
|
296
|
+
UserConfigTransferSkill
|
297
|
+
UserFeed
|
298
|
+
UserLicense
|
299
|
+
UserLogin
|
300
|
+
UserMembershipSharingRule
|
301
|
+
UserPackageLicense
|
302
|
+
UserPreference
|
303
|
+
UserProfile
|
304
|
+
UserProfileFeed
|
305
|
+
UserRecordAccess
|
306
|
+
UserRole
|
307
|
+
UserShare
|
308
|
+
UserTeamMember
|
309
|
+
UserTerritory
|
310
|
+
UserTerritory2Association
|
311
|
+
Vote
|
312
|
+
WebLink
|
313
|
+
WebLinkLocalization
|
314
|
+
WorkAccess
|
315
|
+
WorkAccessShare
|
316
|
+
WorkBadge
|
317
|
+
WorkBadgeDefinition
|
318
|
+
WorkBadgeDefinitionHistory
|
319
|
+
WorkBadgeDefinitionShare
|
320
|
+
WorkCoaching
|
321
|
+
WorkCoachingFeed
|
322
|
+
WorkCoachingHistory
|
323
|
+
WorkCoachingShare
|
324
|
+
WorkFeedback
|
325
|
+
WorkFeedbackQuestion
|
326
|
+
WorkFeedbackQuestionHistory
|
327
|
+
WorkFeedbackQuestionSet
|
328
|
+
WorkFeedbackQuestionSetShare
|
329
|
+
WorkFeedbackQuestionShare
|
330
|
+
WorkFeedbackRequest
|
331
|
+
WorkFeedbackRequestHistory
|
332
|
+
WorkFeedbackRequestShare
|
333
|
+
WorkFeedbackShare
|
334
|
+
WorkGoal
|
335
|
+
WorkGoalCollaborator
|
336
|
+
WorkGoalCollaboratorHistory
|
337
|
+
WorkGoalFeed
|
338
|
+
WorkGoalHistory
|
339
|
+
WorkGoalLink
|
340
|
+
WorkGoalShare
|
341
|
+
WorkPerformanceCycle
|
342
|
+
WorkPerformanceCycleFeed
|
343
|
+
WorkPerformanceCycleHistory
|
344
|
+
WorkPerformanceCycleShare
|
345
|
+
WorkReward
|
346
|
+
WorkRewardFund
|
347
|
+
WorkRewardFundHistory
|
348
|
+
WorkRewardFundShare
|
349
|
+
WorkRewardFundType
|
350
|
+
WorkRewardFundTypeHistory
|
351
|
+
WorkRewardFundTypeShare
|
352
|
+
WorkRewardHistory
|
353
|
+
WorkRewardShare
|
354
|
+
WorkThanks
|
355
|
+
WorkThanksShare
|
356
|
+
]
|
357
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'active_force/standard_types'
|
2
|
+
|
3
|
+
module ActiveForce
|
4
|
+
class Table
|
5
|
+
|
6
|
+
def initialize klass
|
7
|
+
@klass = klass.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def table_name name = nil
|
11
|
+
@name = name || @name || pick_table_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@name ||= pick_table_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def custom_table?
|
19
|
+
!StandardTypes::STANDARD_TYPES.include?(name_without_namespace)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def pick_table_name
|
25
|
+
if custom_table?
|
26
|
+
"#{ name_without_namespace }__c"
|
27
|
+
else
|
28
|
+
name_without_namespace
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def name_without_namespace
|
33
|
+
@klass.split('::').last
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/lib/active_force.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module ActiveForce
|
2
|
+
class ModelGenerator < Rails::Generators::NamedBase
|
3
|
+
desc 'This generator loads the table fields from SFDC and generates the fields for the SObject with a more ruby names'
|
4
|
+
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
def create_model_file
|
8
|
+
@table_name = file_name.capitalize
|
9
|
+
@class_name = @table_name.gsub '__c', ''
|
10
|
+
template "model.rb.erb", "app/models/#{@class_name.downcase}.rb" if table_exists?
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
Attribute = Struct.new :field, :column
|
16
|
+
|
17
|
+
def attributes
|
18
|
+
@attributes ||= sfdc_columns.map do |column|
|
19
|
+
Attribute.new column_to_field(column), column
|
20
|
+
end
|
21
|
+
@attributes - [:id]
|
22
|
+
end
|
23
|
+
|
24
|
+
def sfdc_columns
|
25
|
+
@columns ||= ActiveForce::SObject.sfdc_client.describe(@table_name).fields.map do |field|
|
26
|
+
field.name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def table_exists?
|
31
|
+
!! sfdc_columns
|
32
|
+
rescue Faraday::Error::ResourceNotFound
|
33
|
+
puts "The specified table name is not found. Be sure to append __c if it's custom"
|
34
|
+
end
|
35
|
+
|
36
|
+
def column_to_field column
|
37
|
+
column.underscore.gsub("__c", "").to_sym
|
38
|
+
end
|
39
|
+
|
40
|
+
def attribute_line attribute
|
41
|
+
"field :#{ attribute.field },#{ space_justify attribute.field } from: '#{ attribute.column }'"
|
42
|
+
end
|
43
|
+
|
44
|
+
def space_justify field_name
|
45
|
+
longest_field = attributes.map { |attr| attr.field.length } .max
|
46
|
+
justify_count = longest_field - field_name.length
|
47
|
+
" " * justify_count
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class String
|
52
|
+
def underscore
|
53
|
+
self.gsub(/::/, '/').
|
54
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
55
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
56
|
+
tr("-", "_").
|
57
|
+
downcase
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveForce::ActiveQuery do
|
4
|
+
let(:sobject) do
|
5
|
+
double("sobject", {
|
6
|
+
table_name: "table_name",
|
7
|
+
fields: [],
|
8
|
+
mappings: mappings
|
9
|
+
})
|
10
|
+
end
|
11
|
+
let(:mappings){ { id: "Id", field: "Field__c", other_field: "Other_Field" } }
|
12
|
+
let(:client){ double("client") }
|
13
|
+
let(:active_query){ ActiveForce::ActiveQuery.new(sobject) }
|
14
|
+
|
15
|
+
before do
|
16
|
+
allow(active_query).to receive(:sfdc_client).and_return client
|
17
|
+
allow(active_query).to receive(:build).and_return Object.new
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "to_a" do
|
21
|
+
before do
|
22
|
+
expect(client).to receive(:query)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return an array of objects" do
|
26
|
+
result = active_query.where("Text_Label = 'foo'").to_a
|
27
|
+
expect(result).to be_a Array
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should allow to chain query methods" do
|
31
|
+
result = active_query.where("Text_Label = 'foo'").where("Checkbox_Label = true").to_a
|
32
|
+
expect(result).to be_a Array
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "select only some field using mappings" do
|
37
|
+
it "should return a query only with selected field" do
|
38
|
+
active_query.select(:field)
|
39
|
+
expect(active_query.to_s).to eq("SELECT Field__c FROM table_name")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "condition mapping" do
|
44
|
+
it "maps conditions for a .where" do
|
45
|
+
active_query.where(field: 123)
|
46
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123)")
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'transforms an array to a WHERE/IN clause' do
|
50
|
+
active_query.where(field: ['foo', 'bar'])
|
51
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c IN ('foo','bar'))")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "encloses the value in quotes if it's a string" do
|
55
|
+
active_query.where field: "hello"
|
56
|
+
expect(active_query.to_s).to end_with("(Field__c = 'hello')")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "puts NULL when a field is set as nil" do
|
60
|
+
active_query.where field: nil
|
61
|
+
expect(active_query.to_s).to end_with("(Field__c = NULL)")
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'bind parameters' do
|
65
|
+
let(:mappings) do
|
66
|
+
super().merge({
|
67
|
+
other_field: 'Other_Field__c',
|
68
|
+
name: 'Name'
|
69
|
+
})
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'accepts bind parameters' do
|
73
|
+
active_query.where('Field__c = ?', 123)
|
74
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123)")
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'accepts nil bind parameters' do
|
78
|
+
active_query.where('Field__c = ?', nil)
|
79
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = NULL)")
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'accepts multiple bind parameters' do
|
83
|
+
active_query.where('Field__c = ? AND Other_Field__c = ? AND Name = ?', 123, 321, 'Bob')
|
84
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob')")
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'complains when there given an incorrect number of bind parameters' do
|
88
|
+
expect{
|
89
|
+
active_query.where('Field__c = ? AND Other_Field__c = ? AND Name = ?', 123, 321)
|
90
|
+
}.to raise_error(ActiveForce::PreparedStatementInvalid, 'wrong number of bind variables (2 for 3)')
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'named bind parameters' do
|
94
|
+
it 'accepts bind parameters' do
|
95
|
+
active_query.where('Field__c = :field', field: 123)
|
96
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123)")
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'accepts nil bind parameters' do
|
100
|
+
active_query.where('Field__c = :field', field: nil)
|
101
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = NULL)")
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'accepts multiple bind parameters' do
|
105
|
+
active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', field: 123, other_field: 321, name: 'Bob')
|
106
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob')")
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'accepts multiple bind parameters orderless' do
|
110
|
+
active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', name: 'Bob', other_field: 321, field: 123)
|
111
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob')")
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'complains when there given an incorrect number of bind parameters' do
|
115
|
+
expect{
|
116
|
+
active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', field: 123, other_field: 321)
|
117
|
+
}.to raise_error(ActiveForce::PreparedStatementInvalid, 'missing value for :name in Field__c = :field AND Other_Field__c = :other_field AND Name = :name')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#find_by" do
|
124
|
+
it "should query the client, with the SFDC field names and correctly enclosed values" do
|
125
|
+
expect(client).to receive :query
|
126
|
+
active_query.find_by field: 123
|
127
|
+
expect(active_query.to_s).to eq "SELECT Id FROM table_name WHERE (Field__c = 123) LIMIT 1"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "responding as an enumerable" do
|
132
|
+
before do
|
133
|
+
expect(active_query).to receive(:to_a).and_return([])
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should call to_a when receiving each" do
|
137
|
+
active_query.each {}
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should call to_a when receiving map" do
|
141
|
+
active_query.map {}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "prevent SOQL injection attacks" do
|
146
|
+
let(:mappings){ { quote_field: "QuoteField", backslash_field: "Backslash_Field__c", number_field: "NumberField" } }
|
147
|
+
let(:quote_input){ "' OR Id!=NULL OR Id='" }
|
148
|
+
let(:backslash_input){ "\\" }
|
149
|
+
let(:number_input){ 123 }
|
150
|
+
let(:expected_query){ "SELECT Id FROM table_name WHERE (Backslash_Field__c = '\\\\' AND NumberField = 123 AND QuoteField = ''' OR Id!=NULL OR Id=''')" }
|
151
|
+
|
152
|
+
it 'escapes quotes and backslashes in bind parameters' do
|
153
|
+
active_query.where('Backslash_Field__c = :backslash_field AND NumberField = :number_field AND QuoteField = :quote_field', number_field: number_input, backslash_field: backslash_input, quote_field: quote_input)
|
154
|
+
expect(active_query.to_s).to eq(expected_query)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'escapes quotes and backslashes in named bind parameters' do
|
158
|
+
active_query.where('Backslash_Field__c = ? AND NumberField = ? AND QuoteField = ?', backslash_input, number_input, quote_input)
|
159
|
+
expect(active_query.to_s).to eq(expected_query)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'escapes quotes and backslashes in hash conditions' do
|
163
|
+
active_query.where(backslash_field: backslash_input, number_field: number_input, quote_field: quote_input)
|
164
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Backslash_Field__c = '\\\\') AND (NumberField = 123) AND (QuoteField = ''' OR Id!=NULL OR Id=''')")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#none' do
|
169
|
+
it 'returns a query with a where clause that is impossible to satisfy' do
|
170
|
+
expect(active_query.none.to_s).to eq "SELECT Id FROM table_name WHERE (Id = '111111111111111111') AND (Id = '000000000000000000')"
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'does not query the API' do
|
174
|
+
expect(client).to_not receive :query
|
175
|
+
active_query.none.to_a
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|