salesforce_bulk 0.0.1 → 0.0.3
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/README.rdoc +54 -0
- data/lib/salesforce_bulk.rb +1 -1
- data/lib/salesforce_bulk/connection.rb +90 -0
- data/lib/salesforce_bulk/job.rb +126 -0
- data/lib/salesforce_bulk/version.rb +1 -1
- data/salesforce_bulk.gemspec +1 -1
- metadata +5 -2
data/README.rdoc
ADDED
@@ -0,0 +1,54 @@
|
|
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).
|
6
|
+
|
7
|
+
==How to use
|
8
|
+
|
9
|
+
Using this gem is simple and straight forward.
|
10
|
+
|
11
|
+
To initialize:
|
12
|
+
|
13
|
+
require 'salesforce_bulk'
|
14
|
+
salesforce = SalesforceBulk::Api.new("YOUR_SALESFORCE_USERNAME", "YOUR_SALESFORCE_PASSWORD")
|
15
|
+
|
16
|
+
Sample operations:
|
17
|
+
|
18
|
+
# Insert/Create
|
19
|
+
new_account = Hash["name" => "Test Account", "type" => "Other"] # Add as many fields per record as needed.
|
20
|
+
records_to_insert = Array.new
|
21
|
+
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.
|
22
|
+
result = salesforce.create("Account", records_to_insert)
|
23
|
+
puts "result is: #{result.inspect}"
|
24
|
+
|
25
|
+
# Update
|
26
|
+
updated_account = Hash["name" => "Test Account -- Updated", id => "a00A0001009zA2m"] # Nearly identical to an insert, but we need to pass the salesforce id.
|
27
|
+
records_to_update = Array.new
|
28
|
+
records_to_update.push(updated_account)
|
29
|
+
salesforce.update("Account", records_to_update)
|
30
|
+
|
31
|
+
# Upsert
|
32
|
+
upserted_account = Hash["name" => "Test Account -- Upserted", "External_Field_Name" => "123456"] # Fields to be updated. External field must be included
|
33
|
+
records_to_upsert = Array.new
|
34
|
+
records_to_upsert.push(upserted_account)
|
35
|
+
salesforce.upsert("Account", records_to_upsert, "External_Field_Name") # Note that upsert accepts an extra parameter for the external field name
|
36
|
+
|
37
|
+
# Delete
|
38
|
+
deleted_account = Hash["id" => "a00A0001009zA2m"] # We only specify the id of the records to delete
|
39
|
+
records_to_delete = Array.new
|
40
|
+
records_to_delete.push(deleted_account)
|
41
|
+
salesforce.delete("Account", records_to_delete)
|
42
|
+
|
43
|
+
# Query
|
44
|
+
res = salesforce.query("Account", "select id, name, createddate from Account limit 3") # We just need to pass the sobject name and the query string
|
45
|
+
|
46
|
+
==Installation
|
47
|
+
[sudo] gem install salesforce_bulk
|
48
|
+
|
49
|
+
== Copyright
|
50
|
+
|
51
|
+
Copyright (c) 2011 Jorge Valdivia.
|
52
|
+
|
53
|
+
===end
|
54
|
+
|
data/lib/salesforce_bulk.rb
CHANGED
@@ -0,0 +1,90 @@
|
|
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)
|
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
|
+
|
20
|
+
login()
|
21
|
+
end
|
22
|
+
|
23
|
+
#private
|
24
|
+
|
25
|
+
def login()
|
26
|
+
|
27
|
+
xml = '<?xml version="1.0" encoding="utf-8" ?>'
|
28
|
+
xml += "<env:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
|
29
|
+
xml += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
|
30
|
+
xml += " xmlns:env=\"http://schemas.xmlsoap.org/soap/envelope/\">"
|
31
|
+
xml += " <env:Body>"
|
32
|
+
xml += " <n1:login xmlns:n1=\"urn:partner.soap.sforce.com\">"
|
33
|
+
xml += " <n1:username>#{@username}</n1:username>"
|
34
|
+
xml += " <n1:password>#{@password}</n1:password>"
|
35
|
+
xml += " </n1:login>"
|
36
|
+
xml += " </env:Body>"
|
37
|
+
xml += "</env:Envelope>"
|
38
|
+
|
39
|
+
headers = Hash['Content-Type' => 'text/xml; charset=utf-8', 'SOAPAction' => 'login']
|
40
|
+
|
41
|
+
response = post_xml(@@LOGIN_HOST, @@LOGIN_PATH, xml, headers)
|
42
|
+
response_parsed = XmlSimple.xml_in(response)
|
43
|
+
|
44
|
+
@session_id = response_parsed['Body'][0]['loginResponse'][0]['result'][0]['sessionId'][0]
|
45
|
+
@server_url = response_parsed['Body'][0]['loginResponse'][0]['result'][0]['serverUrl'][0]
|
46
|
+
@instance = parse_instance()
|
47
|
+
|
48
|
+
@@INSTANCE_HOST = "#{@instance}.salesforce.com"
|
49
|
+
end
|
50
|
+
|
51
|
+
def post_xml(host, path, xml, headers)
|
52
|
+
|
53
|
+
host = host || @@INSTANCE_HOST
|
54
|
+
|
55
|
+
if host != @@LOGIN_HOST # Not login, need to add session id to header
|
56
|
+
headers['X-SFDC-Session'] = @session_id;
|
57
|
+
#puts "session id is: #{@session_id} --- #{headers.inspect}\n"
|
58
|
+
path = "#{@@PATH_PREFIX}#{path}"
|
59
|
+
end
|
60
|
+
|
61
|
+
#puts "#{host} -- #{path} -- #{headers.inspect}\n"
|
62
|
+
|
63
|
+
http = Net::HTTP.new(host)
|
64
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
65
|
+
resp = http.post(path, xml, headers)
|
66
|
+
return resp.body
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_request(host, path, headers)
|
70
|
+
host = host || @@INSTANCE_HOST
|
71
|
+
path = "#{@@PATH_PREFIX}#{path}"
|
72
|
+
|
73
|
+
if host != @@LOGIN_HOST # Not login, need to add session id to header
|
74
|
+
headers['X-SFDC-Session'] = @session_id;
|
75
|
+
end
|
76
|
+
|
77
|
+
http = Net::HTTP.new(host)
|
78
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
79
|
+
resp = http.get(path, headers)
|
80
|
+
return resp.body
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse_instance()
|
84
|
+
@server_url =~ /https:\/\/([a-z]{2,2}[0-9]{1,1})-api/
|
85
|
+
@instance = $~.captures[0]
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module SalesforceBulk
|
2
|
+
|
3
|
+
class Job
|
4
|
+
|
5
|
+
def initialize(operation, sobject, records, external_field, connection)
|
6
|
+
|
7
|
+
@@operation = operation
|
8
|
+
@@sobject = sobject
|
9
|
+
@@external_field = external_field
|
10
|
+
@@records = records
|
11
|
+
@@connection = connection
|
12
|
+
@@XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_job()
|
17
|
+
xml = "#{@@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
|
18
|
+
xml += "<operation>#{@@operation}</operation>"
|
19
|
+
xml += "<object>#{@@sobject}</object>"
|
20
|
+
if !@@external_field.nil? # This only happens on upsert
|
21
|
+
xml += "<externalIdFieldName>#{@@external_field}</externalIdFieldName>"
|
22
|
+
end
|
23
|
+
xml += "<contentType>CSV</contentType>"
|
24
|
+
xml += "</jobInfo>"
|
25
|
+
|
26
|
+
path = "job"
|
27
|
+
headers = Hash['Content-Type' => 'application/xml; charset=utf-8']
|
28
|
+
|
29
|
+
response = @@connection.post_xml(nil, path, xml, headers)
|
30
|
+
response_parsed = XmlSimple.xml_in(response)
|
31
|
+
|
32
|
+
@@job_id = response_parsed['id'][0]
|
33
|
+
end
|
34
|
+
|
35
|
+
def close_job()
|
36
|
+
xml = "#{@@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
|
37
|
+
xml += "state>Closed</state>"
|
38
|
+
xml += "</jobInfo>"
|
39
|
+
|
40
|
+
path = "job/#{@@job_id}"
|
41
|
+
headers = Hash['Content-Type' => 'application/xml; charset=utf-8']
|
42
|
+
|
43
|
+
response = @@connection.post_xml(nil, path, xml, headers)
|
44
|
+
response_parsed = XmlSimple.xml_in(response)
|
45
|
+
|
46
|
+
#job_id = response_parsed['id'][0]
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_query
|
50
|
+
path = "job/#{@@job_id}/batch/"
|
51
|
+
headers = Hash["Content-Type" => "text/csv; charset=UTF-8"]
|
52
|
+
|
53
|
+
response = @@connection.post_xml(nil, path, @@records, headers)
|
54
|
+
response_parsed = XmlSimple.xml_in(response)
|
55
|
+
|
56
|
+
@@batch_id = response_parsed['id'][0]
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_batch()
|
60
|
+
keys = @@records.reduce({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h}.keys
|
61
|
+
headers = keys.to_csv
|
62
|
+
|
63
|
+
output_csv = headers
|
64
|
+
|
65
|
+
@@records.each do |r|
|
66
|
+
fields = Array.new
|
67
|
+
keys.each do |k|
|
68
|
+
fields.push(r[k])
|
69
|
+
end
|
70
|
+
|
71
|
+
row_csv = fields.to_csv
|
72
|
+
output_csv += row_csv
|
73
|
+
end
|
74
|
+
|
75
|
+
path = "job/#{@@job_id}/batch/"
|
76
|
+
headers = Hash["Content-Type" => "text/csv; charset=UTF-8"]
|
77
|
+
|
78
|
+
response = @@connection.post_xml(nil, path, output_csv, headers)
|
79
|
+
response_parsed = XmlSimple.xml_in(response)
|
80
|
+
|
81
|
+
@@batch_id = response_parsed['id'][0]
|
82
|
+
end
|
83
|
+
|
84
|
+
def check_batch_status()
|
85
|
+
path = "job/#{@@job_id}/batch/#{@@batch_id}"
|
86
|
+
headers = Hash.new
|
87
|
+
|
88
|
+
response = @@connection.get_request(nil, path, headers)
|
89
|
+
response_parsed = XmlSimple.xml_in(response)
|
90
|
+
|
91
|
+
begin
|
92
|
+
#puts "check: #{response_parsed.inspect}\n"
|
93
|
+
response_parsed['state'][0]
|
94
|
+
rescue Exception => e
|
95
|
+
#puts "check: #{response_parsed.inspect}\n"
|
96
|
+
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_batch_result()
|
102
|
+
path = "job/#{@@job_id}/batch/#{@@batch_id}/result"
|
103
|
+
headers = Hash["Content-Type" => "text/xml; charset=UTF-8"]
|
104
|
+
|
105
|
+
response = @@connection.get_request(nil, path, headers)
|
106
|
+
|
107
|
+
if(@@operation == "query") # The query op requires us to do another request to get the results
|
108
|
+
response_parsed = XmlSimple.xml_in(response)
|
109
|
+
result_id = response_parsed["result"][0]
|
110
|
+
|
111
|
+
path = "job/#{@@job_id}/batch/#{@@batch_id}/result/#{result_id}"
|
112
|
+
headers = Hash.new
|
113
|
+
headers = Hash["Content-Type" => "text/xml; charset=UTF-8"]
|
114
|
+
#puts "path is: #{path}\n"
|
115
|
+
|
116
|
+
response = @@connection.get_request(nil, path, headers)
|
117
|
+
#puts "\n\nres2: #{response.inspect}\n\n"
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
response = response.lines.to_a[1..-1].join
|
122
|
+
csvRows = CSV.parse(response)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
data/salesforce_bulk.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = SalesforceBulk::VERSION
|
8
8
|
s.authors = ["Jorge Valdivia"]
|
9
9
|
s.email = ["jorge@valdivia.me"]
|
10
|
-
s.homepage = ""
|
10
|
+
s.homepage = "https://github.com/jorgevaldivia/salesforce_bulk"
|
11
11
|
s.summary = %q{Ruby support for the Salesforce Bulk API}
|
12
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
13
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: salesforce_bulk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -21,11 +21,14 @@ extra_rdoc_files: []
|
|
21
21
|
files:
|
22
22
|
- .gitignore
|
23
23
|
- Gemfile
|
24
|
+
- README.rdoc
|
24
25
|
- Rakefile
|
25
26
|
- lib/salesforce_bulk.rb
|
27
|
+
- lib/salesforce_bulk/connection.rb
|
28
|
+
- lib/salesforce_bulk/job.rb
|
26
29
|
- lib/salesforce_bulk/version.rb
|
27
30
|
- salesforce_bulk.gemspec
|
28
|
-
homepage:
|
31
|
+
homepage: https://github.com/jorgevaldivia/salesforce_bulk
|
29
32
|
licenses: []
|
30
33
|
post_install_message:
|
31
34
|
rdoc_options: []
|