restforce 0.1.10 → 1.0.0

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/Gemfile CHANGED
@@ -3,4 +3,5 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in restforce.gemspec
4
4
  gemspec
5
5
 
6
+ gem 'rake'
6
7
  gem 'jruby-openssl', :platforms => :jruby
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # Restforce [![travis-ci](https://secure.travis-ci.org/ejholmes/restforce.png)](https://secure.travis-ci.org/ejholmes/restforce) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/ejholmes/restforce)
1
+ # Restforce
2
+
3
+ [![travis-ci](https://secure.travis-ci.org/ejholmes/restforce.png)](https://secure.travis-ci.org/ejholmes/restforce) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/ejholmes/restforce) [![Dependency Status](https://gemnasium.com/ejholmes/restforce.png)](https://gemnasium.com/ejholmes/restforce)
2
4
 
3
5
  Restforce is a ruby gem for the [Salesforce REST api](http://www.salesforce.com/us/developer/docs/api_rest/index.htm).
4
6
  It's meant to be a lighter weight alternative to the [databasedotcom gem](https://github.com/heroku/databasedotcom) that offers
@@ -10,7 +12,6 @@ Features include:
10
12
  * Support for interacting with multiple users from different orgs.
11
13
  * Support for parent-to-child relationships.
12
14
  * Support for aggregate queries.
13
- * Remove the need to materialize constants.
14
15
  * Support for the [Streaming API](#streaming)
15
16
  * Support for blob data types.
16
17
  * Support for GZIP compression.
@@ -140,6 +141,25 @@ _See also: http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_que
140
141
 
141
142
  * * *
142
143
 
144
+ ### find(sobject, id, field=nil)
145
+
146
+ Finds the record with the specified id and the specified sobject type and
147
+ returns all fields for the sobject. An external id field can be used instead
148
+ of the default Id field by specifiying the name of the external id field as the
149
+ last parameter.
150
+
151
+ ```ruby
152
+ client.find('Account', '001D000000INjVe')
153
+ # => #<Restforce::SObject Id="001D000000INjVe" Name="Test" LastModifiedBy="005G0000002f8FHIAY" ... >
154
+
155
+ client.find('Account', '1234', 'Some_External_Id_Field__c')
156
+ # => #<Restforce::SObject Id="001D000000INjVe" Name="Test" LastModifiedBy="005G0000002f8FHIAY" ... >
157
+ ```
158
+
159
+ _See also: http://www.salesforce.com/us/developer/docs/api_rest/Content/resources_sobject_upsert.htm_
160
+
161
+ * * *
162
+
143
163
  ### search(sosl)
144
164
 
145
165
  Performs a sosl query and returns the result. The result will be a
@@ -319,8 +339,7 @@ Restforce supports the [Streaming API](http://wiki.developerforce.com/page/Getti
319
339
  pub/sub with Salesforce a trivial task:
320
340
 
321
341
  ```ruby
322
- # Restforce uses faye as the underlying implementation for CometD. I recommend
323
- # using faye 0.8.3.
342
+ # Restforce uses faye as the underlying implementation for CometD.
324
343
  require 'faye'
325
344
 
326
345
  # Initialize a client with your username/password/oauth token/etc.
@@ -191,7 +191,7 @@ module Restforce
191
191
  # Returns the Id of the newly created record if the record was created.
192
192
  # Raises an error if something bad happens.
193
193
  def upsert!(sobject, field, attrs)
194
- external_id = attrs.has_key?(field.to_sym) ? attrs.delete(field.to_sym) : attrs.delete(field.to_s)
194
+ external_id = attrs.delete(attrs.keys.find { |k| k.to_s.downcase == field.to_s.downcase })
195
195
  response = api_patch "sobjects/#{sobject}/#{field.to_s}/#{external_id}", attrs
196
196
  (response.body && response.body['id']) ? response.body['id'] : true
197
197
  end
@@ -219,6 +219,18 @@ module Restforce
219
219
  true
220
220
  end
221
221
 
222
+ # Public: Finds a single record and returns all fields.
223
+ #
224
+ # sobject - The String name of the sobject.
225
+ # id - The id of the record. If field is specified, id should be the id
226
+ # of the external field.
227
+ # field - External ID field to use (default: nil).
228
+ #
229
+ # Returns the Restforce::SObject sobject record.
230
+ def find(sobject, id, field=nil)
231
+ api_get(field ? "sobjects/#{sobject}/#{field}/#{id}" : "sobjects/#{sobject}/#{id}").body
232
+ end
233
+
222
234
  private
223
235
 
224
236
  # Internal: Returns a path to an api endpoint
@@ -20,18 +20,31 @@ module Restforce
20
20
  # Internal: Internal faraday connection where all requests go through
21
21
  def connection
22
22
  @connection ||= Faraday.new(@options[:instance_url]) do |builder|
23
+ # Parses JSON into Hashie::Mash structures.
23
24
  builder.use Restforce::Middleware::Mashify, self, @options
25
+ # Handles multipart file uploads for blobs.
24
26
  builder.use Restforce::Middleware::Multipart
27
+ # Converts the request into JSON.
25
28
  builder.request :json
29
+ # Handles reauthentication for 403 responses.
26
30
  builder.use authentication_middleware, self, @options if authentication_middleware
31
+ # Sets the oauth token in the headers.
27
32
  builder.use Restforce::Middleware::Authorization, self, @options
33
+ # Ensures the instance url is set.
28
34
  builder.use Restforce::Middleware::InstanceURL, self, @options
35
+ # Parses returned JSON response into a hash.
29
36
  builder.response :json
37
+ # Caches GET requests.
30
38
  builder.use Restforce::Middleware::Caching, cache, @options if cache
39
+ # Follows 30x redirects.
31
40
  builder.use FaradayMiddleware::FollowRedirects
41
+ # Raises errors for 40x responses.
32
42
  builder.use Restforce::Middleware::RaiseError
43
+ # Log request/responses
33
44
  builder.use Restforce::Middleware::Logger, Restforce.configuration.logger, @options if Restforce.log?
45
+ # Compress/Decompress the request/response
34
46
  builder.use Restforce::Middleware::Gzip, self, @options
47
+
35
48
  builder.adapter Faraday.default_adapter
36
49
  end
37
50
  end
@@ -4,12 +4,12 @@ module Restforce
4
4
 
5
5
  # Public: Subscribe to a PushTopic
6
6
  #
7
- # channel - The name of the PushTopic channel to subscribe to.
8
- # block - A block to run when a new message is received.
7
+ # channels - The name of the PushTopic channel(s) to subscribe to.
8
+ # block - A block to run when a new message is received.
9
9
  #
10
10
  # Returns a Faye::Subscription
11
- def subscribe(channel, &block)
12
- faye.subscribe "/topic/#{channel}", &block
11
+ def subscribe(channels, &block)
12
+ faye.subscribe Array(channels).map { |channel| "/topic/#{channel}" }, &block
13
13
  end
14
14
 
15
15
  # Public: Faye client to use for subscribing to PushTopics
@@ -67,7 +67,7 @@ module Restforce
67
67
  attr_accessor :compress
68
68
 
69
69
  def initialize
70
- @api_version ||= '24.0'
70
+ @api_version ||= '26.0'
71
71
  @host ||= 'login.salesforce.com'
72
72
  @authentication_retries ||= 3
73
73
  end
@@ -33,16 +33,16 @@ module Restforce
33
33
 
34
34
  def create_multipart(env, params)
35
35
  boundary = env[:request][:boundary]
36
-
37
36
  parts = []
38
- skip = []
39
- params.each do |key, value|
40
- if value.respond_to? :content_type
41
- parts << Faraday::Parts::Part.new(boundary, key.to_s, value); skip << key
42
- end
37
+
38
+ # Fields
39
+ parts << Faraday::Parts::Part.new(boundary, 'entity_content', params.reject { |k,v| v.respond_to? :content_type }.to_json)
40
+
41
+ # Files
42
+ params.each do |k,v|
43
+ parts << Faraday::Parts::Part.new(boundary, k.to_s, v) if v.respond_to? :content_type
43
44
  end
44
- parts << Faraday::Parts::Part.new(boundary, 'entity_content', params.reject { |key, _| skip.include? key }.to_json)
45
- parts.reverse!
45
+
46
46
  parts << Faraday::Parts::EpiloguePart.new(boundary)
47
47
 
48
48
  body = Faraday::CompositeReadIO.new(parts)
@@ -1,3 +1,3 @@
1
1
  module Restforce
2
- VERSION = '0.1.10'
2
+ VERSION = '1.0.0'
3
3
  end
data/restforce.gemspec CHANGED
@@ -15,14 +15,13 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Restforce::VERSION
17
17
 
18
- gem.add_dependency 'rake'
19
18
  gem.add_dependency 'faraday', '~> 0.8.4'
20
- gem.add_dependency 'faraday_middleware', '~> 0.8.8'
19
+ gem.add_dependency 'faraday_middleware', '>= 0.8.8'
21
20
  gem.add_dependency 'json', '~> 1.7.5'
22
21
  gem.add_dependency 'hashie', '~> 1.2.0'
23
22
 
24
23
  gem.add_development_dependency 'rspec', '~> 2.12.0'
25
24
  gem.add_development_dependency 'webmock'
26
25
  gem.add_development_dependency 'simplecov'
27
- gem.add_development_dependency 'faye', '0.8.3' unless RUBY_PLATFORM == 'java'
26
+ gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
28
27
  end
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "totalSize" : 2,
3
3
  "done" : false,
4
- "nextRecordsUrl" : "/services/data/v24.0/query/01gD",
4
+ "nextRecordsUrl" : "/services/data/v26.0/query/01gD",
5
5
  "records" : [ {
6
6
  "attributes" : {
7
7
  "type" : "Whizbang",
@@ -50,31 +50,22 @@ shared_examples_for 'methods' do
50
50
 
51
51
  context 'without required options for authentication middleware to be provided' do
52
52
  let(:client_options) { {} }
53
-
54
53
  it { should be_nil }
55
54
  end
56
55
 
57
56
  context 'with username, password, security token, client id and client secret provided' do
58
57
  let(:client_options) { password_options }
59
-
60
58
  it { should eq Restforce::Middleware::Authentication::Password }
61
59
  end
62
60
 
63
61
  context 'with refresh token, client id and client secret provided' do
64
62
  let(:client_options) { oauth_options }
65
-
66
63
  it { should eq Restforce::Middleware::Authentication::Token }
67
64
  end
68
65
  end
69
66
 
70
67
  describe '.list_sobjects' do
71
- before do
72
- @request = stub_api_request :sobjects, :with => 'sobject/describe_sobjects_success_response'
73
- end
74
-
75
- after do
76
- expect(@request).to have_been_requested
77
- end
68
+ requests :sobjects, :fixture => 'sobject/describe_sobjects_success_response'
78
69
 
79
70
  subject { client.list_sobjects }
80
71
  it { should be_an Array }
@@ -83,28 +74,14 @@ shared_examples_for 'methods' do
83
74
 
84
75
  describe '.describe' do
85
76
  context 'with no arguments' do
86
- before do
87
- @request = stub_api_request :sobjects,
88
- :with => 'sobject/describe_sobjects_success_response'
89
- end
90
-
91
- after do
92
- expect(@request).to have_been_requested
93
- end
77
+ requests :sobjects, :fixture => 'sobject/describe_sobjects_success_response'
94
78
 
95
79
  subject { client.describe }
96
80
  it { should be_an Array }
97
81
  end
98
82
 
99
83
  context 'with an argument' do
100
- before do
101
- @request = stub_api_request 'sobjects/Whizbang/describe',
102
- :with => 'sobject/sobject_describe_success_response'
103
- end
104
-
105
- after do
106
- expect(@request).to have_been_requested
107
- end
84
+ requests 'sobjects/Whizbang/describe', :fixture => 'sobject/sobject_describe_success_response'
108
85
 
109
86
  subject { client.describe('Whizbang') }
110
87
  its(['name']) { should eq 'Whizbang' }
@@ -112,28 +89,14 @@ shared_examples_for 'methods' do
112
89
  end
113
90
 
114
91
  describe '.query' do
115
- before do
116
- @request = stub_api_request 'query\?q=SELECT%20some,%20fields%20FROM%20object',
117
- :with => 'sobject/query_success_response'
118
- end
119
-
120
- after do
121
- expect(@request).to have_been_requested
122
- end
92
+ requests 'query\?q=SELECT%20some,%20fields%20FROM%20object', :fixture => 'sobject/query_success_response'
123
93
 
124
94
  subject { client.query('SELECT some, fields FROM object') }
125
95
  it { should be_an Array }
126
96
  end
127
97
 
128
98
  describe '.search' do
129
- before do
130
- @request = stub_api_request 'search\?q=FIND%20%7Bbar%7D',
131
- :with => 'sobject/search_success_response'
132
- end
133
-
134
- after do
135
- expect(@request).to have_been_requested
136
- end
99
+ requests 'search\?q=FIND%20%7Bbar%7D', :fixture => 'sobject/search_success_response'
137
100
 
138
101
  subject { client.search('FIND {bar}') }
139
102
  it { should be_an Array }
@@ -141,14 +104,7 @@ shared_examples_for 'methods' do
141
104
  end
142
105
 
143
106
  describe '.org_id' do
144
- before do
145
- @request = stub_api_request 'query\?q=select%20id%20from%20Organization',
146
- :with => 'sobject/org_query_response'
147
- end
148
-
149
- after do
150
- expect(@request).to have_been_requested
151
- end
107
+ requests 'query\?q=select%20id%20from%20Organization', :fixture => 'sobject/org_query_response'
152
108
 
153
109
  subject { client.org_id }
154
110
  it { should eq '00Dx0000000BV7z' }
@@ -156,32 +112,20 @@ shared_examples_for 'methods' do
156
112
 
157
113
  describe '.create' do
158
114
  context 'without multipart' do
159
- before do
160
- @request = stub_api_request 'sobjects/Account',
161
- :with => 'sobject/create_success_response',
162
- :method => :post,
163
- :body => "{\"Name\":\"Foobar\"}"
164
- end
165
-
166
- after do
167
- expect(@request).to have_been_requested
168
- end
115
+ requests 'sobjects/Account',
116
+ :method => :post,
117
+ :with_body => "{\"Name\":\"Foobar\"}",
118
+ :fixture => 'sobject/create_success_response'
169
119
 
170
120
  subject { client.create('Account', :Name => 'Foobar') }
171
121
  it { should eq 'some_id' }
172
122
  end
173
123
 
174
124
  context 'with multipart' do
175
- before do
176
- @request = stub_api_request 'sobjects/Account',
177
- :with => 'sobject/create_success_response',
178
- :method => :post,
179
- :body => %r(----boundary_string\r\nContent-Disposition: form-data; name=\"entity_content\";\r\nContent-Type: application/json\r\n\r\n{\"Name\":\"Foobar\"}\r\n----boundary_string\r\nContent-Disposition: form-data; name=\"Blob\"; filename=\"blob.jpg\"\r\nContent-Length: 42171\r\nContent-Type: image/jpeg\r\nContent-Transfer-Encoding: binary)
180
- end
181
-
182
- after do
183
- expect(@request).to have_been_requested
184
- end
125
+ requests 'sobjects/Account',
126
+ :method => :post,
127
+ :with_body => %r(----boundary_string\r\nContent-Disposition: form-data; name=\"entity_content\";\r\nContent-Type: application/json\r\n\r\n{\"Name\":\"Foobar\"}\r\n----boundary_string\r\nContent-Disposition: form-data; name=\"Blob\"; filename=\"blob.jpg\"\r\nContent-Length: 42171\r\nContent-Type: image/jpeg\r\nContent-Transfer-Encoding: binary),
128
+ :fixture => 'sobject/create_success_response'
185
129
 
186
130
  subject { client.create('Account', :Name => 'Foobar', :Blob => Restforce::UploadIO.new(File.expand_path('../../fixtures/blob.jpg', __FILE__), 'image/jpeg')) }
187
131
  it { should eq 'some_id' }
@@ -190,17 +134,11 @@ shared_examples_for 'methods' do
190
134
 
191
135
  describe '.update!' do
192
136
  context 'with invalid Id' do
193
- before do
194
- @request = stub_api_request 'sobjects/Account/001D000000INjVe',
195
- :with => 'sobject/delete_error_response',
196
- :method => :patch,
197
- :body => "{\"Name\":\"Foobar\"}",
198
- :status => 404
199
- end
200
-
201
- after do
202
- expect(@request).to have_been_requested
203
- end
137
+ requests 'sobjects/Account/001D000000INjVe',
138
+ :method => :patch,
139
+ :with_body => "{\"Name\":\"Foobar\"}",
140
+ :status => 404,
141
+ :fixture => 'sobject/delete_error_response'
204
142
 
205
143
  subject { client.update!('Account', :Id => '001D000000INjVe', :Name => 'Foobar') }
206
144
  specify { expect { subject }.to raise_error Faraday::Error::ResourceNotFound }
@@ -214,61 +152,35 @@ shared_examples_for 'methods' do
214
152
  end
215
153
 
216
154
  context 'with invalid Id' do
217
- before do
218
- @request = stub_api_request 'sobjects/Account/001D000000INjVe',
219
- :with => 'sobject/delete_error_response',
220
- :method => :patch,
221
- :body => "{\"Name\":\"Foobar\"}",
222
- :status => 404
223
- end
224
-
225
- after do
226
- expect(@request).to have_been_requested
227
- end
155
+ requests 'sobjects/Account/001D000000INjVe',
156
+ :method => :patch,
157
+ :with_body => "{\"Name\":\"Foobar\"}",
158
+ :status => 404,
159
+ :fixture => 'sobject/delete_error_response'
228
160
 
229
161
  subject { client.update('Account', :Id => '001D000000INjVe', :Name => 'Foobar') }
230
162
  it { should be_false }
231
163
  end
232
164
 
233
165
  context 'with success' do
234
- before do
235
- @request = stub_api_request 'sobjects/Account/001D000000INjVe',
236
- :method => :patch,
237
- :body => "{\"Name\":\"Foobar\"}"
238
- end
239
-
240
- after do
241
- expect(@request).to have_been_requested
242
- end
243
-
244
- context 'with symbol Id key' do
245
- subject { client.update('Account', :Id => '001D000000INjVe', :Name => 'Foobar') }
246
- it { should be_true }
247
- end
248
-
249
- context 'with string Id key' do
250
- subject { client.update('Account', 'Id' => '001D000000INjVe', 'Name' => 'Foobar') }
251
- it { should be_true }
252
- end
253
-
254
- context 'with a lower case id' do
255
- subject { client.update('Account', 'id' => '001D000000INjVe', 'Name' => 'Foobar') }
256
- it { should be_true }
166
+ requests 'sobjects/Account/001D000000INjVe',
167
+ :method => :patch,
168
+ :with_body => "{\"Name\":\"Foobar\"}"
169
+
170
+ [:Id, :id, 'Id', 'id'].each do |key|
171
+ context "with #{key.inspect} as the key" do
172
+ subject { client.update('Account', key => '001D000000INjVe', :Name => 'Foobar') }
173
+ it { should be_true }
174
+ end
257
175
  end
258
176
  end
259
177
  end
260
178
 
261
179
  describe '.upsert!' do
262
180
  context 'when updated' do
263
- before do
264
- @request = stub_api_request 'sobjects/Account/External__c/foobar',
265
- :method => :patch,
266
- :body => "{\"Name\":\"Foobar\"}"
267
- end
268
-
269
- after do
270
- expect(@request).to have_been_requested
271
- end
181
+ requests 'sobjects/Account/External__c/foobar',
182
+ :method => :patch,
183
+ :with_body => "{\"Name\":\"Foobar\"}"
272
184
 
273
185
  context 'with symbol external Id key' do
274
186
  subject { client.upsert!('Account', 'External__c', :External__c => 'foobar', :Name => 'Foobar') }
@@ -282,25 +194,16 @@ shared_examples_for 'methods' do
282
194
  end
283
195
 
284
196
  context 'when created' do
285
- before do
286
- @request = stub_api_request 'sobjects/Account/External__c/foobar',
287
- :method => :patch,
288
- :body => "{\"Name\":\"Foobar\"}",
289
- :with => 'sobject/upsert_created_success_response'
290
- end
291
-
292
- after do
293
- expect(@request).to have_been_requested
294
- end
295
-
296
- context 'with symbol external Id key' do
297
- subject { client.upsert!('Account', 'External__c', :External__c => 'foobar', :Name => 'Foobar') }
298
- it { should eq 'foo' }
299
- end
300
-
301
- context 'with string external Id key' do
302
- subject { client.upsert!('Account', 'External__c', 'External__c' => 'foobar', 'Name' => 'Foobar') }
303
- it { should eq 'foo' }
197
+ requests 'sobjects/Account/External__c/foobar',
198
+ :method => :patch,
199
+ :with_body => "{\"Name\":\"Foobar\"}",
200
+ :fixture => 'sobject/upsert_created_success_response'
201
+
202
+ [:External__c, 'External__c', :external__c, 'external__c'].each do |key|
203
+ context "with #{key.inspect} as the external id" do
204
+ subject { client.upsert!('Account', 'External__c', key => 'foobar', :Name => 'Foobar') }
205
+ it { should eq 'foo' }
206
+ end
304
207
  end
305
208
  end
306
209
  end
@@ -309,28 +212,16 @@ shared_examples_for 'methods' do
309
212
  subject { client.destroy!('Account', '001D000000INjVe') }
310
213
 
311
214
  context 'with invalid Id' do
312
- before do
313
- @request = stub_api_request 'sobjects/Account/001D000000INjVe',
314
- :with => 'sobject/delete_error_response',
315
- :method => :delete,
316
- :status => 404
317
- end
318
-
319
- after do
320
- expect(@request).to have_been_requested
321
- end
215
+ requests 'sobjects/Account/001D000000INjVe',
216
+ :fixture => 'sobject/delete_error_response',
217
+ :method => :delete,
218
+ :status => 404
322
219
 
323
220
  specify { expect { subject }.to raise_error Faraday::Error::ResourceNotFound }
324
221
  end
325
222
 
326
223
  context 'with success' do
327
- before do
328
- @request = stub_api_request 'sobjects/Account/001D000000INjVe', :method => :delete
329
- end
330
-
331
- after do
332
- expect(@request).to have_been_requested
333
- end
224
+ requests 'sobjects/Account/001D000000INjVe', :method => :delete
334
225
 
335
226
  it { should be_true }
336
227
  end
@@ -340,36 +231,42 @@ shared_examples_for 'methods' do
340
231
  subject { client.destroy('Account', '001D000000INjVe') }
341
232
 
342
233
  context 'with invalid Id' do
343
- before do
344
- @request = stub_api_request 'sobjects/Account/001D000000INjVe',
345
- :with => 'sobject/delete_error_response',
346
- :method => :delete,
347
- :status => 404
348
- end
349
-
350
- after do
351
- expect(@request).to have_been_requested
352
- end
234
+ requests 'sobjects/Account/001D000000INjVe',
235
+ :fixture => 'sobject/delete_error_response',
236
+ :method => :delete,
237
+ :status => 404
353
238
 
354
239
  it { should be_false }
355
240
  end
356
241
 
357
242
  context 'with success' do
358
- before do
359
- @request = stub_api_request 'sobjects/Account/001D000000INjVe', :method => :delete
360
- end
361
-
362
- after do
363
- expect(@request).to have_been_requested
364
- end
243
+ requests 'sobjects/Account/001D000000INjVe', :method => :delete
365
244
 
366
245
  it { should be_true }
367
246
  end
368
247
  end
369
248
 
249
+ describe '.find' do
250
+ context 'with no external id passed' do
251
+ requests 'sobjects/Account/001D000000INjVe',
252
+ :fixture => 'sobject/sobject_find_success_response'
253
+
254
+ subject { client.find('Account', '001D000000INjVe') }
255
+ it { should be_a Hash }
256
+ end
257
+
258
+ context 'when an external id is passed' do
259
+ requests 'sobjects/Account/External_Field__c/1234',
260
+ :fixture => 'sobject/sobject_find_success_response'
261
+
262
+ subject { client.find('Account', '1234', 'External_Field__c') }
263
+ it { should be_a Hash }
264
+ end
265
+ end
266
+
370
267
  describe '.authenticate!' do
371
268
  before do
372
- @request = stub_login_request(:body => "grant_type=password&client_id=client_id&client_secret=" \
269
+ @request = stub_login_request(:with_body => "grant_type=password&client_id=client_id&client_secret=" \
373
270
  "client_secret&username=foo&password=barsecurity_token").
374
271
  to_return(:status => 200, :body => fixture(:auth_success_response))
375
272
  end
@@ -424,45 +321,69 @@ shared_examples_for 'methods' do
424
321
  end
425
322
 
426
323
  describe '.without_caching' do
427
- let(:cache) { MockCache.new }
324
+ requests 'query\?q=SELECT%20some,%20fields%20FROM%20object',
325
+ :fixture => 'sobject/query_success_response'
428
326
 
429
327
  before do
430
- @request = stub_api_request 'query\?q=SELECT%20some,%20fields%20FROM%20object',
431
- :with => 'sobject/query_success_response'
432
328
  cache.should_receive(:delete).and_call_original
433
329
  cache.should_receive(:fetch).and_call_original
434
330
  end
435
331
 
436
- after do
437
- expect(@request).to have_been_requested
438
- end
439
-
332
+ let(:cache) { MockCache.new }
440
333
  subject { client.without_caching { client.query('SELECT some, fields FROM object') } }
441
334
  it { should be_an Array }
442
335
  end
443
336
 
444
- describe '.faye' do
445
- subject { client.send(:faye) }
337
+ unless RUBY_PLATFORM == 'java'
338
+ describe '.faye', :eventmachine => true do
339
+ subject { client.faye }
446
340
 
447
- context 'with missing instance url' do
448
- let(:instance_url) { nil }
449
- specify { expect { subject }.to raise_error RuntimeError, 'Instance URL missing. Call .authenticate! first.' }
341
+ context 'with missing instance url' do
342
+ let(:instance_url) { nil }
343
+ specify { expect { subject }.to raise_error RuntimeError, 'Instance URL missing. Call .authenticate! first.' }
344
+ end
345
+
346
+ context 'with oauth token and instance url' do
347
+ let(:instance_url) { 'http://google.com' }
348
+ let(:oauth_token) { 'bar' }
349
+ specify { expect { subject }.to_not raise_error }
350
+ end
351
+
352
+ context 'when the connection goes down' do
353
+ it 'should reauthenticate' do
354
+ access_token = double('access token')
355
+ access_token.stub(:access_token).and_return('token')
356
+ client.should_receive(:authenticate!).and_return(access_token)
357
+ client.faye.should_receive(:set_header).with('Authorization', "OAuth token")
358
+ client.faye.trigger('transport:down')
359
+ end
360
+ end
450
361
  end
451
362
 
452
- context 'with oauth token and instance url' do
453
- let(:instance_url) { 'http://foobar' }
454
- let(:oauth_token) { 'bar' }
455
- specify { expect { subject }.to_not raise_error }
363
+ describe '.subcribe', :eventmachine => true do
364
+ context 'when given a single pushtopic' do
365
+ it 'subscribes to the pushtopic' do
366
+ client.faye.should_receive(:subscribe).with(['/topic/PushTopic'])
367
+ client.subscribe('PushTopic')
368
+ end
369
+ end
370
+
371
+ context 'when given an array of pushtopics' do
372
+ it 'subscribes to each pushtopic' do
373
+ client.faye.should_receive(:subscribe).with(['/topic/PushTopic1', '/topic/PushTopic2'])
374
+ client.subscribe(['PushTopic1', 'PushTopic2'])
375
+ end
376
+ end
456
377
  end
457
- end unless RUBY_PLATFORM == 'java'
378
+ end
458
379
 
459
380
  describe 'authentication retries' do
460
381
  context 'when retries reaches 0' do
461
382
  before do
462
383
  @auth_request = stub_api_request('query\?q=SELECT%20some,%20fields%20FROM%20object',
463
384
  :status => 401,
464
- :with => 'expired_session_response')
465
- @query_request = stub_login_request(:body => "grant_type=password&client_id=client_id&client_secret=" \
385
+ :fixture => 'expired_session_response')
386
+ @query_request = stub_login_request(:with_body => "grant_type=password&client_id=client_id&client_secret=" \
466
387
  "client_secret&username=foo&password=barsecurity_token").
467
388
  to_return(:status => 200, :body => fixture(:auth_success_response))
468
389
  end
@@ -481,7 +402,7 @@ shared_examples_for 'methods' do
481
402
  to_return(:status => 401, :body => fixture('expired_session_response')).then.
482
403
  to_return(:status => 200, :body => fixture('sobject/query_success_response'))
483
404
 
484
- @login = stub_login_request(:body => "grant_type=password&client_id=client_id&client_secret=" \
405
+ @login = stub_login_request(:with_body => "grant_type=password&client_id=client_id&client_secret=" \
485
406
  "client_secret&username=foo&password=barsecurity_token").
486
407
  to_return(:status => 200, :body => fixture(:auth_success_response))
487
408
  end
@@ -509,16 +430,8 @@ describe 'with mashify middleware' do
509
430
 
510
431
  describe '.query' do
511
432
  context 'with pagination' do
512
- before do
513
- @requests = [].tap do |requests|
514
- requests << stub_api_request('query\?q', :with => 'sobject/query_paginated_first_page_response')
515
- requests << stub_api_request('query/01gD', :with => 'sobject/query_paginated_last_page_response')
516
- end
517
- end
518
-
519
- after do
520
- @requests.each { |request| expect(request).to have_been_requested }
521
- end
433
+ requests 'query\?q', :fixture => 'sobject/query_paginated_first_page_response'
434
+ requests 'query/01gD', :fixture => 'sobject/query_paginated_last_page_response'
522
435
 
523
436
  subject { client.query('SELECT some, fields FROM object').next_page }
524
437
  it { should be_a Restforce::Collection }
@@ -18,11 +18,8 @@ describe Restforce::Collection do
18
18
  specify { expect(subject.instance_variable_get(:@client)).to eq client }
19
19
 
20
20
  describe 'each record' do
21
- it 'should be a Restforce::SObject' do
22
- records.each do |record|
23
- expect(record).to be_a Restforce::SObject
24
- end
25
- end
21
+ subject { records }
22
+ it { should be_all { |record| expect(record).to be_a Restforce::SObject } }
26
23
  end
27
24
  end
28
25
 
@@ -34,7 +31,7 @@ describe Restforce::Collection do
34
31
  it { should respond_to :each }
35
32
  its(:size) { should eq 1 }
36
33
  its(:total_size) { should eq 2 }
37
- its(:next_page_url) { should eq '/services/data/v24.0/query/01gD' }
34
+ its(:next_page_url) { should eq "/services/data/v#{Restforce.configuration.api_version}/query/01gD" }
38
35
  specify { expect(subject.instance_variable_get(:@client)).to eq client }
39
36
 
40
37
  describe '.next_page' do
@@ -11,7 +11,7 @@ describe Restforce do
11
11
  it { should be_a Restforce::Configuration }
12
12
 
13
13
  context 'by default' do
14
- its(:api_version) { should eq '24.0' }
14
+ its(:api_version) { should eq '26.0' }
15
15
  its(:host) { should eq 'login.salesforce.com' }
16
16
  its(:authentication_retries) { should eq 3 }
17
17
  [:username, :password, :security_token, :client_id, :client_secret,
@@ -53,16 +53,13 @@ describe Restforce::SObject do
53
53
  end
54
54
 
55
55
  context 'when an Id is present' do
56
+ requests 'sobjects/Whizbang/001D000000INjVe',
57
+ :method => :patch,
58
+ :with_body => "{\"Checkbox_Label\":false,\"Text_Label\":\"Hi there!\",\"Date_Label\":\"2010-01-01\"," +
59
+ "\"DateTime_Label\":\"2011-07-07T00:37:00.000+0000\",\"Picklist_Multiselect_Label\":\"four;six\"}"
60
+
56
61
  before do
57
62
  hash.merge!(:Id => '001D000000INjVe')
58
- @request = stub_api_request 'sobjects/Whizbang/001D000000INjVe',
59
- :method => :patch,
60
- :body => "{\"Checkbox_Label\":false,\"Text_Label\":\"Hi there!\",\"Date_Label\":\"2010-01-01\"," +
61
- "\"DateTime_Label\":\"2011-07-07T00:37:00.000+0000\",\"Picklist_Multiselect_Label\":\"four;six\"}"
62
- end
63
-
64
- after do
65
- expect(@request).to have_been_requested
66
63
  end
67
64
 
68
65
  specify { expect { subject }.to_not raise_error }
@@ -73,16 +70,13 @@ describe Restforce::SObject do
73
70
  subject { sobject.save! }
74
71
 
75
72
  context 'when an exception is raised' do
73
+ requests 'sobjects/Whizbang/001D000000INjVe',
74
+ :fixture => 'sobject/delete_error_response',
75
+ :method => :patch,
76
+ :status => 404
77
+
76
78
  before do
77
79
  hash.merge!(:Id => '001D000000INjVe')
78
- @request = stub_api_request 'sobjects/Whizbang/001D000000INjVe',
79
- :with => 'sobject/delete_error_response',
80
- :method => :patch,
81
- :status => 404
82
- end
83
-
84
- after do
85
- expect(@request).to have_been_requested
86
80
  end
87
81
 
88
82
  specify { expect { subject }.to raise_error Faraday::Error::ResourceNotFound }
@@ -97,13 +91,10 @@ describe Restforce::SObject do
97
91
  end
98
92
 
99
93
  context 'when an Id is present' do
94
+ requests 'sobjects/Whizbang/001D000000INjVe', :method => :delete
95
+
100
96
  before do
101
97
  hash.merge!(:Id => '001D000000INjVe')
102
- @request = stub_api_request 'sobjects/Whizbang/001D000000INjVe', :method => :delete
103
- end
104
-
105
- after do
106
- expect(@request).to have_been_requested
107
98
  end
108
99
 
109
100
  specify { expect { subject }.to_not raise_error }
@@ -114,16 +105,13 @@ describe Restforce::SObject do
114
105
  subject { sobject.destroy! }
115
106
 
116
107
  context 'when an exception is raised' do
108
+ requests 'sobjects/Whizbang/001D000000INjVe',
109
+ :fixture => 'sobject/delete_error_response',
110
+ :method => :delete,
111
+ :status => 404
112
+
117
113
  before do
118
114
  hash.merge!(:Id => '001D000000INjVe')
119
- @request = stub_api_request 'sobjects/Whizbang/001D000000INjVe',
120
- :with => 'sobject/delete_error_response',
121
- :method => :delete,
122
- :status => 404
123
- end
124
-
125
- after do
126
- expect(@request).to have_been_requested
127
115
  end
128
116
 
129
117
  specify { expect { subject }.to raise_error Faraday::Error::ResourceNotFound }
@@ -131,14 +119,8 @@ describe Restforce::SObject do
131
119
  end
132
120
 
133
121
  describe '.describe' do
134
- before do
135
- @request = stub_api_request 'sobjects/Whizbang/describe',
136
- :with => 'sobject/sobject_describe_success_response'
137
- end
138
-
139
- after do
140
- expect(@request).to have_been_requested
141
- end
122
+ requests 'sobjects/Whizbang/describe',
123
+ :fixture => 'sobject/sobject_describe_success_response'
142
124
 
143
125
  subject { sobject.describe }
144
126
  it { should be_a Hash }
data/spec/spec_helper.rb CHANGED
@@ -14,5 +14,11 @@ WebMock.disable_net_connect!
14
14
  Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each {|f| require f}
15
15
 
16
16
  RSpec.configure do |config|
17
- config.include FixtureHelpers
17
+ config.around :eventmachine => true do |example|
18
+ EM.run {
19
+ example.run
20
+ EM.stop
21
+ }
22
+ end
18
23
  end
24
+
@@ -1,31 +1,45 @@
1
1
  module FixtureHelpers
2
+ module InstanceMethods
2
3
 
3
- def stub_api_request(endpoint, options = {})
4
- options = {
5
- :method => :get,
6
- :status => 200,
7
- :api_version => '24.0',
8
- :with => nil
9
- }.merge(options)
10
-
11
- stub = stub_request(options[:method], %r{/services/data/v#{options[:api_version]}/#{endpoint}})
12
- stub = stub.with(:body => options[:body]) if options[:body] && !RUBY_VERSION.match(/^1.8/)
13
- stub = stub.to_return(:status => options[:status], :body => fixture(options[:with])) if options[:with]
14
- stub
15
- end
4
+ def stub_api_request(endpoint, options={})
5
+ options = {
6
+ :method => :get,
7
+ :status => 200,
8
+ :api_version => Restforce.configuration.api_version
9
+ }.merge(options)
10
+
11
+ stub = stub_request(options[:method], %r{/services/data/v#{options[:api_version]}/#{endpoint}})
12
+ stub = stub.with(:body => options[:with_body]) if options[:with_body] && !RUBY_VERSION.match(/^1.8/)
13
+ stub = stub.to_return(:status => options[:status], :body => fixture(options[:fixture])) if options[:fixture]
14
+ stub
15
+ end
16
+
17
+ def stub_login_request(options={})
18
+ stub = stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
19
+ stub = stub.with(:body => options[:with_body]) if options[:with_body] && !RUBY_VERSION.match(/^1.8/)
20
+ stub
21
+ end
16
22
 
17
- def stub_login_request(options = {})
18
- options = {
19
- :body => nil
20
- }.merge(options)
23
+ def fixture(f)
24
+ File.read(File.expand_path("../../fixtures/#{f}.json", __FILE__))
25
+ end
21
26
 
22
- stub = stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
23
- stub = stub.with(:body => options[:body]) if options[:body] && !RUBY_VERSION.match(/^1.8/)
24
- stub
25
27
  end
26
28
 
27
- def fixture(f)
28
- File.read(File.expand_path("../../fixtures/#{f}.json", __FILE__))
29
+ module ClassMethods
30
+ def requests(endpoint, options={})
31
+ before do
32
+ (@requests ||= []) << stub_api_request(endpoint, options)
33
+ end
34
+
35
+ after do
36
+ @requests.each { |request| expect(request).to have_been_requested }
37
+ end
38
+ end
29
39
  end
40
+ end
30
41
 
42
+ RSpec.configure do |config|
43
+ config.include FixtureHelpers::InstanceMethods
44
+ config.extend FixtureHelpers::ClassMethods
31
45
  end
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.10
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-14 00:00:00.000000000 Z
12
+ date: 2012-12-23 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: rake
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
30
14
  - !ruby/object:Gem::Dependency
31
15
  name: faraday
32
16
  requirement: !ruby/object:Gem::Requirement
@@ -48,7 +32,7 @@ dependencies:
48
32
  requirement: !ruby/object:Gem::Requirement
49
33
  none: false
50
34
  requirements:
51
- - - ~>
35
+ - - ! '>='
52
36
  - !ruby/object:Gem::Version
53
37
  version: 0.8.8
54
38
  type: :runtime
@@ -56,7 +40,7 @@ dependencies:
56
40
  version_requirements: !ruby/object:Gem::Requirement
57
41
  none: false
58
42
  requirements:
59
- - - ~>
43
+ - - ! '>='
60
44
  - !ruby/object:Gem::Version
61
45
  version: 0.8.8
62
46
  - !ruby/object:Gem::Dependency
@@ -144,17 +128,17 @@ dependencies:
144
128
  requirement: !ruby/object:Gem::Requirement
145
129
  none: false
146
130
  requirements:
147
- - - '='
131
+ - - ! '>='
148
132
  - !ruby/object:Gem::Version
149
- version: 0.8.3
133
+ version: '0'
150
134
  type: :development
151
135
  prerelease: false
152
136
  version_requirements: !ruby/object:Gem::Requirement
153
137
  none: false
154
138
  requirements:
155
- - - '='
139
+ - - ! '>='
156
140
  - !ruby/object:Gem::Version
157
- version: 0.8.3
141
+ version: '0'
158
142
  description: A lightweight ruby client for the Salesforce REST api.
159
143
  email:
160
144
  - eric@ejholmes.net