salesforce_bulk_api 0.0.12 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ec07d0cc7d5f0c5795459e444948d45ed308dde1
4
- data.tar.gz: 0faae2875e7ec75bf9a8bb8dfe54025b607cedb6
2
+ SHA256:
3
+ metadata.gz: bd5b061f299027dc456e2c65f6b33480c7192d9c5af15a5fecd3cd1b81b79589
4
+ data.tar.gz: 4e5a70fe4541df2b944bdb3f59c0acdc78f91d8f2100ae164c03a23e54e09031
5
5
  SHA512:
6
- metadata.gz: c68f01bc8d22feb69025bed933e52049e7227e70a8df2e4496ac89f224e6a7571199510ac2a5c61e9764e8f828a6d776a8b05bd78d88560f8baf1bde5ccc18a2
7
- data.tar.gz: d91276c381a0f188f105af7442a95ef5c4e75f399f4d55a37906b3e5c1bfa0f6bcaecc6c2d8fde967a10ac8ea15e01913b71a426f7e71a216e18023d9e7d7ecb
6
+ metadata.gz: b453618a4cddde3851d766ca672df92177bae019152192bf628ebd2f8e08e92f2662226daf85bf81ea92d64a7e9756ccd7a495e170f32924985f4bbbd5c4b727
7
+ data.tar.gz: f44136ac331cc22b288f8f27a87b8f5def897f4aca051dcc3c2e1dd860fb1d2f9fb0d337b40858f63cbdeabdc407644ea69ed3a1f129491ba6f50a12f4e893ac
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ Gemfile.lock
6
6
  pkg/*
7
7
  auth_credentials.yml
8
8
  *.swp
9
+ .idea
data/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Yatish Mehta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,112 +1,141 @@
1
1
  # Salesforce-Bulk-Api
2
2
  [![Gem Version](https://badge.fury.io/rb/salesforce_bulk_api.png)](http://badge.fury.io/rb/salesforce_bulk_api)
3
+
3
4
  ## Overview
4
5
 
5
- Salesforce bulk API is a simple ruby gem for connecting to and using the Salesforce Bulk API. It is actually a re-written code from [salesforce_bulk](https://github.com/jorgevaldivia/salesforce_bulk).Written to suit many more other features as well.
6
+ `SalesforceBulkApi` is a ruby wrapper for the Salesforce Bulk API.
7
+ It is rewritten from [salesforce_bulk](https://github.com/jorgevaldivia/salesforce_bulk).
8
+ It adds some missing features of `salesforce_bulk`.
6
9
 
7
10
  ## How to use
8
11
 
9
12
  Using this gem is simple and straight forward.
10
13
 
11
- To initialize:
14
+ ### Install
12
15
 
13
- `sudo gem install salesforce_bulk_api`
16
+ `gem install salesforce_bulk_api`
14
17
 
15
- or add
18
+ or add it to your Gemfile
16
19
 
17
20
  `gem salesforce_bulk_api`
18
-
19
- in your Gemfile
20
21
 
21
- There are two ways to authenticate with SalesForce to use the Bulk API: databasedotcom & restforce.
22
- Please check out the entire documentation of the gem you decide to use to learn the various ways of authentication.
22
+ ### Authenticate
23
+
24
+ You can authenticate with Salesforce using two gems, `databasedotcom` & `restforce`.
25
+
26
+ Please check the documentation of the respective gems to learn how to authenticate with Salesforce
23
27
 
24
28
  [Databasedotcom](https://github.com/heroku/databasedotcom)
25
29
  [Restforce](https://github.com/ejholmes/restforce)
26
30
 
27
-
28
31
  You can use username password combo, OmniAuth, Oauth2
29
32
  You can use as many records possible in the Array. Governor limits are taken care of inside the gem.
30
33
 
34
+ ```ruby
35
+ require 'salesforce_bulk_api'
31
36
 
32
- require 'salesforce_bulk_api'
33
- client = Databasedotcom::Client.new :client_id => SFDC_APP_CONFIG["client_id"], :client_secret => SFDC_APP_CONFIG["client_secret"] #client_id and client_secret respectively
34
- client.authenticate :token => "my-oauth-token", :instance_url => "http://na1.salesforce.com" #=> "my-oauth-token"
37
+ client = Databasedotcom::Client.new(
38
+ :client_id => SFDC_APP_CONFIG["client_id"],
39
+ :client_secret => SFDC_APP_CONFIG["client_secret"]
40
+ )
41
+ client.authenticate(
42
+ :token => " ",
43
+ :instance_url => "http://na1.salesforce.com"
44
+ )
35
45
 
36
- salesforce = SalesforceBulkApi::Api.new(client)
46
+ salesforce = SalesforceBulkApi::Api.new(client)
47
+ ```
37
48
 
38
49
  OR
39
50
 
40
- require 'salesforce_bulk_api'
41
- client = Restforce.new(
42
- username: SFDC_APP_CONFIG['SFDC_USERNAME'],
43
- password: SFDC_APP_CONFIG['SFDC_PASSWORD'],
44
- security_token: SFDC_APP_CONFIG['SFDC_SECURITY_TOKEN'],
45
- client_id: SFDC_APP_CONFIG['SFDC_CLIENT_ID'],
46
- client_secret: SFDC_APP_CONFIG['SFDC_CLIENT_SECRET'].to_i,
47
- host: SFDC_APP_CONFIG['SFDC_HOST']
48
- )
49
-
50
- salesforce = SalesforceBulkApi::Api.new(client)
51
-
51
+ ```ruby
52
+ require 'salesforce_bulk_api'
53
+ client = Restforce.new(
54
+ username: SFDC_APP_CONFIG['SFDC_USERNAME'],
55
+ password: SFDC_APP_CONFIG['SFDC_PASSWORD'],
56
+ security_token: SFDC_APP_CONFIG['SFDC_SECURITY_TOKEN'],
57
+ client_id: SFDC_APP_CONFIG['SFDC_CLIENT_ID'],
58
+ client_secret: SFDC_APP_CONFIG['SFDC_CLIENT_SECRET'].to_i,
59
+ host: SFDC_APP_CONFIG['SFDC_HOST']
60
+ )
61
+ client.authenticate!
62
+
63
+ salesforce = SalesforceBulkApi::Api.new(client)
64
+ ```
52
65
 
53
66
  ### Sample operations:
54
67
 
55
- # Insert/Create
56
- # Add as many fields per record as needed.
57
- new_account = Hash["name" => "Test Account", "type" => "Other"]
58
- records_to_insert = Array.new
59
- # You can add as many records as you want here, just keep in mind that Salesforce has governor limits.
60
- records_to_insert.push(new_account)
61
- result = salesforce.create("Account", records_to_insert)
62
- puts "result is: #{result.inspect}"
63
-
64
- # Update
65
- updated_account = Hash["name" => "Test Account -- Updated", id => "a00A0001009zA2m"] # Nearly identical to an insert, but we need to pass the salesforce id.
66
- records_to_update = Array.new
67
- records_to_update.push(updated_account)
68
- salesforce.update("Account", records_to_update)
69
-
70
- # Upsert
71
- upserted_account = Hash["name" => "Test Account -- Upserted", "External_Field_Name" => "123456"] # Fields to be updated. External field must be included
72
- records_to_upsert = Array.new
73
- records_to_upsert.push(upserted_account)
74
- salesforce.upsert("Account", records_to_upsert, "External_Field_Name") # Note that upsert accepts an extra parameter for the external field name
75
-
76
- # Delete
77
- deleted_account = Hash["id" => "a00A0001009zA2m"] # We only specify the id of the records to delete
78
- records_to_delete = Array.new
79
- records_to_delete.push(deleted_account)
80
- salesforce.delete("Account", records_to_delete)
81
-
82
- # Query
83
- res = salesforce.query("Account", "select id, name, createddate from Account limit 3") # We just need to pass the sobject name and the query string
68
+ ```ruby
69
+ # Insert/Create
70
+ # Add as many fields per record as needed.
71
+ new_account = Hash["name" => "Test Account", "type" => "Other"]
72
+ records_to_insert = Array.new
73
+ # You can add as many records as you want here, just keep in mind that Salesforce has governor limits.
74
+ records_to_insert.push(new_account)
75
+ result = salesforce.create("Account", records_to_insert)
76
+ puts "result is: #{result.inspect}"
77
+
78
+ # Update
79
+ updated_account = Hash["name" => "Test Account -- Updated", id => "a00A0001009zA2m"] # Nearly identical to an insert, but we need to pass the salesforce id.
80
+ records_to_update = Array.new
81
+ records_to_update.push(updated_account)
82
+ salesforce.update("Account", records_to_update)
83
+
84
+ # Upsert
85
+ upserted_account = Hash["name" => "Test Account -- Upserted", "External_Field_Name" => "123456"] # Fields to be updated. External field must be included
86
+ records_to_upsert = Array.new
87
+ records_to_upsert.push(upserted_account)
88
+ salesforce.upsert("Account", records_to_upsert, "External_Field_Name") # Note that upsert accepts an extra parameter for the external field name
89
+
90
+ # Delete
91
+ deleted_account = Hash["id" => "a00A0001009zA2m"] # We only specify the id of the records to delete
92
+ records_to_delete = Array.new
93
+ records_to_delete.push(deleted_account)
94
+ salesforce.delete("Account", records_to_delete)
95
+
96
+ # Query
97
+ res = salesforce.query("Account", "select id, name, createddate from Account limit 3") # We just need to pass the sobject name and the query string
98
+ ```
84
99
 
85
100
  ### Helpful methods:
86
101
 
87
- # Check status of a job via #job_from_id
88
- job = salesforce.job_from_id('a00A0001009zA2m') # Returns a SalesforceBulkApi::Job instance
89
- puts "status is: #{job.check_job_status.inspect}"
102
+ ```ruby
103
+ # Check status of a job via #job_from_id
104
+ job = salesforce.job_from_id('a00A0001009zA2m') # Returns a SalesforceBulkApi::Job instance
105
+ puts "status is: #{job.check_job_status.inspect}"
106
+ ```
90
107
 
91
108
  ### Listening to events:
92
109
 
93
- # A job is created
94
- # Useful when you need to store the job_id before any work begins, then if you fail during a complex load scenario, you can wait for your
95
- # previous job(s) to finish.
96
- salesforce.on_job_created do |job|
97
- puts "Job #{job.job_id} created!"
98
- end
110
+ ```ruby
111
+ # A job is created
112
+ # Useful when you need to store the job_id before any work begins, then if you fail during a complex load scenario, you can wait for your
113
+ # previous job(s) to finish.
114
+ salesforce.on_job_created do |job|
115
+ puts "Job #{job.job_id} created!"
116
+ end
117
+ ```
99
118
 
100
- ### Throttling API calls:
119
+ ### Fetching records from a batch
120
+
121
+ ```ruby
122
+ job_id = 'l02A0231009Za8m'
123
+ batch_id = 'H24a0708089zA2J'
124
+ salesforce.get_batch_records(job_id, batch_id)
125
+ # => [{"Id"=>["RECORD_ID_1"], "AField__c"=>["123123"]},
126
+ {"Id"=>["RECORD_ID_2"], "AField__c"=>["123123"]},
127
+ {"Id"=>["RECORD_ID_3"], "AField__c"=>["123123"]}]
101
128
 
102
- # By default, this gem (and maybe your app driving it) will query job/batch statuses at an unbounded rate. We
103
- # can fix that, e.g.:
104
- salesforce.connection.set_status_throttle(30) # only check status of individual jobs/batches every 30 seconds
129
+ ```
130
+
131
+ ### Throttling API calls:
105
132
 
106
- ## Installation
133
+ ```ruby
134
+ # By default, this gem (and maybe your app driving it) will query job/batch statuses at an unbounded rate. We
135
+ # can fix that, e.g.:
136
+ salesforce.connection.set_status_throttle(30) # only check status of individual jobs/batches every 30 seconds
137
+ ```
107
138
 
108
- sudo gem install salesforce_bulk_api
109
-
110
139
  ## Contribute
111
140
 
112
141
  Feel to fork and send Pull request
@@ -1,23 +1,22 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
- Bundler.require()
4
- require 'salesforce_bulk_api/version'
5
3
  require 'net/https'
6
4
  require 'xmlsimple'
7
5
  require 'csv'
6
+
7
+ require 'salesforce_bulk_api/version'
8
8
  require 'salesforce_bulk_api/concerns/throttling'
9
9
  require 'salesforce_bulk_api/job'
10
10
  require 'salesforce_bulk_api/connection'
11
11
 
12
12
  module SalesforceBulkApi
13
-
14
13
  class Api
15
14
  attr_reader :connection
16
15
 
17
- @@SALESFORCE_API_VERSION = '32.0'
16
+ SALESFORCE_API_VERSION = '46.0'
18
17
 
19
18
  def initialize(client)
20
- @connection = SalesforceBulkApi::Connection.new(@@SALESFORCE_API_VERSION, client)
19
+ @connection = SalesforceBulkApi::Connection.new(SALESFORCE_API_VERSION, client)
21
20
  @listeners = { job_created: [] }
22
21
  end
23
22
 
@@ -53,12 +52,11 @@ module SalesforceBulkApi
53
52
  }
54
53
  end
55
54
 
56
-
57
- ##
58
55
  # Allows you to attach a listener that accepts the created job (which has a useful #job_id field). This is useful
59
56
  # for recording a job ID persistently before you begin batch work (i.e. start modifying the salesforce database),
60
57
  # so if the load process you are writing needs to recover, it can be aware of previous jobs it started and wait
61
58
  # for them to finish.
59
+ #
62
60
  def on_job_created(&block)
63
61
  @listeners[:job_created] << block
64
62
  end
@@ -70,7 +68,13 @@ module SalesforceBulkApi
70
68
  def do_operation(operation, sobject, records, external_field, get_response, timeout, batch_size, send_nulls = false, no_null_list = [])
71
69
  count operation.to_sym
72
70
 
73
- job = SalesforceBulkApi::Job.new(operation: operation, sobject: sobject, records: records, external_field: external_field, connection: @connection)
71
+ job = SalesforceBulkApi::Job.new(
72
+ operation: operation,
73
+ sobject: sobject,
74
+ records: records,
75
+ external_field: external_field,
76
+ connection: @connection
77
+ )
74
78
 
75
79
  job.create_job(batch_size, send_nulls, no_null_list)
76
80
  @listeners[:job_created].each {|callback| callback.call(job)}
@@ -81,6 +85,7 @@ module SalesforceBulkApi
81
85
  end
82
86
 
83
87
  private
88
+
84
89
  def get_counters
85
90
  @counters ||= Hash.new(0)
86
91
  end
@@ -11,7 +11,7 @@ module SalesforceBulkApi::Concerns
11
11
  end
12
12
 
13
13
  def set_status_throttle(limit_seconds)
14
- set_throttle_limit_in_seconds(limit_seconds, [:http_method, :path], ->(details) { details[:path] == :get })
14
+ set_throttle_limit_in_seconds(limit_seconds, [:http_method, :path], ->(details) { details[:http_method] == :get })
15
15
  end
16
16
 
17
17
  def set_throttle_limit_in_seconds(limit_seconds, throttle_by_keys, only_if)
@@ -57,4 +57,4 @@ module SalesforceBulkApi::Concerns
57
57
  end
58
58
 
59
59
  end
60
- end
60
+ end
@@ -1,22 +1,15 @@
1
- module SalesforceBulkApi
2
1
  require 'timeout'
3
2
 
3
+ module SalesforceBulkApi
4
4
  class Connection
5
5
  include Concerns::Throttling
6
6
 
7
- @@XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
8
- @@API_VERSION = nil
9
- @@LOGIN_HOST = 'login.salesforce.com'
10
- @@INSTANCE_HOST = nil # Gets set in login()
7
+ LOGIN_HOST = 'login.salesforce.com'
11
8
 
12
- def initialize(api_version,client)
13
- @client=client
14
- @session_id = nil
15
- @server_url = nil
16
- @instance = nil
17
- @@API_VERSION = api_version
18
- @@LOGIN_PATH = "/services/Soap/u/#{@@API_VERSION}"
19
- @@PATH_PREFIX = "/services/async/#{@@API_VERSION}/"
9
+ def initialize(api_version, client)
10
+ @client = client
11
+ @api_version = api_version
12
+ @path_prefix = "/services/async/#{@api_version}/"
20
13
 
21
14
  login()
22
15
  end
@@ -25,21 +18,21 @@ require 'timeout'
25
18
  client_type = @client.class.to_s
26
19
  case client_type
27
20
  when "Restforce::Data::Client"
28
- @session_id=@client.options[:oauth_token]
29
- @server_url=@client.options[:instance_url]
21
+ @session_id = @client.options[:oauth_token]
22
+ @server_url = @client.options[:instance_url]
30
23
  else
31
- @session_id=@client.oauth_token
32
- @server_url=@client.instance_url
24
+ @session_id = @client.oauth_token
25
+ @server_url = @client.instance_url
33
26
  end
34
27
  @instance = parse_instance()
35
- @@INSTANCE_HOST = "#{@instance}.salesforce.com"
28
+ @instance_host = "#{@instance}.salesforce.com"
36
29
  end
37
30
 
38
31
  def post_xml(host, path, xml, headers)
39
- host = host || @@INSTANCE_HOST
40
- if host != @@LOGIN_HOST # Not login, need to add session id to header
32
+ host = host || @instance_host
33
+ if host != LOGIN_HOST # Not login, need to add session id to header
41
34
  headers['X-SFDC-Session'] = @session_id
42
- path = "#{@@PATH_PREFIX}#{path}"
35
+ path = "#{@path_prefix}#{path}"
43
36
  end
44
37
  i = 0
45
38
  begin
@@ -59,9 +52,9 @@ require 'timeout'
59
52
  end
60
53
 
61
54
  def get_request(host, path, headers)
62
- host = host || @@INSTANCE_HOST
63
- path = "#{@@PATH_PREFIX}#{path}"
64
- if host != @@LOGIN_HOST # Not login, need to add session id to header
55
+ host = host || @instance_host
56
+ path = "#{@path_prefix}#{path}"
57
+ if host != LOGIN_HOST # Not login, need to add session id to header
65
58
  headers['X-SFDC-Session'] = @session_id;
66
59
  end
67
60
 
@@ -77,12 +70,6 @@ require 'timeout'
77
70
  req
78
71
  end
79
72
 
80
- def parse_instance()
81
- @instance = @server_url.match(/https:\/\/[a-z]{2}[0-9]{1,2}/).to_s.gsub("https://","")
82
- @instance = @server_url.split(".salesforce.com")[0].split("://")[1] if @instance.nil? || @instance.empty?
83
- return @instance
84
- end
85
-
86
73
  def counters
87
74
  {
88
75
  get: get_counters[:get],
@@ -100,6 +87,12 @@ require 'timeout'
100
87
  get_counters[http_method] += 1
101
88
  end
102
89
 
90
+ def parse_instance()
91
+ @instance = @server_url.match(/https:\/\/[a-z]{2}[0-9]{1,2}\./).to_s.gsub("https://","").split(".")[0]
92
+ @instance = @server_url.split(".salesforce.com")[0].split("://")[1] if @instance.nil? || @instance.empty?
93
+ @instance
94
+ end
95
+
103
96
  end
104
97
 
105
98
  end
@@ -16,8 +16,6 @@ module SalesforceBulkApi
16
16
  @XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
17
17
  end
18
18
 
19
-
20
-
21
19
  def create_job(batch_size, send_nulls, no_null_list)
22
20
  @batch_size = batch_size
23
21
  @send_nulls = send_nulls
@@ -26,7 +24,8 @@ module SalesforceBulkApi
26
24
  xml = "#{@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
27
25
  xml += "<operation>#{@operation}</operation>"
28
26
  xml += "<object>#{@sobject}</object>"
29
- if !@external_field.nil? # This only happens on upsert
27
+ # This only happens on upsert
28
+ if !@external_field.nil?
30
29
  xml += "<externalIdFieldName>#{@external_field}</externalIdFieldName>"
31
30
  end
32
31
  xml += "<contentType>XML</contentType>"
@@ -42,7 +41,6 @@ module SalesforceBulkApi
42
41
  raise SalesforceException.new("#{response_parsed['exceptionMessage'][0]} (#{response_parsed['exceptionCode'][0]})") if response_parsed['exceptionCode']
43
42
 
44
43
  @job_id = response_parsed['id'][0]
45
-
46
44
  end
47
45
 
48
46
  def close_job()
@@ -74,7 +72,7 @@ module SalesforceBulkApi
74
72
  @records_dup = @records.clone
75
73
 
76
74
  super_records = []
77
- (@records_dup.size/@batch_size).to_i.times do
75
+ (@records_dup.size / @batch_size).to_i.times do
78
76
  super_records << @records_dup.pop(@batch_size)
79
77
  end
80
78
  super_records << @records_dup unless @records_dup.empty?
@@ -102,6 +100,11 @@ module SalesforceBulkApi
102
100
  data.keys.each do |k|
103
101
  if k.is_a?(Hash)
104
102
  xml += build_sobject(k)
103
+ elsif k.to_s.include? '.'
104
+ relations = k.to_s.split('.')
105
+ parent = relations[0]
106
+ child = relations[1..-1].join('.')
107
+ xml += "<#{parent}>#{build_sobject({ child => data[k] })}</#{parent}>"
105
108
  elsif data[k] != :type
106
109
  xml += "<#{k}>#{data[k]}</#{k}>"
107
110
  end
@@ -109,6 +112,21 @@ module SalesforceBulkApi
109
112
  xml += '</sObject>'
110
113
  end
111
114
 
115
+ def build_relationship_sobject(key, value)
116
+ if key.to_s.include? '.'
117
+ relations = key.to_s.split('.')
118
+ parent = relations[0]
119
+ child = relations[1..-1].join('.')
120
+ xml = "<#{parent}>"
121
+ xml += "<sObject>"
122
+ xml += build_relationship_sobject(child, value)
123
+ xml += "</sObject>"
124
+ xml += "</#{parent}>"
125
+ else
126
+ xml = "<#{key}>#{value}</#{key}>"
127
+ end
128
+ end
129
+
112
130
  def create_sobject(keys, r)
113
131
  sobject_xml = '<sObject>'
114
132
  keys.each do |k|
@@ -116,6 +134,8 @@ module SalesforceBulkApi
116
134
  sobject_xml += "<#{k}>"
117
135
  sobject_xml += build_sobject(r[k])
118
136
  sobject_xml += "</#{k}>"
137
+ elsif k.to_s.include? '.'
138
+ sobject_xml += build_relationship_sobject(k, r[k])
119
139
  elsif !r[k].to_s.empty?
120
140
  sobject_xml += "<#{k}>"
121
141
  if r[k].respond_to?(:encode)
@@ -126,7 +146,7 @@ module SalesforceBulkApi
126
146
  sobject_xml += r[k].to_s
127
147
  end
128
148
  sobject_xml += "</#{k}>"
129
- elsif @send_nulls && !@no_null_list.include?(k)
149
+ elsif @send_nulls && !@no_null_list.include?(k) && r.key?(k)
130
150
  sobject_xml += "<#{k} xsi:nil=\"true\"/>"
131
151
  end
132
152
  end
@@ -171,11 +191,18 @@ module SalesforceBulkApi
171
191
  state = []
172
192
  Timeout::timeout(timeout, SalesforceBulkApi::JobTimeout) do
173
193
  while true
174
- if self.check_job_status['state'][0] == 'Closed'
175
- @batch_ids.each do |batch_id|
176
- batch_state = self.check_batch_status(batch_id)
177
- if batch_state['state'][0] != "Queued" && batch_state['state'][0] != "InProgress"
178
- state << (batch_state)
194
+ job_status = self.check_job_status
195
+ if job_status && job_status['state'] && job_status['state'][0] == 'Closed'
196
+ batch_statuses = {}
197
+
198
+ batches_ready = @batch_ids.all? do |batch_id|
199
+ batch_state = batch_statuses[batch_id] = self.check_batch_status(batch_id)
200
+ batch_state && batch_state['state'] && batch_state['state'][0] && !['Queued', 'InProgress'].include?(batch_state['state'][0])
201
+ end
202
+
203
+ if batches_ready
204
+ @batch_ids.each do |batch_id|
205
+ state.insert(0, batch_statuses[batch_id])
179
206
  @batch_ids.delete(batch_id)
180
207
  end
181
208
  end
@@ -219,6 +246,17 @@ module SalesforceBulkApi
219
246
  results
220
247
  end
221
248
 
249
+ def get_batch_records(batch_id)
250
+ path = "job/#{@job_id}/batch/#{batch_id}/request"
251
+ headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
252
+
253
+ response = @connection.get_request(nil, path, headers)
254
+ response_parsed = XmlSimple.xml_in(response)
255
+ results = response_parsed['sObject']
256
+
257
+ results
258
+ end
259
+
222
260
  end
223
261
 
224
262
  class JobTimeout < StandardError
@@ -1,3 +1,3 @@
1
1
  module SalesforceBulkApi
2
- VERSION = '0.0.12'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -12,14 +12,12 @@ Gem::Specification.new do |s|
12
12
  s.summary = %q{It uses the bulk api of salesforce to communicate with Salesforce CRM}
13
13
  s.description = %q{Salesforce Bulk API with governor limits taken care of}
14
14
 
15
- s.rubyforge_project = 'salesforce_bulk_api'
16
-
17
15
  s.add_dependency('json', ['>= 0'])
18
16
  s.add_dependency('xml-simple', ['>= 0'])
19
17
 
20
18
  s.add_development_dependency 'rspec'
21
- s.add_development_dependency 'restforce', '~> 1.5.1'
22
- s.add_development_dependency 'rake', '~> 10.4.2'
19
+ s.add_development_dependency 'restforce', '~> 3.0.0'
20
+ s.add_development_dependency "rake", ">= 12.3.3"
23
21
  s.add_development_dependency 'pry'
24
22
 
25
23
  s.files = `git ls-files`.split("\n")
@@ -111,18 +111,18 @@ describe SalesforceBulkApi do
111
111
  context 'when passed get_result = true with batches' do
112
112
  it 'returns the results array' do
113
113
  res = @api.update('Account', [{:Id => @account_id, :Website => 'www.test.com'}, {:Id => @account_id, :Website => 'www.test.com'}, {:Id => @account_id, :Website => 'www.test.com'}, {:Id => 'abc123', :Website => 'www.test.com'}], true, false, [], 2)
114
+
114
115
  res['batches'][0]['response'][0]['id'][0].should start_with(@account_id)
115
116
  res['batches'][0]['response'][0]['success'].should eq ['true']
116
117
  res['batches'][0]['response'][0]['created'].should eq ['false']
117
-
118
- res['batches'][0]['response'][1].should eq({"errors"=>[{"fields"=>["Id"], "message"=>["Account ID: id value of incorrect type: abc123"], "statusCode"=>["MALFORMED_ID"]}], "success"=>["false"], "created"=>["false"]})
118
+ res['batches'][0]['response'][1]['id'][0].should start_with(@account_id)
119
+ res['batches'][0]['response'][1]['success'].should eq ['true']
120
+ res['batches'][0]['response'][1]['created'].should eq ['false']
119
121
 
120
122
  res['batches'][1]['response'][0]['id'][0].should start_with(@account_id)
121
123
  res['batches'][1]['response'][0]['success'].should eq ['true']
122
124
  res['batches'][1]['response'][0]['created'].should eq ['false']
123
- res['batches'][1]['response'][1]['id'][0].should start_with(@account_id)
124
- res['batches'][1]['response'][1]['success'].should eq ['true']
125
- res['batches'][1]['response'][1]['created'].should eq ['false']
125
+ res['batches'][1]['response'][1].should eq({"errors"=>[{"fields"=>["Id"], "message"=>["Account ID: id value of incorrect type: abc123"], "statusCode"=>["MALFORMED_ID"]}], "success"=>["false"], "created"=>["false"]})
126
126
  end
127
127
  end
128
128
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: salesforce_bulk_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.12
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yatish Mehta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-31 00:00:00.000000000 Z
11
+ date: 2020-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -58,28 +58,28 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 1.5.1
61
+ version: 3.0.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 1.5.1
68
+ version: 3.0.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: 10.4.2
75
+ version: 12.3.3
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: 10.4.2
82
+ version: 12.3.3
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: pry
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -104,9 +104,10 @@ files:
104
104
  - ".gitignore"
105
105
  - ".rspec"
106
106
  - Gemfile
107
+ - LICENCE
107
108
  - README.md
108
109
  - Rakefile
109
- - example_auth_credentials.yml
110
+ - auth_credentials.yml.example
110
111
  - lib/salesforce_bulk_api.rb
111
112
  - lib/salesforce_bulk_api/concerns/throttling.rb
112
113
  - lib/salesforce_bulk_api/connection.rb
@@ -133,8 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
134
  - !ruby/object:Gem::Version
134
135
  version: '0'
135
136
  requirements: []
136
- rubyforge_project: salesforce_bulk_api
137
- rubygems_version: 2.4.5
137
+ rubygems_version: 3.1.2
138
138
  signing_key:
139
139
  specification_version: 4
140
140
  summary: It uses the bulk api of salesforce to communicate with Salesforce CRM