activesalesforce 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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