salesforklift 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|