restforce 2.5.4 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +56 -0
  3. data/.rubocop.yml +27 -14
  4. data/.rubocop_todo.yml +128 -81
  5. data/CHANGELOG.md +37 -3
  6. data/CONTRIBUTING.md +3 -3
  7. data/Gemfile +4 -2
  8. data/Guardfile +3 -1
  9. data/LICENSE +1 -1
  10. data/README.md +120 -19
  11. data/Rakefile +2 -1
  12. data/lib/restforce.rb +23 -1
  13. data/lib/restforce/abstract_client.rb +3 -0
  14. data/lib/restforce/attachment.rb +3 -0
  15. data/lib/restforce/client.rb +2 -0
  16. data/lib/restforce/collection.rb +3 -1
  17. data/lib/restforce/concerns/api.rb +20 -14
  18. data/lib/restforce/concerns/authentication.rb +2 -0
  19. data/lib/restforce/concerns/base.rb +2 -0
  20. data/lib/restforce/concerns/batch_api.rb +87 -0
  21. data/lib/restforce/concerns/caching.rb +4 -2
  22. data/lib/restforce/concerns/canvas.rb +3 -0
  23. data/lib/restforce/concerns/connection.rb +26 -20
  24. data/lib/restforce/concerns/picklists.rb +9 -6
  25. data/lib/restforce/concerns/streaming.rb +60 -1
  26. data/lib/restforce/concerns/verbs.rb +3 -1
  27. data/lib/restforce/config.rb +4 -1
  28. data/lib/restforce/data/client.rb +2 -0
  29. data/lib/restforce/document.rb +3 -0
  30. data/lib/restforce/mash.rb +2 -0
  31. data/lib/restforce/middleware.rb +2 -0
  32. data/lib/restforce/middleware/authentication.rb +8 -6
  33. data/lib/restforce/middleware/authentication/password.rb +2 -0
  34. data/lib/restforce/middleware/authentication/token.rb +2 -0
  35. data/lib/restforce/middleware/authorization.rb +3 -1
  36. data/lib/restforce/middleware/caching.rb +3 -1
  37. data/lib/restforce/middleware/custom_headers.rb +2 -0
  38. data/lib/restforce/middleware/gzip.rb +5 -3
  39. data/lib/restforce/middleware/instance_url.rb +7 -3
  40. data/lib/restforce/middleware/logger.rb +2 -0
  41. data/lib/restforce/middleware/mashify.rb +2 -0
  42. data/lib/restforce/middleware/multipart.rb +8 -4
  43. data/lib/restforce/middleware/raise_error.rb +26 -8
  44. data/lib/restforce/patches/parts.rb +2 -0
  45. data/lib/restforce/signed_request.rb +3 -0
  46. data/lib/restforce/sobject.rb +3 -0
  47. data/lib/restforce/tooling/client.rb +5 -3
  48. data/lib/restforce/upload_io.rb +2 -0
  49. data/lib/restforce/version.rb +3 -1
  50. data/restforce.gemspec +19 -12
  51. data/spec/fixtures/sobject/sobject_describe_success_response.json +48 -1
  52. data/spec/integration/abstract_client_spec.rb +51 -7
  53. data/spec/integration/data/client_spec.rb +24 -5
  54. data/spec/spec_helper.rb +2 -0
  55. data/spec/support/client_integration.rb +2 -0
  56. data/spec/support/concerns.rb +2 -0
  57. data/spec/support/event_machine.rb +2 -0
  58. data/spec/support/fixture_helpers.rb +4 -2
  59. data/spec/support/matchers.rb +2 -0
  60. data/spec/support/middleware.rb +3 -1
  61. data/spec/support/mock_cache.rb +4 -2
  62. data/spec/unit/abstract_client_spec.rb +2 -0
  63. data/spec/unit/attachment_spec.rb +2 -0
  64. data/spec/unit/collection_spec.rb +5 -3
  65. data/spec/unit/concerns/api_spec.rb +40 -11
  66. data/spec/unit/concerns/authentication_spec.rb +4 -2
  67. data/spec/unit/concerns/base_spec.rb +2 -0
  68. data/spec/unit/concerns/batch_api_spec.rb +107 -0
  69. data/spec/unit/concerns/caching_spec.rb +2 -0
  70. data/spec/unit/concerns/canvas_spec.rb +3 -1
  71. data/spec/unit/concerns/connection_spec.rb +5 -3
  72. data/spec/unit/concerns/streaming_spec.rb +115 -1
  73. data/spec/unit/config_spec.rb +10 -8
  74. data/spec/unit/data/client_spec.rb +2 -0
  75. data/spec/unit/document_spec.rb +2 -0
  76. data/spec/unit/mash_spec.rb +3 -1
  77. data/spec/unit/middleware/authentication/password_spec.rb +2 -0
  78. data/spec/unit/middleware/authentication/token_spec.rb +2 -0
  79. data/spec/unit/middleware/authentication_spec.rb +3 -1
  80. data/spec/unit/middleware/authorization_spec.rb +2 -0
  81. data/spec/unit/middleware/custom_headers_spec.rb +3 -1
  82. data/spec/unit/middleware/gzip_spec.rb +4 -2
  83. data/spec/unit/middleware/instance_url_spec.rb +2 -0
  84. data/spec/unit/middleware/logger_spec.rb +2 -0
  85. data/spec/unit/middleware/mashify_spec.rb +3 -1
  86. data/spec/unit/middleware/raise_error_spec.rb +34 -11
  87. data/spec/unit/signed_request_spec.rb +2 -0
  88. data/spec/unit/sobject_spec.rb +5 -3
  89. data/spec/unit/tooling/client_spec.rb +2 -0
  90. metadata +38 -20
  91. data/.travis.yml +0 -16
  92. data/Gemfile.travis +0 -8
@@ -1,11 +1,11 @@
1
1
  # Contributing
2
2
 
3
3
  We love pull requests from everyone. By participating in this project, you
4
- agree to abide by our [code of conduct](https://github.com/ejholmes/restforce/blob/master/CODE_OF_CONDUCT.md).
4
+ agree to abide by our [code of conduct](https://github.com/restforce/restforce/blob/master/CODE_OF_CONDUCT.md).
5
5
 
6
6
  Fork, then clone the repo:
7
7
 
8
- git clone git@github.com:ejholmes/restforce.git
8
+ git clone git@github.com:restforce/restforce.git
9
9
 
10
10
  Set up your machine:
11
11
 
@@ -23,7 +23,7 @@ Make your change. Add tests for your change. Make the tests pass:
23
23
 
24
24
  script/test
25
25
 
26
- Push to your fork and [submit a pull request](https://github.com/ejholmes/restforce/compare/).
26
+ Push to your fork and [submit a pull request](https://github.com/restforce/restforce/compare/).
27
27
 
28
28
  At this point you're waiting on us. We like to at least comment on pull requests
29
29
  within a few days. We may suggest
data/Gemfile CHANGED
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
  gemspec
3
5
 
6
+ gem 'faraday', '~> 0.17.0'
7
+ gem 'jruby-openssl', platforms: :jruby
4
8
  gem 'rake'
5
- gem 'jruby-openssl', :platforms => :jruby
6
- gem 'faraday', '~> 0.11.0'
7
9
 
8
10
  group :development do
9
11
  gem 'guard-rspec'
data/Guardfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  guard 'rspec', cmd: 'bundle exec rspec', all_on_start: false, all_after_pass: false do
2
4
  watch(%r{^spec/.+_spec\.rb$})
3
5
  watch('spec/spec_helper.rb') { "spec" }
@@ -8,6 +10,6 @@ guard 'rspec', cmd: 'bundle exec rspec', all_on_start: false, all_after_pass: fa
8
10
  end
9
11
 
10
12
  guard :rubocop, all_on_start: false do
11
- watch(%r{.+\.rb$})
13
+ watch(/.+\.rb$/)
12
14
  watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
13
15
  end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Eric J. Holmes
1
+ Copyright (c) 2018 Eric J. Holmes and Tim Rogers
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Restforce
2
2
 
3
- [![travis-ci](https://travis-ci.org/ejholmes/restforce.png?branch=master)](https://travis-ci.org/ejholmes/restforce) [![Code Climate](https://codeclimate.com/github/ejholmes/restforce.png)](https://codeclimate.com/github/ejholmes/restforce) [![Dependency Status](https://gemnasium.com/ejholmes/restforce.png)](https://gemnasium.com/ejholmes/restforce)
3
+ [![CircleCI](https://circleci.com/gh/restforce/restforce.svg?style=svg)](https://circleci.com/gh/restforce/restforce) [![Code Climate](https://codeclimate.com/github/restforce/restforce.png)](https://codeclimate.com/github/restforce/restforce) [![Dependency Status](https://gemnasium.com/restforce/restforce.png)](https://gemnasium.com/restforce/restforce)
4
4
  ![](https://img.shields.io/gem/dt/restforce.svg)
5
5
 
6
6
  Restforce is a ruby gem for the [Salesforce REST api](http://www.salesforce.com/us/developer/docs/api_rest/index.htm).
@@ -19,13 +19,13 @@ Features include:
19
19
  * Support for dependent picklists.
20
20
  * Support for decoding [Force.com Canvas](http://www.salesforce.com/us/developer/docs/platform_connectpre/canvas_framework.pdf) signed requests. (NEW!)
21
21
 
22
- [Official Website](http://restforce.org/) | [Documentation](http://rubydoc.info/gems/restforce/frames) | [Changelog](https://github.com/ejholmes/restforce/tree/master/CHANGELOG.md)
22
+ [Official Website](http://restforce.org/) | [Documentation](http://rubydoc.info/gems/restforce/frames) | [Changelog](https://github.com/restforce/restforce/tree/master/CHANGELOG.md)
23
23
 
24
24
  ## Installation
25
25
 
26
26
  Add this line to your application's Gemfile:
27
27
 
28
- gem 'restforce', '~> 2.5.3'
28
+ gem 'restforce', '~> 4.0.0'
29
29
 
30
30
  And then execute:
31
31
 
@@ -35,15 +35,15 @@ Or install it yourself as:
35
35
 
36
36
  $ gem install restforce
37
37
 
38
- __As of [version 2.5.0](https://github.com/ejholmes/restforce/blob/master/CHANGELOG.md#250-dec-5-2016), this gem is only compatible with Ruby 2.0.0 and later.__ To use Ruby 1.9.3, you'll need to manually specify that you wish to use version 2.4.2, or 1.5.3 for Ruby 1.9.2 support.
38
+ __As of [version 4.0.0](https://github.com/restforce/restforce/blob/master/CHANGELOG.md#400-oct-9-2019), this gem is only compatible with Ruby 2.4.0 and later.__ You'll need to use version 3.2.0 or earlier if you're running on Ruby 2.3. If you're running on Ruby 2.2, 2.1 or 2.0, use version 2.5.3 or earlier. For Ruby 1.9.3, you'll need to manually specify that you wish to use version 2.4.2.
39
39
 
40
- This gem is versioned using [Semantic Versioning](http://semver.org/), so you can be confident when updating that there will not be breaking changes outside of a major version (following format MAJOR.MINOR.PATCH, so for instance moving from 2.3.0 to 3.0.0 would be allowed to include incompatible API changes). See the [changelog](https://github.com/ejholmes/restforce/tree/master/CHANGELOG.md) for details on what has changed in each version.
40
+ This gem is versioned using [Semantic Versioning](http://semver.org/), so you can be confident when updating that there will not be breaking changes outside of a major version (following format MAJOR.MINOR.PATCH, so for instance moving from 3.1.0 to 4.0.0 would be allowed to include incompatible API changes). See the [changelog](https://github.com/restforce/restforce/tree/master/CHANGELOG.md) for details on what has changed in each version.
41
41
 
42
42
  ## Usage
43
43
 
44
44
  Restforce is designed with flexibility and ease of use in mind. By default, all API calls will
45
45
  return [Hashie::Mash](https://github.com/intridea/hashie/tree/v1.2.0) objects,
46
- so you can do things like `client.query('select Id, (select Name from Children__r) from Account').Children__r.first.Name`.
46
+ so you can do things like `client.query('select Id, (select Name from Children__r) from Account').first.Children__r.first.Name`.
47
47
 
48
48
  ### Initialization
49
49
 
@@ -63,7 +63,7 @@ It is also important to note that the client object should not be reused across
63
63
  ```ruby
64
64
  client = Restforce.new(oauth_token: 'access_token',
65
65
  instance_url: 'instance url',
66
- api_version: '38.0')
66
+ api_version: '41.0')
67
67
  ```
68
68
 
69
69
  Although the above will work, you'll probably want to take advantage of the (re)authentication middleware by specifying `refresh_token`, `client_id`, `client_secret`, and `authentication_callback`:
@@ -75,7 +75,7 @@ client = Restforce.new(oauth_token: 'access_token',
75
75
  client_id: 'client_id',
76
76
  client_secret: 'client_secret',
77
77
  authentication_callback: Proc.new { |x| Rails.logger.debug x.to_s },
78
- api_version: '38.0')
78
+ api_version: '41.0')
79
79
  ```
80
80
 
81
81
  The middleware will use the `refresh_token` automatically to acquire a new `access_token` if the existing `access_token` is invalid.
@@ -108,7 +108,7 @@ client = Restforce.new(username: 'foo',
108
108
  security_token: 'security token',
109
109
  client_id: 'client_id',
110
110
  client_secret: 'client_secret',
111
- api_version: '38.0')
111
+ api_version: '41.0')
112
112
  ```
113
113
 
114
114
  You can also set the username, password, security token, client ID, client
@@ -120,7 +120,7 @@ export SALESFORCE_PASSWORD="password"
120
120
  export SALESFORCE_SECURITY_TOKEN="security token"
121
121
  export SALESFORCE_CLIENT_ID="client id"
122
122
  export SALESFORCE_CLIENT_SECRET="client secret"
123
- export SALESFORCE_API_VERSION="38.0"
123
+ export SALESFORCE_API_VERSION="41.0"
124
124
  ```
125
125
 
126
126
  ```ruby
@@ -138,7 +138,7 @@ client = Restforce.new(username: 'foo',
138
138
  client_id: 'client_id',
139
139
  client_secret: 'client_secret',
140
140
  proxy_uri: 'http://proxy.example.com:123',
141
- api_version: '38.0')
141
+ api_version: '41.0')
142
142
  ```
143
143
 
144
144
  You may specify a username and password for the proxy with a URL along the lines of 'http://user:password@proxy.example.com:123'.
@@ -168,19 +168,19 @@ end
168
168
 
169
169
  By default, the gem defaults to using Version 26.0 (Winter '13) of the Salesforce API. This maintains backwards compatibility for existing users.
170
170
 
171
- __We strongly suggest configuring Restforce to use the most recent API version, currently Version 38.0 (Winter '17) to get the best Salesforce API experience__ - for example, some more recently-added API endpoints will not be available without moving to a more recent
171
+ __We strongly suggest configuring Restforce to use the most recent API version, currently Version 41.0 (Winter '18) to get the best Salesforce API experience__ - for example, some more recently-added API endpoints will not be available without moving to a more recent
172
172
  version. If you're trying to use a method that is unavailable with your API version,
173
173
  Restforce will raise an `APIVersionError`.
174
174
 
175
175
  There are three ways to set the API version:
176
176
 
177
- * Passing in an `api_version` option when instantiating `Restforce` (i.e. `Restforce.new(api_version: '38.0')`)
178
- * Setting the `SALESFORCE_API_VERSION` environment variable (i.e. `export SALESFORCE_API_VERSION="38.0"`)
177
+ * Passing in an `api_version` option when instantiating `Restforce` (i.e. `Restforce.new(api_version: '41.0')`)
178
+ * Setting the `SALESFORCE_API_VERSION` environment variable (i.e. `export SALESFORCE_API_VERSION="41.0"`)
179
179
  * Configuring the version globally with `Restforce.configure`:
180
180
 
181
181
  ```ruby
182
182
  Restforce.configure do |config|
183
- config.api_version = '38.0'
183
+ config.api_version = '41.0'
184
184
  # ...
185
185
  end
186
186
  ```
@@ -206,7 +206,7 @@ to include the `sforce-auto-assign` header in all client HTTP requests:
206
206
  ```ruby
207
207
  client = Restforce.new(oauth_token: 'access_token',
208
208
  instance_url: 'instance url',
209
- api_version: '38.0',
209
+ api_version: '41.0',
210
210
  request_headers: { 'sforce-auto-assign' => 'FALSE' })
211
211
 
212
212
  ```
@@ -473,6 +473,12 @@ document = client.query('select Id, Name, Body from Document').first
473
473
  File.open(document.Name, 'wb') { |f| f.write(document.Body) }
474
474
  ```
475
475
 
476
+ **Note:** The example above is only applicable if your SOQL query returns a single Document record. If more than one record is returned,
477
+ the Body field contains an URL to retrieve the BLOB content for the first 2000 records returned. Subsequent records contain the BLOB content
478
+ in the Body field. This is confusing and hard to debug. See notes in [Issue #301](https://github.com/restforce/restforce/issues/301#issuecomment-298972959) explaining this detail.
479
+ **Executive Summary:** Don't retrieve the Body field in a SOQL query; instead, use the BLOB retrieval URL documented
480
+ in [SObject BLOB Retrieve](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_blob_retrieve.htm)
481
+
476
482
  * * *
477
483
 
478
484
  ### Custom Apex REST endpoints
@@ -517,7 +523,7 @@ require 'faye'
517
523
  # Initialize a client with your username/password/oauth token/etc.
518
524
  client = Restforce.new(username: 'foo',
519
525
  password: 'bar',
520
- security_token: 'security token'
526
+ security_token: 'security token',
521
527
  client_id: 'client_id',
522
528
  client_secret: 'client_secret')
523
529
 
@@ -541,7 +547,102 @@ end
541
547
  Boom, you're now receiving push notifications when Accounts are
542
548
  created/updated.
543
549
 
544
- _See also: [Force.com Streaming API docs](http://www.salesforce.com/us/developer/docs/api_streaming/index.htm)_
550
+ #### Replaying Events
551
+
552
+ Since API version 37.0, Salesforce stores events for 24 hours and they can be
553
+ replayed if your application experienced some downtime.
554
+
555
+ In order to replay past events, all you need to do is specify the last known
556
+ event ID when subscribing and you will receive all events that happened since
557
+ that event ID:
558
+
559
+ ```ruby
560
+ EM.run {
561
+ # Subscribe to the PushTopic.
562
+ client.subscribe 'AllAccounts', replay: 10 do |message|
563
+ puts message.inspect
564
+ end
565
+ }
566
+ ```
567
+
568
+ In this specific case you will see events with replay ID 11, 12 and so on.
569
+
570
+ There are two magic values for the replay ID accepted by Salesforce:
571
+
572
+ * `-2`, for getting all the events that appeared in the last 24 hours
573
+ * `-1`, for getting only newer events
574
+
575
+ **Warning**: Only use a replay ID of a event from the last 24 hours otherwise
576
+ Salesforce will not send anything, including newer events. If in doubt, use one
577
+ of the two magic replay IDs mentioned above.
578
+
579
+ You might want to store the replay ID in some sort of datastore so you can
580
+ access it, for example between application restarts. In that case, there is the
581
+ option of passing a custom replay handler which responds to `[]` and `[]=`.
582
+
583
+ Below is a sample replay handler that stores the replay ID for each channel in
584
+ memory using a Hash, stores a timestamp and has some rudimentary logic that
585
+ will use one of the magic IDs depending on the value of the timestamp:
586
+
587
+ ```ruby
588
+ class SimpleReplayHandler
589
+
590
+ MAX_AGE = 86_400 # 24 hours
591
+
592
+ INIT_REPLAY_ID = -1
593
+ DEFAULT_REPLAY_ID = -2
594
+
595
+ def initialize
596
+ @channels = {}
597
+ @last_modified = nil
598
+ end
599
+
600
+ # This method is called during the initial subscribe phase
601
+ # in order to send the correct replay ID.
602
+ def [](channel)
603
+ if @last_modified.nil?
604
+ puts "[#{channel}] No timestamp defined, sending magic replay ID #{INIT_REPLAY_ID}"
605
+
606
+ INIT_REPLAY_ID
607
+ elsif old_replay_id?
608
+ puts "[#{channel}] Old timestamp, sending magic replay ID #{DEFAULT_REPLAY_ID}"
609
+
610
+ DEFAULT_REPLAY_ID
611
+ else
612
+ @channels[channel]
613
+ end
614
+ end
615
+
616
+ def []=(channel, replay_id)
617
+ puts "[#{channel}] Writing replay ID: #{replay_id}"
618
+
619
+ @last_modified = Time.now
620
+ @channels[channel] = replay_id
621
+ end
622
+
623
+ def old_replay_id?
624
+ @last_modified.is_a?(Time) && Time.now - @last_modified > MAX_AGE
625
+ end
626
+ end
627
+ ```
628
+
629
+ In order to use it, simply pass the object as the value of the `replay` option
630
+ of the subscription:
631
+
632
+ ```ruby
633
+ EM.run {
634
+ # Subscribe to the PushTopic and use the custom replay handler to store any
635
+ # received replay ID.
636
+ client.subscribe 'AllAccounts', replay: SimpleReplayHandler.new do |message|
637
+ puts message.inspect
638
+ end
639
+ }
640
+ ```
641
+
642
+ _See also_:
643
+
644
+ * [Force.com Streaming API docs](http://www.salesforce.com/us/developer/docs/api_streaming/index.htm)
645
+ * [Message Durability docs](https://developer.salesforce.com/docs/atlas.en-us.api_streaming.meta/api_streaming/using_streaming_api_durability.htm)
545
646
 
546
647
  *Note:* Restforce's streaming implementation is known to be compatible with version `0.8.9` of the faye gem.
547
648
 
@@ -645,7 +746,7 @@ Callbacks.
645
746
 
646
747
  We welcome all contributions - they help us make Restforce the best gem possible.
647
748
 
648
- See our [CONTRIBUTING.md](https://github.com/ejholmes/restforce/blob/master/CONTRIBUTING.md) file for help with getting set up to work on the project locally.
749
+ See our [CONTRIBUTING.md](https://github.com/restforce/restforce/blob/master/CONTRIBUTING.md) file for help with getting set up to work on the project locally.
649
750
 
650
751
  1. Fork it
651
752
  2. Create your feature branch (`git checkout -b my-new-feature`)
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env rake
1
+ # frozen_string_literal: true
2
+
2
3
  require "bundler/gem_tasks"
3
4
 
4
5
  task default: [:spec]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
  require 'faraday_middleware'
3
5
  require 'json'
@@ -27,6 +29,7 @@ module Restforce
27
29
  autoload :Verbs, 'restforce/concerns/verbs'
28
30
  autoload :Base, 'restforce/concerns/base'
29
31
  autoload :API, 'restforce/concerns/api'
32
+ autoload :BatchAPI, 'restforce/concerns/batch_api'
30
33
  end
31
34
 
32
35
  module Data
@@ -42,6 +45,25 @@ module Restforce
42
45
  AuthenticationError = Class.new(Error)
43
46
  UnauthorizedError = Class.new(Error)
44
47
  APIVersionError = Class.new(Error)
48
+ BatchAPIError = Class.new(Error)
49
+
50
+ # Inherit from Faraday::Error::ResourceNotFound for backwards-compatibility
51
+ # Consumers of this library that rescue and handle Faraday::Error::ResourceNotFound
52
+ # can continue to do so.
53
+ NotFoundError = Class.new(Faraday::Error::ResourceNotFound)
54
+
55
+ # Inherit from Faraday::Error::ClientError for backwards-compatibility
56
+ # Consumers of this library that rescue and handle Faraday::Error::ClientError
57
+ # can continue to do so.
58
+ ResponseError = Class.new(Faraday::Error::ClientError)
59
+ MatchesMultipleError= Class.new(ResponseError)
60
+ EntityTooLargeError = Class.new(ResponseError)
61
+
62
+ module ErrorCode
63
+ def self.const_missing(constant_name)
64
+ const_set constant_name, Class.new(ResponseError)
65
+ end
66
+ end
45
67
 
46
68
  class << self
47
69
  # Alias for Restforce::Data::Client.new
@@ -72,7 +94,7 @@ module Restforce
72
94
  self
73
95
  end
74
96
  end
75
- Object.send :include, Restforce::CoreExtensions unless Object.respond_to? :tap
97
+ Object.include Restforce::CoreExtensions unless Object.respond_to? :tap
76
98
  end
77
99
 
78
100
  if ENV['PROXY_URI']
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Restforce
2
4
  class AbstractClient
3
5
  include Restforce::Concerns::Base
@@ -5,5 +7,6 @@ module Restforce
5
7
  include Restforce::Concerns::Authentication
6
8
  include Restforce::Concerns::Caching
7
9
  include Restforce::Concerns::API
10
+ include Restforce::Concerns::BatchAPI
8
11
  end
9
12
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Restforce
2
4
  class Attachment < Restforce::SObject
3
5
  # Public: Returns the body of the attachment.
@@ -15,6 +17,7 @@ module Restforce
15
17
 
16
18
  def ensure_body
17
19
  return true if self.Body?
20
+
18
21
  raise 'You need to query the Body for the record first.'
19
22
  end
20
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Restforce
2
4
  Client = Data::Client
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Restforce
2
4
  class Collection
3
5
  include Enumerable
@@ -29,7 +31,7 @@ module Restforce
29
31
  def size
30
32
  @raw_page['totalSize']
31
33
  end
32
- alias_method :length, :size
34
+ alias length size
33
35
 
34
36
  # Return array of the elements on the current page
35
37
  def current_page
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
1
4
  require 'uri'
2
5
  require 'restforce/concerns/verbs'
3
6
 
@@ -81,7 +84,7 @@ module Restforce
81
84
  def get_updated(sobject, start_time, end_time)
82
85
  start_time = start_time.utc.iso8601
83
86
  end_time = end_time.utc.iso8601
84
- url = "/sobjects/#{sobject}/updated/?start=#{start_time}&end=#{end_time}"
87
+ url = "sobjects/#{sobject}/updated/?start=#{start_time}&end=#{end_time}"
85
88
  api_get(url).body
86
89
  end
87
90
 
@@ -101,7 +104,7 @@ module Restforce
101
104
  def get_deleted(sobject, start_time, end_time)
102
105
  start_time = start_time.utc.iso8601
103
106
  end_time = end_time.utc.iso8601
104
- url = "/sobjects/#{sobject}/deleted/?start=#{start_time}&end=#{end_time}"
107
+ url = "sobjects/#{sobject}/deleted/?start=#{start_time}&end=#{end_time}"
105
108
  api_get(url).body
106
109
  end
107
110
 
@@ -265,7 +268,7 @@ module Restforce
265
268
  rescue *exceptions
266
269
  false
267
270
  end
268
- alias_method :insert, :create
271
+ alias insert create
269
272
 
270
273
  # Public: Insert a new record.
271
274
  #
@@ -283,7 +286,7 @@ module Restforce
283
286
  def create!(sobject, attrs)
284
287
  api_post("sobjects/#{sobject}", attrs).body['id']
285
288
  end
286
- alias_method :insert!, :create!
289
+ alias insert! create!
287
290
 
288
291
  # Public: Update a record.
289
292
  #
@@ -316,8 +319,9 @@ module Restforce
316
319
  # Returns true if the sobject was successfully updated.
317
320
  # Raises an exception if an error is returned from Salesforce.
318
321
  def update!(sobject, attrs)
319
- id = attrs.fetch(attrs.keys.find { |k, v| k.to_s.downcase == 'id' }, nil)
322
+ id = attrs.fetch(attrs.keys.find { |k, v| k.to_s.casecmp('id').zero? }, nil)
320
323
  raise ArgumentError, 'ID field missing from provided attributes' unless id
324
+
321
325
  attrs_without_id = attrs.reject { |k, v| k.to_s.casecmp("id").zero? }
322
326
  api_patch "sobjects/#{sobject}/#{CGI.escape(id)}", attrs_without_id
323
327
  true
@@ -363,7 +367,7 @@ module Restforce
363
367
  def upsert!(sobject, field, attrs)
364
368
  attrs = attrs.dup
365
369
  external_id =
366
- extract_case_insensitive_string_or_symbol_key_from_hash!(attrs, field)
370
+ extract_case_insensitive_string_or_symbol_key_from_hash!(attrs, field).to_s
367
371
  if field.to_s != "Id" && (external_id.nil? || external_id.strip.empty?)
368
372
  raise ArgumentError, 'Specified external ID field missing from provided ' \
369
373
  'attributes'
@@ -375,10 +379,11 @@ module Restforce
375
379
  api_post "sobjects/#{sobject}/#{field}", attrs
376
380
  end
377
381
  else
378
- api_patch "sobjects/#{sobject}/#{field}/#{CGI.escape(external_id)}", attrs
382
+ api_patch "sobjects/#{sobject}/#{field}/" \
383
+ "#{ERB::Util.url_encode(external_id)}", attrs
379
384
  end
380
385
 
381
- (response.body && response.body['id']) ? response.body['id'] : true
386
+ response.body.respond_to?(:fetch) ? response.body.fetch('id', true) : true
382
387
  end
383
388
 
384
389
  # Public: Delete a record.
@@ -412,7 +417,7 @@ module Restforce
412
417
  # Returns true of the sobject was successfully deleted.
413
418
  # Raises an exception if an error is returned from Salesforce.
414
419
  def destroy!(sobject, id)
415
- api_delete "sobjects/#{sobject}/#{CGI.escape(id)}"
420
+ api_delete "sobjects/#{sobject}/#{ERB::Util.url_encode(id)}"
416
421
  true
417
422
  end
418
423
 
@@ -426,9 +431,9 @@ module Restforce
426
431
  # Returns the Restforce::SObject sobject record.
427
432
  def find(sobject, id, field = nil)
428
433
  url = if field
429
- "sobjects/#{sobject}/#{field}/#{CGI.escape(id)}"
434
+ "sobjects/#{sobject}/#{field}/#{ERB::Util.url_encode(id)}"
430
435
  else
431
- "sobjects/#{sobject}/#{CGI.escape(id)}"
436
+ "sobjects/#{sobject}/#{ERB::Util.url_encode(id)}"
432
437
  end
433
438
  api_get(url).body
434
439
  end
@@ -444,11 +449,12 @@ module Restforce
444
449
  #
445
450
  def select(sobject, id, select, field = nil)
446
451
  path = if field
447
- "sobjects/#{sobject}/#{field}/#{CGI.escape(id)}"
452
+ "sobjects/#{sobject}/#{field}/#{ERB::Util.url_encode(id)}"
448
453
  else
449
- "sobjects/#{sobject}/#{CGI.escape(id)}"
454
+ "sobjects/#{sobject}/#{ERB::Util.url_encode(id)}"
450
455
  end
451
- path << "?fields=#{select.join(',')}" if select && select.any?
456
+
457
+ path = "#{path}?fields=#{select.join(',')}" if select&.any?
452
458
 
453
459
  api_get(path).body
454
460
  end