salesforce_bulk_quickfix 1.0.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.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .project
6
+ *~
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in salesforce_bulk.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,75 @@
1
+ = salesforce-bulk
2
+
3
+ ==Overview
4
+
5
+ Salesforce bulk is a simple ruby gem for connecting to and using the Salesforce Bulk API (http://www.salesforce.com/us/developer/docs/api_asynch/index.htm). There are already some gems out there that provide connectivity to the Salesforce SOAP and Rest APIs, if your needs are simple, I recommend using those, specifically raygao's asf-rest-adapter (https://github.com/raygao/asf-rest-adapter) or databasedotcom (https://rubygems.org/gems/databasedotcom).
6
+
7
+ ==Installation
8
+
9
+ sudo gem install salesforce_bulk
10
+
11
+ ==How to use
12
+
13
+ Using this gem is simple and straight forward.
14
+
15
+ To initialize:
16
+
17
+ require 'salesforce_bulk'
18
+ salesforce = SalesforceBulk::Api.new("YOUR_SALESFORCE_USERNAME", "YOUR_SALESFORCE_PASSWORD+YOUR_SALESFORCE_TOKEN")
19
+
20
+ To use sandbox:
21
+ salesforce = SalesforceBulk::Api.new("YOUR_SALESFORCE_SANDBOX_USERNAME", "YOUR_SALESFORCE_PASSWORD+YOUR_SALESFORCE_SANDBOX_TOKEN", true)
22
+
23
+ Note: the second parameter is a combination of your Salesforce token and password. So if your password is xxxx and your token is yyyy, the second parameter will be xxxxyyyy
24
+
25
+ Sample operations:
26
+
27
+ # Insert/Create
28
+ new_account = Hash["name" => "Test Account", "type" => "Other"] # Add as many fields per record as needed.
29
+ records_to_insert = Array.new
30
+ records_to_insert.push(new_account) # You can add as many records as you want here, just keep in mind that Salesforce has governor limits.
31
+ result = salesforce.create("Account", records_to_insert)
32
+ puts "result is: #{result.inspect}"
33
+
34
+ # Update
35
+ updated_account = Hash["name" => "Test Account -- Updated", "id" => "a00A0001009zA2m"] # Nearly identical to an insert, but we need to pass the salesforce id.
36
+ records_to_update = Array.new
37
+ records_to_update.push(updated_account)
38
+ salesforce.update("Account", records_to_update)
39
+
40
+ # Upsert
41
+ upserted_account = Hash["name" => "Test Account -- Upserted", "External_Field_Name" => "123456"] # Fields to be updated. External field must be included
42
+ records_to_upsert = Array.new
43
+ records_to_upsert.push(upserted_account)
44
+ salesforce.upsert("Account", records_to_upsert, "External_Field_Name") # Note that upsert accepts an extra parameter for the external field name
45
+
46
+ OR
47
+
48
+ salesforce.upsert("Account", records_to_upsert, "External_Field_Name", true) # last parameter indicates whether to wait until the batch finishes
49
+
50
+ # Delete
51
+ deleted_account = Hash["id" => "a00A0001009zA2m"] # We only specify the id of the records to delete
52
+ records_to_delete = Array.new
53
+ records_to_delete.push(deleted_account)
54
+ salesforce.delete("Account", records_to_delete)
55
+
56
+ # Query
57
+ res = salesforce.query("Account", "select id, name, createddate from Account limit 3") # We just need to pass the sobject name and the query string
58
+ puts res.result.records.inspect
59
+
60
+ Result reporting:
61
+
62
+ new_account = Hash["type" => "Other"] # Missing required field "name."
63
+ records_to_insert = Array.new
64
+ records_to_insert.push(new_account) # You can add as many records as you want here, just keep in mind that Salesforce has governor limits.
65
+ result = salesforce.create("Account", records_to_insert, true) # Result reporting can only be used if wait is set to true
66
+ puts result.success? # false
67
+ puts result.has_errors? # true
68
+ puts result.result.errors # An indexed hash detailing the errors
69
+
70
+ == Copyright
71
+
72
+ Copyright (c) 2012 Jorge Valdivia.
73
+
74
+ ===end
75
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,114 @@
1
+ module SalesforceBulk
2
+
3
+ class Connection
4
+
5
+ @@XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
6
+ @@API_VERSION = nil
7
+ @@LOGIN_HOST = 'login.salesforce.com'
8
+ @@INSTANCE_HOST = nil # Gets set in login()
9
+
10
+ def initialize(username, password, api_version, in_sandbox)
11
+ @username = username
12
+ @password = password
13
+ @session_id = nil
14
+ @server_url = nil
15
+ @instance = nil
16
+ @@API_VERSION = api_version
17
+ @@LOGIN_PATH = "/services/Soap/u/#{@@API_VERSION}"
18
+ @@PATH_PREFIX = "/services/async/#{@@API_VERSION}/"
19
+ @@LOGIN_HOST = 'test.salesforce.com' if in_sandbox
20
+
21
+ login()
22
+ end
23
+
24
+ #private
25
+
26
+ def login()
27
+
28
+ xml = '<?xml version="1.0" encoding="utf-8" ?>'
29
+ xml += "<env:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
30
+ xml += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
31
+ xml += " xmlns:env=\"http://schemas.xmlsoap.org/soap/envelope/\">"
32
+ xml += " <env:Body>"
33
+ xml += " <n1:login xmlns:n1=\"urn:partner.soap.sforce.com\">"
34
+ xml += " <n1:username>#{@username}</n1:username>"
35
+ xml += " <n1:password>#{@password}</n1:password>"
36
+ xml += " </n1:login>"
37
+ xml += " </env:Body>"
38
+ xml += "</env:Envelope>"
39
+
40
+ headers = Hash['Content-Type' => 'text/xml; charset=utf-8', 'SOAPAction' => 'login']
41
+
42
+ response = post_xml(@@LOGIN_HOST, @@LOGIN_PATH, xml, headers)
43
+ # response_parsed = XmlSimple.xml_in(response)
44
+ response_parsed = parse_response response
45
+
46
+ @session_id = response_parsed['Body'][0]['loginResponse'][0]['result'][0]['sessionId'][0]
47
+ @server_url = response_parsed['Body'][0]['loginResponse'][0]['result'][0]['serverUrl'][0]
48
+ @instance = parse_instance()
49
+
50
+ @@INSTANCE_HOST = "#{@instance}.salesforce.com"
51
+ end
52
+
53
+ def post_xml(host, path, xml, headers)
54
+
55
+ host = host || @@INSTANCE_HOST
56
+
57
+ if host != @@LOGIN_HOST # Not login, need to add session id to header
58
+ headers['X-SFDC-Session'] = @session_id;
59
+ path = "#{@@PATH_PREFIX}#{path}"
60
+ end
61
+
62
+ https(host).post(path, xml, headers).body
63
+ end
64
+
65
+ def get_request(host, path, headers)
66
+ host = host || @@INSTANCE_HOST
67
+ path = "#{@@PATH_PREFIX}#{path}"
68
+
69
+ if host != @@LOGIN_HOST # Not login, need to add session id to header
70
+ headers['X-SFDC-Session'] = @session_id;
71
+ end
72
+
73
+ https(host).get(path, headers).body
74
+ end
75
+
76
+ def https(host)
77
+ req = Net::HTTP.new(host, 443)
78
+ req.use_ssl = true
79
+ req.verify_mode = OpenSSL::SSL::VERIFY_NONE
80
+ req
81
+ end
82
+
83
+ def parse_instance()
84
+ #@server_url =~ /https:\/\/([a-z]{2,2}[0-9]{1,2})-api/
85
+ #@instance = $~.captures[0]
86
+ # JOHN RYAN: had to do this fix quick after a Salesforce API change
87
+ @server_url = @instance
88
+ end
89
+
90
+ def parse_response response
91
+ response_parsed = XmlSimple.xml_in(response)
92
+
93
+ if response.downcase.include?("faultstring") || response.downcase.include?("exceptionmessage")
94
+ begin
95
+
96
+ if response.downcase.include?("faultstring")
97
+ error_message = response_parsed["Body"][0]["Fault"][0]["faultstring"][0]
98
+ elsif response.downcase.include?("exceptionmessage")
99
+ error_message = response_parsed["exceptionMessage"][0]
100
+ end
101
+
102
+ rescue
103
+ raise "An unknown error has occured within the salesforce_bulk gem. This is most likely caused by bad request, but I am unable to parse the correct error message. Here is a dump of the response for your convenience. #{response}"
104
+ end
105
+
106
+ raise error_message
107
+ end
108
+
109
+ response_parsed
110
+ end
111
+
112
+ end
113
+
114
+ end
@@ -0,0 +1,174 @@
1
+ module SalesforceBulk
2
+
3
+ class Job
4
+
5
+ attr :result
6
+
7
+ def initialize(operation, sobject, records, external_field, connection)
8
+
9
+ @@operation = operation
10
+ @@sobject = sobject
11
+ @@external_field = external_field
12
+ @@records = records
13
+ @@connection = connection
14
+ @@XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
15
+
16
+ # @result = {"errors" => [], "success" => nil, "records" => [], "raw" => nil, "message" => 'The job has been queued.'}
17
+ @result = JobResult.new
18
+
19
+ end
20
+
21
+ def create_job()
22
+ xml = "#{@@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
23
+ xml += "<operation>#{@@operation}</operation>"
24
+ xml += "<object>#{@@sobject}</object>"
25
+ if !@@external_field.nil? # This only happens on upsert
26
+ xml += "<externalIdFieldName>#{@@external_field}</externalIdFieldName>"
27
+ end
28
+ xml += "<contentType>CSV</contentType>"
29
+ xml += "</jobInfo>"
30
+
31
+ path = "job"
32
+ headers = Hash['Content-Type' => 'application/xml; charset=utf-8']
33
+
34
+ response = @@connection.post_xml(nil, path, xml, headers)
35
+ response_parsed = @@connection.parse_response response
36
+
37
+ @@job_id = response_parsed['id'][0]
38
+ end
39
+
40
+ def close_job()
41
+ xml = "#{@@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
42
+ xml += "<state>Closed</state>"
43
+ xml += "</jobInfo>"
44
+
45
+ path = "job/#{@@job_id}"
46
+ headers = Hash['Content-Type' => 'application/xml; charset=utf-8']
47
+
48
+ response = @@connection.post_xml(nil, path, xml, headers)
49
+ response_parsed = XmlSimple.xml_in(response)
50
+
51
+ #job_id = response_parsed['id'][0]
52
+ end
53
+
54
+ def add_query
55
+ path = "job/#{@@job_id}/batch/"
56
+ headers = Hash["Content-Type" => "text/csv; charset=UTF-8"]
57
+
58
+ response = @@connection.post_xml(nil, path, @@records, headers)
59
+ response_parsed = XmlSimple.xml_in(response)
60
+
61
+ @@batch_id = response_parsed['id'][0]
62
+ end
63
+
64
+ def add_batch()
65
+ keys = @@records.first.keys
66
+
67
+ output_csv = keys.to_csv
68
+
69
+ @@records.each do |r|
70
+ fields = Array.new
71
+ keys.each do |k|
72
+ fields.push(r[k])
73
+ end
74
+
75
+ row_csv = fields.to_csv
76
+ output_csv += row_csv
77
+ end
78
+
79
+ path = "job/#{@@job_id}/batch/"
80
+ headers = Hash["Content-Type" => "text/csv; charset=UTF-8"]
81
+
82
+ response = @@connection.post_xml(nil, path, output_csv, headers)
83
+ response_parsed = XmlSimple.xml_in(response)
84
+
85
+ @@batch_id = response_parsed['id'][0]
86
+ end
87
+
88
+ def check_batch_status()
89
+ path = "job/#{@@job_id}/batch/#{@@batch_id}"
90
+ headers = Hash.new
91
+
92
+ response = @@connection.get_request(nil, path, headers)
93
+ response_parsed = XmlSimple.xml_in(response)
94
+
95
+ begin
96
+ #puts "check: #{response_parsed.inspect}\n"
97
+ response_parsed['state'][0]
98
+ rescue Exception => e
99
+ #puts "check: #{response_parsed.inspect}\n"
100
+
101
+ nil
102
+ end
103
+ end
104
+
105
+ def get_batch_result()
106
+ path = "job/#{@@job_id}/batch/#{@@batch_id}/result"
107
+ headers = Hash["Content-Type" => "text/xml; charset=UTF-8"]
108
+
109
+ response = @@connection.get_request(nil, path, headers)
110
+
111
+ if(@@operation == "query") # The query op requires us to do another request to get the results
112
+ response_parsed = XmlSimple.xml_in(response)
113
+ result_id = response_parsed["result"][0]
114
+
115
+ path = "job/#{@@job_id}/batch/#{@@batch_id}/result/#{result_id}"
116
+ headers = Hash.new
117
+ headers = Hash["Content-Type" => "text/xml; charset=UTF-8"]
118
+
119
+ response = @@connection.get_request(nil, path, headers)
120
+
121
+ end
122
+
123
+ parse_results response
124
+
125
+ response = response.lines.to_a[1..-1].join
126
+ # csvRows = CSV.parse(response, :headers => true)
127
+ end
128
+
129
+ def parse_results response
130
+ @result.success = true
131
+ @result.raw = response.lines.to_a[1..-1].join
132
+ csvRows = CSV.parse(response, :headers => true)
133
+
134
+ csvRows.each_with_index do |row, index|
135
+ if @@operation != "query"
136
+ row["Created"] = row["Created"] == "true" ? true : false
137
+ row["Success"] = row["Success"] == "true" ? true : false
138
+ end
139
+
140
+ @result.records.push row
141
+ if row["Success"] == false
142
+ @result.success = false
143
+ @result.errors.push({"#{index}" => row["Error"]}) if row["Error"]
144
+ end
145
+ end
146
+
147
+ @result.message = "The job has been closed."
148
+
149
+ end
150
+
151
+ end
152
+
153
+ class JobResult
154
+ attr_writer :errors, :success, :records, :raw, :message
155
+ attr_reader :errors, :success, :records, :raw, :message
156
+
157
+ def initialize
158
+ @errors = []
159
+ @success = nil
160
+ @records = []
161
+ @raw = nil
162
+ @message = 'The job has been queued.'
163
+ end
164
+
165
+ def success?
166
+ @success
167
+ end
168
+
169
+ def has_errors?
170
+ @errors.count > 0
171
+ end
172
+ end
173
+
174
+ end
@@ -0,0 +1,3 @@
1
+ module SalesforceBulk
2
+ VERSION = "1.0.1"
3
+ end
@@ -0,0 +1,83 @@
1
+ require 'net/https'
2
+ require 'xmlsimple'
3
+ require 'csv'
4
+ require "salesforce_bulk/version"
5
+ require 'salesforce_bulk/job'
6
+ require 'salesforce_bulk/connection'
7
+
8
+ module SalesforceBulk
9
+ # Your code goes here...
10
+ class Api
11
+
12
+ @@SALESFORCE_API_VERSION = '24.0'
13
+
14
+ def initialize(username, password, in_sandbox=false)
15
+ @connection = SalesforceBulk::Connection.new(username, password, @@SALESFORCE_API_VERSION, in_sandbox)
16
+ end
17
+
18
+ def upsert(sobject, records, external_field, wait=false)
19
+ self.do_operation('upsert', sobject, records, external_field, wait)
20
+ end
21
+
22
+ def update(sobject, records, wait=false)
23
+ self.do_operation('update', sobject, records, nil, wait)
24
+ end
25
+
26
+ def create(sobject, records, wait=false)
27
+ self.do_operation('insert', sobject, records, nil, wait)
28
+ end
29
+
30
+ def delete(sobject, records, wait=false)
31
+ self.do_operation('delete', sobject, records, nil, wait)
32
+ end
33
+
34
+ def query(sobject, query)
35
+ self.do_operation('query', sobject, query, nil)
36
+ end
37
+
38
+ def do_operation(operation, sobject, records, external_field, wait=false)
39
+ job = SalesforceBulk::Job.new(operation, sobject, records, external_field, @connection)
40
+
41
+ # TODO: put this in one function
42
+ job_id = job.create_job()
43
+ if(operation == "query")
44
+ batch_id = job.add_query()
45
+ else
46
+ batch_id = job.add_batch()
47
+ end
48
+ job.close_job()
49
+
50
+ if wait or operation == 'query'
51
+ while true
52
+ state = job.check_batch_status()
53
+ if state != "Queued" && state != "InProgress"
54
+ break
55
+ end
56
+ sleep(2) # wait x seconds and check again
57
+ end
58
+
59
+ if state == 'Completed'
60
+ job.get_batch_result()
61
+ job
62
+ else
63
+ job.result.message = "There is an error in your job. The response returned a state of #{state}. Please check your query/parameters and try again."
64
+ job.result.success = false
65
+ return job
66
+
67
+ end
68
+ else
69
+ return job
70
+ end
71
+
72
+ end
73
+
74
+ def parse_batch_result result
75
+ begin
76
+ CSV.parse(result, :headers => true)
77
+ rescue
78
+ result
79
+ end
80
+ end
81
+
82
+ end # End class
83
+ end # End module
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "salesforce_bulk/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "salesforce_bulk_quickfix"
7
+ s.version = SalesforceBulk::VERSION
8
+ s.authors = ["Jorge Valdivia, John Ryan"]
9
+ s.email = ["jorge@valdivia.me;johnny@patchapps.com"]
10
+ s.homepage = "https://github.com/jorgevaldivia/salesforce_bulk"
11
+ s.summary = %q{Ruby support for the Salesforce Bulk API}
12
+ s.description = %q{This gem provides a super simple interface for the Salesforce Bulk API. It provides support for insert, update, upsert, delete, and query.}
13
+
14
+ s.rubyforge_project = "salesforce_bulk_quickfix"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ # s.add_runtime_dependency "rest-client"
24
+
25
+ s.add_dependency "xml-simple"
26
+
27
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: salesforce_bulk_quickfix
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jorge Valdivia, John Ryan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: xml-simple
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: This gem provides a super simple interface for the Salesforce Bulk API.
31
+ It provides support for insert, update, upsert, delete, and query.
32
+ email:
33
+ - jorge@valdivia.me;johnny@patchapps.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - Gemfile
40
+ - README.rdoc
41
+ - Rakefile
42
+ - lib/salesforce_bulk.rb
43
+ - lib/salesforce_bulk/connection.rb
44
+ - lib/salesforce_bulk/job.rb
45
+ - lib/salesforce_bulk/version.rb
46
+ - salesforce_bulk.gemspec
47
+ homepage: https://github.com/jorgevaldivia/salesforce_bulk
48
+ licenses: []
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project: salesforce_bulk_quickfix
67
+ rubygems_version: 1.8.24
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Ruby support for the Salesforce Bulk API
71
+ test_files: []
72
+ has_rdoc: