lib/rforce.rb in activesalesforce-0.4.3 vs lib/rforce.rb in activesalesforce-0.4.4
- old
+ new
@@ -1,26 +1,26 @@
=begin
- RForce v0.1
- Copyright (c) 2005 Ian Dees
+RForce v0.1
+Copyright (c) 2005 Ian Dees
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
=end
# RForce is a simple Ruby binding to the SalesForce CRM system.
# Rather than enforcing adherence to the sforce.com schema,
# RForce assumes you are familiar with the API. Ruby method names
@@ -57,81 +57,81 @@
require 'rubygems'
require_gem 'builder'
module RForce
-
+
#Allows indexing hashes like method calls: hash.key
#to supplement the traditional way of indexing: hash[key]
module FlashHash
def method_missing(method, *args)
self[method]
end
end
-
+
#Turns an XML response from the server into a Ruby
#object whose methods correspond to nested XML elements.
class SoapResponse
include FlashHash
-
+
#Parses an XML string into structured data.
def initialize(content)
document = REXML::Document.new content
node = REXML::XPath.first document, '//soapenv:Body'
@parsed = SoapResponse.parse node
end
-
+
#Allows this object to act like a hash (and therefore
#as a FlashHash via the include above).
def [](symbol)
@parsed[symbol]
end
-
+
#Digests an XML DOM node into nested Ruby types.
def SoapResponse.parse(node)
#Convert text nodes into simple strings.
return node.text unless node.has_elements?
-
+
#Convert nodes with children into FlashHashes.
elements = {}
class << elements
include FlashHash
end
-
+
#Add all the element's children to the hash.
node.each_element do |e|
name = e.name.to_sym
-
+
case elements[name]
#The most common case: unique child element tags.
when NilClass: elements[name] = parse(e)
-
+
#Non-unique child elements become arrays:
-
+
#We've already created the array: just
#add the element.
when Array: elements[name] << parse(e)
-
+
#We haven't created the array yet: do so,
#then put the existing element in, followed
#by the new one.
else
elements[name] = [elements[name]]
elements[name] << parse(e)
end
end
-
+
return elements
end
end
-
+
#Implements the connection to the SalesForce server.
class Binding
DEFAULT_BATCH_SIZE = 10
- attr_accessor :batch_size, :url
-
+ attr_accessor :batch_size, :url, :assignment_rule_id, :use_default_rule, :update_mru
+
#Fill in the guts of this typical SOAP envelope
#with the session ID and the body of the SOAP request.
Envelope = <<-HERE
<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -142,124 +142,135 @@
<sessionId>%s</sessionId>
</SessionHeader>
<QueryOptions>
<batchSize>%d</batchSize>
</QueryOptions>
+ %s
</env:Header>
<env:Body>
%s
</env:Body>
</env:Envelope>
HERE
+
+ AssignmentRuleHeaderUsingRuleId = "<AssignmentRuleHeader><assignment_rule_id>%s</assignment_rule_id></AssignmentRuleHeader>"
+ AssignmentRuleHeaderUsingDefaultRule = "<AssignmentRuleHeader><use_default_rule>true</use_default_rule></AssignmentRuleHeader>"
+ MruHeader = "<MruHeader><update_mru>true</update_mru></MruHeader>"
-
+
#Connect to the server securely.
def initialize(url, sid)
init_server(url)
-
+
@session_id = sid
@batch_size = DEFAULT_BATCH_SIZE
end
-
-
+
+
def show_debug
$DEBUG or ENV['SHOWSOAP']
end
-
+
def init_server(url)
@url = URI.parse(url)
@server = Net::HTTP.new(@url.host, @url.port)
@server.use_ssl = @url.scheme == 'https'
@server.verify_mode = OpenSSL::SSL::VERIFY_NONE
-
- # run ruby with -d to see SOAP wiredumps.
+
+ # run ruby with -d or env variable SHOWSOAP=true to see SOAP wiredumps.
@server.set_debug_output $stderr if show_debug
end
-
-
+
+
#Log in to the server and remember the session ID
#returned to us by SalesForce.
def login(user, password)
- puts "\n\nIn login(#{user})\n\n"
-
@user = user
@password = password
-
+
response = call_remote(:login, [:username, user, :password, password])
-
- raise "Incorrect user name / password [#{response.fault}]" unless response.loginResponse
-
+
+ unless response.loginResponse
+ pp response
+ raise "Incorrect user name / password [#{response.fault}]"
+ end
+
result = response[:loginResponse][:result]
@session_id = result[:sessionId]
-
+
init_server(result[:serverUrl])
-
+
response
end
-
+
#Call a method on the remote server. Arguments can be
#a hash or (if order is important) an array of alternating
#keys and values.
def call_remote(method, args)
#Create XML text from the arguments.
expanded = ''
@builder = Builder::XmlMarkup.new(:target => expanded)
expand({method => args}, 'urn:partner.soap.sforce.com')
+
+ extra_headers = ""
+ extra_headers << (AssignmentRuleHeaderUsingRuleId % assignment_rule_id) if assignment_rule_id
+ extra_headers << AssignmentRuleHeaderUsingDefaultRule if use_default_rule
+ extra_headers << MruHeader if update_mru
#Fill in the blanks of the SOAP envelope with our
#session ID and the expanded XML of our request.
- request = (Envelope % [@session_id, @batch_size, expanded])
-
+ request = (Envelope % [@session_id, @batch_size, extra_headers, expanded])
+
# reset the batch size for the next request
@batch_size = DEFAULT_BATCH_SIZE
-
+
# gzip request
request = encode(request)
-
+
headers = {
'Connection' => 'Keep-Alive',
'Content-Type' => 'text/xml',
'SOAPAction' => '""',
- 'User-Agent' => 'ActiveSalesforce RForce'
+ 'User-Agent' => 'ActiveSalesforce'
}
unless show_debug
headers['Accept-Encoding'] = 'gzip'
headers['Content-Encoding'] = 'gzip'
end
-
+
#Send the request to the server and read the response.
- response = @server.post2(@url.path, request, headers)
-
+ response = @server.post2(@url.path, request.lstrip, headers)
+
# decode if we have encoding
content = decode(response)
-
+
# Check to see if INVALID_SESSION_ID was raised and try to relogin in
if method != :login and @session_id and response =~ /<faultcode>sf\:INVALID_SESSION_ID<\/faultcode>/
puts "\n\nSession timeout error - auto relogin activated"
-
+
login(@user, @password)
-
+
#Send the request to the server and read the response.
response = @server.post2(@url.path, request, headers)
-
+
content = decode(response)
end
-
+
SoapResponse.new(content)
end
-
+
# decode gzip
def decode(response)
encoding = response['Content-Encoding']
-
+
# return body if no encoding
if !encoding then return response.body end
-
+
# decode gzip
case encoding.strip
when 'gzip':
begin
gzr = Zlib::GzipReader.new(StringIO.new(response.body))
@@ -271,11 +282,11 @@
else
response.body
end
end
-
+
# encode gzip
def encode(request)
return request if show_debug
begin
@@ -285,50 +296,50 @@
ostream.string
ensure
gzw.close
end
end
-
-
+
+
#Turns method calls on this object into remote SOAP calls.
def method_missing(method, *args)
unless args.size == 1 && [Hash, Array].include?(args[0].class)
raise 'Expected 1 Hash or Array argument'
end
-
+
call_remote method, args[0]
end
-
+
#Expand Ruby data structures into XML.
def expand(args, xmlns = nil)
#Nest arrays: [:a, 1, :b, 2] => [[:a, 1], [:b, 2]]
if (args.class == Array)
args.each_index{|i| args[i, 2] = [args[i, 2]]}
end
-
+
args.each do |key, value|
attributes = xmlns ? {:xmlns => xmlns} : {}
-
+
#If the XML tag requires attributes,
#the tag name will contain a space
#followed by a string representation
#of a hash of attributes.
#
#e.g. 'sObject {"xsi:type" => "Opportunity"}'
#becomes <sObject xsi:type="Opportunity>...</sObject>
if key.is_a? String
key, modifier = key.split(' ', 2)
-
+
attributes.merge!(eval(modifier)) if modifier
end
-
+
#Create an XML element and fill it with this
#value's sub-items.
case value
when Hash, Array
@builder.tag!(key, attributes) do expand value; end
-
+
when String
@builder.tag!(key, attributes) { @builder.text! value }
end
end
end