restforce 5.0.6 → 5.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6644bf699621fb5414dce1dce275e5a265fca50305bcfca98864b6c1683f1687
4
- data.tar.gz: 5ef0177a3a91dbe1637fc493b7f0bc478fca34c49e392abb1eccdf8bf951aa66
3
+ metadata.gz: 891d81fbf377945b09db6058467d21da1720d985e70ad060e4bcbe35d5efc9f6
4
+ data.tar.gz: 2ce207d94d49504e28680d7b854633cc3c2c0b329ed23b04769528799b71785e
5
5
  SHA512:
6
- metadata.gz: 7c7d3e8b58743f8d4ae6ae9255c2d89cc78b06a5e963350784ff3c884396bf1ef03fb986f2aba7bd231d8ae9af2f0b2af78a77c98fcb62356ac09b1411753e13
7
- data.tar.gz: 5c8bb85fdcd28e5eba8d0939ef1868d42b99d079cddd056941ec7c4cf591d3789caa2b569cafeb8415a89755e567de1a93486dc9c18c5507275adad5631374cc
6
+ metadata.gz: 04e9f867ebadf6a8d61ffad3ebdb14b9924011df9b408efb9f17bb8a65c7ad5a4de1caa18b78dd365b889bf6b32801d7c30d7de02f105be38a3b4111cf83b792
7
+ data.tar.gz: c72397f42ef4a4f95a053557bf8971e3eeff78994b21ccdc232638ef1847c08e7e1c8e9e33f6055725c9fbc57755de85d474cf6063094ec6e33e211a93ba14d4
data/.circleci/config.yml CHANGED
@@ -34,23 +34,23 @@ references:
34
34
  destination: test-results
35
35
 
36
36
  jobs:
37
- build-ruby271:
37
+ build-ruby30:
38
38
  docker:
39
- - image: circleci/ruby:2.7.1
39
+ - image: circleci/ruby:3.0
40
40
  steps: *steps
41
- build-ruby266:
41
+ build-ruby27:
42
42
  docker:
43
- - image: circleci/ruby:2.6.6
43
+ - image: circleci/ruby:2.7
44
44
  steps: *steps
45
- build-ruby258:
45
+ build-ruby26:
46
46
  docker:
47
- - image: circleci/ruby:2.5.8
47
+ - image: circleci/ruby:2.6
48
48
  steps: *steps
49
49
 
50
50
  workflows:
51
51
  version: 2
52
52
  tests:
53
53
  jobs:
54
- - build-ruby271
55
- - build-ruby266
56
- - build-ruby258
54
+ - build-ruby30
55
+ - build-ruby27
56
+ - build-ruby26
data/.rubocop.yml CHANGED
@@ -8,7 +8,7 @@ AllCops:
8
8
  - .*/**/*
9
9
  - vendor/**/*
10
10
  NewCops: enable
11
- TargetRubyVersion: 2.5
11
+ TargetRubyVersion: 2.6
12
12
 
13
13
  # Limit lines to 90 characters.
14
14
  Layout/LineLength:
@@ -71,4 +71,4 @@ Naming/FileName:
71
71
  - Guardfile
72
72
 
73
73
  Lint/UriEscapeUnescape:
74
- Enabled: false
74
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ # 5.2.1 (Dec 8, 2021)
2
+
3
+ * Handle the `OPERATION_TOO_LARGE` error returned by Salesforce (@timrogers)
4
+ * Handle the `INVALID_SIGNUP_COUNTRY` error returned by Salesforce (@timrogers)
5
+
6
+ ## 5.2.0 (Oct 15, 2021)
7
+
8
+ * Add support for Salesforce's Composite API and Composite Batch API (@meenie, @amacdougall)
9
+ * Improve the performance of counting numbers of query results with `Restforce::Collection#count`, avoiding unnecessary API requests (@jhass)
10
+
11
+ ## 5.1.1 (Oct 13, 2021)
12
+
13
+ * Handle the `INVALID_REPLICATION_DATE` error returned by Salesforce (@michaelwnyc)
14
+ * Handle the `BIG_OBJECT_UNSUPPORTED_OPERATION` error returned by Salesforce (@remon)
15
+
16
+ ## 5.1.0 (Aug 26, 2021)
17
+
18
+ * Add official support for Ruby 3.0 (@timrogers)
19
+ * Drop support for Ruby 2.5, which has reached end-of-life (@timrogers)
20
+ * Handle the `QUERY_TIMEOUT` error returned by Salesforce (@timrogers)
21
+ * Remove unnecessary development dependencies for the gem, which can just be in the project's `Gemfile` (@timrogers)
22
+
1
23
  ## 5.0.6 (Jun 17, 2021)
2
24
 
3
25
  * Handle the `API_DISABLED_FOR_ORG` error returned by Salesforce (@cmac)
data/Gemfile CHANGED
@@ -3,11 +3,15 @@
3
3
  source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
+ gem 'faye' unless RUBY_PLATFORM == 'java'
7
+ gem 'guard-rspec'
8
+ gem 'guard-rubocop'
6
9
  gem 'jruby-openssl', platforms: :jruby
7
- gem 'jwt'
8
10
  gem 'rake'
9
-
10
- group :development do
11
- gem 'guard-rspec'
12
- gem 'guard-rubocop'
13
- end
11
+ gem 'rspec', '~> 3.10.0'
12
+ gem 'rspec-collection_matchers', '~> 1.2.0'
13
+ gem 'rspec-its', '~> 1.3.0'
14
+ gem 'rspec_junit_formatter', '~> 0.4.1'
15
+ gem 'rubocop', '~> 1.23.0'
16
+ gem 'simplecov', '~> 0.21.2'
17
+ gem 'webmock', '~> 3.14.0'
data/README.md CHANGED
@@ -12,6 +12,8 @@ Features include:
12
12
  * Support for parent-to-child relationships.
13
13
  * Support for aggregate queries.
14
14
  * Support for the [Streaming API](#streaming)
15
+ * Support for the [Composite API](#composite-api)
16
+ * Support for the [Composite Batch API](#composite-batch-api)
15
17
  * Support for the GetUpdated API
16
18
  * Support for blob data types.
17
19
  * Support for GZIP compression.
@@ -25,7 +27,7 @@ Features include:
25
27
 
26
28
  Add this line to your application's Gemfile:
27
29
 
28
- gem 'restforce', '~> 5.0.6'
30
+ gem 'restforce', '~> 5.2.1'
29
31
 
30
32
  And then execute:
31
33
 
@@ -35,8 +37,9 @@ Or install it yourself as:
35
37
 
36
38
  $ gem install restforce
37
39
 
38
- __As of version 5.0.0, this gem is only compatible with Ruby 2.5.0 and later.__ If you're using an earlier Ruby version:
40
+ __As of version 5.1.0, this gem is only compatible with Ruby 2.6.0 and later.__ If you're using an earlier Ruby version:
39
41
 
42
+ * for Ruby 2.5, use version 5.0.6 or earlier
40
43
  * for Ruby 2.4, use version 4.3.0 or earlier
41
44
  * for Ruby 2.3, use version 3.2.0 or earlier
42
45
  * for Ruby versions 2.2, 2.1 and 2.0, use version 2.5.3 or earlier
@@ -459,7 +462,7 @@ info.user_id
459
462
 
460
463
  ### File Uploads
461
464
 
462
- Using the new [Blob Data](http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_sobject_insert_update_blob.htm) api feature (500mb limit):
465
+ Using the new [Blob Data](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm) api feature (500mb limit):
463
466
 
464
467
  ```ruby
465
468
  client.create('Document', FolderId: '00lE0000000FJ6H',
@@ -477,7 +480,7 @@ client.create('Document', FolderId: '00lE0000000FJ6H',
477
480
  Body: Base64::encode64(File.read('image.jpg'))
478
481
  ```
479
482
 
480
- _See also: [Inserting or updating blob data](http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_sobject_insert_update_blob.htm)_
483
+ _See also: [Inserting or updating blob data](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm)_
481
484
 
482
485
  * * *
483
486
 
@@ -572,6 +575,52 @@ end
572
575
  Boom, you're now receiving push notifications when Accounts are
573
576
  created/updated.
574
577
 
578
+ #### Composite API
579
+
580
+ Restforce supports the [Composite API](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_composite.htm).
581
+ This feature permits the user to send a composite object—that is, a complex
582
+ object with nested children—in a single API call. Up to 25 requests may be
583
+ included in a single composite.
584
+
585
+ Note that `GET` is not yet implemented for this API.
586
+
587
+ ```ruby
588
+ # build up an array of requests:
589
+ requests << {
590
+ method: :update,
591
+ sobject: sobject, # e.g. "Contact"
592
+ reference_id: reference_id,
593
+ data: data
594
+ }
595
+
596
+ # send every 25 requests as a subrequest in a single composite call
597
+ requests.each_slice(25).map do |req_slice|
598
+ client.composite do |subrequest|
599
+ req_slice.each do |r|
600
+ subrequest.send *r.values
601
+ end
602
+ end
603
+ end
604
+
605
+ # note that we're using `map` to return an array of each responses to each
606
+ # composite call; 100 requests will produce 4 responses
607
+ ```
608
+
609
+ #### Composite Batch API
610
+
611
+ Restforce supports the [Composite Batch API](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_batch.htm).
612
+ This feature permits up to 25 subrequests in a single request, though each
613
+ subrequest counts against the API limit. On the other hand, it has fewer
614
+ limitations than the Composite API.
615
+
616
+ ```
617
+ client.batch do |subrequests|
618
+ subrequests.create('Object', name: 'test')
619
+ subrequests.update('Object', id: '123', name: 'test')
620
+ subrequests.destroy('Object', '123')
621
+ end
622
+ ```
623
+
575
624
  #### Replaying Events
576
625
 
577
626
  Since API version 37.0, Salesforce stores events for 24 hours and they can be
@@ -8,5 +8,6 @@ module Restforce
8
8
  include Restforce::Concerns::Caching
9
9
  include Restforce::Concerns::API
10
10
  include Restforce::Concerns::BatchAPI
11
+ include Restforce::Concerns::CompositeAPI
11
12
  end
12
13
  end
@@ -27,12 +27,26 @@ module Restforce
27
27
  @raw_page['records'].size
28
28
  end
29
29
 
30
- # Return the size of the Collection without making any additional requests.
30
+ # Return the number of items in the Collection without making any additional
31
+ # requests and going through all of the pages of results, one by one. Instead,
32
+ # we can rely on the total count of results which Salesforce returns.
31
33
  def size
32
34
  @raw_page['totalSize']
33
35
  end
34
36
  alias length size
35
37
 
38
+ def count(*args)
39
+ # By default, `Enumerable`'s `#count` uses `#each`, which means going through all
40
+ # of the pages of results, one by one. Instead, we can use `#size` which we have
41
+ # already overridden to work in a smarter, more efficient way. This only works for
42
+ # the simple version of `#count` with no arguments. When called with an argument or
43
+ # a block, you need to know what the items in the collection actually are, so we
44
+ # call `super` and end up iterating through each item in the collection.
45
+ return size unless block_given? || !args.empty?
46
+
47
+ super
48
+ end
49
+
36
50
  # Returns true if the size of the Collection is zero.
37
51
  def empty?
38
52
  size.zero?
@@ -380,7 +380,7 @@ module Restforce
380
380
  end
381
381
  else
382
382
  api_patch "sobjects/#{sobject}/#{field}/" \
383
- "#{ERB::Util.url_encode(external_id)}", attrs
383
+ "#{ERB::Util.url_encode(external_id)}", attrs
384
384
  end
385
385
 
386
386
  response.body.respond_to?(:fetch) ? response.body.fetch('id', true) : true
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'restforce/concerns/verbs'
4
+
5
+ module Restforce
6
+ module Concerns
7
+ module CompositeAPI
8
+ extend Restforce::Concerns::Verbs
9
+
10
+ define_verbs :post
11
+
12
+ def composite(all_or_none: false, collate_subrequests: false)
13
+ subrequests = Subrequests.new(options)
14
+ yield(subrequests)
15
+
16
+ if subrequests.requests.length > 25
17
+ raise ArgumentError, 'Cannot have more than 25 subrequests.'
18
+ end
19
+
20
+ properties = {
21
+ compositeRequest: subrequests.requests,
22
+ allOrNone: all_or_none,
23
+ collateSubrequests: collate_subrequests
24
+ }
25
+ response = api_post('composite', properties.to_json)
26
+
27
+ results = response.body['CompositeResponse']
28
+ has_errors = results.any? { |result| result['HttpStatusCode'].digits.last == 4 }
29
+ if all_or_none && has_errors
30
+ last_error_index = results.rindex { |result| result['HttpStatusCode'] != 412 }
31
+ last_error = results[last_error_index]
32
+ raise CompositeAPIError, last_error['Body'][0]['errorCode']
33
+ end
34
+
35
+ results
36
+ end
37
+
38
+ def composite!(collate_subrequests: false, &block)
39
+ composite(all_or_none: true, collate_subrequests: collate_subrequests, &block)
40
+ end
41
+
42
+ class Subrequests
43
+ def initialize(options)
44
+ @options = options
45
+ @requests = []
46
+ end
47
+ attr_reader :options, :requests
48
+
49
+ def create(sobject, reference_id, attrs)
50
+ requests << {
51
+ method: 'POST',
52
+ url: composite_api_path(sobject),
53
+ body: attrs,
54
+ referenceId: reference_id
55
+ }
56
+ end
57
+
58
+ def update(sobject, reference_id, attrs)
59
+ id = attrs.fetch(attrs.keys.find { |k, _v| k.to_s.casecmp?('id') }, nil)
60
+ raise ArgumentError, 'Id field missing from attrs.' unless id
61
+
62
+ attrs_without_id = attrs.reject { |k, _v| k.to_s.casecmp?('id') }
63
+ requests << {
64
+ method: 'PATCH',
65
+ url: composite_api_path("#{sobject}/#{id}"),
66
+ body: attrs_without_id,
67
+ referenceId: reference_id
68
+ }
69
+ end
70
+
71
+ def destroy(sobject, reference_id, id)
72
+ requests << {
73
+ method: 'DELETE',
74
+ url: composite_api_path("#{sobject}/#{id}"),
75
+ referenceId: reference_id
76
+ }
77
+ end
78
+
79
+ def upsert(sobject, reference_id, ext_field, attrs)
80
+ raise ArgumentError, 'External id field missing.' unless ext_field
81
+
82
+ ext_id = attrs.fetch(attrs.keys.find do |k, _v|
83
+ k.to_s.casecmp?(ext_field.to_s)
84
+ end, nil)
85
+ raise ArgumentError, 'External id missing from attrs.' unless ext_id
86
+
87
+ attrs_without_ext_id = attrs.reject { |k, _v| k.to_s.casecmp?(ext_field) }
88
+ requests << {
89
+ method: 'PATCH',
90
+ url: composite_api_path("#{sobject}/#{ext_field}/#{ext_id}"),
91
+ body: attrs_without_ext_id,
92
+ referenceId: reference_id
93
+ }
94
+ end
95
+
96
+ private
97
+
98
+ def composite_api_path(path)
99
+ "/services/data/v#{options[:api_version]}/sobjects/#{path}"
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -85,7 +85,7 @@ module Restforce
85
85
  def valid?(picklist_entry)
86
86
  valid_for = picklist_entry['validFor'].ljust(16, 'A').unpack1('m').
87
87
  unpack('C*')
88
- (valid_for[index >> 3] & (0x80 >> index % 8)).positive?
88
+ (valid_for[index >> 3] & (0x80 >> (index % 8))).positive?
89
89
  end
90
90
  end
91
91
  end
@@ -3,8 +3,8 @@
3
3
  module Restforce
4
4
  module ErrorCode
5
5
  GITHUB_ISSUE_URL = "https://github.com/restforce/restforce/issues/new?template=" \
6
- "unhandled-salesforce-error.md&title=Unhandled+Salesforce+error%3A+%3Cinsert+" \
7
- "error+code+here%3E"
6
+ "unhandled-salesforce-error.md&title=Unhandled+Salesforce+error" \
7
+ "%3A+%3Cinsert+error+code+here%3E"
8
8
 
9
9
  # We define all of the known errors returned by Salesforce based on the
10
10
  # documentation at
@@ -31,6 +31,8 @@ module Restforce
31
31
 
32
32
  class BccSelfNotAllowedIfBccComplianceEnabled < ResponseError; end
33
33
 
34
+ class BigObjectUnsupportedOperation < ResponseError; end
35
+
34
36
  class CannotCascadeProductActive < ResponseError; end
35
37
 
36
38
  class CannotChangeFieldTypeOfApexReferencedField < ResponseError; end
@@ -237,10 +239,14 @@ module Restforce
237
239
 
238
240
  class InvalidReadOnlyUserDml < ResponseError; end
239
241
 
242
+ class InvalidReplicationDate < ResponseError; end
243
+
240
244
  class InvalidSaveAsActivityFlag < ResponseError; end
241
245
 
242
246
  class InvalidSessionId < ResponseError; end
243
247
 
248
+ class InvalidSignupCountry < ResponseError; end
249
+
244
250
  class InvalidStatus < ResponseError; end
245
251
 
246
252
  class InvalidType < ResponseError; end
@@ -327,6 +333,8 @@ module Restforce
327
333
 
328
334
  class OpWithInvalidUserTypeException < ResponseError; end
329
335
 
336
+ class OperationTooLarge < ResponseError; end
337
+
330
338
  class OptedOutOfMassMail < ResponseError; end
331
339
 
332
340
  class PackageLicenseRequired < ResponseError; end
@@ -341,6 +349,8 @@ module Restforce
341
349
 
342
350
  class PrivateContactOnAsset < ResponseError; end
343
351
 
352
+ class QueryTimeout < ResponseError; end
353
+
344
354
  class RecordInUseByWorkflow < ResponseError; end
345
355
 
346
356
  class RequestLimitExceeded < ResponseError; end
@@ -411,6 +421,7 @@ module Restforce
411
421
  BccNotAllowedIfBccComplianceEnabled,
412
422
  "BCC_SELF_NOT_ALLOWED_IF_BCC_COMPLIANCE_ENABLED" =>
413
423
  BccSelfNotAllowedIfBccComplianceEnabled,
424
+ "BIG_OBJECT_UNSUPPORTED_OPERATION" => BigObjectUnsupportedOperation,
414
425
  "CANNOT_CASCADE_PRODUCT_ACTIVE" => CannotCascadeProductActive,
415
426
  "CANNOT_CHANGE_FIELD_TYPE_OF_APEX_REFERENCED_FIELD" =>
416
427
  CannotChangeFieldTypeOfApexReferencedField,
@@ -521,8 +532,10 @@ module Restforce
521
532
  "INVALID_PARTNER_NETWORK_STATUS" => InvalidPartnerNetworkStatus,
522
533
  "INVALID_PERSON_ACCOUNT_OPERATION" => InvalidPersonAccountOperation,
523
534
  "INVALID_READ_ONLY_USER_DML" => InvalidReadOnlyUserDml,
535
+ "INVALID_REPLICATION_DATE" => InvalidReplicationDate,
524
536
  "INVALID_SAVE_AS_ACTIVITY_FLAG" => InvalidSaveAsActivityFlag,
525
537
  "INVALID_SESSION_ID" => InvalidSessionId,
538
+ "INVALID_SIGNUP_COUNTRY" => InvalidSignupCountry,
526
539
  "INVALID_STATUS" => InvalidStatus,
527
540
  "INVALID_TYPE" => InvalidType,
528
541
  "INVALID_TYPE_FOR_OPERATION" => InvalidTypeForOperation,
@@ -566,6 +579,7 @@ module Restforce
566
579
  "NUMBER_OUTSIDE_VALID_RANGE" => NumberOutsideValidRange,
567
580
  "NUM_HISTORY_FIELDS_BY_SOBJECT_EXCEEDED" => NumHistoryFieldsBySobjectExceeded,
568
581
  "OP_WITH_INVALID_USER_TYPE_EXCEPTION" => OpWithInvalidUserTypeException,
582
+ "OPERATION_TOO_LARGE" => OperationTooLarge,
569
583
  "OPTED_OUT_OF_MASS_MAIL" => OptedOutOfMassMail,
570
584
  "PACKAGE_LICENSE_REQUIRED" => PackageLicenseRequired,
571
585
  "PLATFORM_EVENT_ENCRYPTION_ERROR" => PlatformEventEncryptionError,
@@ -573,6 +587,7 @@ module Restforce
573
587
  "PLATFORM_EVENT_PUBLISH_FAILED" => PlatformEventPublishFailed,
574
588
  "PORTAL_USER_ALREADY_EXISTS_FOR_CONTACT" => PortalUserAlreadyExistsForContact,
575
589
  "PRIVATE_CONTACT_ON_ASSET" => PrivateContactOnAsset,
590
+ "QUERY_TIMEOUT" => QueryTimeout,
576
591
  "RECORD_IN_USE_BY_WORKFLOW" => RecordInUseByWorkflow,
577
592
  "REQUEST_LIMIT_EXCEEDED" => RequestLimitExceeded,
578
593
  "REQUEST_RUNNING_TOO_LONG" => RequestRunningTooLong,
@@ -606,9 +621,9 @@ module Restforce
606
621
  def self.get_exception_class(error_code)
607
622
  ERROR_EXCEPTION_CLASSES.fetch(error_code) do |_|
608
623
  warn "[restforce] An unrecognised error code, `#{error_code}` has been " \
609
- "received from Salesforce. Instead of raising an error-specific exception, " \
610
- "we'll raise a generic `ResponseError`. Please report this missing error code" \
611
- " on GitHub at <#{GITHUB_ISSUE_URL}>."
624
+ "received from Salesforce. Instead of raising an error-specific exception" \
625
+ ", we'll raise a generic `ResponseError`. Please report this missing " \
626
+ "error code on GitHub at <#{GITHUB_ISSUE_URL}>."
612
627
 
613
628
  # If we've received an unexpected error where we don't have a specific
614
629
  # class defined, we can return a generic ResponseError instead
@@ -618,10 +633,10 @@ module Restforce
618
633
 
619
634
  def self.const_missing(constant_name)
620
635
  warn "[restforce] You're referring to a Restforce error that isn't defined, " \
621
- "`#{name}::#{constant_name}` (for example by trying to `rescue` it). This might " \
622
- "be our fault - we've recently made some changes to how errors are defined. If " \
623
- "you're sure that this is a valid Salesforce error, then please create an " \
624
- "issue on GitHub at <#{GITHUB_ISSUE_URL}>."
636
+ "`#{name}::#{constant_name}` (for example by trying to `rescue` it). This " \
637
+ "might be our fault - we've recently made some changes to how errors are " \
638
+ "defined. If you're sure that this is a valid Salesforce error, then " \
639
+ "please create an issue on GitHub at <#{GITHUB_ISSUE_URL}>."
625
640
 
626
641
  super(constant_name)
627
642
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Restforce
4
- VERSION = '5.0.6'
4
+ VERSION = '5.2.1'
5
5
  end
data/lib/restforce.rb CHANGED
@@ -32,6 +32,7 @@ module Restforce
32
32
  autoload :Base, 'restforce/concerns/base'
33
33
  autoload :API, 'restforce/concerns/api'
34
34
  autoload :BatchAPI, 'restforce/concerns/batch_api'
35
+ autoload :CompositeAPI, 'restforce/concerns/composite_api'
35
36
  end
36
37
 
37
38
  module Data
@@ -48,6 +49,7 @@ module Restforce
48
49
  UnauthorizedError = Class.new(Faraday::ClientError)
49
50
  APIVersionError = Class.new(Error)
50
51
  BatchAPIError = Class.new(Error)
52
+ CompositeAPIError = Class.new(Error)
51
53
 
52
54
  # Inherit from Faraday::ResourceNotFound for backwards-compatibility
53
55
  # Consumers of this library that rescue and handle Faraday::ResourceNotFound
data/restforce.gemspec CHANGED
@@ -19,23 +19,14 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.metadata = {
21
21
  'source_code_uri' => 'https://github.com/restforce/restforce',
22
- 'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md'
22
+ 'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md',
23
+ 'rubygems_mfa_required' => 'true'
23
24
  }
24
25
 
25
- gem.required_ruby_version = '>= 2.5'
26
+ gem.required_ruby_version = '>= 2.6'
26
27
 
27
28
  gem.add_dependency 'faraday', '<= 2.0', '>= 0.9.0'
28
29
  gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 2.0']
29
-
30
+ gem.add_dependency 'hashie', '>= 1.2.0', '< 6.0'
30
31
  gem.add_dependency 'jwt', ['>= 1.5.6']
31
-
32
- gem.add_dependency 'hashie', '>= 1.2.0', '< 5.0'
33
-
34
- gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
35
- gem.add_development_dependency 'rspec', '~> 2.14.0'
36
- gem.add_development_dependency 'rspec_junit_formatter', '~> 0.4.1'
37
-
38
- gem.add_development_dependency 'rubocop', '~> 1.17.0'
39
- gem.add_development_dependency 'simplecov', '~> 0.21.2'
40
- gem.add_development_dependency 'webmock', '~> 3.13.0'
41
32
  end
@@ -129,18 +129,15 @@ shared_examples_for Restforce::AbstractClient do
129
129
  JSON.parse(fixture('sobject/delete_error_response'))
130
130
  end
131
131
 
132
- subject do
133
- lambda do
134
- client.update!('Account', Id: '001D000000INjVe', Name: 'Foobar')
135
- end
136
- end
132
+ it "raises Faraday::ResourceNotFound" do
133
+ expect { client.update!('Account', Id: '001D000000INjVe', Name: 'Foobar') }.
134
+ to raise_error do |exception|
135
+ expect(exception).to be_a(Faraday::ResourceNotFound)
137
136
 
138
- it {
139
- should raise_error(
140
- Faraday::ResourceNotFound,
141
- "#{error.first['errorCode']}: #{error.first['message']}"
142
- )
143
- }
137
+ expect(exception.message).
138
+ to start_with("#{error.first['errorCode']}: #{error.first['message']}")
139
+ end
140
+ end
144
141
  end
145
142
  end
146
143
 
@@ -158,7 +155,7 @@ shared_examples_for Restforce::AbstractClient do
158
155
  fixture: 'sobject/delete_error_response'
159
156
 
160
157
  subject { client.update('Account', Id: '001D000000INjVe', Name: 'Foobar') }
161
- it { should be_false }
158
+ it { should be false }
162
159
  end
163
160
 
164
161
  context 'with success' do
@@ -172,7 +169,7 @@ shared_examples_for Restforce::AbstractClient do
172
169
  client.update('Account', key => '001D000000INjVe', :Name => 'Foobar')
173
170
  end
174
171
 
175
- it { should be_true }
172
+ it { should be true }
176
173
  end
177
174
  end
178
175
  end
@@ -191,7 +188,7 @@ shared_examples_for Restforce::AbstractClient do
191
188
  Name: 'Foobar')
192
189
  end
193
190
 
194
- it { should be_true }
191
+ it { should be true }
195
192
  end
196
193
 
197
194
  context 'with string external Id key' do
@@ -200,7 +197,7 @@ shared_examples_for Restforce::AbstractClient do
200
197
  'Name' => 'Foobar')
201
198
  end
202
199
 
203
- it { should be_true }
200
+ it { should be true }
204
201
  end
205
202
  end
206
203
 
@@ -257,14 +254,14 @@ shared_examples_for Restforce::AbstractClient do
257
254
  context 'with success' do
258
255
  requests 'sobjects/Account/001D000000INjVe', method: :delete
259
256
 
260
- it { should be_true }
257
+ it { should be true }
261
258
  end
262
259
 
263
260
  context 'with a space in the id' do
264
261
  subject(:destroy!) { client.destroy!('Account', '001D000000 INjVe') }
265
262
  requests 'sobjects/Account/001D000000%20INjVe', method: :delete
266
263
 
267
- it { should be_true }
264
+ it { should be true }
268
265
  end
269
266
  end
270
267
 
@@ -277,13 +274,13 @@ shared_examples_for Restforce::AbstractClient do
277
274
  method: :delete,
278
275
  status: 404
279
276
 
280
- it { should be_false }
277
+ it { should be false }
281
278
  end
282
279
 
283
280
  context 'with success' do
284
281
  requests 'sobjects/Account/001D000000INjVe', method: :delete
285
282
 
286
- it { should be_true }
283
+ it { should be true }
287
284
  end
288
285
  end
289
286
 
@@ -368,8 +365,8 @@ shared_examples_for Restforce::AbstractClient do
368
365
  before do
369
366
  @request = stub_login_request(
370
367
  with_body: "grant_type=password&client_id=client_id" \
371
- "&client_secret=client_secret&username=foo" \
372
- "&password=barsecurity_token"
368
+ "&client_secret=client_secret&username=foo" \
369
+ "&password=barsecurity_token"
373
370
  ).to_return(status: 200, body: fixture(:auth_success_response))
374
371
  end
375
372
 
@@ -420,8 +417,8 @@ shared_examples_for Restforce::AbstractClient do
420
417
 
421
418
  @query_request = stub_login_request(
422
419
  with_body: "grant_type=password&client_id=client_id" \
423
- "&client_secret=client_secret&username=foo&" \
424
- "password=barsecurity_token"
420
+ "&client_secret=client_secret&username=foo&" \
421
+ "password=barsecurity_token"
425
422
  ).to_return(status: 200, body: fixture(:auth_success_response))
426
423
  end
427
424
 
@@ -445,7 +442,7 @@ shared_examples_for Restforce::AbstractClient do
445
442
 
446
443
  @login = stub_login_request(
447
444
  with_body: "grant_type=password&client_id=client_id&client_secret=" \
448
- "client_secret&username=foo&password=barsecurity_token"
445
+ "client_secret&username=foo&password=barsecurity_token"
449
446
  ).to_return(status: 200, body: fixture(:auth_success_response))
450
447
  end
451
448