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.
Files changed (3) hide show
  1. data/lib/column_definition.rb +1 -1
  2. data/lib/rforce.rb +101 -48
  3. metadata +2 -2
@@ -30,7 +30,7 @@ require 'pp'
30
30
  module ActiveRecord
31
31
  module ConnectionAdapters
32
32
  class SalesforceColumn < Column
33
- attr_reader :label, :readonly
33
+ attr_reader :label, :readonly, :reference_to
34
34
 
35
35
  def initialize(field)
36
36
  @name = field[:name]
@@ -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
- #Send the request to the server and read the response.
200
- response = @server.post2(@url.path, request, {'SOAPAction' => method.to_s, 'content-type' => 'text/xml'})
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, {'SOAPAction' => method.to_s, 'content-type' => 'text/xml'})
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(response.body)
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.0
7
- date: 2006-01-27 00:00:00 -05:00
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