salesforce_bulk_api 0.0.8 → 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: e63ba9f2d9be3dfa89bf6d896ee94fe847bc1b57
4
- data.tar.gz: e054528cb047b09383e98fe2d54b1e3ad628bb0e
2
+ SHA256:
3
+ metadata.gz: bd5b061f299027dc456e2c65f6b33480c7192d9c5af15a5fecd3cd1b81b79589
4
+ data.tar.gz: 4e5a70fe4541df2b944bdb3f59c0acdc78f91d8f2100ae164c03a23e54e09031
5
5
  SHA512:
6
- metadata.gz: 7d66df90281412848aba6eed1a5a5476d7883d1e774f28b21dcd2d3a3952027a9e527a474ec2cd702e5b1018c678a25892572206524d1cfbcbd064e7a7577495
7
- data.tar.gz: 0326e9e4f4389d5052ce92aa04ff2daf65c26df997dc31d8b353f3cca60f1eb5634da45d126fb2db2d3221873353cd78df6ca8bab55d43f14199dd0d0de71c33
6
+ metadata.gz: b453618a4cddde3851d766ca672df92177bae019152192bf628ebd2f8e08e92f2662226daf85bf81ea92d64a7e9756ccd7a495e170f32924985f4bbbd5c4b727
7
+ data.tar.gz: f44136ac331cc22b288f8f27a87b8f5def897f4aca051dcc3c2e1dd860fb1d2f9fb0d337b40858f63cbdeabdc407644ea69ed3a1f129491ba6f50a12f4e893ac
data/.gitignore CHANGED
@@ -5,3 +5,5 @@ Gemfile.lock
5
5
  .ruby-version
6
6
  pkg/*
7
7
  auth_credentials.yml
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,91 +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
-
52
-
53
- Sample operations:
54
-
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
84
-
85
- ## Installation
86
-
87
- sudo gem install salesforce_bulk_api
88
-
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
+ ```
65
+
66
+ ### Sample operations:
67
+
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
+ ```
99
+
100
+ ### Helpful methods:
101
+
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
+ ```
107
+
108
+ ### Listening to events:
109
+
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
+ ```
118
+
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"]}]
128
+
129
+ ```
130
+
131
+ ### Throttling API calls:
132
+
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
+ ```
138
+
89
139
  ## Contribute
90
140
 
91
141
  Feel to fork and send Pull request
@@ -0,0 +1,7 @@
1
+ salesforce:
2
+ client_id: client_id_here
3
+ client_secret: client_secret_here
4
+ user: sf_user@example.com
5
+ passwordandtoken: passandtokenhere
6
+ test_account_id: 0013000000ymMBh
7
+ host: 'login.salesforce.com' # use test.salesforce.com if it is a sandbox
@@ -1,53 +1,98 @@
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
+ require 'salesforce_bulk_api/concerns/throttling'
8
9
  require 'salesforce_bulk_api/job'
9
10
  require 'salesforce_bulk_api/connection'
10
11
 
11
12
  module SalesforceBulkApi
12
-
13
13
  class Api
14
+ attr_reader :connection
14
15
 
15
- @@SALESFORCE_API_VERSION = '23.0'
16
+ SALESFORCE_API_VERSION = '46.0'
16
17
 
17
18
  def initialize(client)
18
- @connection = SalesforceBulkApi::Connection.new(@@SALESFORCE_API_VERSION,client)
19
+ @connection = SalesforceBulkApi::Connection.new(SALESFORCE_API_VERSION, client)
20
+ @listeners = { job_created: [] }
19
21
  end
20
22
 
21
23
  def upsert(sobject, records, external_field, get_response = false, send_nulls = false, no_null_list = [], batch_size = 10000, timeout = 1500)
22
- self.do_operation('upsert', sobject, records, external_field, get_response, timeout, batch_size, send_nulls, no_null_list)
24
+ do_operation('upsert', sobject, records, external_field, get_response, timeout, batch_size, send_nulls, no_null_list)
23
25
  end
24
26
 
25
27
  def update(sobject, records, get_response = false, send_nulls = false, no_null_list = [], batch_size = 10000, timeout = 1500)
26
- self.do_operation('update', sobject, records, nil, get_response, timeout, batch_size, send_nulls, no_null_list)
28
+ do_operation('update', sobject, records, nil, get_response, timeout, batch_size, send_nulls, no_null_list)
27
29
  end
28
30
 
29
31
  def create(sobject, records, get_response = false, send_nulls = false, batch_size = 10000, timeout = 1500)
30
- self.do_operation('insert', sobject, records, nil, get_response, timeout, batch_size, send_nulls)
32
+ do_operation('insert', sobject, records, nil, get_response, timeout, batch_size, send_nulls)
31
33
  end
32
34
 
33
35
  def delete(sobject, records, get_response = false, batch_size = 10000, timeout = 1500)
34
- self.do_operation('delete', sobject, records, nil, get_response, timeout, batch_size)
36
+ do_operation('delete', sobject, records, nil, get_response, timeout, batch_size)
35
37
  end
36
38
 
37
39
  def query(sobject, query, batch_size = 10000, timeout = 1500)
38
- self.do_operation('query', sobject, query, nil, true, timeout, batch_size)
40
+ do_operation('query', sobject, query, nil, true, timeout, batch_size)
41
+ end
42
+
43
+ def counters
44
+ {
45
+ http_get: @connection.counters[:get],
46
+ http_post: @connection.counters[:post],
47
+ upsert: get_counters[:upsert],
48
+ update: get_counters[:update],
49
+ create: get_counters[:create],
50
+ delete: get_counters[:delete],
51
+ query: get_counters[:query]
52
+ }
39
53
  end
40
54
 
41
- #private
55
+ # Allows you to attach a listener that accepts the created job (which has a useful #job_id field). This is useful
56
+ # for recording a job ID persistently before you begin batch work (i.e. start modifying the salesforce database),
57
+ # so if the load process you are writing needs to recover, it can be aware of previous jobs it started and wait
58
+ # for them to finish.
59
+ #
60
+ def on_job_created(&block)
61
+ @listeners[:job_created] << block
62
+ end
63
+
64
+ def job_from_id(job_id)
65
+ SalesforceBulkApi::Job.new(job_id: job_id, connection: @connection)
66
+ end
42
67
 
43
68
  def do_operation(operation, sobject, records, external_field, get_response, timeout, batch_size, send_nulls = false, no_null_list = [])
44
- job = SalesforceBulkApi::Job.new(operation, sobject, records, external_field, @connection)
69
+ count operation.to_sym
70
+
71
+ job = SalesforceBulkApi::Job.new(
72
+ operation: operation,
73
+ sobject: sobject,
74
+ records: records,
75
+ external_field: external_field,
76
+ connection: @connection
77
+ )
45
78
 
46
79
  job.create_job(batch_size, send_nulls, no_null_list)
80
+ @listeners[:job_created].each {|callback| callback.call(job)}
47
81
  operation == "query" ? job.add_query() : job.add_batches()
48
82
  response = job.close_job
49
83
  response.merge!({'batches' => job.get_job_result(get_response, timeout)}) if get_response == true
50
84
  response
51
85
  end
52
- end
53
- end
86
+
87
+ private
88
+
89
+ def get_counters
90
+ @counters ||= Hash.new(0)
91
+ end
92
+
93
+ def count(name)
94
+ get_counters[name] += 1
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,60 @@
1
+ module SalesforceBulkApi::Concerns
2
+ module Throttling
3
+
4
+ def throttles
5
+ @throttles.dup
6
+ end
7
+
8
+ def add_throttle(&throttling_callback)
9
+ @throttles ||= []
10
+ @throttles << throttling_callback
11
+ end
12
+
13
+ def set_status_throttle(limit_seconds)
14
+ set_throttle_limit_in_seconds(limit_seconds, [:http_method, :path], ->(details) { details[:http_method] == :get })
15
+ end
16
+
17
+ def set_throttle_limit_in_seconds(limit_seconds, throttle_by_keys, only_if)
18
+ add_throttle do |details|
19
+ limit_log = get_limit_log(Time.now - limit_seconds)
20
+ key = extract_constraint_key_from(details, throttle_by_keys)
21
+ last_request = limit_log[key]
22
+
23
+ if !last_request.nil? && only_if.call(details)
24
+ seconds_since_last_request = Time.now.to_f - last_request.to_f
25
+ need_to_wait_seconds = limit_seconds - seconds_since_last_request
26
+ sleep(need_to_wait_seconds) if need_to_wait_seconds > 0
27
+ end
28
+
29
+ limit_log[key] = Time.now
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def extract_constraint_key_from(details, throttle_by_keys)
36
+ hash = {}
37
+ throttle_by_keys.each { |k| hash[k] = details[k] }
38
+ hash
39
+ end
40
+
41
+ def get_limit_log(prune_older_than)
42
+ @limits ||= Hash.new(0)
43
+
44
+ @limits.delete_if do |k, v|
45
+ v < prune_older_than
46
+ end
47
+
48
+ @limits
49
+ end
50
+
51
+ def throttle(details={})
52
+ (@throttles || []).each do |callback|
53
+ args = [details]
54
+ args = args[0..callback.arity]
55
+ callback.call(*args)
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -1,49 +1,43 @@
1
- module SalesforceBulkApi
2
1
  require 'timeout'
3
2
 
3
+ module SalesforceBulkApi
4
4
  class Connection
5
+ include Concerns::Throttling
5
6
 
6
- @@XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
7
- @@API_VERSION = nil
8
- @@LOGIN_HOST = 'login.salesforce.com'
9
- @@INSTANCE_HOST = nil # Gets set in login()
7
+ LOGIN_HOST = 'login.salesforce.com'
10
8
 
11
- def initialize(api_version,client)
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}/"
9
+ def initialize(api_version, client)
10
+ @client = client
11
+ @api_version = api_version
12
+ @path_prefix = "/services/async/#{@api_version}/"
19
13
 
20
14
  login()
21
15
  end
22
16
 
23
- #private
24
-
25
17
  def login()
26
18
  client_type = @client.class.to_s
27
19
  case client_type
28
20
  when "Restforce::Data::Client"
29
- @session_id=@client.options[:oauth_token]
30
- @server_url=@client.options[:instance_url]
21
+ @session_id = @client.options[:oauth_token]
22
+ @server_url = @client.options[:instance_url]
31
23
  else
32
- @session_id=@client.oauth_token
33
- @server_url=@client.instance_url
24
+ @session_id = @client.oauth_token
25
+ @server_url = @client.instance_url
34
26
  end
35
27
  @instance = parse_instance()
36
- @@INSTANCE_HOST = "#{@instance}.salesforce.com"
28
+ @instance_host = "#{@instance}.salesforce.com"
37
29
  end
38
30
 
39
31
  def post_xml(host, path, xml, headers)
40
- host = host || @@INSTANCE_HOST
41
- if host != @@LOGIN_HOST # Not login, need to add session id to header
42
- headers['X-SFDC-Session'] = @session_id;
43
- path = "#{@@PATH_PREFIX}#{path}"
32
+ host = host || @instance_host
33
+ if host != LOGIN_HOST # Not login, need to add session id to header
34
+ headers['X-SFDC-Session'] = @session_id
35
+ path = "#{@path_prefix}#{path}"
44
36
  end
45
37
  i = 0
46
38
  begin
39
+ count :post
40
+ throttle(http_method: :post, path: path)
47
41
  https(host).post(path, xml, headers).body
48
42
  rescue
49
43
  i += 1
@@ -58,11 +52,14 @@ require 'timeout'
58
52
  end
59
53
 
60
54
  def get_request(host, path, headers)
61
- host = host || @@INSTANCE_HOST
62
- path = "#{@@PATH_PREFIX}#{path}"
63
- 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
64
58
  headers['X-SFDC-Session'] = @session_id;
65
59
  end
60
+
61
+ count :get
62
+ throttle(http_method: :get, path: path)
66
63
  https(host).get(path, headers).body
67
64
  end
68
65
 
@@ -73,10 +70,27 @@ require 'timeout'
73
70
  req
74
71
  end
75
72
 
73
+ def counters
74
+ {
75
+ get: get_counters[:get],
76
+ post: get_counters[:post]
77
+ }
78
+ end
79
+
80
+ private
81
+
82
+ def get_counters
83
+ @counters ||= Hash.new(0)
84
+ end
85
+
86
+ def count(http_method)
87
+ get_counters[http_method] += 1
88
+ end
89
+
76
90
  def parse_instance()
77
- @instance=@server_url.match(/https:\/\/[a-z]{2}[0-9]{1,2}/).to_s.gsub("https://","")
78
- @instance = @server_url.split(".salesforce.com")[0].split("://")[1] if @instance.blank?
79
- return @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
80
94
  end
81
95
 
82
96
  end
@@ -1,13 +1,17 @@
1
1
  module SalesforceBulkApi
2
2
 
3
3
  class Job
4
+ attr_reader :job_id
4
5
 
5
- def initialize(operation, sobject, records, external_field, connection)
6
- @operation = operation
7
- @sobject = sobject
8
- @external_field = external_field
9
- @records = records
10
- @connection = connection
6
+ class SalesforceException < StandardError; end
7
+
8
+ def initialize(args)
9
+ @job_id = args[:job_id]
10
+ @operation = args[:operation]
11
+ @sobject = args[:sobject]
12
+ @external_field = args[:external_field]
13
+ @records = args[:records]
14
+ @connection = args[:connection]
11
15
  @batch_ids = []
12
16
  @XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
13
17
  end
@@ -20,7 +24,8 @@ module SalesforceBulkApi
20
24
  xml = "#{@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
21
25
  xml += "<operation>#{@operation}</operation>"
22
26
  xml += "<object>#{@sobject}</object>"
23
- if !@external_field.nil? # This only happens on upsert
27
+ # This only happens on upsert
28
+ if !@external_field.nil?
24
29
  xml += "<externalIdFieldName>#{@external_field}</externalIdFieldName>"
25
30
  end
26
31
  xml += "<contentType>XML</contentType>"
@@ -31,6 +36,10 @@ module SalesforceBulkApi
31
36
 
32
37
  response = @connection.post_xml(nil, path, xml, headers)
33
38
  response_parsed = XmlSimple.xml_in(response)
39
+
40
+ # response may contain an exception, so raise it
41
+ raise SalesforceException.new("#{response_parsed['exceptionMessage'][0]} (#{response_parsed['exceptionCode'][0]})") if response_parsed['exceptionCode']
42
+
34
43
  @job_id = response_parsed['id'][0]
35
44
  end
36
45
 
@@ -58,12 +67,12 @@ module SalesforceBulkApi
58
67
 
59
68
  def add_batches
60
69
  raise 'Records must be an array of hashes.' unless @records.is_a? Array
61
- keys = @records.reduce({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h}.keys
70
+ keys = @records.reduce({}) {|h, pairs| pairs.each {|k, v| (h[k] ||= []) << v}; h}.keys
62
71
 
63
72
  @records_dup = @records.clone
64
73
 
65
74
  super_records = []
66
- (@records_dup.size/@batch_size).to_i.times do
75
+ (@records_dup.size / @batch_size).to_i.times do
67
76
  super_records << @records_dup.pop(@batch_size)
68
77
  end
69
78
  super_records << @records_dup unless @records_dup.empty?
@@ -86,18 +95,58 @@ module SalesforceBulkApi
86
95
  response_parsed['id'][0] if response_parsed['id']
87
96
  end
88
97
 
98
+ def build_sobject(data)
99
+ xml = '<sObject>'
100
+ data.keys.each do |k|
101
+ if k.is_a?(Hash)
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}>"
108
+ elsif data[k] != :type
109
+ xml += "<#{k}>#{data[k]}</#{k}>"
110
+ end
111
+ end
112
+ xml += '</sObject>'
113
+ end
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
+
89
130
  def create_sobject(keys, r)
90
131
  sobject_xml = '<sObject>'
91
132
  keys.each do |k|
92
- if !r[k].to_s.empty?
133
+ if r[k].is_a?(Hash)
134
+ sobject_xml += "<#{k}>"
135
+ sobject_xml += build_sobject(r[k])
136
+ sobject_xml += "</#{k}>"
137
+ elsif k.to_s.include? '.'
138
+ sobject_xml += build_relationship_sobject(k, r[k])
139
+ elsif !r[k].to_s.empty?
93
140
  sobject_xml += "<#{k}>"
94
141
  if r[k].respond_to?(:encode)
95
142
  sobject_xml += r[k].encode(:xml => :text)
143
+ elsif r[k].respond_to?(:iso8601) # timestamps
144
+ sobject_xml += r[k].iso8601.to_s
96
145
  else
97
146
  sobject_xml += r[k].to_s
98
147
  end
99
148
  sobject_xml += "</#{k}>"
100
- elsif @send_nulls && !@no_null_list.include?(k)
149
+ elsif @send_nulls && !@no_null_list.include?(k) && r.key?(k)
101
150
  sobject_xml += "<#{k} xsi:nil=\"true\"/>"
102
151
  end
103
152
  end
@@ -142,11 +191,18 @@ module SalesforceBulkApi
142
191
  state = []
143
192
  Timeout::timeout(timeout, SalesforceBulkApi::JobTimeout) do
144
193
  while true
145
- if self.check_job_status['state'][0] == 'Closed'
146
- @batch_ids.each do |batch_id|
147
- batch_state = self.check_batch_status(batch_id)
148
- if batch_state['state'][0] != "Queued" && batch_state['state'][0] != "InProgress"
149
- 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])
150
206
  @batch_ids.delete(batch_id)
151
207
  end
152
208
  end
@@ -190,6 +246,17 @@ module SalesforceBulkApi
190
246
  results
191
247
  end
192
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
+
193
260
  end
194
261
 
195
262
  class JobTimeout < StandardError
@@ -1,3 +1,3 @@
1
1
  module SalesforceBulkApi
2
- VERSION = '0.0.8'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -3,27 +3,26 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
  require "salesforce_bulk_api/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = "salesforce_bulk_api"
6
+ s.name = 'salesforce_bulk_api'
7
7
  s.version = SalesforceBulkApi::VERSION
8
- s.authors = ["Yatish Mehta"]
9
- s.email = ["yatishmehta27@gmail.com"]
8
+ s.authors = ['Yatish Mehta']
9
+ s.email = ['yatishmehta27@gmail.com']
10
10
 
11
- s.homepage = "https://github.com/yatishmehta27/salesforce_bulk_api"
11
+ s.homepage = 'https://github.com/yatishmehta27/salesforce_bulk_api'
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"
15
+ s.add_dependency('json', ['>= 0'])
16
+ s.add_dependency('xml-simple', ['>= 0'])
17
+
18
+ s.add_development_dependency 'rspec'
19
+ s.add_development_dependency 'restforce', '~> 3.0.0'
20
+ s.add_development_dependency "rake", ">= 12.3.3"
21
+ s.add_development_dependency 'pry'
16
22
 
17
- s.add_dependency(%q<json>, [">= 0"])
18
- s.add_dependency(%q<xml-simple>, [">= 0"])
19
-
20
- s.add_development_dependency "rspec"
21
- s.add_development_dependency("webmock", ["~> 1.13"])
22
- s.add_development_dependency("vcr", ['~> 2.5'])
23
-
24
23
  s.files = `git ls-files`.split("\n")
25
24
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
25
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
- s.require_paths = ["lib"]
26
+ s.require_paths = ['lib']
28
27
 
29
28
  end
@@ -1,63 +1,80 @@
1
1
  require 'spec_helper'
2
2
  require 'yaml'
3
- require 'databasedotcom'
3
+ require 'restforce'
4
4
 
5
5
  describe SalesforceBulkApi do
6
6
 
7
7
  before :each do
8
- auth_hash = YAML.load(File.read('auth_credentials.yml'))
9
- @sf_client = Databasedotcom::Client.new(:client_id => auth_hash['salesforce']['client_id'],
10
- :client_secret => auth_hash['salesforce']['client_secret'])
11
- @sf_client.authenticate(:username => auth_hash['salesforce']['user'], :password => auth_hash['salesforce']['passwordandtoken'])
8
+ auth_hash = YAML.load_file('auth_credentials.yml')
9
+ sfdc_auth_hash = auth_hash['salesforce']
10
+
11
+ @sf_client = Restforce.new(
12
+ username: sfdc_auth_hash['user'],
13
+ password: sfdc_auth_hash['passwordandtoken'],
14
+ client_id: sfdc_auth_hash['client_id'],
15
+ client_secret: sfdc_auth_hash['client_secret'],
16
+ host: sfdc_auth_hash['host'])
17
+ @sf_client.authenticate!
18
+
19
+ @account_id = auth_hash['salesforce']['test_account_id']
20
+
12
21
  @api = SalesforceBulkApi::Api.new(@sf_client)
13
22
  end
14
23
 
24
+ after :each do
25
+
26
+ end
27
+
15
28
  describe 'upsert' do
16
29
 
17
30
  context 'when not passed get_result' do
18
31
  it "doesn't return the batches array" do
19
- res = @api.upsert('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'}], 'Id')
32
+ res = @api.upsert('Account', [{:Id => @account_id, :Website => 'www.test.com'}], 'Id')
20
33
  res['batches'].should be_nil
21
34
  end
22
35
  end
23
36
 
24
37
  context 'when passed get_result = true' do
25
38
  it 'returns the batches array' do
26
- res = @api.upsert('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'}], 'Id', true)
39
+ res = @api.upsert('Account', [{:Id => @account_id, :Website => 'www.test.com'}], 'Id', true)
27
40
  res['batches'][0]['response'].is_a? Array
28
- res['batches'][0]['response'][0].should eq({'id'=>['0013000000ymMBhAAM'], 'success'=>['true'], 'created'=>['false']})
41
+
42
+ res['batches'][0]['response'][0]['id'][0].should start_with(@account_id)
43
+ res['batches'][0]['response'][0]['success'].should eq ['true']
44
+ res['batches'][0]['response'][0]['created'].should eq ['false']
45
+
29
46
  end
30
47
  end
31
48
 
32
49
  context 'when passed send_nulls = true' do
33
50
  it 'sets the nil and empty attributes to NULL' do
34
- @api.update('Account', [{:Id => '0013000000ymMBh', :Website => 'abc123', :Other_Phone__c => '5678', :Gold_Star__c => true}], true)
35
- res = @api.query('Account', "SELECT Website, Other_Phone__c From Account WHERE Id = '0013000000ymMBh'")
51
+ @api.update('Account', [{:Id => @account_id, :Website => 'abc123', :Phone => '5678'}], true)
52
+ res = @api.query('Account', "SELECT Website, Phone From Account WHERE Id = '#{@account_id}'")
36
53
  res['batches'][0]['response'][0]['Website'][0].should eq 'abc123'
37
- res['batches'][0]['response'][0]['Other_Phone__c'][0].should eq '5678'
38
- res = @api.upsert('Account', [{:Id => '0013000000ymMBh', :Website => '', :Other_Phone__c => nil, :Gold_Star__c => false, :CRM_Last_Modified__c => nil}], 'Id', true, true)
39
- res['batches'][0]['response'][0].should eq({'id'=>['0013000000ymMBhAAM'], 'success'=>['true'], 'created'=>['false']})
40
- res = @api.query('Account', "SELECT Website, Other_Phone__c, Gold_Star__c, CRM_Last_Modified__c From Account WHERE Id = '0013000000ymMBh'")
54
+ res['batches'][0]['response'][0]['Phone'][0].should eq '5678'
55
+ res = @api.upsert('Account', [{:Id => @account_id, :Website => '', :Phone => nil}], 'Id', true, true)
56
+ res['batches'][0]['response'][0]['id'][0].should start_with(@account_id)
57
+ res['batches'][0]['response'][0]['success'].should eq ['true']
58
+ res['batches'][0]['response'][0]['created'].should eq ['false']
59
+ res = @api.query('Account', "SELECT Website, Phone From Account WHERE Id = '#{@account_id}'")
41
60
  res['batches'][0]['response'][0]['Website'][0].should eq({"xsi:nil" => "true"})
42
- res['batches'][0]['response'][0]['Other_Phone__c'][0].should eq({"xsi:nil" => "true"})
43
- res['batches'][0]['response'][0]['Gold_Star__c'][0].should eq('false')
44
- res['batches'][0]['response'][0]['CRM_Last_Modified__c'][0].should eq({"xsi:nil" => "true"})
61
+ res['batches'][0]['response'][0]['Phone'][0].should eq({"xsi:nil" => "true"})
45
62
  end
46
63
  end
47
64
 
48
65
  context 'when passed send_nulls = true and an array of fields not to null' do
49
66
  it 'sets the nil and empty attributes to NULL, except for those included in the list of fields to ignore' do
50
- @api.update('Account', [{:Id => '0013000000ymMBh', :Website => 'abc123', :Other_Phone__c => '5678', :Gold_Star__c => true}], true)
51
- res = @api.query('Account', "SELECT Website, Other_Phone__c From Account WHERE Id = '0013000000ymMBh'")
67
+ @api.update('Account', [{:Id => @account_id, :Website => 'abc123', :Phone => '5678'}], true)
68
+ res = @api.query('Account', "SELECT Website, Phone From Account WHERE Id = '#{@account_id}'")
52
69
  res['batches'][0]['response'][0]['Website'][0].should eq 'abc123'
53
- res['batches'][0]['response'][0]['Other_Phone__c'][0].should eq '5678'
54
- res = @api.upsert('Account', [{:Id => '0013000000ymMBh', :Website => '', :Other_Phone__c => nil, :Gold_Star__c => false, :CRM_Last_Modified__c => nil}], 'Id', true, true, [:Website, :Other_Phone__c])
55
- res['batches'][0]['response'][0].should eq({'id'=>['0013000000ymMBhAAM'], 'success'=>['true'], 'created'=>['false']})
56
- res = @api.query('Account', "SELECT Website, Other_Phone__c, Gold_Star__c, CRM_Last_Modified__c From Account WHERE Id = '0013000000ymMBh'")
70
+ res['batches'][0]['response'][0]['Phone'][0].should eq '5678'
71
+ res = @api.upsert('Account', [{:Id => @account_id, :Website => '', :Phone => nil}], 'Id', true, true, [:Website, :Phone])
72
+ res['batches'][0]['response'][0]['id'][0].should start_with(@account_id)
73
+ res['batches'][0]['response'][0]['success'].should eq ['true']
74
+ res['batches'][0]['response'][0]['created'].should eq ['false']
75
+ res = @api.query('Account', "SELECT Website, Phone From Account WHERE Id = '#{@account_id}'")
57
76
  res['batches'][0]['response'][0]['Website'][0].should eq('abc123')
58
- res['batches'][0]['response'][0]['Other_Phone__c'][0].should eq('5678')
59
- res['batches'][0]['response'][0]['Gold_Star__c'][0].should eq('false')
60
- res['batches'][0]['response'][0]['CRM_Last_Modified__c'][0].should eq({"xsi:nil" => "true"})
77
+ res['batches'][0]['response'][0]['Phone'][0].should eq('5678')
61
78
  end
62
79
  end
63
80
 
@@ -67,16 +84,18 @@ describe SalesforceBulkApi do
67
84
  context 'when there is not an error' do
68
85
  context 'when not passed get_result' do
69
86
  it "doesnt return the batches array" do
70
- res = @api.update('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'}])
87
+ res = @api.update('Account', [{:Id => @account_id, :Website => 'www.test.com'}])
71
88
  res['batches'].should be_nil
72
89
  end
73
90
  end
74
91
 
75
92
  context 'when passed get_result = true' do
76
93
  it 'returns the batches array' do
77
- res = @api.update('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'}], true)
94
+ res = @api.update('Account', [{:Id => @account_id, :Website => 'www.test.com'}], true)
78
95
  res['batches'][0]['response'].is_a? Array
79
- res['batches'][0]['response'][0].should eq({'id'=>['0013000000ymMBhAAM'], 'success'=>['true'], 'created'=>['false']})
96
+ res['batches'][0]['response'][0]['id'][0].should start_with(@account_id)
97
+ res['batches'][0]['response'][0]['success'].should eq ['true']
98
+ res['batches'][0]['response'][0]['created'].should eq ['false']
80
99
  end
81
100
  end
82
101
  end
@@ -84,16 +103,26 @@ describe SalesforceBulkApi do
84
103
  context 'when there is an error' do
85
104
  context 'when not passed get_result' do
86
105
  it "doesn't return the results array" do
87
- res = @api.update('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'},{:Id => 'abc123', :Website => 'www.test.com'}])
106
+ res = @api.update('Account', [{:Id => @account_id, :Website => 'www.test.com'},{:Id => 'abc123', :Website => 'www.test.com'}])
88
107
  res['batches'].should be_nil
89
108
  end
90
109
  end
91
110
 
92
111
  context 'when passed get_result = true with batches' do
93
112
  it 'returns the results array' do
94
- res = @api.update('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'}, {:Id => '0013000000ymMBh', :Website => 'www.test.com'}, {:Id => '0013000000ymMBh', :Website => 'www.test.com'}, {:Id => 'abc123', :Website => 'www.test.com'}], true, false, [], 2)
95
- res['batches'][0]['response'].should eq([{"id"=>["0013000000ymMBhAAM"], "success"=>["true"], "created"=>["false"]}, {"errors"=>[{"fields"=>["Id"], "message"=>["Account ID: id value of incorrect type: abc123"], "statusCode"=>["MALFORMED_ID"]}], "success"=>["false"], "created"=>["false"]}])
96
- res['batches'][1]['response'].should eq([{"id"=>["0013000000ymMBhAAM"], "success"=>["true"], "created"=>["false"]},{"id"=>["0013000000ymMBhAAM"], "success"=>["true"], "created"=>["false"]}])
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
+
115
+ res['batches'][0]['response'][0]['id'][0].should start_with(@account_id)
116
+ res['batches'][0]['response'][0]['success'].should eq ['true']
117
+ res['batches'][0]['response'][0]['created'].should eq ['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']
121
+
122
+ res['batches'][1]['response'][0]['id'][0].should start_with(@account_id)
123
+ res['batches'][1]['response'][0]['success'].should eq ['true']
124
+ res['batches'][1]['response'][0]['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"]})
97
126
  end
98
127
  end
99
128
  end
@@ -116,13 +145,10 @@ describe SalesforceBulkApi do
116
145
  res['batches'][0]['response'].length.should > 1
117
146
  res['batches'][0]['response'][0]['Id'].should_not be_nil
118
147
  end
148
+
119
149
  context 'and there are multiple batches' do
120
- it 'returns the query results in a merged hash' do
121
- pending 'need dev to create > 10k records in dev organization'
122
- res = @api.query('Account', "SELECT id, Name From Account WHERE Name LIKE 'Test%'")
123
- res['batches'][0]['response'].length.should > 1
124
- res['batches'][0]['response'][0]['Id'].should_not be_nil
125
- end
150
+ # need dev to create > 10k records in dev organization
151
+ it 'returns the query results in a merged hash'
126
152
  end
127
153
  end
128
154
 
@@ -142,4 +168,26 @@ describe SalesforceBulkApi do
142
168
 
143
169
  end
144
170
 
171
+ describe 'counters' do
172
+ context 'when read operations are called' do
173
+ it 'increments operation count and http GET count' do
174
+ @api.counters[:http_get].should eq 0
175
+ @api.counters[:query].should eq 0
176
+ @api.query('Account', "SELECT Website, Phone From Account WHERE Id = '#{@account_id}'")
177
+ @api.counters[:http_get].should eq 1
178
+ @api.counters[:query].should eq 1
179
+ end
180
+ end
181
+
182
+ context 'when update operations are called' do
183
+ it 'increments operation count and http POST count' do
184
+ @api.counters[:http_post].should eq 0
185
+ @api.counters[:update].should eq 0
186
+ @api.update('Account', [{:Id => @account_id, :Website => 'abc123', :Phone => '5678'}], true)
187
+ @api.counters[:http_post].should eq 1
188
+ @api.counters[:update].should eq 1
189
+ end
190
+ end
191
+ end
192
+
145
193
  end
@@ -1,16 +1,8 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
- #require 'webmock/rspec'
4
- #require 'vcr'
5
3
  require 'salesforce_bulk_api'
6
4
 
7
5
  RSpec.configure do |c|
8
6
  c.filter_run :focus => true
9
7
  c.run_all_when_everything_filtered = true
10
8
  end
11
-
12
- # enable this and record the test requests using a SF developer org.
13
- # VCR.configure do |c|
14
- # c.cassette_library_dir = 'spec/cassettes'
15
- # c.hook_into :webmock
16
- # end
metadata CHANGED
@@ -1,85 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: salesforce_bulk_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
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: 2014-01-13 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
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: xml-simple
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: webmock
56
+ name: restforce
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.13'
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.13'
68
+ version: 3.0.0
69
69
  - !ruby/object:Gem::Dependency
70
- name: vcr
70
+ name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ~>
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '2.5'
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: '2.5'
82
+ version: 12.3.3
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  description: Salesforce Bulk API with governor limits taken care of
84
98
  email:
85
99
  - yatishmehta27@gmail.com
@@ -87,13 +101,15 @@ executables: []
87
101
  extensions: []
88
102
  extra_rdoc_files: []
89
103
  files:
90
- - .gitignore
91
- - .rspec
104
+ - ".gitignore"
105
+ - ".rspec"
92
106
  - Gemfile
107
+ - LICENCE
93
108
  - README.md
94
109
  - Rakefile
95
- - example_auth_credentials.yml
110
+ - auth_credentials.yml.example
96
111
  - lib/salesforce_bulk_api.rb
112
+ - lib/salesforce_bulk_api/concerns/throttling.rb
97
113
  - lib/salesforce_bulk_api/connection.rb
98
114
  - lib/salesforce_bulk_api/job.rb
99
115
  - lib/salesforce_bulk_api/version.rb
@@ -109,17 +125,16 @@ require_paths:
109
125
  - lib
110
126
  required_ruby_version: !ruby/object:Gem::Requirement
111
127
  requirements:
112
- - - '>='
128
+ - - ">="
113
129
  - !ruby/object:Gem::Version
114
130
  version: '0'
115
131
  required_rubygems_version: !ruby/object:Gem::Requirement
116
132
  requirements:
117
- - - '>='
133
+ - - ">="
118
134
  - !ruby/object:Gem::Version
119
135
  version: '0'
120
136
  requirements: []
121
- rubyforge_project: salesforce_bulk_api
122
- rubygems_version: 2.1.11
137
+ rubygems_version: 3.1.2
123
138
  signing_key:
124
139
  specification_version: 4
125
140
  summary: It uses the bulk api of salesforce to communicate with Salesforce CRM
@@ -1,5 +0,0 @@
1
- salesforce:
2
- client_id: client_id_here
3
- client_secret: client_secret_here
4
- user: sf_user@example.com
5
- passwordandtoken: passandtokenhere