salesforce_bulk 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|