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