active_force 0.25.0 → 0.26.0

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: 2717b024f19c2c0dbf7530d606797a5880dfe629a154e6eebd083da381b917b1
4
- data.tar.gz: ab29915f960873acd523a12f73af63f8c13b200e8cddbe4465ef28038cdebf2e
3
+ metadata.gz: 657c5d9e979b06641cae8f6734a9a0371669dcc3fca816e89c0a23fca20537fb
4
+ data.tar.gz: c6e3646ea293a22d459faca15f8fbf9b16cbaf4482b7c38dc4e135dac22da6e7
5
5
  SHA512:
6
- metadata.gz: dc4aa9e5110884da1008add3a996387d66af6c337328d56e773457fb24e61410a63d5bd3a734ab5bf79a44c4b5962a55aca1c00314af41d1275df663fa133f16
7
- data.tar.gz: 6702930b7f9d47c2290c4e00300160f5e3261ed9c22834ab4bef55a8438f6ec778f97e51aab197b23d10b1d2b6c6d20dc3e2991b728a847529cf16e7121d6983
6
+ metadata.gz: b9a1fb7822ad1950f32fc86e20e9e00cf29a5f471b2792eda9fe8ae9aae1db6f2b1817ac8fcf6fc9b0ec74ee351e62befb0aab8a6e83a7a5ec09a3761e656f85
7
+ data.tar.gz: 7f1cb6448305cef1fd185cce72571f1eff55aa3115444edf215dd031711fe6027b51922832392eb02a1e60a82b9d77616bffe271aad638344686122bdeae91c6
data/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## Not released
4
4
 
5
+ ## 0.26.0
6
+ - Add ability to query using Composite Batch Api (https://github.com/Beyond-Finance/active_force/pull/114)
7
+
5
8
  ## 0.25.0
6
9
  - Add support for Rails 8.x (https://github.com/Beyond-Finance/active_force/pull/108)
7
10
 
@@ -1,6 +1,7 @@
1
1
  require 'active_support/all'
2
2
  require 'active_force/query'
3
3
  require 'active_force/select_builder'
4
+ require 'active_force/composite_batch_query'
4
5
  require 'forwardable'
5
6
 
6
7
  module ActiveForce
@@ -245,7 +246,13 @@ module ActiveForce
245
246
  end
246
247
 
247
248
  def result
248
- sfdc_client.query(self.to_s)
249
+ soql = self.to_s
250
+
251
+ if soql.length >= ActiveForce.composite_batch_query_threshold
252
+ CompositeBatchQuery.call(soql, sfdc_client)
253
+ else
254
+ sfdc_client.query(soql)
255
+ end
249
256
  end
250
257
 
251
258
  def build_order_by(args)
@@ -0,0 +1,42 @@
1
+ require 'active_support/all'
2
+
3
+ module ActiveForce
4
+ class CompositeBatchQuery
5
+ def self.call(soql, sfdc_client = ActiveForce.sfdc_client)
6
+ new(soql, sfdc_client).call
7
+ end
8
+
9
+ attr_reader :sfdc_client, :soql
10
+
11
+ def initialize(soql, sfdc_client)
12
+ @sfdc_client = sfdc_client
13
+ @soql = soql
14
+ end
15
+
16
+ def call
17
+ results = sfdc_client.batch do |subrequests|
18
+ subrequests.requests << {
19
+ method: "GET",
20
+ url: "v#{subrequests.options[:api_version]}/query?" + {q: soql}.to_query
21
+ }
22
+ end
23
+
24
+ r = results.first
25
+
26
+ process_composite_batch_error_response(r) if r.statusCode >= 300
27
+
28
+ r.result
29
+ end
30
+
31
+ private
32
+
33
+ def process_composite_batch_error_response(r)
34
+ response_value = {status: r.statusCode, body: r.result.as_json}
35
+ error_code = r.result.dig(0, "errorCode")
36
+ message = "#{error_code}: #{r.result.dig(0, "message")}"
37
+ message << "\nRESPONSE: #{r.result.to_json}"
38
+
39
+ raise Restforce::ErrorCode.get_exception_class(error_code).new(message, response_value)
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveForce
4
- VERSION = '0.25.0'
4
+ VERSION = '0.26.0'
5
5
  end
data/lib/active_force.rb CHANGED
@@ -9,8 +9,16 @@ module ActiveForce
9
9
 
10
10
  class << self
11
11
  attr_accessor :sfdc_client
12
+ attr_writer :composite_batch_query_threshold
13
+
14
+ def composite_batch_query_threshold
15
+ @composite_batch_query_threshold ||= 100_000
16
+
17
+ return @composite_batch_query_threshold.call if @composite_batch_query_threshold.respond_to?(:call)
18
+
19
+ @composite_batch_query_threshold
20
+ end
12
21
  end
13
22
 
14
23
  self.sfdc_client = Restforce.new
15
-
16
24
  end
@@ -557,6 +557,26 @@ describe ActiveForce::ActiveQuery do
557
557
  end
558
558
  end
559
559
 
560
+ describe '#result routing' do
561
+ after do
562
+ ActiveForce.instance_variable_set(:@composite_batch_query_threshold, nil)
563
+ end
564
+
565
+ it 'uses sfdc_client.query when SOQL is at or below the threshold' do
566
+ ActiveForce.composite_batch_query_threshold = 100_000
567
+ expect(client).to receive(:query).and_return([])
568
+ expect(ActiveForce::CompositeBatchQuery).not_to receive(:call)
569
+ active_query.where("Id = 'foo'").to_a
570
+ end
571
+
572
+ it 'uses CompositeBatchQuery.call when SOQL exceeds the threshold' do
573
+ ActiveForce.composite_batch_query_threshold = 0
574
+ expect(ActiveForce::CompositeBatchQuery).to receive(:call).and_return([])
575
+ expect(client).not_to receive(:query)
576
+ active_query.where("Id = 'foo'").to_a
577
+ end
578
+ end
579
+
560
580
  describe "#order" do
561
581
  context 'when it is symbol' do
562
582
  it "should add an order condition with actual SF field name" do
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveForce::CompositeBatchQuery do
4
+ let(:soql) { "SELECT Id FROM Account WHERE Name = 'Test'" }
5
+ let(:api_version) { '58.0' }
6
+ let(:sfdc_client) { double(Restforce) }
7
+ let(:subrequests) do
8
+ double('subrequests', requests: [], options: { api_version: api_version })
9
+ end
10
+
11
+ describe '.call' do
12
+ it 'delegates to new(...).call' do
13
+ result_record = double('result_record')
14
+ instance = instance_double(described_class)
15
+ allow(described_class).to receive(:new).with(soql, sfdc_client).and_return(instance)
16
+ allow(instance).to receive(:call).and_return(result_record)
17
+
18
+ expect(described_class.call(soql, sfdc_client)).to eq(result_record)
19
+ end
20
+
21
+ it 'defaults to ActiveForce.sfdc_client when no client is provided' do
22
+ default_client = double(Restforce)
23
+ allow(ActiveForce).to receive(:sfdc_client).and_return(default_client)
24
+
25
+ instance = instance_double(described_class)
26
+ allow(described_class).to receive(:new).with(soql, default_client).and_return(instance)
27
+ allow(instance).to receive(:call).and_return(double('result'))
28
+
29
+ expect { described_class.call(soql) }.not_to raise_error
30
+ expect(described_class).to have_received(:new).with(soql, default_client)
31
+ end
32
+ end
33
+
34
+ describe '#call' do
35
+ context 'when the response is successful' do
36
+ let(:result_record) { double(Restforce::Collection) }
37
+ let(:batch_result) do
38
+ double(Restforce::Mash, statusCode: 200, result: result_record)
39
+ end
40
+
41
+ before do
42
+ allow(sfdc_client).to receive(:batch).and_yield(subrequests).and_return([batch_result])
43
+ end
44
+
45
+ it 'sends a GET batch subrequest with the correct URL' do
46
+ described_class.call(soql, sfdc_client)
47
+
48
+ expected_url = "v#{api_version}/query?" + { q: soql }.to_query
49
+ expect(subrequests.requests).to include(
50
+ hash_including(method: 'GET', url: expected_url)
51
+ )
52
+ end
53
+
54
+ it 'returns the result from the batch response' do
55
+ expect(described_class.call(soql, sfdc_client)).to eq(result_record)
56
+ end
57
+ end
58
+
59
+ context 'when the response statusCode is exactly 300' do
60
+ let(:error_body) do
61
+ [{ 'errorCode' => 'MULTIPLE_CHOICES', 'message' => 'multiple records found' }]
62
+ end
63
+ let(:batch_result) do
64
+ double('batch_result', statusCode: 300, result: error_body)
65
+ end
66
+
67
+ before do
68
+ allow(sfdc_client).to receive(:batch).and_yield(subrequests).and_return([batch_result])
69
+ end
70
+
71
+ it 'treats it as an error' do
72
+ expect {
73
+ described_class.call(soql, sfdc_client)
74
+ }.to raise_error(Restforce::ResponseError)
75
+ end
76
+ end
77
+
78
+ context 'when the response is an error' do
79
+ let(:error_body) do
80
+ [{ 'errorCode' => 'MALFORMED_QUERY', 'message' => 'unexpected token' }]
81
+ end
82
+ let(:batch_result) do
83
+ double(Restforce::Mash, statusCode: 400, result: error_body)
84
+ end
85
+
86
+ before do
87
+ allow(sfdc_client).to receive(:batch).and_yield(subrequests).and_return([batch_result])
88
+ end
89
+
90
+ it 'raises a Restforce error with the correct error code and message' do
91
+ expect {
92
+ described_class.call(soql, sfdc_client)
93
+ }.to raise_error(Restforce::ErrorCode::MalformedQuery) do |error|
94
+ expect(error.message).to include('MALFORMED_QUERY')
95
+ expect(error.message).to include('unexpected token')
96
+ expect(error.message).to include('RESPONSE:')
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -4,4 +4,24 @@ describe ActiveForce do
4
4
  it 'should have a version number' do
5
5
  expect(ActiveForce::VERSION).to_not be_nil
6
6
  end
7
+
8
+ describe '.composite_batch_query_threshold' do
9
+ after do
10
+ ActiveForce.instance_variable_set(:@composite_batch_query_threshold, nil)
11
+ end
12
+
13
+ it 'returns 100_000 by default' do
14
+ expect(ActiveForce.composite_batch_query_threshold).to eq(100_000)
15
+ end
16
+
17
+ it 'allows setting a custom integer value' do
18
+ ActiveForce.composite_batch_query_threshold = 25_000
19
+ expect(ActiveForce.composite_batch_query_threshold).to eq(25_000)
20
+ end
21
+
22
+ it 'supports callable values (proc/lambda)' do
23
+ ActiveForce.composite_batch_query_threshold = -> { 50_000 }
24
+ expect(ActiveForce.composite_batch_query_threshold).to eq(50_000)
25
+ end
26
+ end
7
27
  end
metadata CHANGED
@@ -1,17 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_force
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.0
4
+ version: 0.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eloy Espinaco
8
8
  - Pablo Oldani
9
9
  - Armando Andini
10
10
  - José Piccioni
11
- autorequire:
12
11
  bindir: bin
13
12
  cert_chain: []
14
- date: 2025-08-07 00:00:00.000000000 Z
13
+ date: 1980-01-02 00:00:00.000000000 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: activemodel
@@ -173,6 +172,7 @@ files:
173
172
  - lib/active_force/bulk/job.rb
174
173
  - lib/active_force/bulk/job_result.rb
175
174
  - lib/active_force/bulk/records.rb
175
+ - lib/active_force/composite_batch_query.rb
176
176
  - lib/active_force/field.rb
177
177
  - lib/active_force/mapping.rb
178
178
  - lib/active_force/query.rb
@@ -195,6 +195,7 @@ files:
195
195
  - spec/active_force/bulk/record_spec.rb
196
196
  - spec/active_force/bulk_spec.rb
197
197
  - spec/active_force/callbacks_spec.rb
198
+ - spec/active_force/composite_batch_query_spec.rb
198
199
  - spec/active_force/field_spec.rb
199
200
  - spec/active_force/mapping_spec.rb
200
201
  - spec/active_force/query_spec.rb
@@ -218,7 +219,6 @@ metadata:
218
219
  bug_tracker_uri: https://github.com/Beyond-Finance/active_force/issues
219
220
  changelog_uri: https://github.com/Beyond-Finance/active_force/blob/main/CHANGELOG.md
220
221
  source_code_uri: https://github.com/Beyond-Finance/active_force
221
- post_install_message:
222
222
  rdoc_options: []
223
223
  require_paths:
224
224
  - lib
@@ -233,8 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
233
233
  - !ruby/object:Gem::Version
234
234
  version: '0'
235
235
  requirements: []
236
- rubygems_version: 3.4.10
237
- signing_key:
236
+ rubygems_version: 3.6.9
238
237
  specification_version: 4
239
238
  summary: Help you implement models persisting on Sales Force within Rails using RESTForce
240
239
  test_files:
@@ -246,6 +245,7 @@ test_files:
246
245
  - spec/active_force/bulk/record_spec.rb
247
246
  - spec/active_force/bulk_spec.rb
248
247
  - spec/active_force/callbacks_spec.rb
248
+ - spec/active_force/composite_batch_query_spec.rb
249
249
  - spec/active_force/field_spec.rb
250
250
  - spec/active_force/mapping_spec.rb
251
251
  - spec/active_force/query_spec.rb