salesforce_bulk_api 0.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 +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +64 -0
- data/Rakefile +1 -0
- data/lib/salesforce_bulk_api/connection.rb +64 -0
- data/lib/salesforce_bulk_api/job.rb +129 -0
- data/lib/salesforce_bulk_api/version.rb +3 -0
- data/lib/salesforce_bulk_api.rb +73 -0
- data/salesforce_bulk_api.gemspec +27 -0
- metadata +89 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
= Salesforce-Bulk-Api
|
2
|
+
|
3
|
+
==Overview
|
4
|
+
|
5
|
+
Salesforce bulk Api is a simple ruby gem for connecting to and using the Salesforce Bulk API. It is actually a re-written code from salesforce_bulk[https://github.com/jorgevaldivia/salesforce_bulk].Written to suit many more other features as well.
|
6
|
+
|
7
|
+
==How to use
|
8
|
+
|
9
|
+
Using this gem is simple and straight forward.
|
10
|
+
|
11
|
+
To initialize:
|
12
|
+
|
13
|
+
Pl do check the entire documentation of the databasedotcom gem and it various ways of authentication
|
14
|
+
Databasedotcom[https://github.com/heroku/databasedotcom]
|
15
|
+
|
16
|
+
You can use username password combo,OmniAuth,Oauth2
|
17
|
+
|
18
|
+
require 'salesforce_bulk_api'
|
19
|
+
client = Databasedotcom::Client.new :client_id => $SFDC_APP_CONFIG["client_id"], :client_secret => $SFDC_APP_CONFIG["client_secret"] #client_id and client_secret respectively
|
20
|
+
client.authenticate :token => "my-oauth-token", :instance_url => "http://na1.salesforce.com" #=> "my-oauth-token"
|
21
|
+
salesforce = SalesforceBulk::Api.new(client)
|
22
|
+
|
23
|
+
Sample operations:
|
24
|
+
|
25
|
+
# Insert/Create
|
26
|
+
new_account = Hash["name" => "Test Account", "type" => "Other"] # Add as many fields per record as needed.
|
27
|
+
records_to_insert = Array.new
|
28
|
+
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.
|
29
|
+
result = salesforce.create("Account", records_to_insert)
|
30
|
+
puts "result is: #{result.inspect}"
|
31
|
+
|
32
|
+
# Update
|
33
|
+
updated_account = Hash["name" => "Test Account -- Updated", id => "a00A0001009zA2m"] # Nearly identical to an insert, but we need to pass the salesforce id.
|
34
|
+
records_to_update = Array.new
|
35
|
+
records_to_update.push(updated_account)
|
36
|
+
salesforce.update("Account", records_to_update)
|
37
|
+
|
38
|
+
# Upsert
|
39
|
+
upserted_account = Hash["name" => "Test Account -- Upserted", "External_Field_Name" => "123456"] # Fields to be updated. External field must be included
|
40
|
+
records_to_upsert = Array.new
|
41
|
+
records_to_upsert.push(upserted_account)
|
42
|
+
salesforce.upsert("Account", records_to_upsert, "External_Field_Name") # Note that upsert accepts an extra parameter for the external field name
|
43
|
+
|
44
|
+
# Delete
|
45
|
+
deleted_account = Hash["id" => "a00A0001009zA2m"] # We only specify the id of the records to delete
|
46
|
+
records_to_delete = Array.new
|
47
|
+
records_to_delete.push(deleted_account)
|
48
|
+
salesforce.delete("Account", records_to_delete)
|
49
|
+
|
50
|
+
# Query
|
51
|
+
res = salesforce.query("Account", "select id, name, createddate from Account limit 3") # We just need to pass the sobject name and the query string
|
52
|
+
|
53
|
+
==Installation
|
54
|
+
sudo gem install salesforce_bulk_api
|
55
|
+
|
56
|
+
==TODO
|
57
|
+
This is a rough early version of the gem. Immediate plans include better error reporting as there currently is none.
|
58
|
+
|
59
|
+
== Copyright
|
60
|
+
|
61
|
+
Copyright (c) 2012 Yatish Mehta
|
62
|
+
|
63
|
+
===end
|
64
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module SalesforceBulkApi
|
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(api_version,client)
|
11
|
+
@client=client
|
12
|
+
@session_id = nil
|
13
|
+
@server_url = nil
|
14
|
+
@instance = nil
|
15
|
+
@@API_VERSION = api_version
|
16
|
+
@@LOGIN_PATH = "/services/Soap/u/#{@@API_VERSION}"
|
17
|
+
@@PATH_PREFIX = "/services/async/#{@@API_VERSION}/"
|
18
|
+
|
19
|
+
login()
|
20
|
+
end
|
21
|
+
|
22
|
+
#private
|
23
|
+
|
24
|
+
def login()
|
25
|
+
@session_id=@client.oauth_token
|
26
|
+
@server_url=@client.instance_url
|
27
|
+
@instance = parse_instance()
|
28
|
+
puts @instance
|
29
|
+
@@INSTANCE_HOST = "#{@instance}.salesforce.com"
|
30
|
+
puts @@INSTANCE_HOST
|
31
|
+
end
|
32
|
+
|
33
|
+
def post_xml(host, path, xml, headers)
|
34
|
+
host = host || @@INSTANCE_HOST
|
35
|
+
if host != @@LOGIN_HOST # Not login, need to add session id to header
|
36
|
+
headers['X-SFDC-Session'] = @session_id;
|
37
|
+
path = "#{@@PATH_PREFIX}#{path}"
|
38
|
+
end
|
39
|
+
https(host).post(path, xml, headers).body
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_request(host, path, headers)
|
43
|
+
host = host || @@INSTANCE_HOST
|
44
|
+
path = "#{@@PATH_PREFIX}#{path}"
|
45
|
+
if host != @@LOGIN_HOST # Not login, need to add session id to header
|
46
|
+
headers['X-SFDC-Session'] = @session_id;
|
47
|
+
end
|
48
|
+
https(host).get(path, headers).body
|
49
|
+
end
|
50
|
+
|
51
|
+
def https(host)
|
52
|
+
req = Net::HTTP.new(host, 443)
|
53
|
+
req.use_ssl = true
|
54
|
+
req.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
55
|
+
req
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_instance()
|
59
|
+
@instance=@server_url.match(/https:\/\/[a-z]{2}[0-9]{1,2}/).to_s.gsub("https://","")
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module SalesforceBulkApi
|
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>XML</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
|
+
puts response
|
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" => "application/xml; 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
|
62
|
+
@@records_dup=@@records.clone
|
63
|
+
super_records=[]
|
64
|
+
(@@records_dup.size/10000).times do
|
65
|
+
super_records<<@@records_dup.pop(10000)
|
66
|
+
end
|
67
|
+
super_records<<@@records_dup
|
68
|
+
|
69
|
+
super_records.each do|batch|
|
70
|
+
xml = "#{@@XML_HEADER}<sObjects xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
|
71
|
+
batch.each do |r|
|
72
|
+
xml += "<sObject>"
|
73
|
+
keys.each do |k|
|
74
|
+
xml += "<#{k}>#{r[k]}</#{k}>"
|
75
|
+
end
|
76
|
+
xml += "</sObject>"
|
77
|
+
end
|
78
|
+
xml += "</sObjects>"
|
79
|
+
|
80
|
+
|
81
|
+
path = "job/#{@@job_id}/batch/"
|
82
|
+
headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
|
83
|
+
response = @@connection.post_xml(nil, path, xml, headers)
|
84
|
+
response_parsed = XmlSimple.xml_in(response)
|
85
|
+
|
86
|
+
@@batch_id = response_parsed['id'][0]
|
87
|
+
puts @@batch_id
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def check_batch_status()
|
92
|
+
path = "job/#{@@job_id}/batch/#{@@batch_id}"
|
93
|
+
headers = Hash.new
|
94
|
+
|
95
|
+
response = @@connection.get_request(nil, path, headers)
|
96
|
+
response_parsed = XmlSimple.xml_in(response)
|
97
|
+
|
98
|
+
puts response_parsed
|
99
|
+
begin
|
100
|
+
#puts "check: #{response_parsed.inspect}\n"
|
101
|
+
response_parsed['state'][0]
|
102
|
+
rescue Exception => e
|
103
|
+
#puts "check: #{response_parsed.inspect}\n"
|
104
|
+
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_batch_result()
|
110
|
+
path = "job/#{@@job_id}/batch/#{@@batch_id}/result"
|
111
|
+
headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
|
112
|
+
|
113
|
+
response = @@connection.get_request(nil, path, headers)
|
114
|
+
|
115
|
+
if(@@operation == "query") # The query op requires us to do another request to get the results
|
116
|
+
response_parsed = XmlSimple.xml_in(response)
|
117
|
+
result_id = response_parsed["result"][0]
|
118
|
+
|
119
|
+
path = "job/#{@@job_id}/batch/#{@@batch_id}/result/#{result_id}"
|
120
|
+
headers = Hash.new
|
121
|
+
headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
|
122
|
+
response = @@connection.get_request(nil, path, headers)
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "salesforce_bulk_api/version"
|
2
|
+
require 'net/https'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'xmlsimple'
|
5
|
+
require 'csv'
|
6
|
+
require "salesforce_bulk_api/version"
|
7
|
+
require 'salesforce_bulk_api/job'
|
8
|
+
require 'salesforce_bulk_api/connection'
|
9
|
+
module SalesforceBulkApi
|
10
|
+
# Your code goes here...
|
11
|
+
class Api
|
12
|
+
|
13
|
+
@@SALESFORCE_API_VERSION = '23.0'
|
14
|
+
|
15
|
+
def initialize(client)
|
16
|
+
@connection = SalesforceBulk::Connection.new(@@SALESFORCE_API_VERSION,client)
|
17
|
+
end
|
18
|
+
|
19
|
+
def upsert(sobject, records, external_field)
|
20
|
+
self.do_operation('upsert', sobject, records, external_field)
|
21
|
+
end
|
22
|
+
|
23
|
+
def update(sobject, records)
|
24
|
+
self.do_operation('update', sobject, records, nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create(sobject, records)
|
28
|
+
self.do_operation('insert', sobject, records, nil)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(sobject, records)
|
32
|
+
self.do_operation('delete', sobject, records, nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
def query(sobject, query)
|
36
|
+
self.do_operation('query', sobject, query, nil)
|
37
|
+
end
|
38
|
+
|
39
|
+
#private
|
40
|
+
|
41
|
+
def do_operation(operation, sobject, records, external_field)
|
42
|
+
job = SalesforceBulk::Job.new(operation, sobject, records, external_field, @connection)
|
43
|
+
|
44
|
+
# TODO: put this in one function
|
45
|
+
job_id = job.create_job()
|
46
|
+
if(operation == "query")
|
47
|
+
batch_id = job.add_query()
|
48
|
+
else
|
49
|
+
batch_id = job.add_batch()
|
50
|
+
end
|
51
|
+
job.close_job()
|
52
|
+
|
53
|
+
while true
|
54
|
+
state = job.check_batch_status()
|
55
|
+
#puts "\nstate is #{state}\n"
|
56
|
+
if state != "Queued" && state != "InProgress"
|
57
|
+
break
|
58
|
+
end
|
59
|
+
sleep(2) # wait x seconds and check again
|
60
|
+
end
|
61
|
+
|
62
|
+
if state == 'Completed'
|
63
|
+
job.get_batch_result()
|
64
|
+
else
|
65
|
+
return "error"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end # End class
|
71
|
+
end
|
72
|
+
|
73
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "salesforce_bulk_api/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "salesforce_bulk_api"
|
7
|
+
s.version = SalesforceBulkApi::VERSION
|
8
|
+
s.authors = ["Yatish Mehta"]
|
9
|
+
s.email = ["yatishmehta27@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{It uses the bulk api of salesforce to communicate with teh salesforce CRM}
|
12
|
+
s.description = %q{Salesforce Bulk API with governor limits taken care of}
|
13
|
+
|
14
|
+
s.rubyforge_project = "salesforce_bulk_api"
|
15
|
+
s.add_dependency(%q<oauth2>, ["0.4.1"])
|
16
|
+
s.add_dependency(%q<databasedotcom>, [">= 0"])
|
17
|
+
s.add_dependency(%q<json>, [">= 0"])
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
|
24
|
+
# specify any dependencies here; for example:
|
25
|
+
# s.add_development_dependency "rspec"
|
26
|
+
# s.add_runtime_dependency "rest-client"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: salesforce_bulk_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Yatish Mehta
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-12 00:00:00.000000000 +05:30
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: oauth2
|
17
|
+
requirement: &21802940 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - =
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.4.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *21802940
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: databasedotcom
|
28
|
+
requirement: &21802320 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *21802320
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: json
|
39
|
+
requirement: &21801740 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *21801740
|
48
|
+
description: Salesforce Bulk API with governor limits taken care of
|
49
|
+
email:
|
50
|
+
- yatishmehta27@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- Gemfile
|
57
|
+
- README.rdoc
|
58
|
+
- Rakefile
|
59
|
+
- lib/salesforce_bulk_api.rb
|
60
|
+
- lib/salesforce_bulk_api/connection.rb
|
61
|
+
- lib/salesforce_bulk_api/job.rb
|
62
|
+
- lib/salesforce_bulk_api/version.rb
|
63
|
+
- salesforce_bulk_api.gemspec
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: ''
|
66
|
+
licenses: []
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project: salesforce_bulk_api
|
85
|
+
rubygems_version: 1.6.2
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: It uses the bulk api of salesforce to communicate with teh salesforce CRM
|
89
|
+
test_files: []
|