salesforce_bulk_oauth2 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +72 -0
- data/lib/salesforce_bulk/connection.rb +70 -0
- data/lib/salesforce_bulk/job.rb +125 -0
- data/lib/salesforce_bulk/version.rb +3 -0
- data/lib/salesforce_bulk_oauth2.rb +78 -0
- metadata +83 -0
data/README.rdoc
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
= salesforce-bulk
|
2
|
+
|
3
|
+
==Overview
|
4
|
+
|
5
|
+
Salesforce Bulk Oauth2 is an extension of jorgevaldivia's Salesforce Bulk ruby gem for connecting to and using the Salesforce Bulk API (http://www.salesforce.com/us/developer/docs/api_asynch/index.htm) via Oauth2 service instead of username/password.
|
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(:token=>"YOUR_SALESFORCE_TOKEN", :instance_url=>"YOUR_SALESFORCE_INSTANCE_URL")
|
19
|
+
|
20
|
+
Sample operations:
|
21
|
+
|
22
|
+
Same as mentioned on (https://github.com/jorgevaldivia/salesforce_bulk).
|
23
|
+
|
24
|
+
# Insert/Create
|
25
|
+
new_account = Hash["name" => "Test Account", "type" => "Other"] # Add as many fields per record as needed.
|
26
|
+
records_to_insert = Array.new
|
27
|
+
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.
|
28
|
+
result = salesforce.create("Account", records_to_insert)
|
29
|
+
puts "result is: #{result.inspect}"
|
30
|
+
|
31
|
+
# Update
|
32
|
+
updated_account = Hash["name" => "Test Account -- Updated", "id" => "a00A0001009zA2m"] # Nearly identical to an insert, but we need to pass the salesforce id.
|
33
|
+
records_to_update = Array.new
|
34
|
+
records_to_update.push(updated_account)
|
35
|
+
salesforce.update("Account", records_to_update)
|
36
|
+
|
37
|
+
# Upsert
|
38
|
+
upserted_account = Hash["name" => "Test Account -- Upserted", "External_Field_Name" => "123456"] # Fields to be updated. External field must be included
|
39
|
+
records_to_upsert = Array.new
|
40
|
+
records_to_upsert.push(upserted_account)
|
41
|
+
salesforce.upsert("Account", records_to_upsert, "External_Field_Name") # Note that upsert accepts an extra parameter for the external field name
|
42
|
+
|
43
|
+
OR
|
44
|
+
|
45
|
+
salesforce.upsert("Account", records_to_upsert, "External_Field_Name", true) # last parameter indicates whether to wait until the batch finishes
|
46
|
+
|
47
|
+
# Delete
|
48
|
+
deleted_account = Hash["id" => "a00A0001009zA2m"] # We only specify the id of the records to delete
|
49
|
+
records_to_delete = Array.new
|
50
|
+
records_to_delete.push(deleted_account)
|
51
|
+
salesforce.delete("Account", records_to_delete)
|
52
|
+
|
53
|
+
# Query
|
54
|
+
res = salesforce.query("Account", "select id, name, createddate from Account limit 3") # We just need to pass the sobject name and the query string
|
55
|
+
puts res.result.records.inspect
|
56
|
+
|
57
|
+
Result reporting:
|
58
|
+
|
59
|
+
array of true/false depending on wether record was pushed
|
60
|
+
|
61
|
+
example: if you push an array having 10 records. Result maybe
|
62
|
+
result = [true,true,true,true,false,true,false,true,true,true]
|
63
|
+
|
64
|
+
|
65
|
+
Thanks to jorgevaldivia for Salesforce Bulk gem
|
66
|
+
|
67
|
+
== Copyright
|
68
|
+
|
69
|
+
Copyright (c) 2012 Bhushan Lodha.
|
70
|
+
|
71
|
+
===end
|
72
|
+
|
@@ -0,0 +1,70 @@
|
|
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(api_version,client)
|
11
|
+
#@username = username
|
12
|
+
@client=client
|
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
|
+
|
28
|
+
@session_id=@client.oauth_token
|
29
|
+
|
30
|
+
@server_url=@client.instance_url
|
31
|
+
@instance = parse_instance()
|
32
|
+
@@INSTANCE_HOST = "#{@instance}.salesforce.com"
|
33
|
+
end
|
34
|
+
|
35
|
+
def post_xml(host, path, xml, headers)
|
36
|
+
host = host || @@INSTANCE_HOST
|
37
|
+
if host != @@LOGIN_HOST # Not login, need to add session id to header
|
38
|
+
headers['X-SFDC-Session'] = @session_id;
|
39
|
+
path = "#{@@PATH_PREFIX}#{path}"
|
40
|
+
end
|
41
|
+
https(host).post(path, xml, headers).body
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_request(host, path, headers)
|
45
|
+
host = host || @@INSTANCE_HOST
|
46
|
+
path = "#{@@PATH_PREFIX}#{path}"
|
47
|
+
|
48
|
+
if host != @@LOGIN_HOST # Not login, need to add session id to header
|
49
|
+
headers['X-SFDC-Session'] = @session_id;
|
50
|
+
end
|
51
|
+
|
52
|
+
https(host).get(path, headers).body
|
53
|
+
end
|
54
|
+
|
55
|
+
def https(host)
|
56
|
+
req = Net::HTTP.new(host, 443)
|
57
|
+
req.use_ssl = true
|
58
|
+
req.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
59
|
+
req
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_instance()
|
63
|
+
@instance=@server_url.match(/https:\/\/[a-z]{2}[0-9]{1,2}/).to_s.gsub("https://","")
|
64
|
+
#@server_url =~ /https:\/\/[a-z]{2}[0-9]{1,2}/
|
65
|
+
#@instance = "ap1"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,125 @@
|
|
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>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
|
+
@@job_id = response_parsed['id'][0]
|
32
|
+
end
|
33
|
+
|
34
|
+
def close_job()
|
35
|
+
xml = "#{@@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
|
36
|
+
xml += "<state>Closed</state>"
|
37
|
+
xml += "</jobInfo>"
|
38
|
+
|
39
|
+
path = "job/#{@@job_id}"
|
40
|
+
headers = Hash['Content-Type' => 'application/xml; charset=utf-8']
|
41
|
+
|
42
|
+
response = @@connection.post_xml(nil, path, xml, headers)
|
43
|
+
response_parsed = XmlSimple.xml_in(response)
|
44
|
+
|
45
|
+
#job_id = response_parsed['id'][0]
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_query
|
49
|
+
path = "job/#{@@job_id}/batch/"
|
50
|
+
headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
|
51
|
+
response = @@connection.post_xml(nil, path, @@records, headers)
|
52
|
+
response_parsed = XmlSimple.xml_in(response)
|
53
|
+
@@batch_id = response_parsed['id'][0]
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_batch()
|
57
|
+
keys = @@records.inject({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h}.keys ## Target here
|
58
|
+
headers = keys
|
59
|
+
@@records_dup=@@records.clone
|
60
|
+
super_records=[]
|
61
|
+
(@@records_dup.size/10000).times do
|
62
|
+
super_records<<@@records_dup.pop(10000)
|
63
|
+
end
|
64
|
+
super_records<<@@records_dup
|
65
|
+
|
66
|
+
super_records.each do|batch|
|
67
|
+
xml = "#{@@XML_HEADER}<sObjects xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
|
68
|
+
batch.each do |r|
|
69
|
+
xml += "<sObject>"
|
70
|
+
keys.each do |k|
|
71
|
+
xml += "<#{k}>#{r[k]}</#{k}>"
|
72
|
+
end
|
73
|
+
xml += "</sObject>"
|
74
|
+
end
|
75
|
+
xml += "</sObjects>"
|
76
|
+
|
77
|
+
|
78
|
+
path = "job/#{@@job_id}/batch/"
|
79
|
+
headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
|
80
|
+
response = @@connection.post_xml(nil, path, xml, headers)
|
81
|
+
response_parsed = XmlSimple.xml_in(response)
|
82
|
+
@@batch_id = response_parsed['id'][0]
|
83
|
+
puts @@batch_id
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_batch_status()
|
88
|
+
headers = Hash.new
|
89
|
+
response = @@connection.get_request(nil, path, headers)
|
90
|
+
response_parsed = XmlSimple.xml_in(response)
|
91
|
+
puts response_parsed
|
92
|
+
begin
|
93
|
+
#puts "check: #{response_parsed.inspect}\n"
|
94
|
+
response_parsed['state'][0]
|
95
|
+
rescue Exception => e
|
96
|
+
#puts "check: #{response_parsed.inspect}\n"
|
97
|
+
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_batch_result()
|
103
|
+
path = "job/#{@@job_id}/batch/#{@@batch_id}/result"
|
104
|
+
headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
|
105
|
+
|
106
|
+
response = @@connection.get_request(nil, path, headers)
|
107
|
+
|
108
|
+
if(@@operation == "query") # The query op requires us to do another request to get the results
|
109
|
+
response_parsed = XmlSimple.xml_in(response)
|
110
|
+
result_id = response_parsed["result"][0]
|
111
|
+
|
112
|
+
path = "job/#{@@job_id}/batch/#{@@batch_id}/result/#{result_id}"
|
113
|
+
headers = Hash.new
|
114
|
+
headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
|
115
|
+
response = @@connection.get_request(nil, path, headers)
|
116
|
+
end
|
117
|
+
titles = []
|
118
|
+
doc = REXML::Document.new(response)
|
119
|
+
doc.elements.each('results/result/success') do |ele|
|
120
|
+
titles << ele.text
|
121
|
+
end
|
122
|
+
return titles
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'xmlsimple'
|
4
|
+
require 'csv'
|
5
|
+
require "#{File.dirname(File.expand_path(__FILE__))}/salesforce_bulk/version"
|
6
|
+
require "#{File.dirname(File.expand_path(__FILE__))}/salesforce_bulk/job"
|
7
|
+
require "#{File.dirname(File.expand_path(__FILE__))}/salesforce_bulk/connection"
|
8
|
+
require 'databasedotcom'
|
9
|
+
|
10
|
+
module SalesforceBulk
|
11
|
+
class Api
|
12
|
+
|
13
|
+
@@SALESFORCE_API_VERSION = '23.0'
|
14
|
+
|
15
|
+
def initialize(options)
|
16
|
+
client = databasedotcom_client(options[:token], options[:instance_url])
|
17
|
+
@connection = SalesforceBulk::Connection.new(@@SALESFORCE_API_VERSION,client)
|
18
|
+
end
|
19
|
+
|
20
|
+
def databasedotcom_client(token, instance_url)
|
21
|
+
client = Databasedotcom::Client.new("#{Rails.root}/config/databasedotcom.yml")
|
22
|
+
client.authenticate :token => token, :instance_url => instance_url
|
23
|
+
client
|
24
|
+
end
|
25
|
+
|
26
|
+
def upsert(sobject, records, external_field)
|
27
|
+
self.do_operation('upsert', sobject, records, external_field)
|
28
|
+
end
|
29
|
+
|
30
|
+
def update(sobject, records)
|
31
|
+
self.do_operation('update', sobject, records, nil)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(sobject, records)
|
35
|
+
self.do_operation('insert', sobject, records, nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete(sobject, records)
|
39
|
+
self.do_operation('delete', sobject, records, nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
def query(sobject, query)
|
43
|
+
self.do_operation('query', sobject, query, nil)
|
44
|
+
end
|
45
|
+
|
46
|
+
#private
|
47
|
+
|
48
|
+
def do_operation(operation, sobject, records, external_field)
|
49
|
+
job = SalesforceBulk::Job.new(operation, sobject, records, external_field, @connection)
|
50
|
+
|
51
|
+
# TODO: put this in one function
|
52
|
+
job_id = job.create_job()
|
53
|
+
if(operation == "query")
|
54
|
+
batch_id = job.add_query()
|
55
|
+
else
|
56
|
+
batch_id = job.add_batch()
|
57
|
+
end
|
58
|
+
job.close_job()
|
59
|
+
|
60
|
+
while true
|
61
|
+
state = job.check_batch_status()
|
62
|
+
#puts "\nstate is #{state}\n"
|
63
|
+
if state != "Queued" && state != "InProgress"
|
64
|
+
break
|
65
|
+
end
|
66
|
+
sleep(2) # wait x seconds and check again
|
67
|
+
end
|
68
|
+
|
69
|
+
if state == 'Completed'
|
70
|
+
return job.get_batch_result()
|
71
|
+
else
|
72
|
+
return ["error"]
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end # End class
|
78
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: salesforce_bulk_oauth2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bhushan Lodha
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-31 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
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: databasedotcom
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: This gem provides a super simple interface for the Salesforce Bulk API
|
47
|
+
through Oauth2. It provides support for insert, update, upsert, delete, and query.
|
48
|
+
email:
|
49
|
+
- bhushanlodha@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- README.rdoc
|
55
|
+
- lib/salesforce_bulk/connection.rb
|
56
|
+
- lib/salesforce_bulk/job.rb
|
57
|
+
- lib/salesforce_bulk/version.rb
|
58
|
+
- lib/salesforce_bulk_oauth2.rb
|
59
|
+
homepage: https://github.com/bhushan/salesforce_bulk_oauth2
|
60
|
+
licenses: []
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.8.24
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Ruby support for the Salesforce Bulk API using Oauth2
|
83
|
+
test_files: []
|