restforce 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of restforce might be problematic. Click here for more details.

data/README.md CHANGED
@@ -15,7 +15,7 @@ It attempts to solve a couple of key issues that the databasedotcom gem has been
15
15
  * A clean and modular architecture using [Faraday middleware](https://github.com/technoweenie/faraday)
16
16
  * Support for decoding [Force.com Canvas](http://www.salesforce.com/us/developer/docs/platform_connectpre/canvas_framework.pdf) signed requests. (NEW!)
17
17
 
18
- [Documentation](http://rubydoc.info/gems/restforce/frames)
18
+ [Documentation](http://rubydoc.info/gems/restforce/frames) | [Changelog](http://revision.io/restforce)
19
19
 
20
20
  ## Installation
21
21
 
@@ -98,7 +98,7 @@ Restforce.configure do |config|
98
98
  end
99
99
  ```
100
100
 
101
- ### Bang methods
101
+ ### Bang! methods
102
102
 
103
103
  All the CRUD methods (create, update, upsert, destroy) have equivalent methods with
104
104
  a ! at the end (create!, update!, upsert!, destroy!), which can be used if you need
@@ -294,9 +294,6 @@ require 'faye'
294
294
  # Initialize a client with your username/password/oauth token/etc.
295
295
  client = Restforce.new
296
296
 
297
- # Force an authentication request.
298
- client.authenticate!
299
-
300
297
  # Create a PushTopic for subscribing to Account changes.
301
298
  client.create! 'PushTopic', {
302
299
  ApiVersion: '23.0',
@@ -372,6 +369,10 @@ ActiveSupport::Notifications.subscribe('request.faraday') do |name, start, finis
372
369
  end
373
370
  ```
374
371
 
372
+ ## Force.com Canvas
373
+
374
+ You can use Restforce to decode signed requests from Salesforce. See [the example app](https://gist.github.com/4052312).
375
+
375
376
  ## Contributing
376
377
 
377
378
  1. Fork it
@@ -1,5 +1,19 @@
1
+ require 'restforce/client/connection'
2
+ require 'restforce/client/authentication'
3
+ require 'restforce/client/streaming'
4
+ require 'restforce/client/caching'
5
+ require 'restforce/client/canvas'
6
+ require 'restforce/client/api'
7
+
1
8
  module Restforce
2
9
  class Client
10
+ include Restforce::Client::Connection
11
+ include Restforce::Client::Authentication
12
+ include Restforce::Client::Streaming
13
+ include Restforce::Client::Caching
14
+ include Restforce::Client::Canvas
15
+ include Restforce::Client::API
16
+
3
17
  OPTIONS = [:username, :password, :security_token, :client_id, :client_secret, :host, :compress,
4
18
  :api_version, :oauth_token, :refresh_token, :instance_url, :cache, :authentication_retries]
5
19
 
@@ -63,378 +77,5 @@ module Restforce
63
77
  @options = Hash[OPTIONS.map { |option| [option, Restforce.configuration.send(option)] }]
64
78
  @options.merge! opts
65
79
  end
66
-
67
- # Public: Get the names of all sobjects on the org.
68
- #
69
- # Examples
70
- #
71
- # # get the names of all sobjects on the org
72
- # client.list_sobjects
73
- # # => ['Account', 'Lead', ... ]
74
- #
75
- # Returns an Array of String names for each SObject.
76
- def list_sobjects
77
- describe.collect { |sobject| sobject['name'] }
78
- end
79
-
80
- # Public: Returns a detailed describe result for the specified sobject
81
- #
82
- # sobject - Stringish name of the sobject (default: nil).
83
- #
84
- # Examples
85
- #
86
- # # get the global describe for all sobjects
87
- # client.describe
88
- # # => { ... }
89
- #
90
- # # get the describe for the Account object
91
- # client.describe('Account')
92
- # # => { ... }
93
- #
94
- # Returns the Hash representation of the describe call.
95
- def describe(sobject=nil)
96
- if sobject
97
- api_get("sobjects/#{sobject.to_s}/describe").body
98
- else
99
- api_get('sobjects').body['sobjects']
100
- end
101
- end
102
-
103
- # Public: Get the current organization's Id.
104
- #
105
- # Examples
106
- #
107
- # client.org_id
108
- # # => '00Dx0000000BV7z'
109
- #
110
- # Returns the String organization Id
111
- def org_id
112
- query('select id from Organization').first['Id']
113
- end
114
-
115
- # Public: Executs a SOQL query and returns the result.
116
- #
117
- # soql - A SOQL expression.
118
- #
119
- # Examples
120
- #
121
- # # Find the names of all Accounts
122
- # client.query('select Name from Account').map(&:Name)
123
- # # => ['Foo Bar Inc.', 'Whizbang Corp']
124
- #
125
- # Returns a Restforce::Collection if Restforce.configuration.mashify is true.
126
- # Returns an Array of Hash for each record in the result if Restforce.configuration.mashify is false.
127
- def query(soql)
128
- response = api_get 'query', :q => soql
129
- mashify? ? response.body : response.body['records']
130
- end
131
-
132
- # Public: Perform a SOSL search
133
- #
134
- # sosl - A SOSL expression.
135
- #
136
- # Examples
137
- #
138
- # # Find all occurrences of 'bar'
139
- # client.search('FIND {bar}')
140
- # # => #<Restforce::Collection >
141
- #
142
- # # Find accounts match the term 'genepoint' and return the Name field
143
- # client.search('FIND {genepoint} RETURNING Account (Name)').map(&:Name)
144
- # # => ['GenePoint']
145
- #
146
- # Returns a Restforce::Collection if Restforce.configuration.mashify is true.
147
- # Returns an Array of Hash for each record in the result if Restforce.configuration.mashify is false.
148
- def search(sosl)
149
- api_get('search', :q => sosl).body
150
- end
151
-
152
- # Public: Insert a new record.
153
- #
154
- # Examples
155
- #
156
- # # Add a new account
157
- # client.create('Account', Name: 'Foobar Inc.')
158
- # # => '0016000000MRatd'
159
- #
160
- # Returns the String Id of the newly created sobject. Returns false if
161
- # something bad happens
162
- def create(sobject, attrs)
163
- create!(sobject, attrs)
164
- rescue *exceptions
165
- false
166
- end
167
- alias_method :insert, :create
168
-
169
- # See .create
170
- #
171
- # Returns the String Id of the newly created sobject. Raises an error if
172
- # something bad happens.
173
- def create!(sobject, attrs)
174
- api_post("sobjects/#{sobject}", attrs).body['id']
175
- end
176
- alias_method :insert!, :create!
177
-
178
- # Public: Update a record.
179
- #
180
- # Examples
181
- #
182
- # # Update the Account with Id '0016000000MRatd'
183
- # client.update('Account', Id: '0016000000MRatd', Name: 'Whizbang Corp')
184
- #
185
- # Returns true if the sobject was successfully updated, false otherwise.
186
- def update(sobject, attrs)
187
- update!(sobject, attrs)
188
- rescue *exceptions
189
- false
190
- end
191
-
192
- # See .update
193
- #
194
- # Returns true if the sobject was successfully updated, raises an error
195
- # otherwise.
196
- def update!(sobject, attrs)
197
- id = attrs.has_key?(:Id) ? attrs.delete(:Id) : attrs.delete('Id')
198
- raise 'Id field missing.' unless id
199
- api_patch "sobjects/#{sobject}/#{id}", attrs
200
- true
201
- end
202
-
203
- # Public: Update or Create a record based on an external ID
204
- #
205
- # sobject - The name of the sobject to created.
206
- # field - The name of the external Id field to match against.
207
- # attrs - Hash of attributes for the record.
208
- #
209
- # Examples
210
- #
211
- # # Update the record with external ID of 12
212
- # client.upsert('Account', 'External__c', External__c: 12, Name: 'Foobar')
213
- #
214
- # Returns true if the record was found and updated.
215
- # Returns the Id of the newly created record if the record was created.
216
- # Returns false if something bad happens.
217
- def upsert(sobject, field, attrs)
218
- upsert!(sobject, field, attrs)
219
- rescue *exceptions
220
- false
221
- end
222
-
223
- # See .upsert
224
- #
225
- # Returns true if the record was found and updated.
226
- # Returns the Id of the newly created record if the record was created.
227
- # Raises an error if something bad happens.
228
- def upsert!(sobject, field, attrs)
229
- external_id = attrs.has_key?(field.to_sym) ? attrs.delete(field.to_sym) : attrs.delete(field.to_s)
230
- response = api_patch "sobjects/#{sobject}/#{field.to_s}/#{external_id}", attrs
231
- (response.body && response.body['id']) ? response.body['id'] : true
232
- end
233
-
234
- # Public: Delete a record.
235
- #
236
- # Examples
237
- #
238
- # # Delete the Account with Id '0016000000MRatd'
239
- # client.delete('Account', '0016000000MRatd')
240
- #
241
- # Returns true if the sobject was successfully deleted, false otherwise.
242
- def destroy(sobject, id)
243
- destroy!(sobject, id)
244
- rescue *exceptions
245
- false
246
- end
247
-
248
- # See .destroy
249
- #
250
- # Returns true of the sobject was successfully deleted, raises an error
251
- # otherwise.
252
- def destroy!(sobject, id)
253
- api_delete "sobjects/#{sobject}/#{id}"
254
- true
255
- end
256
-
257
- # Public: Runs the block with caching disabled.
258
- #
259
- # block - A query/describe/etc.
260
- #
261
- # Returns the result of the block
262
- def without_caching(&block)
263
- @options[:perform_caching] = false
264
- block.call
265
- ensure
266
- @options.delete(:perform_caching)
267
- end
268
-
269
- # Public: Subscribe to a PushTopic
270
- #
271
- # channel - The name of the PushTopic channel to subscribe to.
272
- # block - A block to run when a new message is received.
273
- #
274
- # Returns a Faye::Subscription
275
- def subscribe(channel, &block)
276
- faye.subscribe "/topic/#{channel}", &block
277
- end
278
-
279
- # Public: Force an authentication
280
- def authenticate!
281
- raise 'No authentication middleware present' unless authentication_middleware
282
- middleware = authentication_middleware.new nil, self, @options
283
- middleware.authenticate!
284
- end
285
-
286
- # Public: Decodes a signed request received from Force.com Canvas.
287
- #
288
- # message - The POST message containing the signed request from Salesforce.
289
- #
290
- # Returns the Hash context if the message is valid.
291
- def decode_signed_request(message)
292
- raise 'client_secret not set' unless @options[:client_secret]
293
- Restforce.decode_signed_request(message, @options[:client_secret])
294
- end
295
-
296
- # Public: Helper methods for performing arbitrary actions against the API using
297
- # various HTTP verbs.
298
- #
299
- # Examples
300
- #
301
- # # Perform a get request
302
- # client.get '/services/data/v24.0/sobjects'
303
- # client.api_get 'sobjects'
304
- #
305
- # # Perform a post request
306
- # client.post '/services/data/v24.0/sobjects/Account', { ... }
307
- # client.api_post 'sobjects/Account', { ... }
308
- #
309
- # # Perform a put request
310
- # client.put '/services/data/v24.0/sobjects/Account/001D000000INjVe', { ... }
311
- # client.api_put 'sobjects/Account/001D000000INjVe', { ... }
312
- #
313
- # # Perform a delete request
314
- # client.delete '/services/data/v24.0/sobjects/Account/001D000000INjVe'
315
- # client.api_delete 'sobjects/Account/001D000000INjVe'
316
- #
317
- # Returns the Faraday::Response.
318
- [:get, :post, :put, :delete, :patch].each do |method|
319
- define_method method do |*args|
320
- retries = @options[:authentication_retries]
321
- begin
322
- connection.send(method, *args)
323
- rescue Restforce::UnauthorizedError
324
- if retries > 0
325
- retries -= 1
326
- connection.url_prefix = @options[:instance_url]
327
- retry
328
- end
329
- raise
330
- end
331
- end
332
-
333
- define_method :"api_#{method}" do |*args|
334
- args[0] = api_path(args[0])
335
- send(method, *args)
336
- end
337
- end
338
-
339
- # Public: The Faraday::Builder instance used for the middleware stack. This
340
- # can be used to insert an custom middleware.
341
- #
342
- # Examples
343
- #
344
- # # Add the instrumentation middleware for Rails.
345
- # client.middleware.use FaradayMiddleware::Instrumentation
346
- #
347
- # Returns the Faraday::Builder for the Faraday connection.
348
- def middleware
349
- connection.builder
350
- end
351
-
352
- private
353
-
354
- # Internal: Returns a path to an api endpoint
355
- #
356
- # Examples
357
- #
358
- # api_path('sobjects')
359
- # # => '/services/data/v24.0/sobjects'
360
- def api_path(path)
361
- "/services/data/v#{@options[:api_version]}/#{path}"
362
- end
363
-
364
- # Internal: Internal faraday connection where all requests go through
365
- def connection
366
- @connection ||= Faraday.new(@options[:instance_url]) do |builder|
367
- builder.use Restforce::Middleware::Mashify, self, @options
368
- builder.use Restforce::Middleware::Multipart
369
- builder.request :json
370
- builder.use authentication_middleware, self, @options if authentication_middleware
371
- builder.use Restforce::Middleware::Authorization, self, @options
372
- builder.use Restforce::Middleware::InstanceURL, self, @options
373
- builder.response :json
374
- builder.use Restforce::Middleware::Caching, cache, @options if cache
375
- builder.use FaradayMiddleware::FollowRedirects
376
- builder.use Restforce::Middleware::RaiseError
377
- builder.use Restforce::Middleware::Logger, Restforce.configuration.logger, @options if Restforce.log?
378
- builder.use Restforce::Middleware::Gzip, self, @options
379
- builder.adapter Faraday.default_adapter
380
- end
381
- end
382
-
383
- # Internal: Determines what middleware will be used based on the options provided
384
- def authentication_middleware
385
- if username_password?
386
- Restforce::Middleware::Authentication::Password
387
- elsif oauth_refresh?
388
- Restforce::Middleware::Authentication::Token
389
- end
390
- end
391
-
392
- # Internal: Returns true if username/password (autonomous) flow should be used for
393
- # authentication.
394
- def username_password?
395
- @options[:username] &&
396
- @options[:password] &&
397
- @options[:client_id] &&
398
- @options[:client_secret]
399
- end
400
-
401
- # Internal: Returns true if oauth token refresh flow should be used for
402
- # authentication.
403
- def oauth_refresh?
404
- @options[:refresh_token] &&
405
- @options[:client_id] &&
406
- @options[:client_secret]
407
- end
408
-
409
- # Internal: Cache to use for the caching middleware
410
- def cache
411
- @options[:cache]
412
- end
413
-
414
- # Internal: Returns true if the middlware stack includes the
415
- # Restforce::Middleware::Mashify middleware.
416
- def mashify?
417
- middleware.handlers.index(Restforce::Middleware::Mashify)
418
- end
419
-
420
- # Internal: Errors that should be rescued from in non-bang methods
421
- def exceptions
422
- [Faraday::Error::ClientError]
423
- end
424
-
425
- # Internal: Faye client to use for subscribing to PushTopics
426
- def faye
427
- raise 'Instance URL missing. Call .authenticate! first.' unless @options[:instance_url]
428
- @faye ||= Faye::Client.new("#{@options[:instance_url]}/cometd/#{@options[:api_version]}").tap do |client|
429
- raise 'OAuth token missing. Call .authenticate! first.' unless @options[:oauth_token]
430
- client.set_header 'Authorization', "OAuth #{@options[:oauth_token]}"
431
- client.bind 'transport:down' do
432
- Restforce.log "[COMETD DOWN]"
433
- end
434
- client.bind 'transport:up' do
435
- Restforce.log "[COMETD UP]"
436
- end
437
- end
438
- end
439
80
  end
440
81
  end
@@ -0,0 +1,257 @@
1
+ module Restforce
2
+ class Client
3
+ module API
4
+
5
+ # Public: Get the names of all sobjects on the org.
6
+ #
7
+ # Examples
8
+ #
9
+ # # get the names of all sobjects on the org
10
+ # client.list_sobjects
11
+ # # => ['Account', 'Lead', ... ]
12
+ #
13
+ # Returns an Array of String names for each SObject.
14
+ def list_sobjects
15
+ describe.collect { |sobject| sobject['name'] }
16
+ end
17
+
18
+ # Public: Returns a detailed describe result for the specified sobject
19
+ #
20
+ # sobject - Stringish name of the sobject (default: nil).
21
+ #
22
+ # Examples
23
+ #
24
+ # # get the global describe for all sobjects
25
+ # client.describe
26
+ # # => { ... }
27
+ #
28
+ # # get the describe for the Account object
29
+ # client.describe('Account')
30
+ # # => { ... }
31
+ #
32
+ # Returns the Hash representation of the describe call.
33
+ def describe(sobject=nil)
34
+ if sobject
35
+ api_get("sobjects/#{sobject.to_s}/describe").body
36
+ else
37
+ api_get('sobjects').body['sobjects']
38
+ end
39
+ end
40
+
41
+ # Public: Get the current organization's Id.
42
+ #
43
+ # Examples
44
+ #
45
+ # client.org_id
46
+ # # => '00Dx0000000BV7z'
47
+ #
48
+ # Returns the String organization Id
49
+ def org_id
50
+ query('select id from Organization').first['Id']
51
+ end
52
+
53
+ # Public: Executs a SOQL query and returns the result.
54
+ #
55
+ # soql - A SOQL expression.
56
+ #
57
+ # Examples
58
+ #
59
+ # # Find the names of all Accounts
60
+ # client.query('select Name from Account').map(&:Name)
61
+ # # => ['Foo Bar Inc.', 'Whizbang Corp']
62
+ #
63
+ # Returns a Restforce::Collection if Restforce.configuration.mashify is true.
64
+ # Returns an Array of Hash for each record in the result if Restforce.configuration.mashify is false.
65
+ def query(soql)
66
+ response = api_get 'query', :q => soql
67
+ mashify? ? response.body : response.body['records']
68
+ end
69
+
70
+ # Public: Perform a SOSL search
71
+ #
72
+ # sosl - A SOSL expression.
73
+ #
74
+ # Examples
75
+ #
76
+ # # Find all occurrences of 'bar'
77
+ # client.search('FIND {bar}')
78
+ # # => #<Restforce::Collection >
79
+ #
80
+ # # Find accounts match the term 'genepoint' and return the Name field
81
+ # client.search('FIND {genepoint} RETURNING Account (Name)').map(&:Name)
82
+ # # => ['GenePoint']
83
+ #
84
+ # Returns a Restforce::Collection if Restforce.configuration.mashify is true.
85
+ # Returns an Array of Hash for each record in the result if Restforce.configuration.mashify is false.
86
+ def search(sosl)
87
+ api_get('search', :q => sosl).body
88
+ end
89
+
90
+ # Public: Insert a new record.
91
+ #
92
+ # Examples
93
+ #
94
+ # # Add a new account
95
+ # client.create('Account', Name: 'Foobar Inc.')
96
+ # # => '0016000000MRatd'
97
+ #
98
+ # Returns the String Id of the newly created sobject. Returns false if
99
+ # something bad happens
100
+ def create(sobject, attrs)
101
+ create!(sobject, attrs)
102
+ rescue *exceptions
103
+ false
104
+ end
105
+ alias_method :insert, :create
106
+
107
+ # See .create
108
+ #
109
+ # Returns the String Id of the newly created sobject. Raises an error if
110
+ # something bad happens.
111
+ def create!(sobject, attrs)
112
+ api_post("sobjects/#{sobject}", attrs).body['id']
113
+ end
114
+ alias_method :insert!, :create!
115
+
116
+ # Public: Update a record.
117
+ #
118
+ # Examples
119
+ #
120
+ # # Update the Account with Id '0016000000MRatd'
121
+ # client.update('Account', Id: '0016000000MRatd', Name: 'Whizbang Corp')
122
+ #
123
+ # Returns true if the sobject was successfully updated, false otherwise.
124
+ def update(sobject, attrs)
125
+ update!(sobject, attrs)
126
+ rescue *exceptions
127
+ false
128
+ end
129
+
130
+ # See .update
131
+ #
132
+ # Returns true if the sobject was successfully updated, raises an error
133
+ # otherwise.
134
+ def update!(sobject, attrs)
135
+ id = attrs.has_key?(:Id) ? attrs.delete(:Id) : attrs.delete('Id')
136
+ raise 'Id field missing.' unless id
137
+ api_patch "sobjects/#{sobject}/#{id}", attrs
138
+ true
139
+ end
140
+
141
+ # Public: Update or Create a record based on an external ID
142
+ #
143
+ # sobject - The name of the sobject to created.
144
+ # field - The name of the external Id field to match against.
145
+ # attrs - Hash of attributes for the record.
146
+ #
147
+ # Examples
148
+ #
149
+ # # Update the record with external ID of 12
150
+ # client.upsert('Account', 'External__c', External__c: 12, Name: 'Foobar')
151
+ #
152
+ # Returns true if the record was found and updated.
153
+ # Returns the Id of the newly created record if the record was created.
154
+ # Returns false if something bad happens.
155
+ def upsert(sobject, field, attrs)
156
+ upsert!(sobject, field, attrs)
157
+ rescue *exceptions
158
+ false
159
+ end
160
+
161
+ # See .upsert
162
+ #
163
+ # Returns true if the record was found and updated.
164
+ # Returns the Id of the newly created record if the record was created.
165
+ # Raises an error if something bad happens.
166
+ def upsert!(sobject, field, attrs)
167
+ external_id = attrs.has_key?(field.to_sym) ? attrs.delete(field.to_sym) : attrs.delete(field.to_s)
168
+ response = api_patch "sobjects/#{sobject}/#{field.to_s}/#{external_id}", attrs
169
+ (response.body && response.body['id']) ? response.body['id'] : true
170
+ end
171
+
172
+ # Public: Delete a record.
173
+ #
174
+ # Examples
175
+ #
176
+ # # Delete the Account with Id '0016000000MRatd'
177
+ # client.delete('Account', '0016000000MRatd')
178
+ #
179
+ # Returns true if the sobject was successfully deleted, false otherwise.
180
+ def destroy(sobject, id)
181
+ destroy!(sobject, id)
182
+ rescue *exceptions
183
+ false
184
+ end
185
+
186
+ # See .destroy
187
+ #
188
+ # Returns true of the sobject was successfully deleted, raises an error
189
+ # otherwise.
190
+ def destroy!(sobject, id)
191
+ api_delete "sobjects/#{sobject}/#{id}"
192
+ true
193
+ end
194
+
195
+ # Public: Helper methods for performing arbitrary actions against the API using
196
+ # various HTTP verbs.
197
+ #
198
+ # Examples
199
+ #
200
+ # # Perform a get request
201
+ # client.get '/services/data/v24.0/sobjects'
202
+ # client.api_get 'sobjects'
203
+ #
204
+ # # Perform a post request
205
+ # client.post '/services/data/v24.0/sobjects/Account', { ... }
206
+ # client.api_post 'sobjects/Account', { ... }
207
+ #
208
+ # # Perform a put request
209
+ # client.put '/services/data/v24.0/sobjects/Account/001D000000INjVe', { ... }
210
+ # client.api_put 'sobjects/Account/001D000000INjVe', { ... }
211
+ #
212
+ # # Perform a delete request
213
+ # client.delete '/services/data/v24.0/sobjects/Account/001D000000INjVe'
214
+ # client.api_delete 'sobjects/Account/001D000000INjVe'
215
+ #
216
+ # Returns the Faraday::Response.
217
+ [:get, :post, :put, :delete, :patch].each do |method|
218
+ define_method method do |*args|
219
+ retries = @options[:authentication_retries]
220
+ begin
221
+ connection.send(method, *args)
222
+ rescue Restforce::UnauthorizedError
223
+ if retries > 0
224
+ retries -= 1
225
+ connection.url_prefix = @options[:instance_url]
226
+ retry
227
+ end
228
+ raise
229
+ end
230
+ end
231
+
232
+ define_method :"api_#{method}" do |*args|
233
+ args[0] = api_path(args[0])
234
+ send(method, *args)
235
+ end
236
+ end
237
+
238
+ private
239
+
240
+ # Internal: Returns a path to an api endpoint
241
+ #
242
+ # Examples
243
+ #
244
+ # api_path('sobjects')
245
+ # # => '/services/data/v24.0/sobjects'
246
+ def api_path(path)
247
+ "/services/data/v#{@options[:api_version]}/#{path}"
248
+ end
249
+
250
+ # Internal: Errors that should be rescued from in non-bang methods
251
+ def exceptions
252
+ [Faraday::Error::ClientError]
253
+ end
254
+
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,40 @@
1
+ module Restforce
2
+ class Client
3
+ module Authentication
4
+
5
+ # Public: Force an authentication
6
+ def authenticate!
7
+ raise 'No authentication middleware present' unless authentication_middleware
8
+ middleware = authentication_middleware.new nil, self, @options
9
+ middleware.authenticate!
10
+ end
11
+
12
+ # Internal: Determines what middleware will be used based on the options provided
13
+ def authentication_middleware
14
+ if username_password?
15
+ Restforce::Middleware::Authentication::Password
16
+ elsif oauth_refresh?
17
+ Restforce::Middleware::Authentication::Token
18
+ end
19
+ end
20
+
21
+ # Internal: Returns true if username/password (autonomous) flow should be used for
22
+ # authentication.
23
+ def username_password?
24
+ @options[:username] &&
25
+ @options[:password] &&
26
+ @options[:client_id] &&
27
+ @options[:client_secret]
28
+ end
29
+
30
+ # Internal: Returns true if oauth token refresh flow should be used for
31
+ # authentication.
32
+ def oauth_refresh?
33
+ @options[:refresh_token] &&
34
+ @options[:client_id] &&
35
+ @options[:client_secret]
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ module Restforce
2
+ class Client
3
+ module Caching
4
+
5
+ # Public: Runs the block with caching disabled.
6
+ #
7
+ # block - A query/describe/etc.
8
+ #
9
+ # Returns the result of the block
10
+ def without_caching(&block)
11
+ @options[:perform_caching] = false
12
+ block.call
13
+ ensure
14
+ @options.delete(:perform_caching)
15
+ end
16
+
17
+ private
18
+
19
+ # Internal: Cache to use for the caching middleware
20
+ def cache
21
+ @options[:cache]
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module Restforce
2
+ class Client
3
+ module Canvas
4
+
5
+ # Public: Decodes a signed request received from Force.com Canvas.
6
+ #
7
+ # message - The POST message containing the signed request from Salesforce.
8
+ #
9
+ # Returns the Hash context if the message is valid.
10
+ def decode_signed_request(message)
11
+ raise 'client_secret not set' unless @options[:client_secret]
12
+ Restforce.decode_signed_request(message, @options[:client_secret])
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,47 @@
1
+ module Restforce
2
+ class Client
3
+ module Connection
4
+
5
+ # Public: The Faraday::Builder instance used for the middleware stack. This
6
+ # can be used to insert an custom middleware.
7
+ #
8
+ # Examples
9
+ #
10
+ # # Add the instrumentation middleware for Rails.
11
+ # client.middleware.use FaradayMiddleware::Instrumentation
12
+ #
13
+ # Returns the Faraday::Builder for the Faraday connection.
14
+ def middleware
15
+ connection.builder
16
+ end
17
+
18
+ private
19
+
20
+ # Internal: Internal faraday connection where all requests go through
21
+ def connection
22
+ @connection ||= Faraday.new(@options[:instance_url]) do |builder|
23
+ builder.use Restforce::Middleware::Mashify, self, @options
24
+ builder.use Restforce::Middleware::Multipart
25
+ builder.request :json
26
+ builder.use authentication_middleware, self, @options if authentication_middleware
27
+ builder.use Restforce::Middleware::Authorization, self, @options
28
+ builder.use Restforce::Middleware::InstanceURL, self, @options
29
+ builder.response :json
30
+ builder.use Restforce::Middleware::Caching, cache, @options if cache
31
+ builder.use FaradayMiddleware::FollowRedirects
32
+ builder.use Restforce::Middleware::RaiseError
33
+ builder.use Restforce::Middleware::Logger, Restforce.configuration.logger, @options if Restforce.log?
34
+ builder.use Restforce::Middleware::Gzip, self, @options
35
+ builder.adapter Faraday.default_adapter
36
+ end
37
+ end
38
+
39
+ # Internal: Returns true if the middlware stack includes the
40
+ # Restforce::Middleware::Mashify middleware.
41
+ def mashify?
42
+ middleware.handlers.index(Restforce::Middleware::Mashify)
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,31 @@
1
+ module Restforce
2
+ class Client
3
+ module Streaming
4
+
5
+ # Public: Subscribe to a PushTopic
6
+ #
7
+ # channel - The name of the PushTopic channel to subscribe to.
8
+ # block - A block to run when a new message is received.
9
+ #
10
+ # Returns a Faye::Subscription
11
+ def subscribe(channel, &block)
12
+ faye.subscribe "/topic/#{channel}", &block
13
+ end
14
+
15
+ # Public: Faye client to use for subscribing to PushTopics
16
+ def faye
17
+ raise 'Instance URL missing. Call .authenticate! first.' unless @options[:instance_url]
18
+ @faye ||= Faye::Client.new("#{@options[:instance_url]}/cometd/#{@options[:api_version]}").tap do |client|
19
+ client.bind 'transport:down' do
20
+ Restforce.log "[COMETD DOWN]"
21
+ client.set_header 'Authorization', "OAuth #{authenticate!.access_token}"
22
+ end
23
+ client.bind 'transport:up' do
24
+ Restforce.log "[COMETD UP]"
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -22,6 +22,11 @@ module Restforce
22
22
  @client.update(sobject_type, attrs)
23
23
  end
24
24
 
25
+ def save!
26
+ ensure_id
27
+ @client.update!(sobject_type, attrs)
28
+ end
29
+
25
30
  # Public: Destroy this record.
26
31
  #
27
32
  # Examples
@@ -33,6 +38,11 @@ module Restforce
33
38
  @client.destroy(sobject_type, self.Id)
34
39
  end
35
40
 
41
+ def destroy!
42
+ ensure_id
43
+ @client.destroy!(sobject_type, self.Id)
44
+ end
45
+
36
46
  # Public: Returns a hash representation of this object with the attributes
37
47
  # key and parent/child relationships removed.
38
48
  def attrs
@@ -1,3 +1,3 @@
1
1
  module Restforce
2
- VERSION = '0.1.8'
2
+ VERSION = '0.1.9'
3
3
  end
@@ -424,12 +424,6 @@ shared_examples_for 'methods' do
424
424
  describe '.faye' do
425
425
  subject { client.send(:faye) }
426
426
 
427
- context 'with missing oauth token' do
428
- let(:instance_url) { 'http://foobar' }
429
- let(:oauth_token) { nil }
430
- specify { expect { subject }.to raise_error RuntimeError, 'OAuth token missing. Call .authenticate! first.' }
431
- end
432
-
433
427
  context 'with missing instance url' do
434
428
  let(:instance_url) { nil }
435
429
  specify { expect { subject }.to raise_error RuntimeError, 'Instance URL missing. Call .authenticate! first.' }
@@ -59,7 +59,10 @@ describe Restforce::SObject do
59
59
  context 'when an Id is present' do
60
60
  before do
61
61
  hash.merge!(:Id => '001D000000INjVe')
62
- @request = stub_api_request 'sobjects/Whizbang/001D000000INjVe', :method => :patch, :body => "{\"Checkbox_Label\":false,\"Text_Label\":\"Hi there!\",\"Date_Label\":\"2010-01-01\",\"DateTime_Label\":\"2011-07-07T00:37:00.000+0000\",\"Picklist_Multiselect_Label\":\"four;six\"}"
62
+ @request = stub_api_request 'sobjects/Whizbang/001D000000INjVe',
63
+ :method => :patch,
64
+ :body => "{\"Checkbox_Label\":false,\"Text_Label\":\"Hi there!\",\"Date_Label\":\"2010-01-01\"," +
65
+ "\"DateTime_Label\":\"2011-07-07T00:37:00.000+0000\",\"Picklist_Multiselect_Label\":\"four;six\"}"
63
66
  end
64
67
 
65
68
  after do
@@ -70,6 +73,26 @@ describe Restforce::SObject do
70
73
  end
71
74
  end
72
75
 
76
+ describe '.save!' do
77
+ subject { sobject.save! }
78
+
79
+ context 'when an exception is raised' do
80
+ before do
81
+ hash.merge!(:Id => '001D000000INjVe')
82
+ @request = stub_api_request 'sobjects/Whizbang/001D000000INjVe',
83
+ :with => 'sobject/delete_error_response',
84
+ :method => :patch,
85
+ :status => 404
86
+ end
87
+
88
+ after do
89
+ @request.should have_been_requested
90
+ end
91
+
92
+ specify { expect { subject }.to raise_error Faraday::Error::ResourceNotFound }
93
+ end
94
+ end
95
+
73
96
  describe '.destroy' do
74
97
  subject { sobject.destroy }
75
98
 
@@ -91,9 +114,30 @@ describe Restforce::SObject do
91
114
  end
92
115
  end
93
116
 
117
+ describe '.destroy!' do
118
+ subject { sobject.destroy! }
119
+
120
+ context 'when an exception is raised' do
121
+ before do
122
+ hash.merge!(:Id => '001D000000INjVe')
123
+ @request = stub_api_request 'sobjects/Whizbang/001D000000INjVe',
124
+ :with => 'sobject/delete_error_response',
125
+ :method => :delete,
126
+ :status => 404
127
+ end
128
+
129
+ after do
130
+ @request.should have_been_requested
131
+ end
132
+
133
+ specify { expect { subject }.to raise_error Faraday::Error::ResourceNotFound }
134
+ end
135
+ end
136
+
94
137
  describe '.describe' do
95
138
  before do
96
- @request = stub_api_request 'sobjects/Whizbang/describe', :with => 'sobject/sobject_describe_success_response'
139
+ @request = stub_api_request 'sobjects/Whizbang/describe',
140
+ :with => 'sobject/sobject_describe_success_response'
97
141
  end
98
142
 
99
143
  after do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restforce
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-10 00:00:00.000000000 Z
12
+ date: 2012-12-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -171,6 +171,12 @@ files:
171
171
  - Rakefile
172
172
  - lib/restforce.rb
173
173
  - lib/restforce/client.rb
174
+ - lib/restforce/client/api.rb
175
+ - lib/restforce/client/authentication.rb
176
+ - lib/restforce/client/caching.rb
177
+ - lib/restforce/client/canvas.rb
178
+ - lib/restforce/client/connection.rb
179
+ - lib/restforce/client/streaming.rb
174
180
  - lib/restforce/collection.rb
175
181
  - lib/restforce/config.rb
176
182
  - lib/restforce/mash.rb
@@ -251,12 +257,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
251
257
  - - ! '>='
252
258
  - !ruby/object:Gem::Version
253
259
  version: '0'
260
+ segments:
261
+ - 0
262
+ hash: -2351979982713256813
254
263
  required_rubygems_version: !ruby/object:Gem::Requirement
255
264
  none: false
256
265
  requirements:
257
266
  - - ! '>='
258
267
  - !ruby/object:Gem::Version
259
268
  version: '0'
269
+ segments:
270
+ - 0
271
+ hash: -2351979982713256813
260
272
  requirements: []
261
273
  rubyforge_project:
262
274
  rubygems_version: 1.8.23