active_force 0.20.1 → 0.21.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows +51 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +14 -0
- data/README.md +11 -0
- data/lib/active_force/active_query.rb +34 -8
- data/lib/active_force/association/eager_load_builder_for_nested_includes.rb +3 -2
- data/lib/active_force/association/eager_load_projection_builder.rb +18 -11
- data/lib/active_force/association/relation_model_builder.rb +5 -1
- data/lib/active_force/query.rb +12 -4
- data/lib/active_force/sobject.rb +51 -19
- data/lib/active_force/version.rb +1 -1
- data/lib/active_model/attribute/uninitialized_value.rb +12 -0
- data/lib/generators/active_force/model/model_generator.rb +1 -1
- data/spec/active_force/active_query_spec.rb +113 -5
- data/spec/active_force/association/relation_model_builder_spec.rb +30 -0
- data/spec/active_force/association_spec.rb +1 -1
- data/spec/active_force/query_spec.rb +16 -0
- data/spec/active_force/sobject/includes_spec.rb +80 -9
- data/spec/active_force/sobject/select_spec.rb +40 -0
- data/spec/active_force/sobject_spec.rb +31 -2
- data/spec/support/sobjects.rb +3 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a2d59fa533f638635afa0e8654fae4f77540310fba228ae697e05eaaf965525
|
4
|
+
data.tar.gz: 0570306ac71f575994b07aace62ad9579d0103f812eceb59aaa83b1a6ddc1911
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ceccbbfb84f6f80190d11ae8722ba8fbbe423cc4164b0567df2fd912f7e45150ddfb4092da3af528d431f03527676dd897e5565f6d5b18fdf70492d489667896
|
7
|
+
data.tar.gz: 91962473ab612ca13f755f7b94b891d8c6f849677999ef0333a98924b9f6a001566cd602fc3ee3c9c8e1b6cdcef039fbfeb13e4f1a8f2e284877e5d771a84e3c
|
data/.github/workflows
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
on:
|
2
|
+
pull_request:
|
3
|
+
types: [opened, reopened, synchronize]
|
4
|
+
pull_request_review_comment:
|
5
|
+
types: [created, edited, deleted]
|
6
|
+
|
7
|
+
name: Metomic Scan
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
scan-secrets:
|
11
|
+
name: Scan For Secrets
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
steps:
|
14
|
+
- name: checkout-repo
|
15
|
+
uses: actions/checkout@v3
|
16
|
+
with:
|
17
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
18
|
+
|
19
|
+
- name: authenticate-with-metomic
|
20
|
+
id: authenticate
|
21
|
+
continue-on-error: ${{ vars.METOMIC_FAIL_ON_CONNECTION_ERROR != 'TRUE' }}
|
22
|
+
run: |
|
23
|
+
curl -s --location --request GET '${{ vars.METOMIC_INTEGRATION_ENDPOINT }}/token' --header 'X-API-Key: ${{ secrets.METOMIC_API_TOKEN }}' -o .metomic_token.json
|
24
|
+
echo METOMIC_ACCESS_TOKEN=$(cat .metomic_token.json | jq -r -c '.accessToken') >> $GITHUB_OUTPUT
|
25
|
+
PASS=$(cat .metomic_token.json | jq -r -c '.accessToken // empty')
|
26
|
+
if [ -z "$PASS" ]; then
|
27
|
+
cat .metomic_token.json
|
28
|
+
echo ""
|
29
|
+
echo "FAIL: failed to fetch auth token from Metomic. Ensure required organisation variables / secrets are set correctly. METOMIC_INTEGRATION_ENDPOINT, METOMIC_API_TOKEN"
|
30
|
+
echo "The correct values for these variables are available from the installations page of the Metomic dashboard"
|
31
|
+
exit 1
|
32
|
+
fi
|
33
|
+
|
34
|
+
- name: checkout-metomic-action
|
35
|
+
id: checkout
|
36
|
+
uses: actions/checkout@v3
|
37
|
+
if: ${{ steps.authenticate.outcome == 'success' }}
|
38
|
+
continue-on-error: ${{ vars.METOMIC_FAIL_ON_CONNECTION_ERROR != 'TRUE' }}
|
39
|
+
with:
|
40
|
+
repository: metomic/metomic-github-integration-action.git
|
41
|
+
path: "./.metomic"
|
42
|
+
ref: "main"
|
43
|
+
token: ${{ steps.authenticate.outputs.METOMIC_ACCESS_TOKEN }}
|
44
|
+
|
45
|
+
- name: scan
|
46
|
+
uses: ./.metomic/.github/actions/scan-secrets
|
47
|
+
if: ${{ steps.authenticate.outputs.METOMIC_ACCESS_TOKEN && steps.checkout.outcome == 'success' }}
|
48
|
+
with:
|
49
|
+
metomic_endpoint: ${{ vars.METOMIC_INTEGRATION_ENDPOINT }}
|
50
|
+
metomic_api_token: ${{ secrets.METOMIC_API_TOKEN }}
|
51
|
+
head_ref: ${{ github.event.pull_request.head.sha }}
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,7 +2,21 @@
|
|
2
2
|
|
3
3
|
## Not released
|
4
4
|
|
5
|
+
## 0.21.1
|
6
|
+
- Fixes #91. Applies scopes to eager-loaded associations when they are nested. (https://github.com/Beyond-Finance/active_force/pull/92)
|
7
|
+
|
8
|
+
## 0.21.0
|
9
|
+
|
10
|
+
- Uninitialized attributes will ERROR instead of returning as `nil` (https://github.com/Beyond-Finance/active_force/pull/78)
|
11
|
+
- Add Range condition support (https://github.com/Beyond-Finance/active_force/pull/87)
|
12
|
+
- Use ':decimal' type for Salesforce currency fields in model generation (#88) (https://github.com/Beyond-Finance/active_force/pull/88)
|
13
|
+
- Fix includes with has_one associations when no associated record (https://github.com/Beyond-Finance/active_force/pull/83)
|
14
|
+
- Raise `UnknownFieldError` if `.where` is given non-existent attribute names (https://github.com/Beyond-Finance/active_force/pull/80)
|
15
|
+
- Fix `.update` and `.update!`: include given `nil` valued attributes in request (https://github.com/Beyond-Finance/active_force/pull/79)
|
16
|
+
- Change `.first` to not query the API if records have already been retrieved (https://github.com/Beyond-Finance/active_force/pull/77)
|
17
|
+
|
5
18
|
## 0.20.1
|
19
|
+
|
6
20
|
- Revert "ActiveForce .first performance enhancement (#73)" (https://github.com/Beyond-Finance/active_force/pull/76)
|
7
21
|
|
8
22
|
## 0.20.0
|
data/README.md
CHANGED
@@ -186,6 +186,17 @@ Account.where(contact_by: 'web').or(Account.where(contact_by: 'email'))
|
|
186
186
|
# OR (contact_by__c = 'email')"
|
187
187
|
```
|
188
188
|
|
189
|
+
You can use Ranges to specify comparisons:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
Account.where(last_activity_date: Date.new(2023, 1, 1)...Date.new(2024, 1, 1))
|
193
|
+
.where(annual_revenue: 1_000..)
|
194
|
+
#=> this will query "SELECT Id, Name...
|
195
|
+
# FROM Account
|
196
|
+
# WHERE (LastActivityDate >= 2023-01-01) AND (LastActivityDate < 2024-01-01)
|
197
|
+
# AND (AnnualRevenue >= 1000)
|
198
|
+
```
|
199
|
+
|
189
200
|
It is also possible to eager load associations:
|
190
201
|
|
191
202
|
```ruby
|
@@ -4,6 +4,13 @@ require 'forwardable'
|
|
4
4
|
|
5
5
|
module ActiveForce
|
6
6
|
class PreparedStatementInvalid < ArgumentError; end
|
7
|
+
|
8
|
+
class UnknownFieldError < StandardError
|
9
|
+
def initialize(object, field)
|
10
|
+
super("unknown field '#{field}' for #{object.name}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
7
14
|
class RecordNotFound < StandardError
|
8
15
|
attr_reader :table_name, :conditions
|
9
16
|
|
@@ -45,9 +52,9 @@ module ActiveForce
|
|
45
52
|
sfdc_client.query(super.to_s).first.expr0
|
46
53
|
end
|
47
54
|
|
48
|
-
def sum
|
55
|
+
def sum(field)
|
49
56
|
raise ArgumentError, 'field is required' if field.blank?
|
50
|
-
raise
|
57
|
+
raise UnknownFieldError.new(sobject, field) unless mappings.key?(field.to_sym)
|
51
58
|
|
52
59
|
sfdc_client.query(super(mappings.fetch(field.to_sym)).to_s).first.expr0
|
53
60
|
end
|
@@ -56,6 +63,10 @@ module ActiveForce
|
|
56
63
|
limit == 1 ? super.to_a.first : super
|
57
64
|
end
|
58
65
|
|
66
|
+
def first
|
67
|
+
super.to_a.first
|
68
|
+
end
|
69
|
+
|
59
70
|
def not args=nil, *rest
|
60
71
|
return self if args.nil?
|
61
72
|
|
@@ -162,16 +173,21 @@ module ActiveForce
|
|
162
173
|
end
|
163
174
|
|
164
175
|
def build_conditions_from_hash(hash)
|
165
|
-
hash.
|
166
|
-
|
176
|
+
hash.flat_map do |key, value|
|
177
|
+
field = mappings[key]
|
178
|
+
raise UnknownFieldError.new(sobject, key) if field.blank?
|
179
|
+
|
180
|
+
applicable_predicates(field, value)
|
167
181
|
end
|
168
182
|
end
|
169
183
|
|
170
|
-
def
|
171
|
-
if value.is_a?
|
172
|
-
in_predicate
|
184
|
+
def applicable_predicates(attribute, value)
|
185
|
+
if value.is_a?(Array)
|
186
|
+
[in_predicate(attribute, value)]
|
187
|
+
elsif value.is_a?(Range)
|
188
|
+
range_predicates(attribute, value)
|
173
189
|
else
|
174
|
-
eq_predicate
|
190
|
+
[eq_predicate(attribute, value)]
|
175
191
|
end
|
176
192
|
end
|
177
193
|
|
@@ -184,6 +200,16 @@ module ActiveForce
|
|
184
200
|
"#{attribute} = #{enclose_value value}"
|
185
201
|
end
|
186
202
|
|
203
|
+
def range_predicates(attribute, range)
|
204
|
+
conditions = []
|
205
|
+
conditions << "#{attribute} >= #{enclose_value(range.begin)}" unless range.begin.nil?
|
206
|
+
unless range.end.nil?
|
207
|
+
operator = range.exclude_end? ? '<' : '<='
|
208
|
+
conditions << "#{attribute} #{operator} #{enclose_value(range.end)}"
|
209
|
+
end
|
210
|
+
conditions
|
211
|
+
end
|
212
|
+
|
187
213
|
def enclose_value value
|
188
214
|
case value
|
189
215
|
when String
|
@@ -62,8 +62,9 @@ module ActiveForce
|
|
62
62
|
private
|
63
63
|
|
64
64
|
def build_relation(association, nested_includes)
|
65
|
-
|
66
|
-
|
65
|
+
builder_class = ActiveForce::Association::EagerLoadProjectionBuilder.projection_builder_class(association)
|
66
|
+
projection_builder = builder_class.new(association)
|
67
|
+
sub_query = projection_builder.query_with_association_fields
|
67
68
|
association_mapping[association.sfdc_association_field.downcase] = association.relation_name
|
68
69
|
nested_includes_query = self.class.build(nested_includes, association.relation_model)
|
69
70
|
sub_query.fields nested_includes_query[:fields]
|
@@ -6,6 +6,13 @@ module ActiveForce
|
|
6
6
|
def build(association, parent_association_field = nil)
|
7
7
|
new(association, parent_association_field).projections
|
8
8
|
end
|
9
|
+
|
10
|
+
def projection_builder_class(association)
|
11
|
+
klass = association.class.name.demodulize
|
12
|
+
ActiveForce::Association.const_get "#{klass}ProjectionBuilder"
|
13
|
+
rescue NameError
|
14
|
+
raise "No projection builder exists for #{klass}"
|
15
|
+
end
|
9
16
|
end
|
10
17
|
|
11
18
|
attr_reader :association, :parent_association_field
|
@@ -16,12 +23,10 @@ module ActiveForce
|
|
16
23
|
end
|
17
24
|
|
18
25
|
def projections
|
19
|
-
|
20
|
-
builder_class = ActiveForce::Association.const_get "#{klass}ProjectionBuilder"
|
26
|
+
builder_class = self.class.projection_builder_class(association)
|
21
27
|
builder_class.new(association, parent_association_field).projections
|
22
|
-
rescue NameError
|
23
|
-
raise "Don't know how to build projections for #{klass}"
|
24
28
|
end
|
29
|
+
|
25
30
|
end
|
26
31
|
|
27
32
|
class AbstractProjectionBuilder
|
@@ -42,26 +47,28 @@ module ActiveForce
|
|
42
47
|
|
43
48
|
query.instance_exec(&association.scoped_as)
|
44
49
|
end
|
45
|
-
end
|
46
50
|
|
47
|
-
class HasManyAssociationProjectionBuilder < AbstractProjectionBuilder
|
48
51
|
###
|
49
52
|
# Use ActiveForce::Query to build a subquery for the SFDC
|
50
53
|
# relationship name. Per SFDC convention, the name needs
|
51
54
|
# to be pluralized
|
52
|
-
def
|
55
|
+
def query_with_association_fields
|
53
56
|
relationship_name = association.sfdc_association_field
|
54
57
|
query = ActiveQuery.new(association.relation_model, relationship_name)
|
55
58
|
query.fields association.relation_model.fields
|
56
|
-
|
59
|
+
apply_association_scope(query)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class HasManyAssociationProjectionBuilder < AbstractProjectionBuilder
|
64
|
+
def projections
|
65
|
+
["(#{query_with_association_fields.to_s})"]
|
57
66
|
end
|
58
67
|
end
|
59
68
|
|
60
69
|
class HasOneAssociationProjectionBuilder < AbstractProjectionBuilder
|
61
70
|
def projections
|
62
|
-
|
63
|
-
query.fields association.relation_model.fields
|
64
|
-
["(#{apply_association_scope(query).to_s})"]
|
71
|
+
["(#{query_with_association_fields.to_s})"]
|
65
72
|
end
|
66
73
|
end
|
67
74
|
|
@@ -50,7 +50,11 @@ module ActiveForce
|
|
50
50
|
|
51
51
|
class BuildFromArray < AbstractBuildFrom
|
52
52
|
def call
|
53
|
-
|
53
|
+
if association.is_a?(HasOneAssociation)
|
54
|
+
association.build(value.first, association_mapping)
|
55
|
+
else
|
56
|
+
value.map { |mash| association.build(mash, association_mapping) }
|
57
|
+
end
|
54
58
|
end
|
55
59
|
end
|
56
60
|
|
data/lib/active_force/query.rb
CHANGED
@@ -78,7 +78,15 @@ module ActiveForce
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def first
|
81
|
-
|
81
|
+
if @records
|
82
|
+
clone_and_set_instance_variables(
|
83
|
+
size: 1,
|
84
|
+
records: [@records.first],
|
85
|
+
decorated_records: [@decorated_records&.first]
|
86
|
+
)
|
87
|
+
else
|
88
|
+
clone_and_set_instance_variables(size: 1)
|
89
|
+
end
|
82
90
|
end
|
83
91
|
|
84
92
|
def last(limit = 1)
|
@@ -126,9 +134,9 @@ module ActiveForce
|
|
126
134
|
|
127
135
|
def clone_and_set_instance_variables instance_variable_hash={}
|
128
136
|
clone = self.clone
|
129
|
-
|
130
|
-
|
131
|
-
|
137
|
+
{ decorated_records: nil, records: nil }
|
138
|
+
.merge(instance_variable_hash)
|
139
|
+
.each { |k,v| clone.instance_variable_set("@#{k.to_s}", v) }
|
132
140
|
clone
|
133
141
|
end
|
134
142
|
end
|
data/lib/active_force/sobject.rb
CHANGED
@@ -7,6 +7,7 @@ require 'yaml'
|
|
7
7
|
require 'forwardable'
|
8
8
|
require 'logger'
|
9
9
|
require 'restforce'
|
10
|
+
require 'active_model/attribute/uninitialized_value'
|
10
11
|
|
11
12
|
module ActiveForce
|
12
13
|
class RecordInvalid < StandardError;end
|
@@ -34,8 +35,24 @@ module ActiveForce
|
|
34
35
|
def_delegators :query, :not, :or, :where, :first, :last, :all, :find, :find!, :find_by, :find_by!, :sum, :count, :includes, :limit, :order, :select, :none
|
35
36
|
def_delegators :mapping, :table, :table_name, :custom_table?, :mappings
|
36
37
|
|
38
|
+
def update(id, attributes)
|
39
|
+
prepare_for_update(id, attributes).update
|
40
|
+
end
|
41
|
+
|
42
|
+
def update!(id, attributes)
|
43
|
+
prepare_for_update(id, attributes).update!
|
44
|
+
end
|
45
|
+
|
37
46
|
private
|
38
47
|
|
48
|
+
def prepare_for_update(id, attributes)
|
49
|
+
new(attributes.merge(id: id)).tap do |obj|
|
50
|
+
attributes.each do |name, value|
|
51
|
+
obj.public_send("#{name}_will_change!") if value.nil?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
39
56
|
###
|
40
57
|
# Provide each subclass with a default id field. Can be overridden
|
41
58
|
# in the subclass if needed
|
@@ -64,6 +81,9 @@ module ActiveForce
|
|
64
81
|
def self.build mash, association_mapping={}
|
65
82
|
return unless mash
|
66
83
|
sobject = new
|
84
|
+
|
85
|
+
attributes_not_selected = sobject.class.fields.reject{|key| mash.keys.include?(key)}
|
86
|
+
sobject.uninitialize_attributes(attributes_not_selected)
|
67
87
|
sobject.build_attributes = mash[:build_attributes] || mash
|
68
88
|
sobject.run_callbacks(:build) do
|
69
89
|
mash.each do |column, value|
|
@@ -110,6 +130,17 @@ module ActiveForce
|
|
110
130
|
self
|
111
131
|
end
|
112
132
|
|
133
|
+
def uninitialize_attributes(attrs)
|
134
|
+
return if attrs.blank?
|
135
|
+
self.instance_variable_get(:@attributes).instance_variable_get(:@attributes).each do |key, value|
|
136
|
+
if attrs.include?(self.mappings.dig(value.name.to_sym))
|
137
|
+
self.instance_variable_get(:@attributes).instance_variable_get(:@attributes)[key] = ActiveModel::Attribute::UninitializedValue.new(value.name, value.type)
|
138
|
+
else
|
139
|
+
key
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
113
144
|
def create
|
114
145
|
create!
|
115
146
|
rescue Faraday::ClientError, RecordInvalid => error
|
@@ -131,14 +162,6 @@ module ActiveForce
|
|
131
162
|
new(args).create!
|
132
163
|
end
|
133
164
|
|
134
|
-
def self.update(id, attributes)
|
135
|
-
new(attributes.merge(id: id)).update
|
136
|
-
end
|
137
|
-
|
138
|
-
def self.update!(id, attributes)
|
139
|
-
new(attributes.merge(id: id)).update!
|
140
|
-
end
|
141
|
-
|
142
165
|
def save!
|
143
166
|
run_callbacks :save do
|
144
167
|
if persisted?
|
@@ -183,18 +206,12 @@ module ActiveForce
|
|
183
206
|
self
|
184
207
|
end
|
185
208
|
|
186
|
-
def write_value
|
187
|
-
if association = self.class.find_association(key.to_sym)
|
188
|
-
|
189
|
-
value = Association::RelationModelBuilder.build(association, value, association_mapping)
|
190
|
-
elsif key.to_sym.in?(mappings.keys)
|
191
|
-
# key is a field name
|
192
|
-
field = key
|
209
|
+
def write_value(key, value, association_mapping = {})
|
210
|
+
if (association = self.class.find_association(key.to_sym))
|
211
|
+
write_association_value(association, value, association_mapping)
|
193
212
|
else
|
194
|
-
|
195
|
-
field = mappings.key(key)
|
213
|
+
write_field_value(key, value)
|
196
214
|
end
|
197
|
-
send "#{field}=", value if field && respond_to?(field)
|
198
215
|
end
|
199
216
|
|
200
217
|
def [](name)
|
@@ -205,7 +222,7 @@ module ActiveForce
|
|
205
222
|
send("#{name.to_sym}=", value)
|
206
223
|
end
|
207
224
|
|
208
|
-
|
225
|
+
private
|
209
226
|
|
210
227
|
def validate!
|
211
228
|
unless valid?
|
@@ -215,6 +232,21 @@ module ActiveForce
|
|
215
232
|
end
|
216
233
|
end
|
217
234
|
|
235
|
+
def write_association_value(association, value, association_mapping)
|
236
|
+
association_cache[association.relation_name] = Association::RelationModelBuilder.build(association, value,
|
237
|
+
association_mapping)
|
238
|
+
end
|
239
|
+
|
240
|
+
def write_field_value(field_key, value)
|
241
|
+
field = if mappings.key?(field_key.to_sym)
|
242
|
+
field_key
|
243
|
+
else
|
244
|
+
mappings.key(field_key)
|
245
|
+
end
|
246
|
+
|
247
|
+
send("#{field}=", value) if field && respond_to?(field)
|
248
|
+
end
|
249
|
+
|
218
250
|
def handle_save_error error
|
219
251
|
return false if error.class == RecordInvalid
|
220
252
|
logger_output __method__, error, attributes
|
data/lib/active_force/version.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
class TestSObject < ActiveForce::SObject
|
4
|
+
def self.decorate(records)
|
5
|
+
records
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
3
9
|
describe ActiveForce::ActiveQuery do
|
4
10
|
let(:sobject) do
|
5
|
-
|
6
|
-
|
7
|
-
fields: [],
|
8
|
-
|
9
|
-
})
|
11
|
+
class_double(
|
12
|
+
TestSObject,
|
13
|
+
{ table_name: 'table_name', fields: [], mappings: mappings, name: 'TableName' }
|
14
|
+
)
|
10
15
|
end
|
11
16
|
let(:mappings){ { id: "Id", field: "Field__c", other_field: "Other_Field" } }
|
12
17
|
let(:client) { double('client', query: nil) }
|
@@ -148,6 +153,74 @@ describe ActiveForce::ActiveQuery do
|
|
148
153
|
expect(new_query.to_s).to end_with("(Field__c = NULL)")
|
149
154
|
end
|
150
155
|
|
156
|
+
describe 'range filter' do
|
157
|
+
def check_endless(query, start, field: 'Field__c')
|
158
|
+
expect(query.to_s).to end_with("(#{field} >= #{start})")
|
159
|
+
end
|
160
|
+
|
161
|
+
def check_beginless_inclusive(query, finish, field: 'Field__c')
|
162
|
+
expect(query.to_s).to end_with("(#{field} <= #{finish})")
|
163
|
+
end
|
164
|
+
|
165
|
+
def check_beginless_exclusive(query, finish, field: 'Field__c')
|
166
|
+
expect(query.to_s).to end_with("(#{field} < #{finish})")
|
167
|
+
end
|
168
|
+
|
169
|
+
def check_inclusive(query, start, finish, field: 'Field__c')
|
170
|
+
expect(query.to_s).to end_with("(#{field} >= #{start}) AND (#{field} <= #{finish})")
|
171
|
+
end
|
172
|
+
|
173
|
+
def check_exclusive(query, start, finish, field: 'Field__c')
|
174
|
+
expect(query.to_s).to end_with("(#{field} >= #{start}) AND (#{field} < #{finish})")
|
175
|
+
end
|
176
|
+
|
177
|
+
def check_ranges(base_query, start, finish, &format_block)
|
178
|
+
formatted_start = format_block&.call(start) || start.to_s
|
179
|
+
formatted_finish = format_block&.call(finish) || finish.to_s
|
180
|
+
check_endless(base_query.where(field: start..), formatted_start)
|
181
|
+
check_beginless_inclusive(base_query.where(field: ..finish), formatted_finish)
|
182
|
+
check_beginless_exclusive(base_query.where(field: ...finish), formatted_finish)
|
183
|
+
check_inclusive(base_query.where(field: start..finish), formatted_start, formatted_finish)
|
184
|
+
check_exclusive(base_query.where(field: start...finish), formatted_start, formatted_finish)
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'renders with Dates' do
|
188
|
+
check_ranges(active_query, Date.new(2024, 2, 2), Date.new(2024, 2, 28))
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'renders with DateTimes' do
|
192
|
+
check_ranges(active_query, DateTime.new(2024, 1, 31, 1, 2, 3), DateTime.new(2024, 1, 31, 1, 2, 4))
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'renders with Times' do
|
196
|
+
check_ranges(active_query, Time.current, Time.current + 1.hour, &:iso8601)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'renders with Strings' do
|
200
|
+
check_ranges(active_query, 'a', 'z') { |x| "'#{x}'"}
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'renders with Integers' do
|
204
|
+
check_ranges(active_query, 1, 99)
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'renders with Floats' do
|
208
|
+
check_ranges(active_query, 0.5, 100.89)
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'renders with BigDecimal' do
|
212
|
+
check_ranges(active_query, BigDecimal('0.888'), BigDecimal('11.0003'))
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'composes with other conditions' do
|
216
|
+
query = active_query.where(id: 'id1'.., field: 1..99, other_field: 'a')
|
217
|
+
.not(id: 'id2')
|
218
|
+
expect(query.to_s).to end_with(
|
219
|
+
"(Id >= 'id1') AND (Field__c >= 1) AND (Field__c <= 99) AND (Other_Field = 'a') AND (NOT ((Id = 'id2')))"
|
220
|
+
)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
151
224
|
describe 'bind parameters' do
|
152
225
|
let(:mappings) do
|
153
226
|
super().merge({
|
@@ -296,6 +369,13 @@ describe ActiveForce::ActiveQuery do
|
|
296
369
|
end
|
297
370
|
end
|
298
371
|
end
|
372
|
+
|
373
|
+
context 'when given attributes Hash with fields that do not exist on the SObject' do
|
374
|
+
it 'raises UnknownFieldError' do
|
375
|
+
expect { active_query.where(xyz: 1) }
|
376
|
+
.to raise_error(ActiveForce::UnknownFieldError, /unknown field 'xyz' for #{sobject.name}/i)
|
377
|
+
end
|
378
|
+
end
|
299
379
|
end
|
300
380
|
|
301
381
|
describe '#not' do
|
@@ -329,6 +409,11 @@ describe ActiveForce::ActiveQuery do
|
|
329
409
|
new_query = active_query.find_by field: 123
|
330
410
|
expect(new_query).to be_nil
|
331
411
|
end
|
412
|
+
|
413
|
+
it 'should raise UnknownFieldError if given invalid field' do
|
414
|
+
expect { active_query.find_by(invalid: true) }
|
415
|
+
.to raise_error(ActiveForce::UnknownFieldError, /unknown field 'invalid' for #{sobject.name}/i)
|
416
|
+
end
|
332
417
|
end
|
333
418
|
|
334
419
|
describe '#find_by!' do
|
@@ -337,6 +422,11 @@ describe ActiveForce::ActiveQuery do
|
|
337
422
|
expect { active_query.find_by!(field: 123) }
|
338
423
|
.to raise_error(ActiveForce::RecordNotFound, "Couldn't find #{sobject.table_name} with {:field=>123}")
|
339
424
|
end
|
425
|
+
|
426
|
+
it 'should raise UnknownFieldError if given invalid field' do
|
427
|
+
expect { active_query.find_by!(invalid: true) }
|
428
|
+
.to raise_error(ActiveForce::UnknownFieldError, /unknown field 'invalid' for #{sobject.name}/i)
|
429
|
+
end
|
340
430
|
end
|
341
431
|
|
342
432
|
describe '#find!' do
|
@@ -463,4 +553,22 @@ describe ActiveForce::ActiveQuery do
|
|
463
553
|
end
|
464
554
|
|
465
555
|
end
|
556
|
+
|
557
|
+
describe '#first' do
|
558
|
+
before do
|
559
|
+
allow(client).to receive(:query).and_return(api_result)
|
560
|
+
api_result.each do |instance|
|
561
|
+
allow(active_query).to receive(:build).with(instance, {}).and_return(double(:sobject, id: instance['Id']))
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'returns a single record when the api was already queried' do
|
566
|
+
active_query.to_a # this will simulate the api call as to_a executes the query and populates the records
|
567
|
+
expect(active_query.first.id).to eq("0000000000AAAAABBB")
|
568
|
+
end
|
569
|
+
|
570
|
+
it 'returns a single record when the api was not already queried' do
|
571
|
+
expect(active_query.first.id).to eq("0000000000AAAAABBB")
|
572
|
+
end
|
573
|
+
end
|
466
574
|
end
|
@@ -70,6 +70,36 @@ module ActiveForce
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
+
context 'with a restforce collection value' do
|
74
|
+
let(:value) do
|
75
|
+
build_restforce_collection([
|
76
|
+
build_restforce_sobject('Id' => 'first'),
|
77
|
+
build_restforce_sobject('Id' => 'second')
|
78
|
+
])
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns a child for the first value' do
|
82
|
+
actual = instance.build_relation_model
|
83
|
+
expect(actual).to be_a(HasOneChild)
|
84
|
+
expect(actual.id).to eq('first')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with an array value' do
|
89
|
+
let(:value) do
|
90
|
+
[
|
91
|
+
build_restforce_sobject('Id' => 'first'),
|
92
|
+
build_restforce_sobject('Id' => 'second')
|
93
|
+
]
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns a child for the first value' do
|
97
|
+
actual = instance.build_relation_model
|
98
|
+
expect(actual).to be_a(HasOneChild)
|
99
|
+
expect(actual.id).to eq('first')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
73
103
|
context 'without a value' do
|
74
104
|
let(:value){ nil }
|
75
105
|
|
@@ -386,7 +386,7 @@ describe ActiveForce::SObject do
|
|
386
386
|
it 'allows passing a foreign key' do
|
387
387
|
Comment.belongs_to :post, foreign_key: :fancy_post_id
|
388
388
|
allow(comment).to receive(:fancy_post_id).and_return "2"
|
389
|
-
expect(client).to receive(:query).with("SELECT Id, Title__c, BlogId FROM Post__c WHERE (Id = '2') LIMIT 1")
|
389
|
+
expect(client).to receive(:query).with("SELECT Id, Title__c, BlogId, IsActive FROM Post__c WHERE (Id = '2') LIMIT 1")
|
390
390
|
comment.post
|
391
391
|
Comment.belongs_to :post # reset association to original value
|
392
392
|
end
|
@@ -165,6 +165,22 @@ describe ActiveForce::Query do
|
|
165
165
|
expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name"
|
166
166
|
expect(new_query.to_s).to eq 'SELECT Id, name, etc FROM table_name LIMIT 1'
|
167
167
|
end
|
168
|
+
|
169
|
+
it "does not query if records have already been fetched" do
|
170
|
+
query = ActiveForce::Query.new 'table_name'
|
171
|
+
query.instance_variable_set(:@records, %w[foo bar])
|
172
|
+
query.instance_variable_set(:@decorated_records, %w[foo bar])
|
173
|
+
expect(query).not_to receive(:clone_and_set_instance_variables).with(size: 1)
|
174
|
+
expect(query).to receive(:clone_and_set_instance_variables).with(size: 1, records: ['foo'], decorated_records: ['foo'])
|
175
|
+
query.first
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'queries the api if it has not been queried yet' do
|
179
|
+
query = ActiveForce::Query.new 'table_name'
|
180
|
+
query.instance_variable_set(:@records, nil)
|
181
|
+
expect(query).to receive(:clone_and_set_instance_variables).with(size: 1)
|
182
|
+
query.first
|
183
|
+
end
|
168
184
|
end
|
169
185
|
|
170
186
|
describe '.last' do
|
@@ -228,7 +228,7 @@ module ActiveForce
|
|
228
228
|
context 'when assocation has a scope' do
|
229
229
|
it 'formulates the correct SOQL query with the scope applied' do
|
230
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')"
|
231
|
+
expect(soql).to eq "SELECT Id, Title__c, BlogId, IsActive, (SELECT Id, PostId, PosterId__c, FancyPostId, Body__c FROM Comments__r WHERE (1 = 0)) FROM Post__c WHERE (Id = '1234')"
|
232
232
|
end
|
233
233
|
end
|
234
234
|
|
@@ -297,10 +297,49 @@ module ActiveForce
|
|
297
297
|
context 'when assocation has a scope' do
|
298
298
|
it 'formulates the correct SOQL query with the scope applied' do
|
299
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')"
|
300
|
+
expect(soql).to eq "SELECT Id, Title__c, BlogId, IsActive, (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
301
|
end
|
302
302
|
end
|
303
303
|
|
304
|
+
context 'when query returns nil for associated record' do
|
305
|
+
let(:response) do
|
306
|
+
[build_restforce_sobject({ 'Id' => '123', 'Membership__r' => nil })]
|
307
|
+
end
|
308
|
+
|
309
|
+
before do
|
310
|
+
allow(client).to receive(:query).and_return(response)
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'the association method returns nil without making another request' do
|
314
|
+
member = ClubMember.includes(:membership).where(id: '123').first
|
315
|
+
membership = member.membership
|
316
|
+
expect(membership).to be_nil
|
317
|
+
expect(client).to have_received(:query).once
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
context 'when query returns an associated record' do
|
323
|
+
let(:response) do
|
324
|
+
[
|
325
|
+
build_restforce_sobject(
|
326
|
+
{
|
327
|
+
'Id' => '123',
|
328
|
+
'Membership__r' => build_restforce_collection([build_restforce_sobject({ 'Id' => '33' })])
|
329
|
+
}
|
330
|
+
)
|
331
|
+
]
|
332
|
+
end
|
333
|
+
|
334
|
+
before do
|
335
|
+
allow(client).to receive(:query).and_return(response)
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'the association method returns the record without making another request' do
|
339
|
+
member = ClubMember.includes(:membership).where(id: '123').first
|
340
|
+
expect(member.membership.id).to eq('33')
|
341
|
+
expect(client).to have_received(:query).once
|
342
|
+
end
|
304
343
|
end
|
305
344
|
|
306
345
|
context 'when invalid associations are passed' do
|
@@ -358,9 +397,41 @@ module ActiveForce
|
|
358
397
|
end
|
359
398
|
end
|
360
399
|
|
400
|
+
context 'when the associations have scopes' do
|
401
|
+
it 'generates the correct SOQL query' do
|
402
|
+
soql = Blog.includes(active_posts: :impossible_comments).where(id: '123').to_s
|
403
|
+
expect(soql).to eq <<-SOQL.squish
|
404
|
+
SELECT Id, Name, Link__c,
|
405
|
+
(SELECT Id, Title__c, BlogId, IsActive,
|
406
|
+
(SELECT Id, PostId, PosterId__c, FancyPostId, Body__c
|
407
|
+
FROM Comments__r WHERE (1 = 0))
|
408
|
+
FROM Posts__r
|
409
|
+
WHERE (IsActive = true))
|
410
|
+
FROM Blog__c
|
411
|
+
WHERE (Id = '123')
|
412
|
+
SOQL
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'builds the associated objects and caches them' do
|
416
|
+
response = [build_restforce_sobject({
|
417
|
+
'Id' => '123',
|
418
|
+
'Posts__r' => build_restforce_collection([
|
419
|
+
{'Id' => '213', 'IsActive' => true, 'Comments__r' => [{'Id' => '987'}]},
|
420
|
+
{'Id' => '214', 'IsActive' => true, 'Comments__r' => [{'Id' => '456'}]}
|
421
|
+
])
|
422
|
+
})]
|
423
|
+
allow(client).to receive(:query).once.and_return response
|
424
|
+
blog = Blog.includes(active_posts: :impossible_comments).find '123'
|
425
|
+
expect(blog.active_posts).to be_an Array
|
426
|
+
expect(blog.active_posts.all? { |o| o.is_a? Post }).to eq true
|
427
|
+
expect(blog.active_posts.first.impossible_comments.first).to be_a Comment
|
428
|
+
expect(blog.active_posts.first.impossible_comments.first.id).to eq '987'
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
361
432
|
context 'with namespaced sobjects' do
|
362
433
|
it 'formulates the correct SOQL query' do
|
363
|
-
soql = Salesforce::Account.includes({
|
434
|
+
soql = Salesforce::Account.includes({opportunities: :owner}).where(id: '123').to_s
|
364
435
|
expect(soql).to eq <<-SOQL.squish
|
365
436
|
SELECT Id, Business_Partner__c,
|
366
437
|
(SELECT Id, OwnerId, AccountId, Business_Partner__c, Owner.Id
|
@@ -378,11 +449,11 @@ module ActiveForce
|
|
378
449
|
{'Id' => '214', 'AccountId' => '123', 'OwnerId' => '321', 'Business_Partner__c' => '123', 'Owner' => {'Id' => '321'}} ])
|
379
450
|
})]
|
380
451
|
allow(client).to receive(:query).once.and_return response
|
381
|
-
account = Salesforce::Account.includes({
|
382
|
-
expect(account.
|
383
|
-
expect(account.
|
384
|
-
expect(account.
|
385
|
-
expect(account.
|
452
|
+
account = Salesforce::Account.includes({opportunities: :owner}).find '123'
|
453
|
+
expect(account.opportunities).to be_an Array
|
454
|
+
expect(account.opportunities.all? { |o| o.is_a? Salesforce::Opportunity }).to eq true
|
455
|
+
expect(account.opportunities.first.owner).to be_a Salesforce::User
|
456
|
+
expect(account.opportunities.first.owner.id).to eq '321'
|
386
457
|
end
|
387
458
|
end
|
388
459
|
|
@@ -592,7 +663,7 @@ module ActiveForce
|
|
592
663
|
soql = Comment.includes(post: :blog).where(id: '123').to_s
|
593
664
|
expect(soql).to eq <<-SOQL.squish
|
594
665
|
SELECT Id, PostId, PosterId__c, FancyPostId, Body__c,
|
595
|
-
PostId.Id, PostId.Title__c, PostId.BlogId,
|
666
|
+
PostId.Id, PostId.Title__c, PostId.BlogId, PostId.IsActive,
|
596
667
|
PostId.BlogId.Id, PostId.BlogId.Name, PostId.BlogId.Link__c
|
597
668
|
FROM Comment__c
|
598
669
|
WHERE (Id = '123')
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ActiveForce
|
4
|
+
describe SObject do
|
5
|
+
let(:client){ double "client" }
|
6
|
+
|
7
|
+
before do
|
8
|
+
ActiveForce.sfdc_client = client
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.select' do
|
12
|
+
it 'has correct fields in query' do
|
13
|
+
query = Territory.select(:name, :id)
|
14
|
+
expect(query.fields).to eq(["Name", "Id"])
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when getting the value of an uninitialized attribute' do
|
18
|
+
let(:territory) { Territory.select(:id).first }
|
19
|
+
let(:response) do
|
20
|
+
[build_restforce_sobject({
|
21
|
+
"Id" => "123",
|
22
|
+
"Quota__c" => "321",
|
23
|
+
})]
|
24
|
+
end
|
25
|
+
|
26
|
+
before do
|
27
|
+
allow(client).to receive(:query).once.and_return response
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'raises missing attribute error if uninitialized variable is called' do
|
31
|
+
expect{territory.name}.to raise_error(ActiveModel::MissingAttributeError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns SObjects with Uninitialized Value' do
|
35
|
+
expect(territory.instance_variable_get(:@attributes)["name"]).to be_an_instance_of(ActiveModel::Attribute::UninitializedValue)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -321,6 +321,15 @@ describe ActiveForce::SObject do
|
|
321
321
|
.and_return(true)
|
322
322
|
Whizbang.update('12345678', text: 'my text')
|
323
323
|
end
|
324
|
+
|
325
|
+
it 'includes given nil values in the request' do
|
326
|
+
allow(client).to receive(:update!).and_return(true)
|
327
|
+
Whizbang.update('test123', text: nil, date: nil)
|
328
|
+
expect(client).to have_received(:update!).with(
|
329
|
+
Whizbang.table_name,
|
330
|
+
{ 'Id' => 'test123', 'Text_Label' => nil, 'Date_Label' => nil, 'Updated_From__c' => 'Rails' }
|
331
|
+
)
|
332
|
+
end
|
324
333
|
end
|
325
334
|
|
326
335
|
describe 'self.update!' do
|
@@ -330,6 +339,15 @@ describe ActiveForce::SObject do
|
|
330
339
|
.and_return(true)
|
331
340
|
Whizbang.update('123456789', text: 'some other text')
|
332
341
|
end
|
342
|
+
|
343
|
+
it 'includes given nil values in the request' do
|
344
|
+
allow(client).to receive(:update!).and_return(true)
|
345
|
+
Whizbang.update!('test123', text: nil, date: nil)
|
346
|
+
expect(client).to have_received(:update!).with(
|
347
|
+
Whizbang.table_name,
|
348
|
+
{ 'Id' => 'test123', 'Text_Label' => nil, 'Date_Label' => nil, 'Updated_From__c' => 'Rails' }
|
349
|
+
)
|
350
|
+
end
|
333
351
|
end
|
334
352
|
end
|
335
353
|
|
@@ -368,9 +386,9 @@ describe ActiveForce::SObject do
|
|
368
386
|
expect { Whizbang.sum(nil) }.to raise_error(ArgumentError, 'field is required')
|
369
387
|
end
|
370
388
|
|
371
|
-
it 'raises
|
389
|
+
it 'raises UnknownFieldError if given invalid field' do
|
372
390
|
expect { Whizbang.sum(:invalid) }
|
373
|
-
.to raise_error(
|
391
|
+
.to raise_error(ActiveForce::UnknownFieldError, /unknown field 'invalid' for Whizbang/i)
|
374
392
|
end
|
375
393
|
|
376
394
|
it 'sends the correct query to the client' do
|
@@ -401,6 +419,11 @@ describe ActiveForce::SObject do
|
|
401
419
|
expect(client).to receive(:query).with("SELECT #{Whizbang.fields.join ', '} FROM Whizbang__c WHERE (Id = 123) AND (Text_Label = 'foo') LIMIT 1")
|
402
420
|
Whizbang.find_by id: 123, text: "foo"
|
403
421
|
end
|
422
|
+
|
423
|
+
it 'raises UnknownFieldError if given invalid field' do
|
424
|
+
expect { Whizbang.find_by(xyz: 1) }
|
425
|
+
.to raise_error(ActiveForce::UnknownFieldError, /unknown field 'xyz' for Whizbang/)
|
426
|
+
end
|
404
427
|
end
|
405
428
|
|
406
429
|
describe "#find_by!" do
|
@@ -408,10 +431,16 @@ describe ActiveForce::SObject do
|
|
408
431
|
expect(client).to receive(:query).with("SELECT #{Whizbang.fields.join ', '} FROM Whizbang__c WHERE (Id = 123) AND (Text_Label = 'foo') LIMIT 1").and_return([Restforce::Mash.new(Id: 123, text: 'foo')])
|
409
432
|
Whizbang.find_by! id: 123, text: "foo"
|
410
433
|
end
|
434
|
+
|
411
435
|
it "raises if nothing found" do
|
412
436
|
expect(client).to receive(:query).with("SELECT #{Whizbang.fields.join ', '} FROM Whizbang__c WHERE (Id = 123) AND (Text_Label = 'foo') LIMIT 1")
|
413
437
|
expect { Whizbang.find_by! id: 123, text: "foo" }.to raise_error(ActiveForce::RecordNotFound)
|
414
438
|
end
|
439
|
+
|
440
|
+
it 'raises UnknownFieldError if given invalid field' do
|
441
|
+
expect { Whizbang.find_by!(xyz: 1) }
|
442
|
+
.to raise_error(ActiveForce::UnknownFieldError, /unknown field 'xyz' for Whizbang/)
|
443
|
+
end
|
415
444
|
end
|
416
445
|
|
417
446
|
describe '.find!' do
|
data/spec/support/sobjects.rb
CHANGED
@@ -11,6 +11,7 @@ class Post < ActiveForce::SObject
|
|
11
11
|
self.table_name = "Post__c"
|
12
12
|
field :title
|
13
13
|
field :blog_id, from: "BlogId"
|
14
|
+
field :is_active, from: "IsActive", as: :boolean
|
14
15
|
has_many :comments
|
15
16
|
has_many :impossible_comments, model: Comment, scoped_as: ->{ where('1 = 0') }
|
16
17
|
has_many :reply_comments, model: Comment, scoped_as: ->(post){ where(body: "RE: #{post.title}").order('CreationDate DESC') }
|
@@ -25,6 +26,7 @@ class Blog < ActiveForce::SObject
|
|
25
26
|
field :name, from: 'Name'
|
26
27
|
field :link, from: 'Link__c'
|
27
28
|
has_many :posts
|
29
|
+
has_many :active_posts, model: 'Post', scoped_as: -> { where(is_active: true) }
|
28
30
|
end
|
29
31
|
class Territory < ActiveForce::SObject
|
30
32
|
field :quota_id, from: "Quota__c"
|
@@ -149,6 +151,7 @@ module Salesforce
|
|
149
151
|
end
|
150
152
|
class Account < ActiveForce::SObject
|
151
153
|
field :business_partner
|
154
|
+
has_many :opportunities, model: Opportunity
|
152
155
|
has_many :partner_opportunities, model: Opportunity, scoped_as: ->(account){ where(business_partner: account.business_partner).includes(:owner) }
|
153
156
|
end
|
154
157
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_force
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.21.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eloy Espinaco
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2024-05-17 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activemodel
|
@@ -134,6 +134,7 @@ files:
|
|
134
134
|
- ".circleci/config.yml"
|
135
135
|
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
136
136
|
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
137
|
+
- ".github/workflows"
|
137
138
|
- ".gitignore"
|
138
139
|
- ".mailmap"
|
139
140
|
- ".rspec"
|
@@ -166,6 +167,7 @@ files:
|
|
166
167
|
- lib/active_force/standard_types.rb
|
167
168
|
- lib/active_force/table.rb
|
168
169
|
- lib/active_force/version.rb
|
170
|
+
- lib/active_model/attribute/uninitialized_value.rb
|
169
171
|
- lib/active_model/type/salesforce/multipicklist.rb
|
170
172
|
- lib/active_model/type/salesforce/percent.rb
|
171
173
|
- lib/generators/active_force/model/USAGE
|
@@ -183,6 +185,7 @@ files:
|
|
183
185
|
- spec/active_force/mapping_spec.rb
|
184
186
|
- spec/active_force/query_spec.rb
|
185
187
|
- spec/active_force/sobject/includes_spec.rb
|
188
|
+
- spec/active_force/sobject/select_spec.rb
|
186
189
|
- spec/active_force/sobject/table_name_spec.rb
|
187
190
|
- spec/active_force/sobject_spec.rb
|
188
191
|
- spec/active_force/table_spec.rb
|
@@ -216,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
216
219
|
- !ruby/object:Gem::Version
|
217
220
|
version: '0'
|
218
221
|
requirements: []
|
219
|
-
rubygems_version: 3.
|
222
|
+
rubygems_version: 3.4.10
|
220
223
|
signing_key:
|
221
224
|
specification_version: 4
|
222
225
|
summary: Help you implement models persisting on Sales Force within Rails using RESTForce
|
@@ -233,6 +236,7 @@ test_files:
|
|
233
236
|
- spec/active_force/mapping_spec.rb
|
234
237
|
- spec/active_force/query_spec.rb
|
235
238
|
- spec/active_force/sobject/includes_spec.rb
|
239
|
+
- spec/active_force/sobject/select_spec.rb
|
236
240
|
- spec/active_force/sobject/table_name_spec.rb
|
237
241
|
- spec/active_force/sobject_spec.rb
|
238
242
|
- spec/active_force/table_spec.rb
|