salesforce_bulk_api 0.0.6 → 0.0.8

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
2
  SHA1:
3
- metadata.gz: 97cff39898ba0d105b249e1e1ba27fdf05e9a518
4
- data.tar.gz: b4765152d9a544e7b80401148ff35ed789da0345
3
+ metadata.gz: e63ba9f2d9be3dfa89bf6d896ee94fe847bc1b57
4
+ data.tar.gz: e054528cb047b09383e98fe2d54b1e3ad628bb0e
5
5
  SHA512:
6
- metadata.gz: 13bf15920245ba4187fb5550249884d04537427c3c849194daf3b867cc1c39f9f3a7f3b7a3ee959b4ff0ba31368a9fa5e0ca75314b126c8097d598aa1cb9e9f6
7
- data.tar.gz: 3396900591b0ccf7a1d2759ee9a52168063bda97716741f993b5cc7979f182c046cad34bff9d38e193f538e5201a4592b6121d0aba0b1bf89a6b4eef4ebc5c6b
6
+ metadata.gz: 7d66df90281412848aba6eed1a5a5476d7883d1e774f28b21dcd2d3a3952027a9e527a474ec2cd702e5b1018c678a25892572206524d1cfbcbd064e7a7577495
7
+ data.tar.gz: 0326e9e4f4389d5052ce92aa04ff2daf65c26df997dc31d8b353f3cca60f1eb5634da45d126fb2db2d3221873353cd78df6ca8bab55d43f14199dd0d0de71c33
data/.gitignore CHANGED
@@ -1,4 +1,7 @@
1
1
  *.gem
2
2
  .bundle
3
3
  Gemfile.lock
4
+ .ruby-gemset
5
+ .ruby-version
4
6
  pkg/*
7
+ auth_credentials.yml
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # Salesforce-Bulk-Api
2
+ [![Gem Version](https://badge.fury.io/rb/salesforce_bulk_api.png)](http://badge.fury.io/rb/salesforce_bulk_api)
3
+ ## Overview
4
+
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
+
7
+ ## How to use
8
+
9
+ Using this gem is simple and straight forward.
10
+
11
+ To initialize:
12
+
13
+ `sudo gem install salesforce_bulk_api`
14
+
15
+ or add
16
+
17
+ `gem salesforce_bulk_api`
18
+
19
+ in your Gemfile
20
+
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.
23
+
24
+ [Databasedotcom](https://github.com/heroku/databasedotcom)
25
+ [Restforce](https://github.com/ejholmes/restforce)
26
+
27
+
28
+ You can use username password combo, OmniAuth, Oauth2
29
+ You can use as many records possible in the Array. Governor limits are taken care of inside the gem.
30
+
31
+
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"
35
+
36
+ salesforce = SalesforceBulkApi::Api.new(client)
37
+
38
+ OR
39
+
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
+
89
+ ## Contribute
90
+
91
+ Feel to fork and send Pull request
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
- require "bundler/gem_tasks"
1
+ require 'rspec/core/rake_task'
2
+ task :default => :spec
3
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,5 @@
1
+ salesforce:
2
+ client_id: client_id_here
3
+ client_secret: client_secret_here
4
+ user: sf_user@example.com
5
+ passwordandtoken: passandtokenhere
@@ -1,4 +1,5 @@
1
1
  module SalesforceBulkApi
2
+ require 'timeout'
2
3
 
3
4
  class Connection
4
5
 
@@ -22,12 +23,17 @@ module SalesforceBulkApi
22
23
  #private
23
24
 
24
25
  def login()
25
- @session_id=@client.oauth_token
26
- @server_url=@client.instance_url
26
+ client_type = @client.class.to_s
27
+ case client_type
28
+ when "Restforce::Data::Client"
29
+ @session_id=@client.options[:oauth_token]
30
+ @server_url=@client.options[:instance_url]
31
+ else
32
+ @session_id=@client.oauth_token
33
+ @server_url=@client.instance_url
34
+ end
27
35
  @instance = parse_instance()
28
- puts @instance
29
36
  @@INSTANCE_HOST = "#{@instance}.salesforce.com"
30
- puts @@INSTANCE_HOST
31
37
  end
32
38
 
33
39
  def post_xml(host, path, xml, headers)
@@ -36,7 +42,19 @@ module SalesforceBulkApi
36
42
  headers['X-SFDC-Session'] = @session_id;
37
43
  path = "#{@@PATH_PREFIX}#{path}"
38
44
  end
39
- https(host).post(path, xml, headers).body
45
+ i = 0
46
+ begin
47
+ https(host).post(path, xml, headers).body
48
+ rescue
49
+ i += 1
50
+ if i < 3
51
+ puts "Request fail #{i}: Retrying #{path}"
52
+ retry
53
+ else
54
+ puts "FATAL: Request to #{path} failed three times."
55
+ raise
56
+ end
57
+ end
40
58
  end
41
59
 
42
60
  def get_request(host, path, headers)
@@ -57,6 +75,8 @@ module SalesforceBulkApi
57
75
 
58
76
  def parse_instance()
59
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
60
80
  end
61
81
 
62
82
  end
@@ -3,22 +3,25 @@ module SalesforceBulkApi
3
3
  class Job
4
4
 
5
5
  def initialize(operation, sobject, records, external_field, connection)
6
-
7
- @@operation = operation
8
- @@sobject = sobject
9
- @@external_field = external_field
10
- @@records = records
11
- @@connection = connection
12
- @@XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
13
-
6
+ @operation = operation
7
+ @sobject = sobject
8
+ @external_field = external_field
9
+ @records = records
10
+ @connection = connection
11
+ @batch_ids = []
12
+ @XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
14
13
  end
15
14
 
16
- def create_job()
17
- xml = "#{@@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
18
- xml += "<operation>#{@@operation}</operation>"
19
- xml += "<object>#{@@sobject}</object>"
20
- if !@@external_field.nil? # This only happens on upsert
21
- xml += "<externalIdFieldName>#{@@external_field}</externalIdFieldName>"
15
+ def create_job(batch_size, send_nulls, no_null_list)
16
+ @batch_size = batch_size
17
+ @send_nulls = send_nulls
18
+ @no_null_list = no_null_list
19
+
20
+ xml = "#{@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
21
+ xml += "<operation>#{@operation}</operation>"
22
+ xml += "<object>#{@sobject}</object>"
23
+ if !@external_field.nil? # This only happens on upsert
24
+ xml += "<externalIdFieldName>#{@external_field}</externalIdFieldName>"
22
25
  end
23
26
  xml += "<contentType>XML</contentType>"
24
27
  xml += "</jobInfo>"
@@ -26,103 +29,169 @@ module SalesforceBulkApi
26
29
  path = "job"
27
30
  headers = Hash['Content-Type' => 'application/xml; charset=utf-8']
28
31
 
29
- response = @@connection.post_xml(nil, path, xml, headers)
32
+ response = @connection.post_xml(nil, path, xml, headers)
30
33
  response_parsed = XmlSimple.xml_in(response)
31
- puts response
32
- @@job_id = response_parsed['id'][0]
34
+ @job_id = response_parsed['id'][0]
33
35
  end
34
36
 
35
37
  def close_job()
36
- xml = "#{@@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
38
+ xml = "#{@XML_HEADER}<jobInfo xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
37
39
  xml += "<state>Closed</state>"
38
40
  xml += "</jobInfo>"
39
41
 
40
- path = "job/#{@@job_id}"
42
+ path = "job/#{@job_id}"
41
43
  headers = Hash['Content-Type' => 'application/xml; charset=utf-8']
42
44
 
43
- response = @@connection.post_xml(nil, path, xml, headers)
44
- response_parsed = XmlSimple.xml_in(response)
45
-
46
- #job_id = response_parsed['id'][0]
45
+ response = @connection.post_xml(nil, path, xml, headers)
46
+ XmlSimple.xml_in(response)
47
47
  end
48
48
 
49
49
  def add_query
50
- path = "job/#{@@job_id}/batch/"
50
+ path = "job/#{@job_id}/batch/"
51
51
  headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
52
52
 
53
- response = @@connection.post_xml(nil, path, @@records, headers)
53
+ response = @connection.post_xml(nil, path, @records, headers)
54
54
  response_parsed = XmlSimple.xml_in(response)
55
55
 
56
- @@batch_id = response_parsed['id'][0]
56
+ @batch_ids << response_parsed['id'][0]
57
57
  end
58
58
 
59
- def add_batch()
60
- keys = @@records.reduce({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h}.keys
61
- headers = keys
62
- @@records_dup=@@records.clone
63
- super_records=[]
64
- (@@records_dup.size/10000).times do
65
- super_records<<@@records_dup.pop(10000)
59
+ def add_batches
60
+ 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
62
+
63
+ @records_dup = @records.clone
64
+
65
+ super_records = []
66
+ (@records_dup.size/@batch_size).to_i.times do
67
+ super_records << @records_dup.pop(@batch_size)
68
+ end
69
+ super_records << @records_dup unless @records_dup.empty?
70
+
71
+ super_records.each do |batch|
72
+ @batch_ids << add_batch(keys, batch)
66
73
  end
67
- super_records<<@@records_dup
68
-
69
- super_records.each do|batch|
70
- xml = "#{@@XML_HEADER}<sObjects xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\">"
71
- batch.each do |r|
72
- xml += "<sObject>"
73
- keys.each do |k|
74
- xml += "<#{k}>#{r[k]}</#{k}>"
74
+ end
75
+
76
+ def add_batch(keys, batch)
77
+ xml = "#{@XML_HEADER}<sObjects xmlns=\"http://www.force.com/2009/06/asyncapi/dataload\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
78
+ batch.each do |r|
79
+ xml += create_sobject(keys, r)
80
+ end
81
+ xml += '</sObjects>'
82
+ path = "job/#{@job_id}/batch/"
83
+ headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
84
+ response = @connection.post_xml(nil, path, xml, headers)
85
+ response_parsed = XmlSimple.xml_in(response)
86
+ response_parsed['id'][0] if response_parsed['id']
87
+ end
88
+
89
+ def create_sobject(keys, r)
90
+ sobject_xml = '<sObject>'
91
+ keys.each do |k|
92
+ if !r[k].to_s.empty?
93
+ sobject_xml += "<#{k}>"
94
+ if r[k].respond_to?(:encode)
95
+ sobject_xml += r[k].encode(:xml => :text)
96
+ else
97
+ sobject_xml += r[k].to_s
75
98
  end
76
- xml += "</sObject>"
99
+ sobject_xml += "</#{k}>"
100
+ elsif @send_nulls && !@no_null_list.include?(k)
101
+ sobject_xml += "<#{k} xsi:nil=\"true\"/>"
77
102
  end
78
- xml += "</sObjects>"
79
-
103
+ end
104
+ sobject_xml += '</sObject>'
105
+ sobject_xml
106
+ end
80
107
 
81
- path = "job/#{@@job_id}/batch/"
82
- headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
83
- response = @@connection.post_xml(nil, path, xml, headers)
84
- response_parsed = XmlSimple.xml_in(response)
108
+ def check_job_status
109
+ path = "job/#{@job_id}"
110
+ headers = Hash.new
111
+ response = @connection.get_request(nil, path, headers)
85
112
 
86
- @@batch_id = response_parsed['id'][0]
87
- puts @@batch_id
113
+ begin
114
+ response_parsed = XmlSimple.xml_in(response) if response
115
+ response_parsed
116
+ rescue StandardError => e
117
+ puts "Error parsing XML response for #{@job_id}"
118
+ puts e
119
+ puts e.backtrace
88
120
  end
89
121
  end
90
122
 
91
- def check_batch_status()
92
- path = "job/#{@@job_id}/batch/#{@@batch_id}"
123
+ def check_batch_status(batch_id)
124
+ path = "job/#{@job_id}/batch/#{batch_id}"
93
125
  headers = Hash.new
94
126
 
95
- response = @@connection.get_request(nil, path, headers)
96
- response_parsed = XmlSimple.xml_in(response)
127
+ response = @connection.get_request(nil, path, headers)
97
128
 
98
- # puts response_parsed
99
129
  begin
100
- #puts "check: #{response_parsed.inspect}\n"
130
+ response_parsed = XmlSimple.xml_in(response) if response
101
131
  response_parsed
102
- rescue Exception => e
103
- #puts "check: #{response_parsed.inspect}\n"
104
- nil
132
+ rescue StandardError => e
133
+ puts "Error parsing XML response for #{@job_id}, batch #{batch_id}"
134
+ puts e
135
+ puts e.backtrace
105
136
  end
106
137
  end
107
138
 
108
- def get_batch_result()
109
- path = "job/#{@@job_id}/batch/#{@@batch_id}/result"
139
+ def get_job_result(return_result, timeout)
140
+ # timeout is in seconds
141
+ begin
142
+ state = []
143
+ Timeout::timeout(timeout, SalesforceBulkApi::JobTimeout) do
144
+ 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)
150
+ @batch_ids.delete(batch_id)
151
+ end
152
+ end
153
+ break if @batch_ids.empty?
154
+ else
155
+ break
156
+ end
157
+ end
158
+ end
159
+ rescue SalesforceBulkApi::JobTimeout => e
160
+ puts 'Timeout waiting for Salesforce to process job batches #{@batch_ids} of job #{@job_id}.'
161
+ puts e
162
+ raise
163
+ end
164
+
165
+ state.each_with_index do |batch_state, i|
166
+ if batch_state['state'][0] == 'Completed' && return_result == true
167
+ state[i].merge!({'response' => self.get_batch_result(batch_state['id'][0])})
168
+ end
169
+ end
170
+ state
171
+ end
172
+
173
+ def get_batch_result(batch_id)
174
+ path = "job/#{@job_id}/batch/#{batch_id}/result"
110
175
  headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
111
176
 
112
- response = @@connection.get_request(nil, path, headers)
177
+ response = @connection.get_request(nil, path, headers)
178
+ response_parsed = XmlSimple.xml_in(response)
179
+ results = response_parsed['result'] unless @operation == 'query'
113
180
 
114
- if(@@operation == "query") # The query op requires us to do another request to get the results
115
- response_parsed = XmlSimple.xml_in(response)
181
+ if(@operation == 'query') # The query op requires us to do another request to get the results
116
182
  result_id = response_parsed["result"][0]
117
-
118
- path = "job/#{@@job_id}/batch/#{@@batch_id}/result/#{result_id}"
183
+ path = "job/#{@job_id}/batch/#{batch_id}/result/#{result_id}"
119
184
  headers = Hash.new
120
185
  headers = Hash["Content-Type" => "application/xml; charset=UTF-8"]
121
- response = @@connection.get_request(nil, path, headers)
186
+ response = @connection.get_request(nil, path, headers)
187
+ response_parsed = XmlSimple.xml_in(response)
188
+ results = response_parsed['records']
122
189
  end
123
-
124
-
190
+ results
125
191
  end
126
192
 
127
193
  end
194
+
195
+ class JobTimeout < StandardError
196
+ end
128
197
  end
@@ -1,3 +1,3 @@
1
1
  module SalesforceBulkApi
2
- VERSION = "0.0.6"
2
+ VERSION = '0.0.8'
3
3
  end
@@ -1,14 +1,15 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.require()
1
4
  require "salesforce_bulk_api/version"
2
5
  require 'net/https'
3
- require 'rubygems'
4
6
  require 'xmlsimple'
5
7
  require 'csv'
6
- require "salesforce_bulk_api/version"
7
8
  require 'salesforce_bulk_api/job'
8
9
  require 'salesforce_bulk_api/connection'
9
10
 
10
11
  module SalesforceBulkApi
11
- # Your code goes here...
12
+
12
13
  class Api
13
14
 
14
15
  @@SALESFORCE_API_VERSION = '23.0'
@@ -17,50 +18,36 @@ module SalesforceBulkApi
17
18
  @connection = SalesforceBulkApi::Connection.new(@@SALESFORCE_API_VERSION,client)
18
19
  end
19
20
 
20
- def upsert(sobject, records, external_field)
21
- self.do_operation('upsert', sobject, records, external_field)
21
+ 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)
22
23
  end
23
24
 
24
- def update(sobject, records)
25
- self.do_operation('update', sobject, records, nil)
25
+ 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)
26
27
  end
27
28
 
28
- def create(sobject, records)
29
- self.do_operation('insert', sobject, records, nil)
29
+ 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)
30
31
  end
31
32
 
32
- def delete(sobject, records)
33
- self.do_operation('delete', sobject, records, nil)
33
+ 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)
34
35
  end
35
36
 
36
- def query(sobject, query)
37
- self.do_operation('query', sobject, query, nil)
37
+ def query(sobject, query, batch_size = 10000, timeout = 1500)
38
+ self.do_operation('query', sobject, query, nil, true, timeout, batch_size)
38
39
  end
39
40
 
40
41
  #private
41
42
 
42
- def do_operation(operation, sobject, records, external_field)
43
+ def do_operation(operation, sobject, records, external_field, get_response, timeout, batch_size, send_nulls = false, no_null_list = [])
43
44
  job = SalesforceBulkApi::Job.new(operation, sobject, records, external_field, @connection)
44
45
 
45
- # TODO: put this in one function
46
- job_id = job.create_job()
47
- batch_id = operation == "query" ? job.add_query() : job.add_batch()
48
- job.close_job()
49
-
50
- while true
51
- state = job.check_batch_status()
52
- if state['state'][0] != "Queued" && state['state'][0] != "InProgress"
53
- break
54
- end
55
- sleep(2) # wait x seconds and check again
56
- end
57
-
58
- if state['state'][0] == 'Completed'
59
- job.get_batch_result()
60
- return state
61
- else
62
- return "error"
63
- end
46
+ job.create_job(batch_size, send_nulls, no_null_list)
47
+ operation == "query" ? job.add_query() : job.add_batches()
48
+ response = job.close_job
49
+ response.merge!({'batches' => job.get_job_result(get_response, timeout)}) if get_response == true
50
+ response
64
51
  end
65
- end # End class
66
- end
52
+ end
53
+ end
@@ -7,21 +7,23 @@ Gem::Specification.new do |s|
7
7
  s.version = SalesforceBulkApi::VERSION
8
8
  s.authors = ["Yatish Mehta"]
9
9
  s.email = ["yatishmehta27@gmail.com"]
10
+
10
11
  s.homepage = "https://github.com/yatishmehta27/salesforce_bulk_api"
11
- s.summary = %q{It uses the bulk api of salesforce to communicate with teh salesforce CRM}
12
+ s.summary = %q{It uses the bulk api of salesforce to communicate with Salesforce CRM}
12
13
  s.description = %q{Salesforce Bulk API with governor limits taken care of}
13
14
 
14
15
  s.rubyforge_project = "salesforce_bulk_api"
15
- s.add_dependency(%q<oauth2>, [">= 0.9.1"])
16
- s.add_dependency(%q<databasedotcom>, [">= 0"])
16
+
17
17
  s.add_dependency(%q<json>, [">= 0"])
18
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
+
19
24
  s.files = `git ls-files`.split("\n")
20
25
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
26
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
27
  s.require_paths = ["lib"]
23
28
 
24
- # specify any dependencies here; for example:
25
- # s.add_development_dependency "rspec"
26
- # s.add_runtime_dependency "rest-client"
27
29
  end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+ require 'yaml'
3
+ require 'databasedotcom'
4
+
5
+ describe SalesforceBulkApi do
6
+
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'])
12
+ @api = SalesforceBulkApi::Api.new(@sf_client)
13
+ end
14
+
15
+ describe 'upsert' do
16
+
17
+ context 'when not passed get_result' do
18
+ it "doesn't return the batches array" do
19
+ res = @api.upsert('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'}], 'Id')
20
+ res['batches'].should be_nil
21
+ end
22
+ end
23
+
24
+ context 'when passed get_result = true' do
25
+ it 'returns the batches array' do
26
+ res = @api.upsert('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'}], 'Id', true)
27
+ res['batches'][0]['response'].is_a? Array
28
+ res['batches'][0]['response'][0].should eq({'id'=>['0013000000ymMBhAAM'], 'success'=>['true'], 'created'=>['false']})
29
+ end
30
+ end
31
+
32
+ context 'when passed send_nulls = true' do
33
+ 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'")
36
+ 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'")
41
+ 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"})
45
+ end
46
+ end
47
+
48
+ context 'when passed send_nulls = true and an array of fields not to null' do
49
+ 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'")
52
+ 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'")
57
+ 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"})
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ describe 'update' do
67
+ context 'when there is not an error' do
68
+ context 'when not passed get_result' do
69
+ it "doesnt return the batches array" do
70
+ res = @api.update('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'}])
71
+ res['batches'].should be_nil
72
+ end
73
+ end
74
+
75
+ context 'when passed get_result = true' do
76
+ it 'returns the batches array' do
77
+ res = @api.update('Account', [{:Id => '0013000000ymMBh', :Website => 'www.test.com'}], true)
78
+ res['batches'][0]['response'].is_a? Array
79
+ res['batches'][0]['response'][0].should eq({'id'=>['0013000000ymMBhAAM'], 'success'=>['true'], 'created'=>['false']})
80
+ end
81
+ end
82
+ end
83
+
84
+ context 'when there is an error' do
85
+ context 'when not passed get_result' do
86
+ 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'}])
88
+ res['batches'].should be_nil
89
+ end
90
+ end
91
+
92
+ context 'when passed get_result = true with batches' do
93
+ 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"]}])
97
+ end
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ describe 'create' do
104
+ pending
105
+ end
106
+
107
+ describe 'delete' do
108
+ pending
109
+ end
110
+
111
+ describe 'query' do
112
+
113
+ context 'when there are results' do
114
+ it 'returns the query results' do
115
+ res = @api.query('Account', "SELECT id, Name From Account WHERE Name LIKE 'Test%'")
116
+ res['batches'][0]['response'].length.should > 1
117
+ res['batches'][0]['response'][0]['Id'].should_not be_nil
118
+ end
119
+ 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
126
+ end
127
+ end
128
+
129
+ context 'when there are no results' do
130
+ it 'returns nil' do
131
+ res = @api.query('Account', "SELECT id From Account WHERE Name = 'ABC'")
132
+ res['batches'][0]['response'].should eq nil
133
+ end
134
+ end
135
+
136
+ context 'when there is an error' do
137
+ it 'returns nil' do
138
+ res = @api.query('Account', "SELECT id From Account WHERE Name = ''ABC'")
139
+ res['batches'][0]['response'].should eq nil
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ #require 'webmock/rspec'
4
+ #require 'vcr'
5
+ require 'salesforce_bulk_api'
6
+
7
+ RSpec.configure do |c|
8
+ c.filter_run :focus => true
9
+ c.run_all_when_everything_filtered = true
10
+ 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,31 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: salesforce_bulk_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yatish Mehta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-16 00:00:00.000000000 Z
11
+ date: 2014-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: oauth2
14
+ name: json
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - '>='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.9.1
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
- version: 0.9.1
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: databasedotcom
28
+ name: xml-simple
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '>='
@@ -39,13 +39,13 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: json
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
- type: :runtime
48
+ type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
@@ -53,19 +53,33 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: xml-simple
56
+ name: webmock
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :runtime
61
+ version: '1.13'
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: '0'
68
+ version: '1.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: vcr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '2.5'
69
83
  description: Salesforce Bulk API with governor limits taken care of
70
84
  email:
71
85
  - yatishmehta27@gmail.com
@@ -74,14 +88,18 @@ extensions: []
74
88
  extra_rdoc_files: []
75
89
  files:
76
90
  - .gitignore
91
+ - .rspec
77
92
  - Gemfile
78
- - README.rdoc
93
+ - README.md
79
94
  - Rakefile
95
+ - example_auth_credentials.yml
80
96
  - lib/salesforce_bulk_api.rb
81
97
  - lib/salesforce_bulk_api/connection.rb
82
98
  - lib/salesforce_bulk_api/job.rb
83
99
  - lib/salesforce_bulk_api/version.rb
84
100
  - salesforce_bulk_api.gemspec
101
+ - spec/salesforce_bulk_api/salesforce_bulk_api_spec.rb
102
+ - spec/spec_helper.rb
85
103
  homepage: https://github.com/yatishmehta27/salesforce_bulk_api
86
104
  licenses: []
87
105
  metadata: {}
@@ -101,8 +119,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
119
  version: '0'
102
120
  requirements: []
103
121
  rubyforge_project: salesforce_bulk_api
104
- rubygems_version: 2.0.0
122
+ rubygems_version: 2.1.11
105
123
  signing_key:
106
124
  specification_version: 4
107
- summary: It uses the bulk api of salesforce to communicate with teh salesforce CRM
108
- test_files: []
125
+ summary: It uses the bulk api of salesforce to communicate with Salesforce CRM
126
+ test_files:
127
+ - spec/salesforce_bulk_api/salesforce_bulk_api_spec.rb
128
+ - spec/spec_helper.rb
data/README.rdoc DELETED
@@ -1,66 +0,0 @@
1
- = Salesforce-Bulk-Api
2
-
3
- ==Overview
4
-
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
-
7
- ==How to use
8
-
9
- Using this gem is simple and straight forward.
10
-
11
- To initialize:
12
- sudo gem install salesforce_bulk_api
13
- or add
14
- gem salesforce_bulk_api
15
- in your Gemfile
16
-
17
-
18
- Please do check the entire documentation of the databasedotcom gem and it various ways of authentication
19
- Databasedotcom[https://github.com/heroku/databasedotcom]
20
-
21
- You can use username password combo, OmniAuth, Oauth2
22
- You can as many records possible in the Array. Governor limits are taken care of inside the gem.
23
-
24
-
25
-
26
- require 'salesforce_bulk_api'
27
- client = Databasedotcom::Client.new :client_id => $SFDC_APP_CONFIG["client_id"], :client_secret => $SFDC_APP_CONFIG["client_secret"] #client_id and client_secret respectively
28
- client.authenticate :token => "my-oauth-token", :instance_url => "http://na1.salesforce.com" #=> "my-oauth-token"
29
- salesforce = SalesforceBulkApi::Api.new(client)
30
-
31
- Sample operations:
32
-
33
- # Insert/Create
34
- new_account = Hash["name" => "Test Account", "type" => "Other"] # Add as many fields per record as needed.
35
- records_to_insert = Array.new
36
- records_to_insert.push(new_account) # You can add as many records as you want here, just keep in mind that Salesforce has governor limits.
37
- result = salesforce.create("Account", records_to_insert)
38
- puts "result is: #{result.inspect}"
39
-
40
- # Update
41
- updated_account = Hash["name" => "Test Account -- Updated", id => "a00A0001009zA2m"] # Nearly identical to an insert, but we need to pass the salesforce id.
42
- records_to_update = Array.new
43
- records_to_update.push(updated_account)
44
- salesforce.update("Account", records_to_update)
45
-
46
- # Upsert
47
- upserted_account = Hash["name" => "Test Account -- Upserted", "External_Field_Name" => "123456"] # Fields to be updated. External field must be included
48
- records_to_upsert = Array.new
49
- records_to_upsert.push(upserted_account)
50
- salesforce.upsert("Account", records_to_upsert, "External_Field_Name") # Note that upsert accepts an extra parameter for the external field name
51
-
52
- # Delete
53
- deleted_account = Hash["id" => "a00A0001009zA2m"] # We only specify the id of the records to delete
54
- records_to_delete = Array.new
55
- records_to_delete.push(deleted_account)
56
- salesforce.delete("Account", records_to_delete)
57
-
58
- # Query
59
- res = salesforce.query("Account", "select id, name, createddate from Account limit 3") # We just need to pass the sobject name and the query string
60
-
61
- ==Installation
62
- sudo gem install salesforce_bulk_api
63
-
64
- == Copyright
65
-
66
- Copyright (c) 2012 Yatish Mehta