restforce 3.0.0 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a6f15b3e19ea1b3b90368ade66b38fafbe055997
4
- data.tar.gz: d30a34bf7ea9ca49efe0edbf1034d60080f5b837
2
+ SHA256:
3
+ metadata.gz: fa2491edb7d7f6b3d48a866be6d9c896e886987a995ee3b1cd6cfd10732cf99b
4
+ data.tar.gz: 33ea3530dc17a98ee4b26b93636499c491352d0000fc8ac35ae81bd0f4e0426b
5
5
  SHA512:
6
- metadata.gz: 97ddd07dbcf2415d01d7940002221d473a81343365abafdde740dc1597c4581850bdbfb571c2a747df94a586d79ce7814a76f1784e97dc9b9136bbb44be6e718
7
- data.tar.gz: 9a91f9b00c0cce33da5593b3c12dd106f7a48dd8ab5005f27255b14f6f0cd93500975571413b570ea154201ef6a86f1b44785cb8261934bcd91c7ac64c2e800a
6
+ metadata.gz: 7ff50cc0872b5491f2a183886f857d381c9c854bcd1932ee4e92e2231dab367d16c0a6a28c4c9525a4ce807acea255ffd3d4d9070766379ae29f51117c9cb024
7
+ data.tar.gz: 82750aac34ffe679439ea478043da95d4fe9b3847704b65ad241a8aaa4bc65092da5321b69c1443296bd3b6bf682fb3f0c907be1356f95ccf2ad3a460979fedf
data/.rubocop.yml CHANGED
@@ -4,46 +4,44 @@ inherit_from: .rubocop_todo.yml
4
4
 
5
5
  AllCops:
6
6
  DisplayCopNames: true
7
- Include:
8
- - Rakefile
9
7
  Exclude:
10
8
  - .*/**/*
11
9
  - vendor/**/*
12
10
  TargetRubyVersion: 2.3
13
11
 
14
12
  # Limit lines to 80 characters.
15
- LineLength:
13
+ Metrics/LineLength:
16
14
  Max: 90
17
15
 
18
- ClassLength:
16
+ Metrics/ClassLength:
19
17
  Enabled: false
20
18
 
21
- ModuleLength:
19
+ Metrics/ModuleLength:
22
20
  Enabled: false
23
21
 
24
22
  # Avoid methods longer than 30 lines of code
25
- MethodLength:
23
+ Metrics/MethodLength:
26
24
  CountComments: false # count full line comments?
27
25
  Max: 87
28
26
 
29
27
  # Avoid single-line methods.
30
- SingleLineMethods:
28
+ Style/SingleLineMethods:
31
29
  AllowIfMethodIsEmpty: true
32
30
 
33
- StringLiterals:
31
+ Style/StringLiterals:
34
32
  Enabled: false
35
33
 
36
- GlobalVars:
34
+ Style/GlobalVars:
37
35
  Enabled: false # We use them Redis + StatsD (though maybe we shouldn't?)
38
36
 
39
37
  # Wants underscores in all large numbers. Pain in the ass for things like
40
38
  # unix timestamps.
41
- NumericLiterals:
39
+ Style/NumericLiterals:
42
40
  Enabled: false
43
41
 
44
42
  # Wants you to use the same argument names for every reduce. This seems kinda
45
43
  # naff compared to naming them semantically
46
- SingleLineBlockParams:
44
+ Style/SingleLineBlockParams:
47
45
  Enabled: false
48
46
 
49
47
  Style/SignalException:
data/.rubocop_todo.yml CHANGED
@@ -1,117 +1,164 @@
1
- # This configuration was generated by `rubocop --auto-gen-config`
2
- # on 2015-06-06 22:47:07 +0100 using RuboCop version 0.32.0.
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2019-10-09 21:39:40 +0100 using RuboCop version 0.75.0.
3
4
  # The point is for the user to remove these configuration records
4
5
  # one by one as the offenses are removed from the code base.
5
6
  # Note that changes in the inspected code, or installation of new
6
7
  # versions of RuboCop, may require this file to be generated again.
7
8
 
8
- # Offense count: 50
9
+ # Offense count: 17
9
10
  # Cop supports --auto-correct.
11
+ # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
12
+ # SupportedHashRocketStyles: key, separator, table
13
+ # SupportedColonStyles: key, separator, table
14
+ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
15
+ Layout/AlignHash:
16
+ Exclude:
17
+ - 'lib/restforce/middleware/logger.rb'
18
+ - 'restforce.gemspec'
19
+ - 'spec/integration/abstract_client_spec.rb'
20
+ - 'spec/unit/config_spec.rb'
21
+
22
+ # Offense count: 2
23
+ # Cop supports --auto-correct.
24
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
25
+ # SupportedStyles: special_inside_parentheses, consistent, align_braces
26
+ Layout/IndentFirstHashElement:
27
+ Exclude:
28
+ - 'lib/restforce/concerns/connection.rb'
29
+
30
+ # Offense count: 1
31
+ # Cop supports --auto-correct.
32
+ # Configuration parameters: AllowForAlignment.
33
+ Layout/SpaceAroundOperators:
34
+ Exclude:
35
+ - 'lib/restforce.rb'
36
+
37
+ # Offense count: 8
38
+ # Cop supports --auto-correct.
39
+ # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
10
40
  Lint/UnusedBlockArgument:
11
- Enabled: false
41
+ Exclude:
42
+ - 'lib/restforce/concerns/api.rb'
43
+ - 'lib/restforce/concerns/batch_api.rb'
44
+ - 'lib/restforce/middleware/multipart.rb'
45
+ - 'spec/unit/config_spec.rb'
12
46
 
13
- # Offense count: 187
47
+ # Offense count: 9
14
48
  Metrics/AbcSize:
15
- Max: 61
49
+ Max: 38
16
50
 
17
- # Offense count: 26
51
+ # Offense count: 1
18
52
  Metrics/CyclomaticComplexity:
19
- Max: 10
53
+ Max: 8
20
54
 
21
- # Offense count: 11
55
+ # Offense count: 1
22
56
  Metrics/PerceivedComplexity:
23
- Max: 10
57
+ Max: 9
24
58
 
25
- # Offense count: 12
26
- Naming/AccessorMethodName:
27
- Enabled: false
28
-
29
- # Offense count: 164
30
- # Cop supports --auto-correct.
31
- # Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle, SupportedLastArgumentHashStyles.
32
- Layout/AlignHash:
33
- Enabled: false
59
+ # Offense count: 5
60
+ # Configuration parameters: EnforcedStyleForLeadingUnderscores.
61
+ # SupportedStylesForLeadingUnderscores: disallowed, required, optional
62
+ Naming/MemoizedInstanceVariableName:
63
+ Exclude:
64
+ - 'lib/restforce/concerns/picklists.rb'
65
+ - 'lib/restforce/concerns/streaming.rb'
66
+
67
+ # Offense count: 2
68
+ # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros.
69
+ # NamePrefix: is_, has_, have_
70
+ # NamePrefixBlacklist: is_, has_, have_
71
+ # NameWhitelist: is_a?
72
+ # MethodDefinitionMacros: define_method, define_singleton_method
73
+ Naming/PredicateName:
74
+ Exclude:
75
+ - 'spec/**/*'
76
+ - 'lib/restforce/collection.rb'
77
+ - 'lib/restforce/middleware/multipart.rb'
34
78
 
35
- # Offense count: 3
79
+ # Offense count: 12
36
80
  # Cop supports --auto-correct.
37
- # Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
81
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods, AllowBracesOnProceduralOneLiners.
82
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
83
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
84
+ # FunctionalMethods: let, let!, subject, watch
85
+ # IgnoredMethods: lambda, proc, it
38
86
  Style/BlockDelimiters:
39
- Enabled: false
87
+ Exclude:
88
+ - 'lib/restforce/concerns/batch_api.rb'
89
+ - 'spec/support/event_machine.rb'
90
+ - 'spec/support/middleware.rb'
91
+ - 'spec/unit/concerns/base_spec.rb'
92
+ - 'spec/unit/concerns/batch_api_spec.rb'
93
+ - 'spec/unit/concerns/caching_spec.rb'
94
+ - 'spec/unit/concerns/streaming_spec.rb'
95
+ - 'spec/unit/middleware/authentication_spec.rb'
96
+ - 'spec/unit/middleware/mashify_spec.rb'
40
97
 
41
- # Offense count: 128
42
- # Configuration parameters: EnforcedStyle, SupportedStyles.
43
- Style/ClassAndModuleChildren:
44
- Enabled: false
45
-
46
- # Offense count: 21
47
- # Cop supports --auto-correct.
48
- Layout/ClosingParenthesisIndentation:
49
- Enabled: false
50
-
51
- # Offense count: 13
98
+ # Offense count: 12
52
99
  # Cop supports --auto-correct.
53
- # Configuration parameters: Keywords.
54
- Style/CommentAnnotation:
55
- Enabled: false
56
-
57
- # Offense count: 743
100
+ # Configuration parameters: AutoCorrect, EnforcedStyle.
101
+ # SupportedStyles: nested, compact
102
+ Style/ClassAndModuleChildren:
103
+ Exclude:
104
+ - 'lib/restforce/middleware/authentication.rb'
105
+ - 'lib/restforce/middleware/authentication/password.rb'
106
+ - 'lib/restforce/middleware/authentication/token.rb'
107
+ - 'lib/restforce/middleware/authorization.rb'
108
+ - 'lib/restforce/middleware/caching.rb'
109
+ - 'lib/restforce/middleware/custom_headers.rb'
110
+ - 'lib/restforce/middleware/gzip.rb'
111
+ - 'lib/restforce/middleware/instance_url.rb'
112
+ - 'lib/restforce/middleware/logger.rb'
113
+ - 'lib/restforce/middleware/mashify.rb'
114
+ - 'lib/restforce/middleware/multipart.rb'
115
+ - 'lib/restforce/middleware/raise_error.rb'
116
+
117
+ # Offense count: 36
58
118
  Style/Documentation:
59
119
  Enabled: false
60
120
 
61
- # Offense count: 9
121
+ # Offense count: 3
62
122
  Style/DoubleNegation:
63
- Enabled: false
123
+ Exclude:
124
+ - 'lib/restforce/concerns/picklists.rb'
125
+ - 'lib/restforce/middleware/instance_url.rb'
64
126
 
65
- # Offense count: 109
127
+ # Offense count: 1
66
128
  # Configuration parameters: MinBodyLength.
67
129
  Style/GuardClause:
68
- Enabled: false
69
-
70
- # Offense count: 48
71
- # Cop supports --auto-correct.
72
- # Configuration parameters: EnforcedStyle, SupportedStyles.
73
- Layout/IndentHash:
74
- Enabled: false
130
+ Exclude:
131
+ - 'lib/restforce/middleware/authentication.rb'
75
132
 
76
- # Offense count: 70
133
+ # Offense count: 25
77
134
  # Cop supports --auto-correct.
135
+ # Configuration parameters: EnforcedStyle.
136
+ # SupportedStyles: line_count_dependent, lambda, literal
78
137
  Style/Lambda:
79
- Enabled: false
80
-
81
- # Offense count: 577
82
- # Cop supports --auto-correct.
83
- # Configuration parameters: EnforcedStyle, SupportedStyles.
84
- Layout/MultilineOperationIndentation:
85
- Enabled: false
86
-
87
- # Offense count: 21
88
- # Configuration parameters: NamePrefix, NamePrefixBlacklist.
89
- Naming/PredicateName:
90
- Enabled: false
138
+ Exclude:
139
+ - 'lib/restforce/config.rb'
140
+ - 'spec/integration/abstract_client_spec.rb'
141
+ - 'spec/unit/concerns/base_spec.rb'
142
+ - 'spec/unit/concerns/streaming_spec.rb'
143
+ - 'spec/unit/middleware/authentication_spec.rb'
144
+ - 'spec/unit/middleware/authorization_spec.rb'
145
+ - 'spec/unit/middleware/custom_headers_spec.rb'
146
+ - 'spec/unit/middleware/gzip_spec.rb'
147
+ - 'spec/unit/middleware/instance_url_spec.rb'
148
+ - 'spec/unit/middleware/logger_spec.rb'
149
+ - 'spec/unit/sobject_spec.rb'
91
150
 
92
- # Offense count: 4
93
- # Cop supports --auto-correct.
94
- Style/Proc:
95
- Enabled: false
96
-
97
- # Offense count: 173
151
+ # Offense count: 5
98
152
  # Cop supports --auto-correct.
99
153
  Style/RedundantSelf:
100
- Enabled: false
154
+ Exclude:
155
+ - 'lib/restforce/mash.rb'
156
+ - 'lib/restforce/sobject.rb'
101
157
 
102
- # Offense count: 28
103
- # Cop supports --auto-correct.
104
- Layout/SpaceBeforeFirstArg:
105
- Enabled: false
106
-
107
- # Offense count: 3
108
- # Cop supports --auto-correct.
109
- # Configuration parameters: MultiSpaceAllowedForOperators.
110
- Layout/SpaceAroundOperators:
111
- Enabled: false
112
-
113
- # Offense count: 5
158
+ # Offense count: 1
114
159
  # Cop supports --auto-correct.
115
160
  # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
161
+ # Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym
116
162
  Style/TrivialAccessors:
117
- Enabled: false
163
+ Exclude:
164
+ - 'lib/restforce/middleware.rb'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 3.2.1 (Dec 8, 2021)
2
+
3
+ * Update the gemspec to reflect that `faraday` `v1.0.0` is not supported (@timrogers)
4
+
5
+ ## 3.2.0 (Oct 9, 2019)
6
+
7
+ * Add support for the Batch API (@gaiottino, @teoulas)
8
+ * Return specific exceptions for errors that might be returned from Salesforce.com - instead of getting a generic `Faraday::Error::ClientError`, you might get something like a `Restforce::EntityTooLargeError` (@boblail)
9
+ * Expose the full response in exceptions' messages to make debugging easier (@boblail)
10
+ * Properly escape IDs with spaces in them when working with existing records (@pushups)
11
+
12
+ ## 3.1.0 (Aug 16, 2018)
13
+
14
+ * Add support for replaying missed messages when using the Salesforce Streaming API (@andreimaxim, @drteeth, @panozzaj)
15
+
16
+ ## 3.0.1 (Aug 4, 2018)
17
+
18
+ * Fix `NoMethodError` when upserting an existing record (@opti)
19
+
1
20
  ## 3.0.0 (Aug 2, 2018)
2
21
 
3
22
  * __Deprecate support for Ruby 2.0, 2.1 and 2.2__, since [even Ruby 2.2 reached its end-of-life](https://www.ruby-lang.org/en/news/2018/06/20/support-of-ruby-2-2-has-ended/) in June 2018. (This is the only breaking change included in this version.)
@@ -7,6 +26,12 @@
7
26
  * Fix `get_updated` and `get_deleted` API calls by removing the erroneous leading forward slash from the path (@scottolsen)
8
27
  * Fix unpacking of dependent picklist options (@parkm)
9
28
 
29
+ ## 2.5.4 (May 15, 2019)
30
+
31
+ See the [`v2`](https://github.com/restforce/restforce/tree/v2) branch for this release.
32
+
33
+ * Escape record IDs passed in to the client to identify records to find, delete, etc. (@jmdx, @apanzerj)
34
+
10
35
  ## 2.5.3 (Apr 25, 2017)
11
36
 
12
37
  * Raise an error where a custom external ID field name is supplied to `upsert` and `upsert!`, but it is missing from the provided attributes (@velveret)
data/Gemfile CHANGED
@@ -3,7 +3,7 @@
3
3
  source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
- gem 'faraday', '~> 0.15.0'
6
+ gem 'faraday', '~> 0.17.0'
7
7
  gem 'jruby-openssl', platforms: :jruby
8
8
  gem 'rake'
9
9
 
data/README.md CHANGED
@@ -25,7 +25,7 @@ Features include:
25
25
 
26
26
  Add this line to your application's Gemfile:
27
27
 
28
- gem 'restforce', '~> 3.0.0'
28
+ gem 'restforce', '~> 3.2.1'
29
29
 
30
30
  And then execute:
31
31
 
@@ -43,7 +43,7 @@ This gem is versioned using [Semantic Versioning](http://semver.org/), so you ca
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
 
@@ -523,7 +523,7 @@ require 'faye'
523
523
  # Initialize a client with your username/password/oauth token/etc.
524
524
  client = Restforce.new(username: 'foo',
525
525
  password: 'bar',
526
- security_token: 'security token'
526
+ security_token: 'security token',
527
527
  client_id: 'client_id',
528
528
  client_secret: 'client_secret')
529
529
 
@@ -547,7 +547,102 @@ end
547
547
  Boom, you're now receiving push notifications when Accounts are
548
548
  created/updated.
549
549
 
550
- _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)
551
646
 
552
647
  *Note:* Restforce's streaming implementation is known to be compatible with version `0.8.9` of the faye gem.
553
648
 
@@ -7,5 +7,6 @@ module Restforce
7
7
  include Restforce::Concerns::Authentication
8
8
  include Restforce::Concerns::Caching
9
9
  include Restforce::Concerns::API
10
+ include Restforce::Concerns::BatchAPI
10
11
  end
11
12
  end
@@ -17,6 +17,7 @@ module Restforce
17
17
 
18
18
  def ensure_body
19
19
  return true if self.Body?
20
+
20
21
  raise 'You need to query the Body for the record first.'
21
22
  end
22
23
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'erb'
3
4
  require 'uri'
4
5
  require 'restforce/concerns/verbs'
5
6
 
@@ -320,6 +321,7 @@ module Restforce
320
321
  def update!(sobject, attrs)
321
322
  id = attrs.fetch(attrs.keys.find { |k, v| k.to_s.casecmp('id').zero? }, nil)
322
323
  raise ArgumentError, 'ID field missing from provided attributes' unless id
324
+
323
325
  attrs_without_id = attrs.reject { |k, v| k.to_s.casecmp("id").zero? }
324
326
  api_patch "sobjects/#{sobject}/#{CGI.escape(id)}", attrs_without_id
325
327
  true
@@ -377,10 +379,11 @@ module Restforce
377
379
  api_post "sobjects/#{sobject}/#{field}", attrs
378
380
  end
379
381
  else
380
- api_patch "sobjects/#{sobject}/#{field}/#{CGI.escape(external_id)}", attrs
382
+ api_patch "sobjects/#{sobject}/#{field}/" \
383
+ "#{ERB::Util.url_encode(external_id)}", attrs
381
384
  end
382
385
 
383
- response&.body&.fetch('id', nil) ? response.body['id'] : true
386
+ response.body.respond_to?(:fetch) ? response.body.fetch('id', true) : true
384
387
  end
385
388
 
386
389
  # Public: Delete a record.
@@ -414,7 +417,7 @@ module Restforce
414
417
  # Returns true of the sobject was successfully deleted.
415
418
  # Raises an exception if an error is returned from Salesforce.
416
419
  def destroy!(sobject, id)
417
- api_delete "sobjects/#{sobject}/#{CGI.escape(id)}"
420
+ api_delete "sobjects/#{sobject}/#{ERB::Util.url_encode(id)}"
418
421
  true
419
422
  end
420
423
 
@@ -428,9 +431,9 @@ module Restforce
428
431
  # Returns the Restforce::SObject sobject record.
429
432
  def find(sobject, id, field = nil)
430
433
  url = if field
431
- "sobjects/#{sobject}/#{field}/#{CGI.escape(id)}"
434
+ "sobjects/#{sobject}/#{field}/#{ERB::Util.url_encode(id)}"
432
435
  else
433
- "sobjects/#{sobject}/#{CGI.escape(id)}"
436
+ "sobjects/#{sobject}/#{ERB::Util.url_encode(id)}"
434
437
  end
435
438
  api_get(url).body
436
439
  end
@@ -446,9 +449,9 @@ module Restforce
446
449
  #
447
450
  def select(sobject, id, select, field = nil)
448
451
  path = if field
449
- "sobjects/#{sobject}/#{field}/#{CGI.escape(id)}"
452
+ "sobjects/#{sobject}/#{field}/#{ERB::Util.url_encode(id)}"
450
453
  else
451
- "sobjects/#{sobject}/#{CGI.escape(id)}"
454
+ "sobjects/#{sobject}/#{ERB::Util.url_encode(id)}"
452
455
  end
453
456
 
454
457
  path = "#{path}?fields=#{select.join(',')}" if select&.any?
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'restforce/concerns/verbs'
4
+
5
+ module Restforce
6
+ module Concerns
7
+ module BatchAPI
8
+ extend Restforce::Concerns::Verbs
9
+
10
+ define_verbs :post
11
+
12
+ def batch(halt_on_error: false)
13
+ subrequests = Subrequests.new(options)
14
+ yield(subrequests)
15
+ subrequests.requests.each_slice(25).map do |requests|
16
+ properties = {
17
+ batchRequests: requests,
18
+ haltOnError: halt_on_error
19
+ }
20
+ response = api_post('composite/batch', properties.to_json)
21
+ body = response.body
22
+ results = body['results']
23
+ if halt_on_error && body['hasErrors']
24
+ last_error_index = results.rindex { |result| result['statusCode'] != 412 }
25
+ last_error = results[last_error_index]
26
+ raise BatchAPIError, last_error['result'][0]['errorCode']
27
+ end
28
+ results.map(&:compact)
29
+ end.flatten
30
+ end
31
+
32
+ def batch!(&block)
33
+ batch(halt_on_error: true, &block)
34
+ end
35
+
36
+ class Subrequests
37
+ def initialize(options)
38
+ @options = options
39
+ @requests = []
40
+ end
41
+ attr_reader :options, :requests
42
+
43
+ def create(sobject, attrs)
44
+ requests << { method: 'POST', url: batch_api_path(sobject), richInput: attrs }
45
+ end
46
+
47
+ def update(sobject, attrs)
48
+ id = attrs.fetch(attrs.keys.find { |k, v| k.to_s.casecmp?('id') }, nil)
49
+ raise ArgumentError, 'Id field missing from attrs.' unless id
50
+
51
+ attrs_without_id = attrs.reject { |k, v| k.to_s.casecmp?('id') }
52
+ requests << {
53
+ method: 'PATCH',
54
+ url: batch_api_path("#{sobject}/#{id}"),
55
+ richInput: attrs_without_id
56
+ }
57
+ end
58
+
59
+ def destroy(sobject, id)
60
+ requests << { method: 'DELETE', url: batch_api_path("#{sobject}/#{id}") }
61
+ end
62
+
63
+ def upsert(sobject, ext_field, attrs)
64
+ raise ArgumentError, 'External id field missing.' unless ext_field
65
+
66
+ ext_id = attrs.fetch(attrs.keys.find { |k, v|
67
+ k.to_s.casecmp?(ext_field.to_s)
68
+ }, nil)
69
+ raise ArgumentError, 'External id missing from attrs.' unless ext_id
70
+
71
+ attrs_without_ext_id = attrs.reject { |k, v| k.to_s.casecmp?(ext_field) }
72
+ requests << {
73
+ method: 'PATCH',
74
+ url: batch_api_path("#{sobject}/#{ext_field}/#{ext_id}"),
75
+ richInput: attrs_without_ext_id
76
+ }
77
+ end
78
+
79
+ private
80
+
81
+ def batch_api_path(path)
82
+ "v#{options[:api_version]}/sobjects/#{path}"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -5,6 +5,7 @@ module Restforce
5
5
  module Canvas
6
6
  def decode_signed_request(signed_request)
7
7
  raise 'client_secret not set.' unless options[:client_secret]
8
+
8
9
  SignedRequest.decode(signed_request, options[:client_secret])
9
10
  end
10
11
  end
@@ -34,6 +34,7 @@ module Restforce
34
34
  @field = field
35
35
  @valid_for = options.delete(:valid_for)
36
36
  raise "#{field} is not a dependent picklist" if @valid_for && !dependent?
37
+
37
38
  replace(picklist_values)
38
39
  end
39
40