salesforce_bulk_oauth2 0.0.5
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 +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: []
|