restforce 0.0.1 → 0.0.2

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
@@ -1,4 +1,4 @@
1
- # Restforce [![travis-ci](https://secure.travis-ci.org/ejholmes/restforce.png)](https://secure.travis-ci.org/ejholmes/restforce)
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)
2
2
 
3
3
  Restforce is a ruby gem for the [Salesforce REST api](http://www.salesforce.com/us/developer/docs/api_rest/index.htm).
4
4
  It's meant to be a lighter weight alternative to the [databasedotcom gem](https://github.com/heroku/databasedotcom).
@@ -10,13 +10,14 @@ It attempts to solve a couple of key issues that the databasedotcom gem has been
10
10
  * Support for aggregate queries.
11
11
  * Remove the need to materialize constants.
12
12
  * Support for the Streaming API
13
+ * Support for blob data types.
13
14
  * A clean and modular architecture using [Faraday middleware](https://github.com/technoweenie/faraday)
14
15
 
15
16
  ## Installation
16
17
 
17
18
  Add this line to your application's Gemfile:
18
19
 
19
- gem 'restforce
20
+ gem 'restforce'
20
21
 
21
22
  And then execute:
22
23
 
@@ -43,8 +44,6 @@ If you're using the gem to interact with a single org (maybe you're building som
43
44
  salesforce integration internally?) then you should use the username/password
44
45
  authentication method.
45
46
 
46
- If you have an access token and an instance url obtained through oauth:
47
-
48
47
  #### OAuth token authentication
49
48
 
50
49
  ```ruby
@@ -151,6 +150,14 @@ client.destroy('Account', '0016000000MRatd')
151
150
  # => true
152
151
  ```
153
152
 
153
+ ### File Uploads
154
+
155
+ ```ruby
156
+ client.create 'Document', FolderId: '00lE0000000FJ6H',
157
+ Description: 'Document test',
158
+ Name: 'My image',
159
+ Body: Restforce::UploadIO.new(File.expand_path('image.jpg', __FILE__), 'image/jpeg'))
160
+ ```
154
161
 
155
162
  ## Contributing
156
163
 
data/lib/restforce.rb CHANGED
@@ -7,6 +7,7 @@ require 'restforce/config'
7
7
  require 'restforce/mash'
8
8
  require 'restforce/collection'
9
9
  require 'restforce/sobject'
10
+ require 'restforce/upload_io'
10
11
  require 'restforce/client'
11
12
 
12
13
  require 'restforce/middleware'
@@ -163,10 +163,20 @@ module Restforce
163
163
  # # Update the Account with Id '0016000000MRatd'
164
164
  # client.update('Account', Id: '0016000000MRatd', Name: 'Whizbang Corp')
165
165
  #
166
- # Returns true if the sobject was successfully update, false otherwise.
166
+ # Returns true if the sobject was successfully updated, false otherwise.
167
167
  def update(sobject, attrs)
168
+ update!(sobject, attrs)
169
+ rescue Faraday::Error::ResourceNotFound
170
+ false
171
+ end
172
+
173
+ # See .update
174
+ #
175
+ # Returns true if the sobject was successfully updated, raises an error
176
+ # otherwise.
177
+ def update!(sobject, attrs)
168
178
  id = attrs.has_key?(:Id) ? attrs.delete(:Id) : attrs.delete('Id')
169
- response = api_patch "sobjects/#{sobject}/#{id}", attrs
179
+ api_patch "sobjects/#{sobject}/#{id}", attrs
170
180
  true
171
181
  end
172
182
 
@@ -179,7 +189,17 @@ module Restforce
179
189
  #
180
190
  # Returns true if the sobject was successfully deleted, false otherwise.
181
191
  def destroy(sobject, id)
182
- response = api_delete "sobjects/#{sobject}/#{id}"
192
+ destroy!(sobject, id)
193
+ rescue Faraday::Error::ResourceNotFound
194
+ false
195
+ end
196
+
197
+ # See .destroy
198
+ #
199
+ # Returns true of the sobject was successfully deleted, raises an error
200
+ # otherwise.
201
+ def destroy!(sobject, id)
202
+ api_delete "sobjects/#{sobject}/#{id}"
183
203
  true
184
204
  end
185
205
 
@@ -237,6 +257,7 @@ module Restforce
237
257
  def connection
238
258
  @connection ||= Faraday.new do |builder|
239
259
  builder.use Restforce::Middleware::Mashify, self, @options
260
+ builder.use Restforce::Middleware::Multipart
240
261
  builder.request :json
241
262
  builder.use authentication_middleware, self, @options if authentication_middleware
242
263
  builder.use Restforce::Middleware::Authorization, self, @options
@@ -246,7 +267,6 @@ module Restforce
246
267
  builder.response :json
247
268
  builder.adapter Faraday.default_adapter
248
269
  end
249
- @connection.headers['Content-Type'] = 'application/json'
250
270
  @connection
251
271
  end
252
272
 
@@ -28,3 +28,4 @@ require 'restforce/middleware/authentication/oauth'
28
28
  require 'restforce/middleware/authorization'
29
29
  require 'restforce/middleware/instance_url'
30
30
  require 'restforce/middleware/mashify'
31
+ require 'restforce/middleware/multipart'
@@ -0,0 +1,53 @@
1
+ module Restforce
2
+ class Middleware::Multipart < Faraday::Request::UrlEncoded
3
+ self.mime_type = 'multipart/form-data'.freeze
4
+ DEFAULT_BOUNDARY = "--boundary_string".freeze
5
+
6
+ def call(env)
7
+ match_content_type(env) do |params|
8
+ env[:request] ||= {}
9
+ env[:request][:boundary] ||= DEFAULT_BOUNDARY
10
+ env[:request_headers][CONTENT_TYPE] += ";boundary=#{env[:request][:boundary]}"
11
+ env[:body] = create_multipart(env, params)
12
+ end
13
+ @app.call env
14
+ end
15
+
16
+ def process_request?(env)
17
+ type = request_type(env)
18
+ env[:body].respond_to?(:each_key) and !env[:body].empty? and (
19
+ (type.empty? and has_multipart?(env[:body])) or
20
+ type == self.class.mime_type
21
+ )
22
+ end
23
+
24
+ def has_multipart?(obj)
25
+ # string is an enum in 1.8, returning list of itself
26
+ if obj.respond_to?(:each) && !obj.is_a?(String)
27
+ (obj.respond_to?(:values) ? obj.values : obj).each do |val|
28
+ return true if (val.respond_to?(:content_type) || has_multipart?(val))
29
+ end
30
+ end
31
+ false
32
+ end
33
+
34
+ def create_multipart(env, params)
35
+ boundary = env[:request][:boundary]
36
+
37
+ 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
43
+ end
44
+ parts << Faraday::Parts::Part.new(boundary, 'entity_content', params.reject { |key, _| skip.include? key }.to_json)
45
+ parts.reverse!
46
+ parts << Faraday::Parts::EpiloguePart.new(boundary)
47
+
48
+ body = Faraday::CompositeReadIO.new(parts)
49
+ env[:request_headers]['Content-Length'] = body.length.to_s
50
+ return body
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ require 'faraday/upload_io'
2
+
3
+ module Restforce
4
+ UploadIO = Faraday::UploadIO
5
+ end
6
+
7
+ module Faraday
8
+ module Parts
9
+ class ParamPart
10
+ def build_part(boundary, name, value)
11
+ part = ''
12
+ part << "--#{boundary}\r\n"
13
+ part << "Content-Disposition: form-data; name=\"#{name.to_s}\";\r\n"
14
+ part << "Content-Type: application/json\r\n"
15
+ part << "\r\n"
16
+ part << "#{value}\r\n"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module Restforce
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/restforce.gemspec CHANGED
@@ -23,6 +23,5 @@ Gem::Specification.new do |gem|
23
23
 
24
24
  gem.add_development_dependency 'rspec'
25
25
  gem.add_development_dependency 'webmock'
26
- gem.add_development_dependency 'mocha'
27
26
  gem.add_development_dependency 'simplecov'
28
27
  end
Binary file
@@ -55,7 +55,11 @@ shared_examples_for 'methods' do
55
55
 
56
56
  describe '.describe_sobjects' do
57
57
  before do
58
- stub_api_request :sobjects, with: 'sobject/describe_sobjects_success_response'
58
+ @request = stub_api_request :sobjects, with: 'sobject/describe_sobjects_success_response'
59
+ end
60
+
61
+ after do
62
+ @request.should have_been_requested
59
63
  end
60
64
 
61
65
  subject { client.describe_sobjects }
@@ -64,7 +68,11 @@ shared_examples_for 'methods' do
64
68
 
65
69
  describe '.list_sobjects' do
66
70
  before do
67
- stub_api_request :sobjects, with: 'sobject/describe_sobjects_success_response'
71
+ @request = stub_api_request :sobjects, with: 'sobject/describe_sobjects_success_response'
72
+ end
73
+
74
+ after do
75
+ @request.should have_been_requested
68
76
  end
69
77
 
70
78
  subject { client.list_sobjects }
@@ -74,7 +82,11 @@ shared_examples_for 'methods' do
74
82
 
75
83
  describe '.describe' do
76
84
  before do
77
- stub_api_request 'sobject/Whizbang/describe', with: 'sobject/sobject_describe_success_response'
85
+ @request = stub_api_request 'sobject/Whizbang/describe', with: 'sobject/sobject_describe_success_response'
86
+ end
87
+
88
+ after do
89
+ @request.should have_been_requested
78
90
  end
79
91
 
80
92
  subject { client.describe('Whizbang') }
@@ -83,7 +95,11 @@ shared_examples_for 'methods' do
83
95
 
84
96
  describe '.query' do
85
97
  before do
86
- stub_api_request :query, with: 'sobject/query_success_response'
98
+ @request = stub_api_request 'query\?q=SELECT%20some,%20fields%20FROM%20object', with: 'sobject/query_success_response'
99
+ end
100
+
101
+ after do
102
+ @request.should have_been_requested
87
103
  end
88
104
 
89
105
  subject { client.query('SELECT some, fields FROM object') }
@@ -92,7 +108,11 @@ shared_examples_for 'methods' do
92
108
 
93
109
  describe '.search' do
94
110
  before do
95
- stub_api_request :search, with: 'sobject/search_success_response'
111
+ @request = stub_api_request 'search\?q=FIND%20%7Bbar%7D', with: 'sobject/search_success_response'
112
+ end
113
+
114
+ after do
115
+ @request.should have_been_requested
96
116
  end
97
117
 
98
118
  subject { client.search('FIND {bar}') }
@@ -102,7 +122,11 @@ shared_examples_for 'methods' do
102
122
 
103
123
  describe '.org_id' do
104
124
  before do
105
- stub_api_request :query, with: 'sobject/org_query_response'
125
+ @request = stub_api_request 'query\?q=select%20id%20from%20Organization', with: 'sobject/org_query_response'
126
+ end
127
+
128
+ after do
129
+ @request.should have_been_requested
106
130
  end
107
131
 
108
132
  subject { client.org_id }
@@ -110,21 +134,64 @@ shared_examples_for 'methods' do
110
134
  end
111
135
 
112
136
  describe '.create' do
113
- before do
114
- @request = stub_api_request 'sobjects/Account', with: 'sobject/create_success_response', method: :post, body: "{\"Name\":\"Foobar\"}"
137
+ context 'without multipart' do
138
+ before do
139
+ @request = stub_api_request 'sobjects/Account', with: 'sobject/create_success_response', method: :post, body: "{\"Name\":\"Foobar\"}"
140
+ end
141
+
142
+ after do
143
+ @request.should have_been_requested
144
+ end
145
+
146
+ subject { client.create('Account', Name: 'Foobar') }
147
+ it { should eq 'some_id' }
115
148
  end
116
149
 
117
- after do
118
- @request.should have_been_requested
150
+ context 'with multipart' do
151
+ before do
152
+ @request = stub_api_request 'sobjects/Account', with: 'sobject/create_success_response', method: :post, 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)
153
+ end
154
+
155
+ after do
156
+ @request.should have_been_requested
157
+ end
158
+
159
+ subject { client.create('Account', Name: 'Foobar', Blob: Restforce::UploadIO.new(File.expand_path('../../fixtures/blob.jpg', __FILE__), 'image/jpeg')) }
160
+ it { should eq 'some_id' }
119
161
  end
162
+ end
163
+
164
+ describe '.update!' do
165
+ context 'with invalid Id' do
166
+ before do
167
+ @request = stub_api_request 'sobjects/Account/001D000000INjVe', with: 'sobject/delete_error_response', method: :patch, body: "{\"Name\":\"Foobar\"}", status: 404
168
+ end
169
+
170
+ after do
171
+ @request.should have_been_requested
172
+ end
120
173
 
121
- subject { client.create('Account', Name: 'Foobar') }
122
- it { should eq 'some_id' }
174
+ subject { client.update!('Account', Id: '001D000000INjVe', Name: 'Foobar') }
175
+ specify { expect { subject }.to raise_error Faraday::Error::ResourceNotFound }
176
+ end
123
177
  end
124
178
 
125
179
  describe '.update' do
126
- pending 'with invalid Id'
127
180
  pending 'with missing Id'
181
+
182
+ context 'with invalid Id' do
183
+ before do
184
+ @request = stub_api_request 'sobjects/Account/001D000000INjVe', with: 'sobject/delete_error_response', method: :patch, body: "{\"Name\":\"Foobar\"}", status: 404
185
+ end
186
+
187
+ after do
188
+ @request.should have_been_requested
189
+ end
190
+
191
+ subject { client.update('Account', Id: '001D000000INjVe', Name: 'Foobar') }
192
+ it { should be_false }
193
+ end
194
+
128
195
  context 'with success' do
129
196
  before do
130
197
  @request = stub_api_request 'sobjects/Account/001D000000INjVe', method: :patch, body: "{\"Name\":\"Foobar\"}"
@@ -146,8 +213,48 @@ shared_examples_for 'methods' do
146
213
  end
147
214
  end
148
215
 
216
+ describe '.destroy!' do
217
+ subject { client.destroy!('Account', '001D000000INjVe') }
218
+
219
+ context 'with invalid Id' do
220
+ before do
221
+ @request = stub_api_request 'sobjects/Account/001D000000INjVe', with: 'sobject/delete_error_response', method: :delete, status: 404
222
+ end
223
+
224
+ after do
225
+ @request.should have_been_requested
226
+ end
227
+
228
+ specify { expect { subject }.to raise_error Faraday::Error::ResourceNotFound }
229
+ end
230
+
231
+ context 'with success' do
232
+ before do
233
+ @request = stub_api_request 'sobjects/Account/001D000000INjVe', method: :delete
234
+ end
235
+
236
+ after do
237
+ @request.should have_been_requested
238
+ end
239
+
240
+ it { should be_true }
241
+ end
242
+ end
243
+
149
244
  describe '.destroy' do
150
- pending 'with invalid Id'
245
+ subject { client.destroy('Account', '001D000000INjVe') }
246
+
247
+ context 'with invalid Id' do
248
+ before do
249
+ @request = stub_api_request 'sobjects/Account/001D000000INjVe', with: 'sobject/delete_error_response', method: :delete, status: 404
250
+ end
251
+
252
+ after do
253
+ @request.should have_been_requested
254
+ end
255
+
256
+ it { should be_false }
257
+ end
151
258
 
152
259
  context 'with success' do
153
260
  before do
@@ -158,7 +265,6 @@ shared_examples_for 'methods' do
158
265
  @request.should have_been_requested
159
266
  end
160
267
 
161
- subject { client.destroy('Account', '001D000000INjVe') }
162
268
  it { should be_true }
163
269
  end
164
270
  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.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-03 00:00:00.000000000 Z
12
+ date: 2012-09-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70254027887340 !ruby/object:Gem::Requirement
16
+ requirement: &70194283587720 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70254027887340
24
+ version_requirements: *70194283587720
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: faraday
27
- requirement: &70254027886840 !ruby/object:Gem::Requirement
27
+ requirement: &70194283586980 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.8.4
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70254027886840
35
+ version_requirements: *70194283586980
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: faraday_middleware
38
- requirement: &70254027886340 !ruby/object:Gem::Requirement
38
+ requirement: &70194283586260 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.8.8
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70254027886340
46
+ version_requirements: *70194283586260
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: json
49
- requirement: &70254027885880 !ruby/object:Gem::Requirement
49
+ requirement: &70194283585800 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.7.5
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70254027885880
57
+ version_requirements: *70194283585800
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: hashie
60
- requirement: &70254027885420 !ruby/object:Gem::Requirement
60
+ requirement: &70194283601640 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.2.0
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70254027885420
68
+ version_requirements: *70194283601640
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
- requirement: &70254027885040 !ruby/object:Gem::Requirement
71
+ requirement: &70194283601040 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70254027885040
79
+ version_requirements: *70194283601040
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: webmock
82
- requirement: &70254027884580 !ruby/object:Gem::Requirement
82
+ requirement: &70194283600360 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,21 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70254027884580
91
- - !ruby/object:Gem::Dependency
92
- name: mocha
93
- requirement: &70254027884160 !ruby/object:Gem::Requirement
94
- none: false
95
- requirements:
96
- - - ! '>='
97
- - !ruby/object:Gem::Version
98
- version: '0'
99
- type: :development
100
- prerelease: false
101
- version_requirements: *70254027884160
90
+ version_requirements: *70194283600360
102
91
  - !ruby/object:Gem::Dependency
103
92
  name: simplecov
104
- requirement: &70254027883740 !ruby/object:Gem::Requirement
93
+ requirement: &70194283599880 !ruby/object:Gem::Requirement
105
94
  none: false
106
95
  requirements:
107
96
  - - ! '>='
@@ -109,7 +98,7 @@ dependencies:
109
98
  version: '0'
110
99
  type: :development
111
100
  prerelease: false
112
- version_requirements: *70254027883740
101
+ version_requirements: *70194283599880
113
102
  description: A lightweight ruby client for the Salesforce REST api.
114
103
  email:
115
104
  - eric@ejholmes.net
@@ -136,12 +125,15 @@ files:
136
125
  - lib/restforce/middleware/authorization.rb
137
126
  - lib/restforce/middleware/instance_url.rb
138
127
  - lib/restforce/middleware/mashify.rb
128
+ - lib/restforce/middleware/multipart.rb
139
129
  - lib/restforce/middleware/raise_error.rb
140
130
  - lib/restforce/sobject.rb
131
+ - lib/restforce/upload_io.rb
141
132
  - lib/restforce/version.rb
142
133
  - restforce.gemspec
143
134
  - spec/fixtures/auth_error_response.json
144
135
  - spec/fixtures/auth_success_response.json
136
+ - spec/fixtures/blob.jpg
145
137
  - spec/fixtures/expired_session_response.json
146
138
  - spec/fixtures/reauth_success_response.json
147
139
  - spec/fixtures/refresh_error_response.json
@@ -212,6 +204,7 @@ summary: A lightweight ruby client for the Salesforce REST api.
212
204
  test_files:
213
205
  - spec/fixtures/auth_error_response.json
214
206
  - spec/fixtures/auth_success_response.json
207
+ - spec/fixtures/blob.jpg
215
208
  - spec/fixtures/expired_session_response.json
216
209
  - spec/fixtures/reauth_success_response.json
217
210
  - spec/fixtures/refresh_error_response.json