diff --git a/lib/restforce/concerns/api.rb b/lib/restforce/concerns/api.rb index a14960a6..9e3945f9 100644 --- a/lib/restforce/concerns/api.rb +++ b/lib/restforce/concerns/api.rb @@ -327,6 +327,125 @@ def update!(sobject, attrs) true end + # Public: Insert collection of records. + # + # attrs_collection - Collection of attributes for new records. + # all_or_none - Fail all collection if one record creation fails. + # + # Examples + # + # # Add new accounts + # client.create_collection([{Name: 'Foo Inc.'}, {Name: 'Bar Corp.'}]) + # # => [{ 'id' => '0016000000MRatd', 'success' => true, 'errors' => [] }, + # # { 'id' => '0016000000MRert', 'success' => true, 'errors' => [] }] + # + # Returns array of result objects. + # Returns false if something bad happens. + def create_collection(*args) + create_collection!(*args) + rescue *exceptions + false + end + + # Public: Insert collection of records. + # + # attrs_collection - Collection of attributes for new records. + # all_or_none - Fail all collection if one record creation fails. + # + # Examples + # + # # Add new accounts + # client.create_collection!([{Name: 'Foo Inc.'}, {Name: 'Bar Corp.'}]) + # # => [{ 'id' => '1', 'success' => true, 'errors' => [] }, + # # { 'id' => '2', 'success' => true, 'errors' => [] }] + # + # Returns array of result objects. + # Raises exceptions if an error is returned from Salesforce. + def create_collection!(attrs_collection, all_or_none = false) + raise ArgumentError, + 'Amount of records to create is limited to 200' if attrs_collection.size > 200 + + api_post('composite/sobjects', {records: attrs_collection, allOrNone: all_or_none}).body + end + + # Public: Update collection of records. + # + # attrs_collection - Collection of attributes for existing records. + # all_or_none - Fail all collection if one record creation fails. + # + # Examples + # + # # Update accounts + # client.update_collection([{Id: '1', Name: 'Foo Inc.'}, {Id: '2', Name: 'Bar Corp.'}]) + # # => [{ 'id' => '1', 'success' => true, 'errors' => [] }, + # # { 'id' => '2', 'success' => true, 'errors' => [] }] + # + # Returns array of result objects. + # Returns false if something bad happens. + def update_collection(*args) + update_collection!(*args) + rescue *exceptions + false + end + + # Public: Update collection of records. + # + # attrs_collection - Collection of attributes for existing records. + # all_or_none - Fail all collection if one record creation fails. + # + # Examples + # + # # Update accounts + # client.update_collection!([{Id: '1', Name: 'Foo Inc.'}, {Id: '2', Name: 'Bar Corp.'}]) + # # => [{ 'id' => '1', 'success' => true, 'errors' => [] }, + # # { 'id' => '2', 'success' => true, 'errors' => [] }] + # + # Returns array of result objects. + # Raises exceptions if an error is returned from Salesforce. + def update_collection!(attrs_collection, all_or_none = false) + raise ArgumentError, + 'Amount of records to update is limited to 200' if attrs_collection.size > 200 + + api_patch('composite/sobjects', {records: attrs_collection, allOrNone: all_or_none}).body + end + + # Public: Delete collection of records. + # + # ids - List of IDs of objects to be deleted. + # all_or_none - Roll back the entire request when the deletion of any object fails. + # + # Examples + # + # # Delete collection of records + # client.destroy_collection('0016000000MRatd', '0016000000ERats') + # + # Returns array of result objects. + # Returns false if an error is returned from Salesforce. + def destroy_collection(*args) + destroy_collection!(*args) + rescue *exceptions + false + end + + # Public: Delete collection of records. + # + # ids - List of IDs of objects to be deleted. + # all_or_none - Roll back the entire request when the deletion of any object fails. + # + # Examples + # + # # Delete collection of records + # client.destroy_collection!('0016000000MRatd', '0016000000ERats') + # + # Returns array of result objects. + # Raises exceptions if an error is returned from Salesforce. + def destroy_collection!(ids, all_or_none = false) + raise ArgumentError, + 'Amount of records to delete is limited to 200' if ids.size > 200 + + api_delete("composite/sobjects?ids=#{ids.join(',')}&allOrNone=#{all_or_none}").body + end + # Public: Update or create a record based on an external ID # # sobject - The name of the sobject to created. diff --git a/spec/fixtures/sobject/composite_sobjects_destroy_bad_request_response.json b/spec/fixtures/sobject/composite_sobjects_destroy_bad_request_response.json new file mode 100644 index 00000000..cebce329 --- /dev/null +++ b/spec/fixtures/sobject/composite_sobjects_destroy_bad_request_response.json @@ -0,0 +1,6 @@ +[ + { + "message" : "At least 1 Id is required.", + "errorCode" : "REQUIRED_FIELD_MISSING" + } +] diff --git a/spec/fixtures/sobject/composite_sobjects_destroy_success_response.json b/spec/fixtures/sobject/composite_sobjects_destroy_success_response.json new file mode 100644 index 00000000..d3cc4c08 --- /dev/null +++ b/spec/fixtures/sobject/composite_sobjects_destroy_success_response.json @@ -0,0 +1,7 @@ +[ + { + "id" : "Foo", + "success" : true, + "errors" : [ ] + } +] diff --git a/spec/fixtures/sobject/composite_sobjects_error_response.json b/spec/fixtures/sobject/composite_sobjects_error_response.json new file mode 100644 index 00000000..9b619018 --- /dev/null +++ b/spec/fixtures/sobject/composite_sobjects_error_response.json @@ -0,0 +1,4 @@ +{ + "message": "Json Deserialization failed on token 'null' and has left off in the middle of parsing a row. Will go to end of row to begin parsing the next row", + "errorCode": "INVALID_FIELD" +} diff --git a/spec/fixtures/sobject/composite_sobjects_success_response.json b/spec/fixtures/sobject/composite_sobjects_success_response.json new file mode 100644 index 00000000..532c1272 --- /dev/null +++ b/spec/fixtures/sobject/composite_sobjects_success_response.json @@ -0,0 +1,7 @@ +[ + { + "id": "some_id", + "errors": [ ], + "success": true + } +] diff --git a/spec/integration/abstract_client_spec.rb b/spec/integration/abstract_client_spec.rb index d8c30fe8..d059ffe4 100644 --- a/spec/integration/abstract_client_spec.rb +++ b/spec/integration/abstract_client_spec.rb @@ -166,6 +166,150 @@ end end + describe '.create_collection!' do + context 'with valid params' do + requests 'composite/sobjects', + method: :post, + with_body: { + allOrNone: false, + records: [{Name: 'Foobar'}] + }, + fixture: 'sobject/composite_sobjects_success_response' + + subject { client.create_collection!([{Name: 'Foobar'}]) } + + it { should eq [{'id' => 'some_id', 'errors' => [], 'success' => true}] } + end + + context 'with invalid params' do + requests 'composite/sobjects', + method: :post, + status: 400, + with_body: { + allOrNone: false, + records: ['Foo'] + }, + fixture: 'sobject/composite_sobjects_error_response' + + subject { + lambda do + client.create_collection!(['Foo']) + end + } + + it { should raise_error(Faraday::ClientError) } + end + end + + describe '.create_collection' do + context 'with invalid params' do + requests 'composite/sobjects', + method: :post, + status: 400, + with_body: { + allOrNone: false, + records: ['Foo'] + }, + fixture: 'sobject/composite_sobjects_error_response' + + subject { client.create_collection(['Foo']) } + + it { should eq false } + end + end + + describe '.update_collection!' do + context 'with valid params' do + requests 'composite/sobjects', + method: :patch, + with_body: { + allOrNone: false, + records: [{Name: 'Foobar'}] + }, + fixture: 'sobject/composite_sobjects_success_response' + + subject { client.update_collection!([{Name: 'Foobar'}]) } + + it { should eq [{'id' => 'some_id', 'errors' => [], 'success' => true}] } + end + + context 'with invalid params' do + requests 'composite/sobjects', + method: :patch, + status: 400, + with_body: { + allOrNone: false, + records: ['Foo'] + }, + fixture: 'sobject/composite_sobjects_error_response' + + subject { + lambda do + client.update_collection!(['Foo']) + end + } + + it { should raise_error(Faraday::ClientError) } + end + end + + describe '.update_collection' do + context 'with invalid params' do + requests 'composite/sobjects', + method: :patch, + status: 400, + with_body: { + allOrNone: false, + records: ['Foo'] + }, + fixture: 'sobject/composite_sobjects_error_response' + + subject { client.update_collection(['Foo']) } + + it { should eq false } + end + end + + describe '.destroy_collection!' do + context 'with valid params' do + requests 'composite/sobjects\?allOrNone=false&ids=Foo', + method: :delete, + fixture: 'sobject/composite_sobjects_destroy_success_response' + + subject { client.destroy_collection!(['Foo']) } + + it { should eq [{'id' => 'Foo', 'errors' => [], 'success' => true}] } + end + + context 'with invalid params' do + requests 'composite/sobjects\?allOrNone=false&ids=', + method: :delete, + status: 400, + fixture: 'sobject/composite_sobjects_destroy_bad_request_response' + + subject { + lambda do + client.destroy_collection!([]) + end + } + + it { should raise_error(Faraday::ClientError) } + end + end + + describe '.destroy_collection' do + context 'with invalid params' do + requests 'composite/sobjects\?allOrNone=false&ids=', + method: :delete, + status: 400, + fixture: 'sobject/composite_sobjects_destroy_bad_request_response' + + subject { client.destroy_collection([]) } + + it { should eq false } + end + end + describe '.upsert!' do context 'when updated' do requests 'sobjects/Account/External__c/foobar', diff --git a/spec/unit/concerns/api_spec.rb b/spec/unit/concerns/api_spec.rb index e109c8d4..8951a416 100644 --- a/spec/unit/concerns/api_spec.rb +++ b/spec/unit/concerns/api_spec.rb @@ -336,6 +336,72 @@ end end + describe '.create_collection!' do + let(:attrs) { [{'Name' => 'Foobar'}] } + subject(:result) { client.create_collection!(attrs) } + + it 'sends an HTTP POST, and returns list of result objects' do + response.stub(:body).and_return([{'id' => '1', 'success' => true, 'errors' => []}]) + client.should_receive(:api_post). + with('composite/sobjects', {records: attrs, allOrNone: false}). + and_return(response) + expect(result[0]).to eq({'id' => '1', 'success' => true, 'errors' => []}) + end + + context 'when attributes collection size is more than 200' do + let(:attrs) { Array.new(201) } + + it "raises an error" do + expect { client.create_collection!(attrs) }.to raise_error( + ArgumentError, 'Amount of records to create is limited to 200') + end + end + end + + describe '.update_collection!' do + let(:attrs) { [{'id' => '1', 'Name' => 'Foobar'}] } + subject(:result) { client.update_collection!(attrs) } + + it 'sends an HTTP PATCH, and returns list of result objects' do + response.stub(:body).and_return([{'id' => '1', 'success' => true, 'errors' => []}]) + client.should_receive(:api_patch). + with('composite/sobjects', {records: attrs, allOrNone: false}). + and_return(response) + expect(result[0]).to eq({'id' => '1', 'success' => true, 'errors' => []}) + end + + context 'when attributes collection size is more than 200' do + let(:attrs) { Array.new(201) } + + it "raises an error" do + expect { client.update_collection!(attrs) }.to raise_error( + ArgumentError, 'Amount of records to update is limited to 200') + end + end + end + + describe '.destroy_collection!' do + let(:attrs) { ['1'] } + subject(:result) { client.destroy_collection!(attrs) } + + it 'sends an HTTP DELETE, and returns list of result objects' do + response.stub(:body).and_return([{'id' => '1', 'success' => true, 'errors' => []}]) + client.should_receive(:api_delete). + with('composite/sobjects?ids=1&allOrNone=false'). + and_return(response) + expect(result[0]).to eq({'id' => '1', 'success' => true, 'errors' => []}) + end + + context 'when ids collection size is more than 200' do + let(:attrs) { Array.new(201) } + + it "raises an error" do + expect { client.destroy_collection!(attrs) }.to raise_error( + ArgumentError, 'Amount of records to delete is limited to 200') + end + end + end + describe '.upsert!' do let(:sobject) { 'Whizbang' } let(:field) { :External_ID__c }