active_force 0.17.0 → 0.18.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: 741f33a6c27d6bc11eaaa7655c2a7fb2ecc772f93d298e608866840b82e94321
4
- data.tar.gz: 465abe2c572e35dc61817ab1bf8ac1d6d0e35e6f26eb2e933af3ed1781c2c68a
3
+ metadata.gz: 6de1df55a3215360cd78f76fa3f55d05ca9e8f9f9603d5ab4257667a5fd961a5
4
+ data.tar.gz: e85f2ae5bd075070adc261ec3a6d566eea2f9cf19c4d4423fa1b859cafdabcc4
5
5
  SHA512:
6
- metadata.gz: 376ab891dc3a0bf92f030dd3b756aa900c8091bfd42b3424ef1c832ecc2ac14384785f96eeadc20764f22ee02315753cba10c4b0aec067294e2175a09754dc60
7
- data.tar.gz: f60cf65275aaee5e96ebb173cc0718cbc663b3cced951bccf8b417df1029055606be56bdf82617e689130d68687b8ce8f8227acab83314c54715e95e6ca28629
6
+ metadata.gz: 5268395d3fcc92a705b909026b6de6deb36b4c6a9b622d0ee63307ed08d16142e96abca2bafb902c55fca02955d75c34aa3348eb84fc14c47784d3548b492f89
7
+ data.tar.gz: 3ea676afd55bb62601e211250c487a86e7902f1dee1f8b6fdc41410b903ce2052e5a4f893cc869798ba3bf5c1270aad2c84ea21c24dd137909e494e7c0564ff9
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Not released
4
4
 
5
+ ## 0.18.0
6
+
7
+ - Fix eager loading of scoped associations. (https://github.com/Beyond-Finance/active_force/pull/67)
8
+ - Adding `.blank?`, `.present?`, and `.any?` delegators to `ActiveQuery`. (https://github.com/Beyond-Finance/active_force/pull/68)
9
+ - Adding `update` and `update!` class methods on `SObject`. (https://github.com/Beyond-Finance/active_force/pull/66)
10
+ - Allow an argument to `last` allowing the query to select the `last(n)` records. Default is 1. (https://github.com/Beyond-Finance/active_force/pull/66)
11
+
5
12
  ## 0.17.0
6
13
 
7
14
  - Fix bug with has_many queries due to query method chaining mutating in-place (https://github.com/Beyond-Finance/active_force/pull/10)
@@ -21,9 +21,9 @@ module ActiveForce
21
21
  attr_reader :sobject, :association_mapping, :belongs_to_association_mapping
22
22
 
23
23
  def_delegators :sobject, :sfdc_client, :build, :table_name, :mappings
24
- def_delegators :to_a, :each, :map, :inspect, :pluck, :each_with_object
24
+ def_delegators :to_a, :blank?, :present?, :any?, :each, :map, :inspect, :pluck, :each_with_object
25
25
 
26
- def initialize (sobject, custom_table_name = nil)
26
+ def initialize(sobject, custom_table_name = nil)
27
27
  @sobject = sobject
28
28
  @association_mapping = {}
29
29
  @belongs_to_association_mapping = {}
@@ -26,6 +26,14 @@ module ActiveForce
26
26
  options[:relationship_name] || relation_model.to_s.constantize.table_name
27
27
  end
28
28
 
29
+ def scoped_as
30
+ options[:scoped_as] || nil
31
+ end
32
+
33
+ def scoped?
34
+ options[:scoped_as].present?
35
+ end
36
+
29
37
  ###
30
38
  # Does this association's relation_model represent
31
39
  # +sfdc_table_name+? Examples of +sfdc_table_name+
@@ -1,5 +1,6 @@
1
1
  module ActiveForce
2
2
  module Association
3
+ class InvalidEagerLoadAssociation < StandardError; end
3
4
  class EagerLoadProjectionBuilder
4
5
  class << self
5
6
  def build(association, parent_association_field = nil)
@@ -34,6 +35,13 @@ module ActiveForce
34
35
  def projections
35
36
  raise "Must define #{self.class.name}#projections"
36
37
  end
38
+
39
+ def apply_association_scope(query)
40
+ return query unless association.scoped?
41
+ raise InvalidEagerLoadAssociation, "Cannot use scopes that expect arguments: #{association.relation_name}" if association.scoped_as.arity.positive?
42
+
43
+ query.instance_exec(&association.scoped_as)
44
+ end
37
45
  end
38
46
 
39
47
  class HasManyAssociationProjectionBuilder < AbstractProjectionBuilder
@@ -43,17 +51,17 @@ module ActiveForce
43
51
  # to be pluralized
44
52
  def projections
45
53
  relationship_name = association.sfdc_association_field
46
- query = Query.new relationship_name
54
+ query = ActiveQuery.new(association.relation_model, relationship_name)
47
55
  query.fields association.relation_model.fields
48
- ["(#{query.to_s})"]
56
+ ["(#{apply_association_scope(query).to_s})"]
49
57
  end
50
58
  end
51
59
 
52
60
  class HasOneAssociationProjectionBuilder < AbstractProjectionBuilder
53
61
  def projections
54
- query = Query.new association.sfdc_association_field
62
+ query = ActiveQuery.new(association.relation_model, association.sfdc_association_field)
55
63
  query.fields association.relation_model.fields
56
- ["(#{query.to_s})"]
64
+ ["(#{apply_association_scope(query).to_s})"]
57
65
  end
58
66
  end
59
67
 
@@ -81,8 +81,8 @@ module ActiveForce
81
81
  limit 1
82
82
  end
83
83
 
84
- def last
85
- order("Id DESC").limit(1)
84
+ def last(limit = 1)
85
+ order("Id DESC").limit(limit)
86
86
  end
87
87
 
88
88
  def join object_query
@@ -129,6 +129,14 @@ module ActiveForce
129
129
  new(args).create!
130
130
  end
131
131
 
132
+ def self.update(id, attributes)
133
+ new(attributes.merge(id: id)).update
134
+ end
135
+
136
+ def self.update!(id, attributes)
137
+ new(attributes.merge(id: id)).update!
138
+ end
139
+
132
140
  def save!
133
141
  run_callbacks :save do
134
142
  if persisted?
@@ -231,7 +239,7 @@ module ActiveForce
231
239
  end
232
240
 
233
241
  def default_attributes
234
- @attributes.each_value.select do |value|
242
+ @attributes.each_value.select do |value|
235
243
  value.is_a?(ActiveModel::Attribute::UserProvidedDefault) || value.instance_values["original_attribute"].is_a?(ActiveModel::Attribute::UserProvidedDefault)
236
244
  end.map(&:name)
237
245
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveForce
4
- VERSION = '0.17.0'
4
+ VERSION = '0.18.0'
5
5
  end
@@ -18,7 +18,6 @@ describe ActiveForce::ActiveQuery do
18
18
  ]
19
19
  end
20
20
 
21
-
22
21
  before do
23
22
  allow(active_query).to receive(:sfdc_client).and_return client
24
23
  allow(active_query).to receive(:build).and_return Object.new
@@ -40,6 +39,72 @@ describe ActiveForce::ActiveQuery do
40
39
  end
41
40
  end
42
41
 
42
+ describe '#blank? delegation' do
43
+ before do
44
+ allow(client).to receive(:query).and_return(api_result)
45
+ end
46
+
47
+ context 'when there are no records' do
48
+ let(:api_result) { [] }
49
+
50
+ it 'returns true' do
51
+ result = active_query.where("Text_Label = 'foo'").blank?
52
+ expect(result).to be true
53
+ end
54
+ end
55
+
56
+ context 'when records are returned' do
57
+ it 'returns false' do
58
+ result = active_query.where("Text_Label = 'foo'").blank?
59
+ expect(result).to be false
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#present? delegation' do
65
+ before do
66
+ allow(client).to receive(:query).and_return(api_result)
67
+ end
68
+
69
+ context 'when there are no records' do
70
+ let(:api_result) { [] }
71
+
72
+ it 'returns false' do
73
+ result = active_query.where("Text_Label = 'foo'").present?
74
+ expect(result).to be false
75
+ end
76
+ end
77
+
78
+ context 'when there are records' do
79
+ it 'returns true' do
80
+ result = active_query.where("Text_Label = 'foo'").present?
81
+ expect(result).to be true
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#any? delegation' do
87
+ before do
88
+ allow(client).to receive(:query).and_return(api_result)
89
+ end
90
+
91
+ context 'when there are no records' do
92
+ let(:api_result) { [] }
93
+
94
+ it 'returns true' do
95
+ result = active_query.where("Text_Label = 'foo'").any?
96
+ expect(result).to be false
97
+ end
98
+ end
99
+
100
+ context 'when records are returned' do
101
+ it 'returns false' do
102
+ result = active_query.where("Text_Label = 'foo'").any?
103
+ expect(result).to be true
104
+ end
105
+ end
106
+ end
107
+
43
108
  describe "select only some field using mappings" do
44
109
  it "should return a query only with selected field" do
45
110
  new_query = active_query.select(:field)
@@ -168,14 +168,30 @@ describe ActiveForce::Query do
168
168
  end
169
169
 
170
170
  describe '.last' do
171
- it 'should return the query for the last record' do
172
- expect(query.last.to_s).to eq 'SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT 1'
173
- end
174
-
175
- it "should not update the original query" do
176
- new_query = query.last
177
- expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name"
178
- expect(new_query.to_s).to eq 'SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT 1'
171
+ context 'without any argument' do
172
+ it 'should return the query for the last record' do
173
+ expect(query.last.to_s).to eq 'SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT 1'
174
+ end
175
+
176
+ it "should not update the original query" do
177
+ new_query = query.last
178
+ expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name"
179
+ expect(new_query.to_s).to eq 'SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT 1'
180
+ end
181
+ end
182
+
183
+ context 'with an argument' do
184
+ let(:last_argument) { 3 }
185
+
186
+ it 'should return the query for the last n records' do
187
+ expect(query.last(last_argument).to_s).to eq "SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT #{last_argument}"
188
+ end
189
+
190
+ it "should not update the original query" do
191
+ new_query = query.last last_argument
192
+ expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name"
193
+ expect(new_query.to_s).to eq "SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT #{last_argument}"
194
+ end
179
195
  end
180
196
  end
181
197
 
@@ -225,6 +225,13 @@ module ActiveForce
225
225
  end
226
226
  end
227
227
 
228
+ context 'when assocation has a scope' do
229
+ it 'formulates the correct SOQL query with the scope applied' do
230
+ soql = Post.includes(:impossible_comments).where(id: '1234').to_s
231
+ expect(soql).to eq "SELECT Id, Title__c, BlogId, (SELECT Id, PostId, PosterId__c, FancyPostId, Body__c FROM Comments__r WHERE (1 = 0)) FROM Post__c WHERE (Id = '1234')"
232
+ end
233
+ end
234
+
228
235
  context 'with namespaced SObjects' do
229
236
  it 'formulates the correct SOQL query' do
230
237
  soql = Salesforce::Quota.includes(:prez_clubs).where(id: '123').to_s
@@ -286,9 +293,26 @@ module ActiveForce
286
293
  end
287
294
  end
288
295
 
296
+ context 'has_one' do
297
+ context 'when assocation has a scope' do
298
+ it 'formulates the correct SOQL query with the scope applied' do
299
+ soql = Post.includes(:last_comment).where(id: '1234').to_s
300
+ expect(soql).to eq "SELECT Id, Title__c, BlogId, (SELECT Id, PostId, PosterId__c, FancyPostId, Body__c FROM Comment__r WHERE (NOT ((Body__c = NULL))) ORDER BY CreatedDate DESC) FROM Post__c WHERE (Id = '1234')"
301
+ end
302
+ end
303
+
304
+ end
305
+
289
306
  context 'when invalid associations are passed' do
290
- it 'raises an error' do
291
- expect { Quota.includes(:invalid).find('123') }.to raise_error(ActiveForce::Association::InvalidAssociationError, 'Association named invalid was not found on Quota')
307
+ context 'when the association is not defined' do
308
+ it 'raises an error' do
309
+ expect { Quota.includes(:invalid).find('123') }.to raise_error(ActiveForce::Association::InvalidAssociationError, 'Association named invalid was not found on Quota')
310
+ end
311
+ end
312
+ context 'when the association is scoped and accepts an argument' do
313
+ it 'raises and error' do
314
+ expect { Post.includes(:reply_comments).find('1234')}.to raise_error(ActiveForce::Association::InvalidEagerLoadAssociation)
315
+ end
292
316
  end
293
317
  end
294
318
  end
@@ -313,6 +313,24 @@ describe ActiveForce::SObject do
313
313
  expect(Whizbang.create(text: 'some text')).to be_instance_of(Whizbang)
314
314
  end
315
315
  end
316
+
317
+ describe 'self.update' do
318
+ it 'uses the client to update the correct record' do
319
+ expect(client).to receive(:update!)
320
+ .with(Whizbang.table_name, { 'Id' => '12345678', 'Text_Label' => 'my text', 'Updated_From__c' => 'Rails' })
321
+ .and_return(true)
322
+ Whizbang.update('12345678', text: 'my text')
323
+ end
324
+ end
325
+
326
+ describe 'self.update!' do
327
+ it 'uses the client to update the correct record' do
328
+ expect(client).to receive(:update!)
329
+ .with(Whizbang.table_name, { 'Id' => '123456789', 'Text_Label' => 'some other text', 'Updated_From__c' => 'Rails' })
330
+ .and_return(true)
331
+ Whizbang.update('123456789', text: 'some other text')
332
+ end
333
+ end
316
334
  end
317
335
 
318
336
  describe '.count' do
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_force
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.18.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:
11
+ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2023-08-23 00:00:00.000000000 Z
14
+ date: 2023-10-02 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activemodel
@@ -193,7 +193,7 @@ metadata:
193
193
  bug_tracker_uri: https://github.com/Beyond-Finance/active_force/issues
194
194
  changelog_uri: https://github.com/Beyond-Finance/active_force/blob/main/CHANGELOG.md
195
195
  source_code_uri: https://github.com/Beyond-Finance/active_force
196
- post_install_message:
196
+ post_install_message:
197
197
  rdoc_options: []
198
198
  require_paths:
199
199
  - lib
@@ -208,8 +208,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
208
208
  - !ruby/object:Gem::Version
209
209
  version: '0'
210
210
  requirements: []
211
- rubygems_version: 3.1.6
212
- signing_key:
211
+ rubygems_version: 3.3.26
212
+ signing_key:
213
213
  specification_version: 4
214
214
  summary: Help you implement models persisting on Sales Force within Rails using RESTForce
215
215
  test_files: