copious-activerecord-activesalesforce-adapter 2.3.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -21,7 +21,7 @@ require 'benchmark'
21
21
  require 'active_record'
22
22
  require 'active_record/connection_adapters/abstract_adapter'
23
23
 
24
- require File.dirname(__FILE__) + '/rforce'
24
+ require File.dirname(__FILE__) + '/../../rforce'
25
25
  require File.dirname(__FILE__) + '/column_definition'
26
26
  require File.dirname(__FILE__) + '/relationship_definition'
27
27
  require File.dirname(__FILE__) + '/boxcar_command'
@@ -0,0 +1,116 @@
1
+ =begin
2
+ RForce v0.3
3
+ Copyright (c) 2005-2008 Ian Dees and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ =end
23
+
24
+ # RForce is a simple Ruby binding to the SalesForce CRM system.
25
+ # Rather than enforcing adherence to the sforce.com schema,
26
+ # RForce assumes you are familiar with the API. Ruby method names
27
+ # become SOAP method names. Nested Ruby hashes become nested
28
+ # XML elements.
29
+ #
30
+ # Example:
31
+ #
32
+ # binding = RForce::Binding.new 'na1-api.salesforce.com'
33
+ # binding.login 'username', 'password'
34
+ # answer = binding.search(
35
+ # :searchString =>
36
+ # 'find {Some Account Name} in name fields returning account(id)')
37
+ # account_id = answer.searchResponse.result.searchRecords.record.Id
38
+ #
39
+ # opportunity = {
40
+ # :accountId => account_id,
41
+ # :amount => "10.00",
42
+ # :name => "New sale",
43
+ # :closeDate => "2005-09-01",
44
+ # :stageName => "Closed Won"
45
+ # }
46
+ #
47
+ # binding.create 'sObject {"xsi:type" => "Opportunity"}' => opportunity
48
+ #
49
+
50
+
51
+ require 'net/https'
52
+ require 'uri'
53
+ require 'zlib'
54
+ require 'stringio'
55
+
56
+ require 'rubygems'
57
+
58
+ gem 'builder', '>= 2.0.0'
59
+ require 'builder'
60
+
61
+ gem 'facets', '>= 2.4'
62
+ require 'facets/openhash'
63
+
64
+ require 'rforce/binding'
65
+ require 'rforce/soap_response_rexml'
66
+ require 'rforce/soap_response_hpricot' rescue nil
67
+ require 'rforce/soap_response_expat' rescue nil
68
+
69
+
70
+ module RForce
71
+ # Use the fastest XML parser available.
72
+ def self.parser(name)
73
+ RForce.const_get(name) rescue nil
74
+ end
75
+
76
+ SoapResponse =
77
+ parser(:SoapResponseExpat) ||
78
+ parser(:SoapResponseHpricot) ||
79
+ SoapResponseRexml
80
+
81
+ # Expand Ruby data structures into XML.
82
+ def expand(builder, args, xmlns = nil)
83
+ # Nest arrays: [:a, 1, :b, 2] => [[:a, 1], [:b, 2]]
84
+ if (args.class == Array)
85
+ args.each_index{|i| args[i, 2] = [args[i, 2]]}
86
+ end
87
+
88
+ args.each do |key, value|
89
+ attributes = xmlns ? {:xmlns => xmlns} : {}
90
+
91
+ # If the XML tag requires attributes,
92
+ # the tag name will contain a space
93
+ # followed by a string representation
94
+ # of a hash of attributes.
95
+ #
96
+ # e.g. 'sObject {"xsi:type" => "Opportunity"}'
97
+ # becomes <sObject xsi:type="Opportunity>...</sObject>
98
+ if key.is_a? String
99
+ key, modifier = key.split(' ', 2)
100
+
101
+ attributes.merge!(eval(modifier)) if modifier
102
+ end
103
+
104
+ # Create an XML element and fill it with this
105
+ # value's sub-items.
106
+ case value
107
+ when Hash, Array
108
+ builder.tag!(key, attributes) do expand builder, value; end
109
+
110
+ when String
111
+ builder.tag!(key, attributes) { builder.text! value }
112
+ end
113
+ end
114
+ end
115
+
116
+ end
@@ -0,0 +1,202 @@
1
+ module RForce
2
+ # Implements the connection to the SalesForce server.
3
+ class Binding
4
+ include RForce
5
+
6
+ DEFAULT_BATCH_SIZE = 10
7
+ attr_accessor :batch_size, :url, :assignment_rule_id, :use_default_rule, :update_mru, :client_id, :trigger_user_email,
8
+ :trigger_other_email, :trigger_auto_response_email
9
+
10
+ # Fill in the guts of this typical SOAP envelope
11
+ # with the session ID and the body of the SOAP request.
12
+ Envelope = <<-HERE
13
+ <?xml version="1.0" encoding="utf-8" ?>
14
+ <soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
15
+ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
16
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
17
+ xmlns:partner="urn:partner.soap.sforce.com">
18
+ xmlns:spartner="urn:sobject.partner.soap.sforce.com">
19
+ <soap:Header>
20
+ <partner:SessionHeader soap:mustUnderstand='1'>
21
+ <partner:sessionId>%s</partner:sessionId>
22
+ </partner:SessionHeader>
23
+ <partner:QueryOptions soap:mustUnderstand='1'>
24
+ <partner:batchSize>%d</partner:batchSize>
25
+ </partner:QueryOptions>
26
+ %s
27
+ </soap:Header>
28
+ <soap:Body>
29
+ %s
30
+ </soap:Body>
31
+ </soap:Envelope>
32
+ HERE
33
+
34
+ AssignmentRuleHeaderUsingRuleId = '<partner:AssignmentRuleHeader soap:mustUnderstand="1"><partner:assignmentRuleId>%s</partner:assignmentRuleId></partner:AssignmentRuleHeader>'
35
+ AssignmentRuleHeaderUsingDefaultRule = '<partner:AssignmentRuleHeader soap:mustUnderstand="1"><partner:useDefaultRule>true</partner:useDefaultRule></partner:AssignmentRuleHeader>'
36
+ MruHeader = '<partner:MruHeader soap:mustUnderstand="1"><partner:updateMru>true</partner:updateMru></partner:MruHeader>'
37
+ ClientIdHeader = '<partner:CallOptions soap:mustUnderstand="1"><partner:client>%s</partner:client></partner:CallOptions>'
38
+
39
+ # Connect to the server securely.
40
+ def initialize(url, sid = nil)
41
+ init_server(url)
42
+
43
+ @session_id = sid
44
+ @batch_size = DEFAULT_BATCH_SIZE
45
+ end
46
+
47
+
48
+ def show_debug
49
+ ENV['SHOWSOAP'] == 'true'
50
+ end
51
+
52
+
53
+ def init_server(url)
54
+ @url = URI.parse(url)
55
+ @server = Net::HTTP.new(@url.host, @url.port)
56
+ @server.use_ssl = @url.scheme == 'https'
57
+ @server.verify_mode = OpenSSL::SSL::VERIFY_NONE
58
+
59
+ # run ruby with -d or env variable SHOWSOAP=true to see SOAP wiredumps.
60
+ @server.set_debug_output $stderr if show_debug
61
+ end
62
+
63
+
64
+ # Log in to the server and remember the session ID
65
+ # returned to us by SalesForce.
66
+ def login(user, password)
67
+ @user = user
68
+ @password = password
69
+
70
+ response = call_remote(:login, [:username, user, :password, password])
71
+
72
+ raise "Incorrect user name / password [#{response[:Fault][:faultstring]}]" unless response.loginResponse
73
+
74
+ result = response[:loginResponse][:result]
75
+ @session_id = result[:sessionId]
76
+
77
+ init_server(result[:serverUrl])
78
+
79
+ response
80
+ end
81
+
82
+
83
+ # Call a method on the remote server. Arguments can be
84
+ # a hash or (if order is important) an array of alternating
85
+ # keys and values.
86
+ def call_remote(method, args)
87
+ # Create XML text from the arguments.
88
+ expanded = ''
89
+ @builder = Builder::XmlMarkup.new(:target => expanded)
90
+ expand(@builder, {method => args}, 'urn:partner.soap.sforce.com')
91
+
92
+ extra_headers = ""
93
+ extra_headers << (AssignmentRuleHeaderUsingRuleId % assignment_rule_id) if assignment_rule_id
94
+ extra_headers << AssignmentRuleHeaderUsingDefaultRule if use_default_rule
95
+ extra_headers << MruHeader if update_mru
96
+ extra_headers << (ClientIdHeader % client_id) if client_id
97
+
98
+ if trigger_user_email or trigger_other_email or trigger_auto_response_email
99
+ extra_headers << '<partner:EmailHeader soap:mustUnderstand="1">'
100
+
101
+ extra_headers << '<partner:triggerUserEmail>true</partner:triggerUserEmail>' if trigger_user_email
102
+ extra_headers << '<partner:triggerOtherEmail>true</partner:triggerOtherEmail>' if trigger_other_email
103
+ extra_headers << '<partner:triggerAutoResponseEmail>true</partner:triggerAutoResponseEmail>' if trigger_auto_response_email
104
+
105
+ extra_headers << '</partner:EmailHeader>'
106
+ end
107
+
108
+ # Fill in the blanks of the SOAP envelope with our
109
+ # session ID and the expanded XML of our request.
110
+ request = (Envelope % [@session_id, @batch_size, extra_headers, expanded])
111
+
112
+ # reset the batch size for the next request
113
+ @batch_size = DEFAULT_BATCH_SIZE
114
+
115
+ # gzip request
116
+ request = encode(request)
117
+
118
+ headers = {
119
+ 'Connection' => 'Keep-Alive',
120
+ 'Content-Type' => 'text/xml',
121
+ 'SOAPAction' => '""',
122
+ 'User-Agent' => 'activesalesforce rforce/1.0'
123
+ }
124
+
125
+ unless show_debug
126
+ headers['Accept-Encoding'] = 'gzip'
127
+ headers['Content-Encoding'] = 'gzip'
128
+ end
129
+
130
+ # Send the request to the server and read the response.
131
+ response = @server.post2(@url.path, request.lstrip, headers)
132
+
133
+ # decode if we have encoding
134
+ content = decode(response)
135
+
136
+ # Check to see if INVALID_SESSION_ID was raised and try to relogin in
137
+ if method != :login and @session_id and content =~ /sf:INVALID_SESSION_ID/
138
+ login(@user, @password)
139
+
140
+ # repackage and rencode request with the new session id
141
+ request = (Envelope % [@session_id, @batch_size, extra_headers, expanded])
142
+ request = encode(request)
143
+
144
+ # Send the request to the server and read the response.
145
+ response = @server.post2(@url.path, request.lstrip, headers)
146
+
147
+ content = decode(response)
148
+ end
149
+
150
+ SoapResponse.new(content).parse
151
+ end
152
+
153
+
154
+ # decode gzip
155
+ def decode(response)
156
+ encoding = response['Content-Encoding']
157
+
158
+ # return body if no encoding
159
+ if !encoding then return response.body end
160
+
161
+ # decode gzip
162
+ case encoding.strip
163
+ when 'gzip':
164
+ begin
165
+ gzr = Zlib::GzipReader.new(StringIO.new(response.body))
166
+ decoded = gzr.read
167
+ ensure
168
+ gzr.close
169
+ end
170
+ decoded
171
+ else
172
+ response.body
173
+ end
174
+ end
175
+
176
+
177
+ # encode gzip
178
+ def encode(request)
179
+ return request if show_debug
180
+
181
+ begin
182
+ ostream = StringIO.new
183
+ gzw = Zlib::GzipWriter.new(ostream)
184
+ gzw.write(request)
185
+ ostream.string
186
+ ensure
187
+ gzw.close
188
+ end
189
+ end
190
+
191
+
192
+ # Turns method calls on this object into remote SOAP calls.
193
+ def method_missing(method, *args)
194
+ unless args.size == 1 && [Hash, Array].include?(args[0].class)
195
+ raise 'Expected 1 Hash or Array argument'
196
+ end
197
+
198
+ call_remote method, args[0]
199
+ end
200
+ end
201
+ end
202
+
@@ -0,0 +1,14 @@
1
+ module RForce
2
+ # Allows indexing hashes like method calls: hash.key
3
+ # to supplement the traditional way of indexing: hash[key]
4
+ module MethodKeys
5
+ def method_missing(method, *args)
6
+ raise NoMethodError unless respond_to?('[]')
7
+ self[method]
8
+ end
9
+ end
10
+
11
+ class MethodHash < Hash
12
+ include MethodKeys
13
+ end
14
+ end
@@ -0,0 +1,90 @@
1
+ module RForce
2
+ module SoapPullable
3
+ SOAP_ENVELOPE = 'soapenv:Envelope'
4
+
5
+ # Split off the local name portion of an XML tag.
6
+ def local(tag)
7
+ first, second = tag.split ':'
8
+ return first if second.nil?
9
+ @namespaces.include?(first) ? second : tag
10
+ end
11
+
12
+ def tag_start(name, attrs)
13
+ tag_name = local name
14
+
15
+ # For shorter hash keys, we can strip any namespaces of the SOAP
16
+ # envelope tag from the tags inside it.
17
+ if name == SOAP_ENVELOPE
18
+ @namespaces = attrs.keys.grep(/xmlns:/).map {|k| k.split(':').last}
19
+ return
20
+ end
21
+
22
+ @stack.push OpenHash.new({})
23
+ end
24
+
25
+ def text(data)
26
+ @current_value = data.strip.empty? ? nil : data
27
+ end
28
+
29
+ def tag_end(name)
30
+ return if @done
31
+
32
+ tag_name = local name
33
+
34
+ return if tag_name == SOAP_ENVELOPE
35
+
36
+ working_hash = @stack.pop
37
+
38
+ # We are either done or working on a certain depth in the current
39
+ # stack.
40
+ if @stack.empty?
41
+ @parsed = working_hash
42
+ @done = true
43
+ return
44
+ else
45
+ index = @stack.size - 1
46
+ end
47
+
48
+ # working_hash and @current_value have a mutually exclusive relationship.
49
+ # If the current element doesn't have a value then it means that there
50
+ # is a nested data structure. In this case then working_hash is populated
51
+ # and @current_value is nil. Conversely, if @current_value has a value
52
+ # then we do not have a nested data sctructure and working_hash will
53
+ # be empty.
54
+ use_value = working_hash.empty? ? @current_value : working_hash
55
+ tag_sym = tag_name.to_sym
56
+ element = @stack[index][tag_sym]
57
+
58
+ if @stack[index].keys.include? tag_sym
59
+ # This is here to handle the Id value being included twice and thus
60
+ # producing an array. We skip the second instance so the array is
61
+ # not created.
62
+ #
63
+ # We also need to clear out the current value, so that the next
64
+ # tag doesn't erroneously pick up the value of the Id.
65
+ if tag_name == 'Id'
66
+ @current_value = nil
67
+ return
68
+ end
69
+
70
+ # We are here because the name of our current element is one that
71
+ # already exists in the hash. If this is the first encounter with
72
+ # the duplicate tag_name then we convert the existing value to an
73
+ # array otherwise we push the value we are working with and add it
74
+ # to the existing array.
75
+ if element.is_a?( Array )
76
+ element << use_value
77
+ else
78
+ @stack[index][tag_sym] = [element, use_value]
79
+ end
80
+ else
81
+ # We are here because the name of our current element has not been
82
+ # assigned yet.
83
+ @stack[index][tag_sym] = use_value
84
+ end
85
+
86
+ # We are done with the current tag so reset the data for the next one
87
+ @current_value = nil
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,35 @@
1
+ require 'xml/parser'
2
+ require 'rforce/soap_pullable'
3
+
4
+ module RForce
5
+ class SoapResponseExpat
6
+ include SoapPullable
7
+
8
+ def initialize(content)
9
+ @content = content
10
+ end
11
+
12
+ def parse
13
+ @current_value = nil
14
+ @stack = []
15
+ @parsed = OpenHash.new({})
16
+ @done = false
17
+ @namespaces = []
18
+
19
+ XML::Parser.new.parse(@content) do |type, name, data|
20
+ case type
21
+ when XML::Parser::START_ELEM
22
+ tag_start name, data
23
+ when XML::Parser::CDATA
24
+ text data
25
+ when XML::Parser::END_ELEM
26
+ tag_end name
27
+ end
28
+
29
+ break if @done
30
+ end
31
+
32
+ @parsed
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ require 'hpricot'
2
+
3
+
4
+ module RForce
5
+ class SoapResponseHpricot
6
+ # Parses an XML string into structured data.
7
+ def initialize(content)
8
+ @content = content
9
+ end
10
+
11
+ # Digests an XML DOM node into nested Ruby types.
12
+ def parse
13
+ document = Hpricot.XML(@content)
14
+ node = document % 'soapenv:Body'
15
+ self.class.node_to_ruby node
16
+ end
17
+
18
+ private
19
+
20
+ def self.node_to_ruby(node)
21
+ # Convert text nodes into simple strings.
22
+ children = node.children.reject do |c|
23
+ c.is_a?(Hpricot::Text) && c.to_s.strip.empty?
24
+ end
25
+
26
+ if node.is_a?(Hpricot::Text)
27
+ return node.inner_text
28
+ end
29
+
30
+ if children.first.is_a?(Hpricot::Text)
31
+ return children.first
32
+ end
33
+
34
+ # Convert nodes with children into MethodHashes.
35
+ elements = OpenHash.new({})
36
+
37
+ # Add all the element's children to the hash.
38
+ children.each do |e|
39
+ next if e.is_a?(Hpricot::Text) && e.to_s.strip.empty?
40
+ name = e.name
41
+
42
+ if name.include? ':'
43
+ name = name.split(':').last
44
+ end
45
+
46
+ name = name.to_sym
47
+
48
+ case elements[name]
49
+ # The most common case: unique child element tags.
50
+ when NilClass: elements[name] = node_to_ruby(e)
51
+
52
+ # Non-unique child elements become arrays:
53
+
54
+ # We've already created the array: just
55
+ # add the element.
56
+ when Array: elements[name] << node_to_ruby(e)
57
+
58
+ # We haven't created the array yet: do so,
59
+ # then put the existing element in, followed
60
+ # by the new one.
61
+ else
62
+ elements[name] = [elements[name]]
63
+ elements[name] << node_to_ruby(e)
64
+ end
65
+ end
66
+
67
+ return elements.empty? ? nil : elements
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ require 'rexml/document'
2
+ require 'rexml/xpath'
3
+ require 'rforce/soap_pullable'
4
+
5
+
6
+ module RForce
7
+ # Turns an XML response from the server into a Ruby
8
+ # object whose methods correspond to nested XML elements.
9
+ class SoapResponseRexml
10
+ include SoapPullable
11
+
12
+ %w(attlistdecl cdata comment doctype doctype_end elementdecl
13
+ entity entitydecl instruction notationdecl xmldecl).each do |unused|
14
+ define_method(unused) {}
15
+ end
16
+
17
+ def initialize(content)
18
+ @content = content
19
+ end
20
+
21
+ # Parses an XML string into structured data.
22
+ def parse
23
+ @current_value = nil
24
+ @stack = []
25
+ @parsed = OpenHash.new({})
26
+ @done = false
27
+ @namespaces = []
28
+
29
+ REXML::Document.parse_stream @content, self
30
+
31
+ @parsed
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module RForce
2
+ VERSION = '0.3'
3
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: copious-activerecord-activesalesforce-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Doug Chasman
@@ -13,7 +13,7 @@ autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
15
 
16
- date: 2008-12-19 00:00:00 -08:00
16
+ date: 2008-12-31 00:00:00 -08:00
17
17
  default_executable:
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
@@ -37,8 +37,26 @@ dependencies:
37
37
  - !ruby/object:Gem::Version
38
38
  version: 1.2.4
39
39
  version:
40
+ - !ruby/object:Gem::Dependency
41
+ name: xmlparser
42
+ version_requirement:
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0.6"
48
+ version:
49
+ - !ruby/object:Gem::Dependency
50
+ name: facets
51
+ version_requirement:
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "2.4"
57
+ version:
40
58
  description:
41
- email: dchasman@salesforce.com
59
+ email: jesse@copiousinc.com
42
60
  executables: []
43
61
 
44
62
  extensions: []
@@ -48,7 +66,6 @@ extra_rdoc_files:
48
66
  files:
49
67
  - lib/active_record
50
68
  - lib/active_record/connection_adapters
51
- - lib/active_record/connection_adapters/rforce.rb
52
69
  - lib/active_record/connection_adapters/relationship_definition.rb
53
70
  - lib/active_record/connection_adapters/result_array.rb
54
71
  - lib/active_record/connection_adapters/activesalesforce_adapter.rb
@@ -60,6 +77,15 @@ files:
60
77
  - lib/active_record/connection_adapters/activesalesforce.rb
61
78
  - lib/active_record/connection_adapters/boxcar_command.rb
62
79
  - lib/active_record/connection_adapters/recording_binding.rb
80
+ - lib/rforce.rb
81
+ - lib/rforce
82
+ - lib/rforce/binding.rb
83
+ - lib/rforce/method_keys.rb
84
+ - lib/rforce/soap_pullable.rb
85
+ - lib/rforce/soap_response_expat.rb
86
+ - lib/rforce/soap_response_hpricot.rb
87
+ - lib/rforce/soap_response_rexml.rb
88
+ - lib/rforce/version.rb
63
89
  - test/unit
64
90
  - test/unit/config.yml
65
91
  - test/unit/recorded_test_case.rb
@@ -83,10 +109,11 @@ files:
83
109
  - test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact_by_first_name.recording
84
110
  - README
85
111
  has_rdoc: true
86
- homepage: http://activesfdc.rubyforge.org/
87
- post_install_message: "this version is compatible with Rails 2.1,\n please report any bug in the group\n http://groups.google.com/group/activesalesforce\n "
88
- rdoc_options: []
89
-
112
+ homepage: http://github.com/copious/activerecord-activesalesforce-adapter
113
+ post_install_message: This version is compatible with Rails 2.1.
114
+ rdoc_options:
115
+ - --main
116
+ - README
90
117
  require_paths:
91
118
  - lib
92
119
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -108,5 +135,25 @@ rubygems_version: 1.2.0
108
135
  signing_key:
109
136
  specification_version: 2
110
137
  summary: ActiveSalesforce (ASF) is a Rails connection adapter that provides direct access to Salesforce.com hosted data and metadata via the ActiveRecord model layer. Objects, fields, and relationships are all auto surfaced as active record attributes and rels.
111
- test_files: []
112
-
138
+ test_files:
139
+ - test/unit
140
+ - test/unit/config.yml
141
+ - test/unit/recorded_test_case.rb
142
+ - test/unit/basic_test.rb
143
+ - test/unit/recorded_results
144
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_batch_insert.recording
145
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_read_all_content_columns.recording
146
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact_by_id.recording
147
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_create_a_contact.recording
148
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_count_contacts.recording
149
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_get_created_by_from_contact.recording
150
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_use_default_rule.recording
151
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_assignment_rule_id.recording
152
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_client_id.recording
153
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_use_update_mru.recording
154
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_master_detail.recording
155
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_add_notes_to_contact.recording
156
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_addresses.recording
157
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_save_a_contact.recording
158
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact.recording
159
+ - test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact_by_first_name.recording
@@ -1,361 +0,0 @@
1
- =begin
2
- RForce v0.1
3
- Copyright (c) 2005 Ian Dees
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
- =end
23
-
24
- # RForce is a simple Ruby binding to the SalesForce CRM system.
25
- # Rather than enforcing adherence to the sforce.com schema,
26
- # RForce assumes you are familiar with the API. Ruby method names
27
- # become SOAP method names. Nested Ruby hashes become nested
28
- # XML elements.
29
- #
30
- # Example:
31
- #
32
- # binding = RForce::Binding.new 'na1-api.salesforce.com'
33
- # binding.login 'username', 'password'
34
- # answer = binding.search(
35
- # :searchString =>
36
- # 'find {Some Account Name} in name fields returning account(id)')
37
- # account_id = answer.searchResponse.result.searchRecords.record.Id
38
- #
39
- # opportunity = {
40
- # :accountId => account_id,
41
- # :amount => "10.00",
42
- # :name => "New sale",
43
- # :closeDate => "2005-09-01",
44
- # :stageName => "Closed Won"
45
- # }
46
- #
47
- # binding.create 'sObject {"xsi:type" => "Opportunity"}' => opportunity
48
- #
49
-
50
-
51
- require 'net/https'
52
- require 'uri'
53
- require 'zlib'
54
- require 'stringio'
55
- require 'rexml/document'
56
- require 'rexml/xpath'
57
- require 'rubygems'
58
- gem 'builder', ">= 2.0.0"
59
-
60
-
61
- module RForce
62
-
63
- #Allows indexing hashes like method calls: hash.key
64
- #to supplement the traditional way of indexing: hash[key]
65
- module FlashHash
66
- def method_missing(method, *args)
67
- self[method]
68
- end
69
- end
70
-
71
- #Turns an XML response from the server into a Ruby
72
- #object whose methods correspond to nested XML elements.
73
- class SoapResponse
74
- include FlashHash
75
-
76
- #Parses an XML string into structured data.
77
- def initialize(content)
78
- document = REXML::Document.new content
79
- node = REXML::XPath.first document, '//soapenv:Body'
80
- @parsed = SoapResponse.parse node
81
- end
82
-
83
- #Allows this object to act like a hash (and therefore
84
- #as a FlashHash via the include above).
85
- def [](symbol)
86
- @parsed[symbol]
87
- end
88
-
89
- #Digests an XML DOM node into nested Ruby types.
90
- def SoapResponse.parse(node)
91
- #Convert text nodes into simple strings.
92
- return node.text unless node.has_elements?
93
-
94
- #Convert nodes with children into FlashHashes.
95
- elements = {}
96
- class << elements
97
- include FlashHash
98
- end
99
-
100
- #Add all the element's children to the hash.
101
- node.each_element do |e|
102
- name = e.name.to_sym
103
-
104
- case elements[name]
105
- #The most common case: unique child element tags.
106
- when NilClass: elements[name] = parse(e)
107
-
108
- #Non-unique child elements become arrays:
109
-
110
- #We've already created the array: just
111
- #add the element.
112
- when Array: elements[name] << parse(e)
113
-
114
- #We haven't created the array yet: do so,
115
- #then put the existing element in, followed
116
- #by the new one.
117
- else
118
- elements[name] = [elements[name]]
119
- elements[name] << parse(e)
120
- end
121
- end
122
-
123
- return elements
124
- end
125
- end
126
-
127
-
128
- #Implements the connection to the SalesForce server.
129
- class Binding
130
- DEFAULT_BATCH_SIZE = 10
131
- attr_accessor :batch_size, :url, :assignment_rule_id, :use_default_rule, :update_mru, :client_id, :trigger_user_email,
132
- :trigger_other_email, :trigger_auto_response_email
133
-
134
- #Fill in the guts of this typical SOAP envelope
135
- #with the session ID and the body of the SOAP request.
136
- Envelope = <<-HERE
137
- <?xml version="1.0" encoding="utf-8" ?>
138
- <soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
139
- xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
140
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
141
- xmlns:partner="urn:partner.soap.sforce.com">
142
- xmlns:spartner="urn:sobject.partner.soap.sforce.com">
143
- <soap:Header>
144
- <partner:SessionHeader soap:mustUnderstand='1'>
145
- <partner:sessionId>%s</partner:sessionId>
146
- </partner:SessionHeader>
147
- <partner:QueryOptions soap:mustUnderstand='1'>
148
- <partner:batchSize>%d</partner:batchSize>
149
- </partner:QueryOptions>
150
- %s
151
- </soap:Header>
152
- <soap:Body>
153
- %s
154
- </soap:Body>
155
- </soap:Envelope>
156
- HERE
157
-
158
- AssignmentRuleHeaderUsingRuleId = '<partner:AssignmentRuleHeader soap:mustUnderstand="1"><partner:assignmentRuleId>%s</partner:assignmentRuleId></partner:AssignmentRuleHeader>'
159
- AssignmentRuleHeaderUsingDefaultRule = '<partner:AssignmentRuleHeader soap:mustUnderstand="1"><partner:useDefaultRule>true</partner:useDefaultRule></partner:AssignmentRuleHeader>'
160
- MruHeader = '<partner:MruHeader soap:mustUnderstand="1"><partner:updateMru>true</partner:updateMru></partner:MruHeader>'
161
- ClientIdHeader = '<partner:CallOptions soap:mustUnderstand="1"><partner:client>%s</partner:client></partner:CallOptions>'
162
-
163
- #Connect to the server securely.
164
- def initialize(url, sid)
165
- init_server(url)
166
-
167
- @session_id = sid
168
- @batch_size = DEFAULT_BATCH_SIZE
169
- end
170
-
171
-
172
- def show_debug
173
- ENV['SHOWSOAP'] == 'true'
174
- end
175
-
176
-
177
- def init_server(url)
178
- @url = URI.parse(url)
179
- @server = Net::HTTP.new(@url.host, @url.port)
180
- @server.use_ssl = @url.scheme == 'https'
181
- @server.verify_mode = OpenSSL::SSL::VERIFY_NONE
182
-
183
- # run ruby with -d or env variable SHOWSOAP=true to see SOAP wiredumps.
184
- @server.set_debug_output $stderr if show_debug
185
- end
186
-
187
-
188
- #Log in to the server and remember the session ID
189
- #returned to us by SalesForce.
190
- def login(user, password)
191
- @user = user
192
- @password = password
193
-
194
- response = call_remote(:login, [:username, user, :password, password])
195
-
196
- raise "Incorrect user name / password [#{response.fault}]" unless response.loginResponse
197
-
198
- result = response[:loginResponse][:result]
199
- @session_id = result[:sessionId]
200
-
201
- init_server(result[:serverUrl])
202
-
203
- response
204
- end
205
-
206
-
207
- #Call a method on the remote server. Arguments can be
208
- #a hash or (if order is important) an array of alternating
209
- #keys and values.
210
- def call_remote(method, args)
211
- #Create XML text from the arguments.
212
- expanded = ''
213
- @builder = Builder::XmlMarkup.new(:target => expanded)
214
- expand({method => args}, 'urn:partner.soap.sforce.com')
215
-
216
- extra_headers = ""
217
- extra_headers << (AssignmentRuleHeaderUsingRuleId % assignment_rule_id) if assignment_rule_id
218
- extra_headers << AssignmentRuleHeaderUsingDefaultRule if use_default_rule
219
- extra_headers << MruHeader if update_mru
220
- extra_headers << (ClientIdHeader % client_id) if client_id
221
-
222
- if trigger_user_email or trigger_other_email or trigger_auto_response_email
223
- extra_headers << '<partner:EmailHeader soap:mustUnderstand="1">'
224
-
225
- extra_headers << '<partner:triggerUserEmail>true</partner:triggerUserEmail>' if trigger_user_email
226
- extra_headers << '<partner:triggerOtherEmail>true</partner:triggerOtherEmail>' if trigger_other_email
227
- extra_headers << '<partner:triggerAutoResponseEmail>true</partner:triggerAutoResponseEmail>' if trigger_auto_response_email
228
-
229
- extra_headers << '</partner:EmailHeader>'
230
- end
231
-
232
- #Fill in the blanks of the SOAP envelope with our
233
- #session ID and the expanded XML of our request.
234
- request = (Envelope % [@session_id, @batch_size, extra_headers, expanded])
235
-
236
- # reset the batch size for the next request
237
- @batch_size = DEFAULT_BATCH_SIZE
238
-
239
- # gzip request
240
- request = encode(request)
241
-
242
- headers = {
243
- 'Connection' => 'Keep-Alive',
244
- 'Content-Type' => 'text/xml',
245
- 'SOAPAction' => '""',
246
- 'User-Agent' => 'activesalesforce rforce/1.0'
247
- }
248
-
249
- unless show_debug
250
- headers['Accept-Encoding'] = 'gzip'
251
- headers['Content-Encoding'] = 'gzip'
252
- end
253
-
254
- #Send the request to the server and read the response.
255
- response = @server.post2(@url.path, request.lstrip, headers)
256
-
257
- # decode if we have encoding
258
- content = decode(response)
259
-
260
- # Check to see if INVALID_SESSION_ID was raised and try to relogin in
261
- if method != :login and @session_id and content =~ /sf:INVALID_SESSION_ID/
262
- login(@user, @password)
263
-
264
- # repackage and rencode request with the new session id
265
- request = (Envelope % [@session_id, @batch_size, extra_headers, expanded])
266
- request = encode(request)
267
-
268
- #Send the request to the server and read the response.
269
- response = @server.post2(@url.path, request.lstrip, headers)
270
-
271
- content = decode(response)
272
- end
273
-
274
- SoapResponse.new(content)
275
- end
276
-
277
-
278
- # decode gzip
279
- def decode(response)
280
- encoding = response['Content-Encoding']
281
-
282
- # return body if no encoding
283
- if !encoding then return response.body end
284
-
285
- # decode gzip
286
- case encoding.strip
287
- when 'gzip':
288
- begin
289
- gzr = Zlib::GzipReader.new(StringIO.new(response.body))
290
- decoded = gzr.read
291
- ensure
292
- gzr.close
293
- end
294
- decoded
295
- else
296
- response.body
297
- end
298
- end
299
-
300
-
301
- # encode gzip
302
- def encode(request)
303
- return request if show_debug
304
-
305
- begin
306
- ostream = StringIO.new
307
- gzw = Zlib::GzipWriter.new(ostream)
308
- gzw.write(request)
309
- ostream.string
310
- ensure
311
- gzw.close
312
- end
313
- end
314
-
315
-
316
- #Turns method calls on this object into remote SOAP calls.
317
- def method_missing(method, *args)
318
- unless args.size == 1 && [Hash, Array].include?(args[0].class)
319
- raise 'Expected 1 Hash or Array argument'
320
- end
321
-
322
- call_remote method, args[0]
323
- end
324
-
325
-
326
- #Expand Ruby data structures into XML.
327
- def expand(args, xmlns = nil)
328
- #Nest arrays: [:a, 1, :b, 2] => [[:a, 1], [:b, 2]]
329
- if (args.class == Array)
330
- args.each_index{|i| args[i, 2] = [args[i, 2]]}
331
- end
332
-
333
- args.each do |key, value|
334
- attributes = xmlns ? {:xmlns => xmlns} : {}
335
-
336
- #If the XML tag requires attributes,
337
- #the tag name will contain a space
338
- #followed by a string representation
339
- #of a hash of attributes.
340
- #
341
- #e.g. 'sObject {"xsi:type" => "Opportunity"}'
342
- #becomes <sObject xsi:type="Opportunity>...</sObject>
343
- if key.is_a? String
344
- key, modifier = key.split(' ', 2)
345
-
346
- attributes.merge!(eval(modifier)) if modifier
347
- end
348
-
349
- #Create an XML element and fill it with this
350
- #value's sub-items.
351
- case value
352
- when Hash, Array
353
- @builder.tag!(key, attributes) do expand value; end
354
-
355
- when String
356
- @builder.tag!(key, attributes) { @builder.text! value }
357
- end
358
- end
359
- end
360
- end
361
- end