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,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
@@ -0,0 +1,3 @@
1
+ module ActiveForce
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_force/version'
2
+ require 'active_force/sobject'
3
+ require 'active_force/query'
4
+
5
+ module ActiveForce
6
+
7
+ class << self
8
+ attr_accessor :sfdc_client
9
+ end
10
+
11
+ self.sfdc_client = Restforce.new
12
+
13
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generate a model to interact with Sales Force.
3
+
4
+ Example:
5
+ rails generate active_force_model Account
6
+
7
+ This will create:
8
+ app/models/account.rb
@@ -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,5 @@
1
+ class <%= @class_name %> < ActiveForce::SObject
2
+ <% attributes.each do |attribute| -%>
3
+ <%= attribute_line attribute %>
4
+ <% end -%>
5
+ 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