activesalesforce 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/column_definition.rb +1 -1
- data/lib/rforce.rb +101 -48
- metadata +2 -2
data/lib/column_definition.rb
CHANGED
data/lib/rforce.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'net/https'
|
2
2
|
require 'uri'
|
3
|
+
require 'zlib'
|
4
|
+
require 'stringio'
|
3
5
|
require 'rexml/document'
|
4
6
|
require 'rexml/xpath'
|
5
7
|
require 'rubygems'
|
@@ -32,8 +34,8 @@ require_gem 'builder'
|
|
32
34
|
# Rather than enforcing adherence to the sforce.com schema,
|
33
35
|
# RForce assumes you are familiar with the API. Ruby method names
|
34
36
|
# become SOAP method names. Nested Ruby hashes become nested
|
35
|
-
# XML elements.
|
36
|
-
#
|
37
|
+
# XML elements.
|
38
|
+
#
|
37
39
|
# Example:
|
38
40
|
#
|
39
41
|
# binding = RForce::Binding.new 'na1-api.salesforce.com'
|
@@ -54,7 +56,7 @@ require_gem 'builder'
|
|
54
56
|
# binding.create 'sObject {"xsi:type" => "Opportunity"}' => opportunity
|
55
57
|
#
|
56
58
|
module RForce
|
57
|
-
|
59
|
+
|
58
60
|
#Allows indexing hashes like method calls: hash.key
|
59
61
|
#to supplement the traditional way of indexing: hash[key]
|
60
62
|
module FlashHash
|
@@ -62,50 +64,50 @@ module RForce
|
|
62
64
|
self[method]
|
63
65
|
end
|
64
66
|
end
|
65
|
-
|
67
|
+
|
66
68
|
#Turns an XML response from the server into a Ruby
|
67
69
|
#object whose methods correspond to nested XML elements.
|
68
70
|
class SoapResponse
|
69
71
|
include FlashHash
|
70
|
-
|
72
|
+
|
71
73
|
#Parses an XML string into structured data.
|
72
74
|
def initialize(content)
|
73
75
|
document = REXML::Document.new content
|
74
76
|
node = REXML::XPath.first document, '//soapenv:Body'
|
75
77
|
@parsed = SoapResponse.parse node
|
76
78
|
end
|
77
|
-
|
79
|
+
|
78
80
|
#Allows this object to act like a hash (and therefore
|
79
81
|
#as a FlashHash via the include above).
|
80
82
|
def [](symbol)
|
81
83
|
@parsed[symbol]
|
82
84
|
end
|
83
|
-
|
85
|
+
|
84
86
|
#Digests an XML DOM node into nested Ruby types.
|
85
87
|
def SoapResponse.parse(node)
|
86
88
|
#Convert text nodes into simple strings.
|
87
89
|
return node.text unless node.has_elements?
|
88
|
-
|
90
|
+
|
89
91
|
#Convert nodes with children into FlashHashes.
|
90
92
|
elements = {}
|
91
93
|
class << elements
|
92
94
|
include FlashHash
|
93
95
|
end
|
94
|
-
|
96
|
+
|
95
97
|
#Add all the element's children to the hash.
|
96
98
|
node.each_element do |e|
|
97
99
|
name = e.name.to_sym
|
98
|
-
|
100
|
+
|
99
101
|
case elements[name]
|
100
102
|
#The most common case: unique child element tags.
|
101
103
|
when NilClass: elements[name] = parse(e)
|
102
|
-
|
104
|
+
|
103
105
|
#Non-unique child elements become arrays:
|
104
|
-
|
106
|
+
|
105
107
|
#We've already created the array: just
|
106
108
|
#add the element.
|
107
109
|
when Array: elements[name] << parse(e)
|
108
|
-
|
110
|
+
|
109
111
|
#We haven't created the array yet: do so,
|
110
112
|
#then put the existing element in, followed
|
111
113
|
#by the new one.
|
@@ -114,16 +116,16 @@ module RForce
|
|
114
116
|
elements[name] << parse(e)
|
115
117
|
end
|
116
118
|
end
|
117
|
-
|
119
|
+
|
118
120
|
return elements
|
119
121
|
end
|
120
122
|
end
|
121
|
-
|
123
|
+
|
122
124
|
#Implements the connection to the SalesForce server.
|
123
125
|
class Binding
|
124
126
|
DEFAULT_BATCH_SIZE = 10
|
125
127
|
attr_accessor :batch_size
|
126
|
-
|
128
|
+
|
127
129
|
#Fill in the guts of this typical SOAP envelope
|
128
130
|
#with the session ID and the body of the SOAP request.
|
129
131
|
Envelope = <<-HERE
|
@@ -144,42 +146,42 @@ module RForce
|
|
144
146
|
</env:Body>
|
145
147
|
</env:Envelope>
|
146
148
|
HERE
|
147
|
-
|
149
|
+
|
148
150
|
#Connect to the server securely.
|
149
151
|
def initialize(url)
|
150
152
|
init_server(url)
|
151
|
-
|
153
|
+
|
152
154
|
@session_id = ''
|
153
|
-
@batch_size = DEFAULT_BATCH_SIZE
|
155
|
+
@batch_size = DEFAULT_BATCH_SIZE
|
154
156
|
end
|
155
|
-
|
157
|
+
|
156
158
|
def init_server(url)
|
157
159
|
@url = URI.parse(url)
|
158
160
|
@server = Net::HTTP.new(@url.host, @url.port)
|
159
161
|
@server.use_ssl = @url.scheme == 'https'
|
160
|
-
|
162
|
+
|
161
163
|
# run ruby with -d to see SOAP wiredumps.
|
162
164
|
@server.set_debug_output $stderr if $DEBUG
|
163
165
|
end
|
164
|
-
|
166
|
+
|
165
167
|
#Log in to the server and remember the session ID
|
166
168
|
#returned to us by SalesForce.
|
167
169
|
def login(user, password)
|
168
170
|
@user = user
|
169
171
|
@password = password
|
170
|
-
|
172
|
+
|
171
173
|
response = call_remote(:login, [:username, user, :password, password])
|
172
|
-
|
174
|
+
|
173
175
|
raise "Incorrect user name / password [#{response.fault}]" unless response.loginResponse
|
174
|
-
|
176
|
+
|
175
177
|
result = response.loginResponse.result
|
176
178
|
@session_id = result.sessionId
|
177
|
-
|
179
|
+
|
178
180
|
init_server(result.serverUrl)
|
179
|
-
|
181
|
+
|
180
182
|
response
|
181
183
|
end
|
182
|
-
|
184
|
+
|
183
185
|
#Call a method on the remote server. Arguments can be
|
184
186
|
#a hash or (if order is important) an array of alternating
|
185
187
|
#keys and values.
|
@@ -188,49 +190,100 @@ module RForce
|
|
188
190
|
expanded = ''
|
189
191
|
@builder = Builder::XmlMarkup.new(:target => expanded)
|
190
192
|
expand({method => args}, 'urn:partner.soap.sforce.com')
|
191
|
-
|
193
|
+
|
192
194
|
#Fill in the blanks of the SOAP envelope with our
|
193
195
|
#session ID and the expanded XML of our request.
|
194
196
|
request = (Envelope % [@session_id, @batch_size, expanded])
|
195
|
-
|
197
|
+
|
196
198
|
# reset the batch size for the next request
|
197
199
|
@batch_size = DEFAULT_BATCH_SIZE
|
198
|
-
|
199
|
-
#
|
200
|
-
|
201
|
-
|
200
|
+
|
201
|
+
# gzip request
|
202
|
+
request = encode(request)
|
203
|
+
|
204
|
+
headers = {
|
205
|
+
'Accept-Encoding' => 'gzip',
|
206
|
+
'Connection' => 'Keep-Alive',
|
207
|
+
'Content-Encoding' => 'gzip',
|
208
|
+
'Content-Type' => 'text/xml',
|
209
|
+
'SOAPAction' => '""',
|
210
|
+
'User-Agent' => 'ActiveSalesforce RForce'
|
211
|
+
}
|
212
|
+
|
213
|
+
#Send the request to the server and read the response.
|
214
|
+
response = @server.post2(@url.path, request, headers)
|
215
|
+
|
216
|
+
# decode if we have encoding
|
217
|
+
content = decode(response)
|
218
|
+
|
202
219
|
# Check to see if INVALID_SESSION_ID was raised and try to relogin in
|
203
220
|
if method != :login and @session_id and response =~ /<faultcode>sf\:INVALID_SESSION_ID<\/faultcode>/
|
204
221
|
puts "\n\nSession timeout error - auto relogin activated"
|
205
|
-
|
222
|
+
|
206
223
|
login(@user, @password)
|
207
|
-
|
208
|
-
#Send the request to the server and read the response.
|
209
|
-
response = @server.post2(@url.path, request,
|
224
|
+
|
225
|
+
#Send the request to the server and read the response.
|
226
|
+
response = @server.post2(@url.path, request, headers)
|
227
|
+
|
228
|
+
content = decode(response)
|
210
229
|
end
|
211
230
|
|
212
|
-
SoapResponse.new(
|
231
|
+
SoapResponse.new(content)
|
213
232
|
end
|
214
|
-
|
233
|
+
|
234
|
+
# decode gzip
|
235
|
+
def decode(response)
|
236
|
+
encoding = response.get_fields('Content-Encoding')
|
237
|
+
|
238
|
+
# return body if no encoding
|
239
|
+
if !encoding then return response.body end
|
240
|
+
|
241
|
+
# decode gzip
|
242
|
+
case encoding[0].strip
|
243
|
+
when 'gzip':
|
244
|
+
begin
|
245
|
+
gzr = Zlib::GzipReader.new(StringIO.new(response.body))
|
246
|
+
decoded = gzr.read
|
247
|
+
ensure
|
248
|
+
gzr.close
|
249
|
+
end
|
250
|
+
decoded
|
251
|
+
else
|
252
|
+
response.body
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# encode gzip
|
257
|
+
def encode(request)
|
258
|
+
begin
|
259
|
+
ostream = StringIO.new
|
260
|
+
gzw = Zlib::GzipWriter.new(ostream)
|
261
|
+
gzw.write(request)
|
262
|
+
ostream.string
|
263
|
+
ensure
|
264
|
+
gzw.close
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
215
268
|
#Turns method calls on this object into remote SOAP calls.
|
216
269
|
def method_missing(method, *args)
|
217
270
|
unless args.size == 1 && [Hash, Array].include?(args[0].class)
|
218
271
|
raise 'Expected 1 Hash or Array argument'
|
219
272
|
end
|
220
|
-
|
221
|
-
call_remote method, args[0]
|
273
|
+
|
274
|
+
call_remote method, args[0]
|
222
275
|
end
|
223
|
-
|
276
|
+
|
224
277
|
#Expand Ruby data structures into XML.
|
225
278
|
def expand(args, xmlns = nil)
|
226
279
|
#Nest arrays: [:a, 1, :b, 2] => [[:a, 1], [:b, 2]]
|
227
280
|
if (args.class == Array)
|
228
281
|
args.each_index{|i| args[i, 2] = [args[i, 2]]}
|
229
282
|
end
|
230
|
-
|
283
|
+
|
231
284
|
args.each do |key, value|
|
232
285
|
attributes = xmlns ? {:xmlns => xmlns} : {}
|
233
|
-
|
286
|
+
|
234
287
|
#If the XML tag requires attributes,
|
235
288
|
#the tag name will contain a space
|
236
289
|
#followed by a string representation
|
@@ -240,16 +293,16 @@ module RForce
|
|
240
293
|
#becomes <sObject xsi:type="Opportunity>...</sObject>
|
241
294
|
if key.is_a? String
|
242
295
|
key, modifier = key.split(' ', 2)
|
243
|
-
|
296
|
+
|
244
297
|
attributes.merge!(eval(modifier)) if modifier
|
245
298
|
end
|
246
|
-
|
299
|
+
|
247
300
|
#Create an XML element and fill it with this
|
248
301
|
#value's sub-items.
|
249
302
|
case value
|
250
303
|
when Hash, Array
|
251
304
|
@builder.tag!(key, attributes) do expand value; end
|
252
|
-
|
305
|
+
|
253
306
|
when String
|
254
307
|
@builder.tag!(key, attributes) { @builder.text! value }
|
255
308
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: activesalesforce
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2006-01-
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2006-01-30 00:00:00 -05:00
|
8
8
|
summary: ActiveSalesforce is an extension to the Rails Framework that allows for the dynamic creation and management of ActiveRecord objects through the use of Salesforce meta-data and uses a Salesforce.com organization as the backing store.
|
9
9
|
require_paths:
|
10
10
|
- lib
|