activesalesforce 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rforce.rb +53 -41
- data/lib/salesforce_active_record.rb +1 -0
- data/lib/salesforce_connection_adapter.rb +21 -16
- metadata +2 -2
data/lib/rforce.rb
CHANGED
@@ -54,7 +54,7 @@ require_gem 'builder'
|
|
54
54
|
# binding.create 'sObject {"xsi:type" => "Opportunity"}' => opportunity
|
55
55
|
#
|
56
56
|
module RForce
|
57
|
-
|
57
|
+
|
58
58
|
#Allows indexing hashes like method calls: hash.key
|
59
59
|
#to supplement the traditional way of indexing: hash[key]
|
60
60
|
module FlashHash
|
@@ -62,65 +62,68 @@ module RForce
|
|
62
62
|
self[method]
|
63
63
|
end
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
#Turns an XML response from the server into a Ruby
|
67
67
|
#object whose methods correspond to nested XML elements.
|
68
68
|
class SoapResponse
|
69
69
|
include FlashHash
|
70
|
-
|
70
|
+
|
71
71
|
#Parses an XML string into structured data.
|
72
72
|
def initialize(content)
|
73
73
|
document = REXML::Document.new content
|
74
74
|
node = REXML::XPath.first document, '//soapenv:Body'
|
75
75
|
@parsed = SoapResponse.parse node
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
#Allows this object to act like a hash (and therefore
|
79
79
|
#as a FlashHash via the include above).
|
80
80
|
def [](symbol)
|
81
81
|
@parsed[symbol]
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
#Digests an XML DOM node into nested Ruby types.
|
85
85
|
def SoapResponse.parse(node)
|
86
86
|
#Convert text nodes into simple strings.
|
87
87
|
return node.text unless node.has_elements?
|
88
|
-
|
88
|
+
|
89
89
|
#Convert nodes with children into FlashHashes.
|
90
90
|
elements = {}
|
91
91
|
class << elements
|
92
92
|
include FlashHash
|
93
93
|
end
|
94
|
-
|
94
|
+
|
95
95
|
#Add all the element's children to the hash.
|
96
96
|
node.each_element do |e|
|
97
97
|
name = e.name.to_sym
|
98
98
|
|
99
99
|
case elements[name]
|
100
100
|
#The most common case: unique child element tags.
|
101
|
-
|
101
|
+
when NilClass: elements[name] = parse(e)
|
102
102
|
|
103
103
|
#Non-unique child elements become arrays:
|
104
104
|
|
105
105
|
#We've already created the array: just
|
106
106
|
#add the element.
|
107
|
-
|
107
|
+
when Array: elements[name] << parse(e)
|
108
108
|
|
109
109
|
#We haven't created the array yet: do so,
|
110
110
|
#then put the existing element in, followed
|
111
111
|
#by the new one.
|
112
|
-
|
113
|
-
|
114
|
-
|
112
|
+
else
|
113
|
+
elements[name] = [elements[name]]
|
114
|
+
elements[name] << parse(e)
|
115
115
|
end
|
116
116
|
end
|
117
|
-
|
117
|
+
|
118
118
|
return elements
|
119
119
|
end
|
120
120
|
end
|
121
|
-
|
121
|
+
|
122
122
|
#Implements the connection to the SalesForce server.
|
123
123
|
class Binding
|
124
|
+
DEFAULT_BATCH_SIZE = 10
|
125
|
+
attr_accessor :batch_size
|
126
|
+
|
124
127
|
#Fill in the guts of this typical SOAP envelope
|
125
128
|
#with the session ID and the body of the SOAP request.
|
126
129
|
Envelope = <<-HERE
|
@@ -132,13 +135,16 @@ module RForce
|
|
132
135
|
<SessionHeader>
|
133
136
|
<sessionId>%s</sessionId>
|
134
137
|
</SessionHeader>
|
138
|
+
<QueryOptions>
|
139
|
+
<batchSize>%d</batchSize>
|
140
|
+
</QueryOptions>
|
135
141
|
</env:Header>
|
136
142
|
<env:Body>
|
137
143
|
%s
|
138
144
|
</env:Body>
|
139
145
|
</env:Envelope>
|
140
146
|
HERE
|
141
|
-
|
147
|
+
|
142
148
|
#Connect to the server securely.
|
143
149
|
def initialize(url)
|
144
150
|
@url = URI.parse(url)
|
@@ -146,21 +152,24 @@ module RForce
|
|
146
152
|
@server.use_ssl = @url.scheme == 'https'
|
147
153
|
|
148
154
|
# run ruby with -d to see SOAP wiredumps.
|
149
|
-
@server.set_debug_output $stderr if $DEBUG
|
155
|
+
@server.set_debug_output $stderr #if $DEBUG
|
150
156
|
|
151
157
|
@session_id = ''
|
158
|
+
@batch_size = DEFAULT_BATCH_SIZE
|
152
159
|
end
|
153
|
-
|
160
|
+
|
161
|
+
|
154
162
|
#Log in to the server and remember the session ID
|
155
163
|
#returned to us by SalesForce.
|
156
164
|
def login(user, pass)
|
157
165
|
response = call_remote(:login, [:username, user, :password, pass])
|
158
|
-
|
166
|
+
|
159
167
|
raise "Incorrect user name / password [#{response.fault}]" unless response.loginResponse
|
160
168
|
@session_id = response.loginResponse.result.sessionId
|
169
|
+
|
161
170
|
response
|
162
171
|
end
|
163
|
-
|
172
|
+
|
164
173
|
#Call a method on the remote server. Arguments can be
|
165
174
|
#a hash or (if order is important) an array of alternating
|
166
175
|
#keys and values.
|
@@ -169,56 +178,59 @@ module RForce
|
|
169
178
|
expanded = ''
|
170
179
|
@builder = Builder::XmlMarkup.new(:target => expanded)
|
171
180
|
expand({method => args}, 'urn:partner.soap.sforce.com')
|
172
|
-
|
181
|
+
|
173
182
|
#Fill in the blanks of the SOAP envelope with our
|
174
183
|
#session ID and the expanded XML of our request.
|
175
|
-
request = (Envelope % [@session_id, expanded])
|
176
|
-
|
184
|
+
request = (Envelope % [@session_id, @batch_size, expanded])
|
185
|
+
|
186
|
+
# reset the batch size for the next request
|
187
|
+
@batch_size = DEFAULT_BATCH_SIZE
|
188
|
+
|
177
189
|
#Send the request to the server and read the response.
|
178
190
|
response = @server.post2(@url.path, request, {'SOAPAction' => method.to_s, 'content-type' => 'text/xml'})
|
179
191
|
SoapResponse.new(response.body)
|
180
192
|
end
|
181
|
-
|
193
|
+
|
182
194
|
#Turns method calls on this object into remote SOAP calls.
|
183
195
|
def method_missing(method, *args)
|
184
196
|
unless args.size == 1 && [Hash, Array].include?(args[0].class)
|
185
197
|
raise 'Expected 1 Hash or Array argument'
|
186
198
|
end
|
187
|
-
|
199
|
+
|
188
200
|
call_remote method, args[0]
|
189
201
|
end
|
190
|
-
|
202
|
+
|
191
203
|
#Expand Ruby data structures into XML.
|
192
204
|
def expand(args, xmlns = nil)
|
193
205
|
#Nest arrays: [:a, 1, :b, 2] => [[:a, 1], [:b, 2]]
|
194
206
|
if (args.class == Array)
|
195
207
|
args.each_index{|i| args[i, 2] = [args[i, 2]]}
|
196
208
|
end
|
197
|
-
|
209
|
+
|
198
210
|
args.each do |key, value|
|
199
211
|
attributes = xmlns ? {:xmlns => xmlns} : {}
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
212
|
+
|
213
|
+
#If the XML tag requires attributes,
|
214
|
+
#the tag name will contain a space
|
215
|
+
#followed by a string representation
|
216
|
+
#of a hash of attributes.
|
217
|
+
#
|
218
|
+
#e.g. 'sObject {"xsi:type" => "Opportunity"}'
|
219
|
+
#becomes <sObject xsi:type="Opportunity>...</sObject>
|
208
220
|
if key.is_a? String
|
209
|
-
|
210
|
-
|
211
|
-
|
221
|
+
key, modifier = key.split(' ', 2)
|
222
|
+
|
223
|
+
attributes.merge!(eval(modifier)) if modifier
|
212
224
|
end
|
213
225
|
|
214
226
|
#Create an XML element and fill it with this
|
215
227
|
#value's sub-items.
|
216
228
|
case value
|
217
|
-
|
218
|
-
|
229
|
+
when Hash, Array
|
230
|
+
@builder.tag!(key, attributes) do expand value; end
|
219
231
|
|
220
|
-
|
221
|
-
|
232
|
+
when String
|
233
|
+
@builder.tag!(key, attributes) { @builder.text! value }
|
222
234
|
end
|
223
235
|
end
|
224
236
|
end
|
@@ -68,6 +68,7 @@ module ActiveRecord
|
|
68
68
|
end
|
69
69
|
|
70
70
|
class SalesforceAdapter < AbstractAdapter
|
71
|
+
attr_accessor :batch_size
|
71
72
|
|
72
73
|
def initialize(connection, logger, connection_options, config)
|
73
74
|
super(connection, logger)
|
@@ -131,7 +132,10 @@ module ActiveRecord
|
|
131
132
|
def select_all(soql, name = nil) #:nodoc:
|
132
133
|
log(soql, name)
|
133
134
|
|
134
|
-
|
135
|
+
@connection.batch_size = @batch_size if @batch_size
|
136
|
+
@batch_size = nil
|
137
|
+
|
138
|
+
records = get_result(@connection.query(:queryString => soql), :query).records
|
135
139
|
|
136
140
|
records = [ records ] unless records.is_a?(Array)
|
137
141
|
|
@@ -150,30 +154,31 @@ module ActiveRecord
|
|
150
154
|
end
|
151
155
|
|
152
156
|
def create(sobject, name = nil) #:nodoc:
|
153
|
-
|
154
|
-
|
155
|
-
raise SalesforceError, result[:errors].message unless result[:success] == "true"
|
156
|
-
|
157
|
-
result[:id]
|
157
|
+
check_result(get_result(@connection.create(sobject), :create))[:id]
|
158
158
|
end
|
159
159
|
|
160
160
|
def update(sobject, name = nil) #:nodoc:
|
161
|
-
|
162
|
-
|
163
|
-
raise SalesforceError, result[:errors].message unless result[:success] == "true"
|
164
|
-
|
165
|
-
# @connection.affected_rows
|
161
|
+
check_result(get_result(@connection.update(sobject), :update))
|
166
162
|
end
|
167
163
|
|
168
164
|
def delete(ids)
|
169
165
|
puts "Delete #{ids}"
|
166
|
+
check_result(get_result(@connection.delete(:ids => ids), :delete))
|
167
|
+
end
|
170
168
|
|
169
|
+
def get_result(response, method)
|
170
|
+
responseName = (method.to_s + "Response").to_sym
|
171
|
+
finalResponse = response[responseName]
|
172
|
+
raise SalesforceError, response.fault unless finalResponse
|
171
173
|
|
172
|
-
result =
|
173
|
-
|
174
|
+
result = finalResponse[:result]
|
175
|
+
end
|
176
|
+
|
177
|
+
def check_result(result)
|
174
178
|
raise SalesforceError, result[:errors].message unless result[:success] == "true"
|
179
|
+
result
|
175
180
|
end
|
176
|
-
|
181
|
+
|
177
182
|
def columns(table_name, name = nil)
|
178
183
|
cached_columns = @columns_map[table_name]
|
179
184
|
return cached_columns if cached_columns
|
@@ -182,8 +187,8 @@ module ActiveRecord
|
|
182
187
|
|
183
188
|
cached_columns = []
|
184
189
|
@columns_map[table_name] = cached_columns
|
185
|
-
|
186
|
-
metadata = @connection.describeSObject(:sObjectType => table_name)
|
190
|
+
|
191
|
+
metadata = get_result(@connection.describeSObject(:sObjectType => table_name), :describeSObject)
|
187
192
|
|
188
193
|
metadata.fields.each do |field|
|
189
194
|
cached_columns << SalesforceColumn.new(field)
|
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.0.
|
7
|
-
date: 2006-01-
|
6
|
+
version: 0.0.5
|
7
|
+
date: 2006-01-23 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
|