salesforklift 0.0.8
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/Gemfile +9 -0
- data/README.md +40 -0
- data/Rakefile +31 -0
- data/lib/salesforklift.rb +7 -0
- data/lib/salesforklift/batch.rb +69 -0
- data/lib/salesforklift/batch_response.rb +18 -0
- data/lib/salesforklift/job.rb +81 -0
- data/lib/salesforklift/job_response.rb +17 -0
- data/lib/salesforklift/logging.rb +17 -0
- data/lib/salesforklift/login.rb +36 -0
- data/lib/salesforklift/login_response.rb +23 -0
- metadata +74 -0
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#salesforklift
|
|
2
|
+
|
|
3
|
+
## A Salesforce Bulk API Ruby Wrapper
|
|
4
|
+
|
|
5
|
+
Salesforce provides Bulk API to ease massive data synchronization from a data store to salesforce. This project provides a gem to use Salesforce Bulk API in a ruby app.
|
|
6
|
+
|
|
7
|
+
## How to build salesforklift gem
|
|
8
|
+
To build a gem, run ��gem build salesforklift.gemspec
|
|
9
|
+
|
|
10
|
+
## How to use salesforklift gem
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
require 'salesforklift'
|
|
14
|
+
|
|
15
|
+
# login
|
|
16
|
+
login = Salesforklift::Login.new
|
|
17
|
+
result = login.login_sforce(TEST_USER_NAME, TEST_USER_PW, TEST_LOGIN_URL)
|
|
18
|
+
|
|
19
|
+
# create a job to bulk insert Contact objects
|
|
20
|
+
job = Salesforklift::Job.new(result.server_instance,
|
|
21
|
+
result.session_id,
|
|
22
|
+
"Contact",
|
|
23
|
+
:insert)
|
|
24
|
+
job.create
|
|
25
|
+
puts job.sf_job_id
|
|
26
|
+
|
|
27
|
+
# create batches
|
|
28
|
+
batch = Salesforklift::Batch.new(result.server_instance, result.session_id, job.sf_job_id)
|
|
29
|
+
batch.create_from_file("spec/data/contact.csv")
|
|
30
|
+
|
|
31
|
+
# close job
|
|
32
|
+
job.close
|
|
33
|
+
|
|
34
|
+
# check result
|
|
35
|
+
batch_response = batch.query_status
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Copyright
|
|
40
|
+
See LICENSE.txt for details.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
begin
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require 'rspec/core/rake_task'
|
|
10
|
+
desc 'Default: run specs.'
|
|
11
|
+
task :default => :spec
|
|
12
|
+
|
|
13
|
+
desc "Run specs"
|
|
14
|
+
RSpec::Core::RakeTask.new do |t|
|
|
15
|
+
t.pattern = "./spec/unit/*_spec.rb" # exclude integration specs
|
|
16
|
+
# Put spec opts in a file named .rspec in root
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc "Run integration specs (make sure you have valid Salesforce login in spec_helper.rb)"
|
|
20
|
+
RSpec::Core::RakeTask.new(:integration) do |t|
|
|
21
|
+
t.pattern = "./spec/integration/*_spec.rb" # run integration specs
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require 'rdoc/task'
|
|
25
|
+
RDoc::Task.new do |rdoc|
|
|
26
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
27
|
+
rdoc.title = 'Salesforklift'
|
|
28
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
|
29
|
+
rdoc.rdoc_files.include('README.rdoc')
|
|
30
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
31
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'restclient'
|
|
2
|
+
|
|
3
|
+
module Salesforklift
|
|
4
|
+
|
|
5
|
+
class Batch
|
|
6
|
+
include Salesforklift::Logging
|
|
7
|
+
|
|
8
|
+
attr_accessor :server_instance, :session_id, :sf_job_id, :batch_id
|
|
9
|
+
|
|
10
|
+
def initialize(server_instance, session_id, sf_job_id)
|
|
11
|
+
@server_instance = server_instance
|
|
12
|
+
@session_id = session_id
|
|
13
|
+
@sf_job_id = sf_job_id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_from_file(data_file)
|
|
17
|
+
batch_content = ""
|
|
18
|
+
File.open(data_file, "r") do |infile|
|
|
19
|
+
while (line = infile.gets)
|
|
20
|
+
batch_content += line
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
logger.debug batch_content
|
|
25
|
+
create(batch_content)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create(batch_content)
|
|
29
|
+
response = RestClient.post("https://#{@server_instance}.salesforce.com/services/async/21.0/job/#{@sf_job_id}/batch",
|
|
30
|
+
batch_content.lstrip,
|
|
31
|
+
{"Content-Type" => "text/csv;charset=UTF-8", "X-SFDC-Session" => @session_id})
|
|
32
|
+
return false if (response.code/100 != 2)
|
|
33
|
+
response = BatchResponse.fromXML(response.body)
|
|
34
|
+
@batch_id = response.batch_id
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def query_status
|
|
38
|
+
http = Net::HTTP.new("#{@server_instance}.salesforce.com", 443)
|
|
39
|
+
http.use_ssl = true
|
|
40
|
+
|
|
41
|
+
response = http.get("/services/async/21.0/job/#{@sf_job_id}/batch/#{@batch_id}",
|
|
42
|
+
{"X-SFDC-Session" => @session_id})
|
|
43
|
+
|
|
44
|
+
return response.body if response.class != Net::HTTPOK
|
|
45
|
+
|
|
46
|
+
return BatchResponse.fromXML(response.body)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def query_result
|
|
50
|
+
http = Net::HTTP.new("#{@server_instance}.salesforce.com", 443)
|
|
51
|
+
http.use_ssl = true
|
|
52
|
+
|
|
53
|
+
response = http.get("/services/async/21.0/job/#{@sf_job_id}/batch/#{@batch_id}/result",
|
|
54
|
+
{"X-SFDC-Session" => @session_id})
|
|
55
|
+
return response.body if response.class != Net::HTTPOK
|
|
56
|
+
|
|
57
|
+
result = Array.new
|
|
58
|
+
response.body.each_line do |line|
|
|
59
|
+
one_record = line.split(",")
|
|
60
|
+
result << {:id => one_record[0], :success => one_record[1],
|
|
61
|
+
:created => one_record[2], :error => one_record[3]}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
result.drop(1) # drop header
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
|
|
3
|
+
module Salesforklift
|
|
4
|
+
|
|
5
|
+
class BatchResponse
|
|
6
|
+
attr_accessor :batch_id, :state
|
|
7
|
+
|
|
8
|
+
def self.fromXML(xml)
|
|
9
|
+
doc = REXML::Document.new(xml)
|
|
10
|
+
|
|
11
|
+
response = BatchResponse.new
|
|
12
|
+
response.batch_id = doc.elements['//id'].text
|
|
13
|
+
response.state = doc.elements['//state'].text
|
|
14
|
+
response
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
module Salesforklift
|
|
5
|
+
|
|
6
|
+
class Job
|
|
7
|
+
include Salesforklift::Logging
|
|
8
|
+
|
|
9
|
+
attr_accessor :server_instance, :session_id, :sf_object, :sf_action,
|
|
10
|
+
:sf_format, :sf_job_id, :external_id, :concurrency_mode
|
|
11
|
+
|
|
12
|
+
def initialize(server_instance, session_id, sf_object, sf_action,
|
|
13
|
+
external_id = nil, sf_format = :CSV, concurrency_mode = :Serial)
|
|
14
|
+
@server_instance = server_instance
|
|
15
|
+
@session_id = session_id
|
|
16
|
+
@sf_object = sf_object
|
|
17
|
+
@sf_action = sf_action
|
|
18
|
+
@sf_format = sf_format
|
|
19
|
+
@external_id = external_id
|
|
20
|
+
@concurrency_mode = concurrency_mode
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def generate_create_request
|
|
24
|
+
request = <<-eos
|
|
25
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
26
|
+
<jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
|
|
27
|
+
<operation>#{@sf_action}</operation>
|
|
28
|
+
<object>#{@sf_object}</object>
|
|
29
|
+
eos
|
|
30
|
+
|
|
31
|
+
if @external_id
|
|
32
|
+
request += <<-eos
|
|
33
|
+
<externalIdFieldName>#{@external_id}</externalIdFieldName>
|
|
34
|
+
eos
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
request += <<-eos
|
|
38
|
+
<concurrencyMode>#{@concurrency_mode.to_s}</concurrencyMode>
|
|
39
|
+
<contentType>#{@sf_format.to_s}</contentType>
|
|
40
|
+
</jobInfo>
|
|
41
|
+
eos
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def generate_close_request
|
|
45
|
+
request = <<-eos
|
|
46
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
47
|
+
<jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
|
|
48
|
+
<state>Closed</state>
|
|
49
|
+
</jobInfo>
|
|
50
|
+
eos
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Create a Salesforce Job
|
|
54
|
+
def create
|
|
55
|
+
http = Net::HTTP.new("#{@server_instance}.salesforce.com", 443)
|
|
56
|
+
http.use_ssl = true
|
|
57
|
+
|
|
58
|
+
job_request = generate_create_request.lstrip
|
|
59
|
+
logger.debug job_request
|
|
60
|
+
response = http.post('/services/async/21.0/job',
|
|
61
|
+
job_request,
|
|
62
|
+
{"Content-Type" => "application/xml;charset=UTF-8", "X-SFDC-Session" => @session_id})
|
|
63
|
+
|
|
64
|
+
return response.body if response.class != Net::HTTPCreated
|
|
65
|
+
|
|
66
|
+
job_response = JobResponse.fromXML(response.body)
|
|
67
|
+
@sf_job_id = job_response.job_id
|
|
68
|
+
job_response
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def close
|
|
72
|
+
http = Net::HTTP.new("#{@server_instance}.salesforce.com", 443)
|
|
73
|
+
http.use_ssl = true
|
|
74
|
+
|
|
75
|
+
response = http.post("/services/async/21.0/job/#{@sf_job_id}",
|
|
76
|
+
generate_close_request.lstrip,
|
|
77
|
+
{"Content-Type" => "application/xml;charset=UTF-8", "X-SFDC-Session" => @session_id})
|
|
78
|
+
return response.class == Net::HTTPOK
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
|
|
3
|
+
module Salesforklift
|
|
4
|
+
|
|
5
|
+
class JobResponse
|
|
6
|
+
attr_accessor :job_id
|
|
7
|
+
|
|
8
|
+
def self.fromXML(xml)
|
|
9
|
+
doc = REXML::Document.new(xml)
|
|
10
|
+
|
|
11
|
+
response = JobResponse.new
|
|
12
|
+
response.job_id = doc.elements['//id'].text
|
|
13
|
+
response
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
module Salesforklift
|
|
4
|
+
module Logging
|
|
5
|
+
|
|
6
|
+
@@defalut_logger = Logger.new(STDOUT)
|
|
7
|
+
@@defalut_logger.level = Logger::INFO
|
|
8
|
+
|
|
9
|
+
def logger
|
|
10
|
+
@logger ||= @@defalut_logger
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.logger=(value)
|
|
14
|
+
@@defalut_logger = value
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
module Salesforklift
|
|
5
|
+
class Login
|
|
6
|
+
include Salesforklift::Logging
|
|
7
|
+
|
|
8
|
+
def login_sforce(user, pass, url)
|
|
9
|
+
loginRequest = <<-eos
|
|
10
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
11
|
+
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
12
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
13
|
+
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
|
|
14
|
+
<env:Body>
|
|
15
|
+
<n1:login xmlns:n1="urn:partner.soap.sforce.com">
|
|
16
|
+
<n1:username>#{user}</n1:username>
|
|
17
|
+
<n1:password>#{pass}</n1:password>
|
|
18
|
+
</n1:login>
|
|
19
|
+
</env:Body>
|
|
20
|
+
</env:Envelope>
|
|
21
|
+
eos
|
|
22
|
+
|
|
23
|
+
http = Net::HTTP.new(url, 443)
|
|
24
|
+
http.use_ssl = true
|
|
25
|
+
|
|
26
|
+
response = http.post('/services/Soap/u/21.0',
|
|
27
|
+
loginRequest.lstrip,
|
|
28
|
+
{"Content-Type" => "text/xml;charset=UTF-8", "SOAPAction" => "login"})
|
|
29
|
+
|
|
30
|
+
return false if response.class != Net::HTTPOK
|
|
31
|
+
|
|
32
|
+
return LoginResponse.fromXML(response.body)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
|
|
3
|
+
module Salesforklift
|
|
4
|
+
|
|
5
|
+
class LoginResponse
|
|
6
|
+
|
|
7
|
+
attr_accessor :server_instance, :session_id, :server_url
|
|
8
|
+
|
|
9
|
+
def self.fromXML(xml)
|
|
10
|
+
doc = REXML::Document.new(xml)
|
|
11
|
+
|
|
12
|
+
response = LoginResponse.new
|
|
13
|
+
response.server_url = doc.elements["//serverUrl"].text
|
|
14
|
+
|
|
15
|
+
if /(https:\/\/)(.+)(.salesforce.com)(.*)/.match(response.server_url)
|
|
16
|
+
response.server_instance = $2
|
|
17
|
+
end
|
|
18
|
+
response.session_id = doc.elements['//sessionId'].text
|
|
19
|
+
response
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: salesforklift
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease:
|
|
5
|
+
version: 0.0.8
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Max Mao
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
|
|
13
|
+
date: 2012-11-15 00:00:00 Z
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: rest-client
|
|
17
|
+
prerelease: false
|
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
19
|
+
none: false
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: "0"
|
|
24
|
+
type: :runtime
|
|
25
|
+
version_requirements: *id001
|
|
26
|
+
description: Salesforce Bulk API ruby wrapper.
|
|
27
|
+
email: jmao@brightcove.com
|
|
28
|
+
executables: []
|
|
29
|
+
|
|
30
|
+
extensions: []
|
|
31
|
+
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
|
|
34
|
+
files:
|
|
35
|
+
- lib/salesforklift/batch.rb
|
|
36
|
+
- lib/salesforklift/batch_response.rb
|
|
37
|
+
- lib/salesforklift/job.rb
|
|
38
|
+
- lib/salesforklift/job_response.rb
|
|
39
|
+
- lib/salesforklift/logging.rb
|
|
40
|
+
- lib/salesforklift/login.rb
|
|
41
|
+
- lib/salesforklift/login_response.rb
|
|
42
|
+
- lib/salesforklift.rb
|
|
43
|
+
- Rakefile
|
|
44
|
+
- Gemfile
|
|
45
|
+
- README.md
|
|
46
|
+
homepage:
|
|
47
|
+
licenses: []
|
|
48
|
+
|
|
49
|
+
post_install_message:
|
|
50
|
+
rdoc_options: []
|
|
51
|
+
|
|
52
|
+
require_paths:
|
|
53
|
+
- lib
|
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
|
+
none: false
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: "0"
|
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
|
+
none: false
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: "0"
|
|
66
|
+
requirements: []
|
|
67
|
+
|
|
68
|
+
rubyforge_project:
|
|
69
|
+
rubygems_version: 1.8.15
|
|
70
|
+
signing_key:
|
|
71
|
+
specification_version: 3
|
|
72
|
+
summary: Salesforce Bulk API ruby wrapper
|
|
73
|
+
test_files: []
|
|
74
|
+
|