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 ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "mocha", "0.9.10"
7
+ gem "rspec", "~> 2.0"
8
+ gem 'rdoc'
9
+ end
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,7 @@
1
+ require "salesforklift/logging"
2
+ require "salesforklift/login_response"
3
+ require "salesforklift/job_response"
4
+ require "salesforklift/batch_response"
5
+ require "salesforklift/login"
6
+ require "salesforklift/job"
7
+ require "salesforklift/batch"
@@ -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
+