active_force 0.7.1 → 0.15.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 +5 -5
- data/.circleci/config.yml +107 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.mailmap +3 -0
- data/CHANGELOG.md +115 -42
- data/CODEOWNERS +2 -0
- data/Gemfile +0 -1
- data/README.md +100 -21
- data/active_force.gemspec +11 -4
- data/lib/active_force/active_query.rb +107 -6
- data/lib/active_force/association/association.rb +47 -3
- data/lib/active_force/association/belongs_to_association.rb +25 -11
- data/lib/active_force/association/eager_load_projection_builder.rb +9 -3
- data/lib/active_force/association/has_many_association.rb +19 -19
- data/lib/active_force/association/has_one_association.rb +30 -0
- data/lib/active_force/association/relation_model_builder.rb +1 -1
- data/lib/active_force/association.rb +6 -4
- data/lib/active_force/{attribute.rb → field.rb} +3 -3
- data/lib/active_force/mapping.rb +6 -32
- data/lib/active_force/query.rb +21 -2
- data/lib/active_force/sobject.rb +74 -28
- data/lib/active_force/version.rb +3 -1
- data/lib/active_force.rb +2 -0
- data/lib/active_model/type/salesforce/multipicklist.rb +29 -0
- data/lib/active_model/type/salesforce/percent.rb +22 -0
- data/lib/generators/active_force/model/model_generator.rb +32 -21
- data/lib/generators/active_force/model/templates/model.rb.erb +3 -1
- data/spec/active_force/active_query_spec.rb +200 -8
- data/spec/active_force/association/relation_model_builder_spec.rb +22 -0
- data/spec/active_force/association_spec.rb +252 -9
- data/spec/active_force/field_spec.rb +34 -0
- data/spec/active_force/query_spec.rb +26 -0
- data/spec/active_force/sobject/includes_spec.rb +10 -10
- data/spec/active_force/sobject_spec.rb +221 -14
- data/spec/fixtures/sobject/single_sobject_hash.yml +1 -1
- data/spec/spec_helper.rb +5 -2
- data/spec/support/bangwhiz.rb +7 -0
- data/spec/support/restforce_factories.rb +1 -1
- data/spec/support/sobjects.rb +17 -1
- data/spec/support/whizbang.rb +2 -2
- metadata +64 -26
- data/lib/active_attr/dirty.rb +0 -24
- data/spec/active_force/attribute_spec.rb +0 -27
@@ -1,24 +1,42 @@
|
|
1
|
+
require 'active_support/all'
|
1
2
|
require 'active_force/query'
|
2
3
|
require 'forwardable'
|
3
4
|
|
4
5
|
module ActiveForce
|
5
6
|
class PreparedStatementInvalid < ArgumentError; end
|
7
|
+
|
8
|
+
class RecordNotFound < StandardError
|
9
|
+
attr_reader :table_name, :conditions
|
10
|
+
|
11
|
+
def initialize(message = nil, table_name = nil, conditions = nil)
|
12
|
+
@table_name = table_name
|
13
|
+
@conditions = conditions
|
14
|
+
|
15
|
+
super(message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
6
19
|
class ActiveQuery < Query
|
7
20
|
extend Forwardable
|
8
21
|
|
9
|
-
attr_reader :sobject
|
22
|
+
attr_reader :sobject, :association_mapping
|
10
23
|
|
11
24
|
def_delegators :sobject, :sfdc_client, :build, :table_name, :mappings
|
12
|
-
def_delegators :to_a, :each, :map, :inspect
|
25
|
+
def_delegators :to_a, :each, :map, :inspect, :pluck, :each_with_object
|
13
26
|
|
14
27
|
def initialize sobject
|
15
28
|
@sobject = sobject
|
29
|
+
@association_mapping = {}
|
16
30
|
super table_name
|
17
31
|
fields sobject.fields
|
18
32
|
end
|
19
33
|
|
20
34
|
def to_a
|
21
|
-
@
|
35
|
+
@decorated_records ||= sobject.try(:decorate, records) || records
|
36
|
+
end
|
37
|
+
|
38
|
+
private def records
|
39
|
+
@records ||= result.to_a.map { |mash| build mash, association_mapping }
|
22
40
|
end
|
23
41
|
|
24
42
|
alias_method :all, :to_a
|
@@ -28,13 +46,34 @@ module ActiveForce
|
|
28
46
|
sfdc_client.query(to_s).first.expr0
|
29
47
|
end
|
30
48
|
|
49
|
+
def sum field
|
50
|
+
super(mappings[field])
|
51
|
+
sfdc_client.query(to_s).first.expr0
|
52
|
+
end
|
53
|
+
|
54
|
+
def pluck(*fields)
|
55
|
+
fields = mappings.keys if fields.blank?
|
56
|
+
|
57
|
+
sfdc_client.query(select(*fields).to_s).map do |record|
|
58
|
+
values = fields.map { |field| cast_value(field, record) }
|
59
|
+
values.length == 1 ? values.first : values
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
31
63
|
def limit limit
|
32
64
|
super
|
33
65
|
limit == 1 ? to_a.first : self
|
34
66
|
end
|
35
67
|
|
68
|
+
def not args=nil, *rest
|
69
|
+
return self if args.nil?
|
70
|
+
super build_condition args, rest
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
36
74
|
def where args=nil, *rest
|
37
75
|
return self if args.nil?
|
76
|
+
return clone_self_and_clear_cache.where(args, *rest) if @decorated_records.present?
|
38
77
|
super build_condition args, rest
|
39
78
|
self
|
40
79
|
end
|
@@ -44,18 +83,48 @@ module ActiveForce
|
|
44
83
|
super *fields
|
45
84
|
end
|
46
85
|
|
86
|
+
def find!(id)
|
87
|
+
result = find(id)
|
88
|
+
raise RecordNotFound.new("Couldn't find #{table_name} with id #{id}", table_name, id: id) if result.nil?
|
89
|
+
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
47
93
|
def find_by conditions
|
48
94
|
where(conditions).limit 1
|
49
95
|
end
|
50
96
|
|
97
|
+
def find_by!(conditions)
|
98
|
+
result = find_by(conditions)
|
99
|
+
raise RecordNotFound.new("Couldn't find #{table_name} with #{conditions}", table_name, conditions) if result.nil?
|
100
|
+
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
51
104
|
def includes(*relations)
|
52
105
|
relations.each do |relation|
|
53
106
|
association = sobject.associations[relation]
|
54
107
|
fields Association::EagerLoadProjectionBuilder.build association
|
108
|
+
# downcase the key and downcase when we do the comparison so we don't do any more crazy string manipulation
|
109
|
+
association_mapping[association.sfdc_association_field.downcase] = association.relation_name
|
55
110
|
end
|
56
111
|
self
|
57
112
|
end
|
58
113
|
|
114
|
+
def none
|
115
|
+
@records = []
|
116
|
+
where(id: '1'*18).where(id: '0'*18)
|
117
|
+
end
|
118
|
+
|
119
|
+
def loaded?
|
120
|
+
!@records.nil?
|
121
|
+
end
|
122
|
+
|
123
|
+
def order *args
|
124
|
+
return self if args.nil?
|
125
|
+
super build_order_by args
|
126
|
+
end
|
127
|
+
|
59
128
|
private
|
60
129
|
|
61
130
|
def build_condition(args, other=[])
|
@@ -130,21 +199,53 @@ module ActiveForce
|
|
130
199
|
def enclose_value value
|
131
200
|
case value
|
132
201
|
when String
|
133
|
-
|
202
|
+
quote_string(value)
|
134
203
|
when NilClass
|
135
204
|
'NULL'
|
205
|
+
when Time
|
206
|
+
value.iso8601
|
136
207
|
else
|
137
208
|
value.to_s
|
138
209
|
end
|
139
210
|
end
|
140
211
|
|
141
212
|
def quote_string(s)
|
142
|
-
#
|
143
|
-
s.gsub(/\\/, '\&\&').gsub(/'/, "''")
|
213
|
+
"'#{s.gsub(/(['\\])/, '\\\\\\1')}'"
|
144
214
|
end
|
145
215
|
|
146
216
|
def result
|
147
217
|
sfdc_client.query(self.to_s)
|
148
218
|
end
|
219
|
+
|
220
|
+
def cast_value(field, object)
|
221
|
+
attribute_type = sobject.attribute_types[field.to_s]
|
222
|
+
value = object[mappings[field]]
|
223
|
+
attribute_type&.cast(value) || value
|
224
|
+
end
|
225
|
+
|
226
|
+
def clone_self_and_clear_cache
|
227
|
+
new_query = self.clone
|
228
|
+
new_query.instance_variable_set(:@decorated_records, nil)
|
229
|
+
new_query.instance_variable_set(:@records, nil)
|
230
|
+
new_query
|
231
|
+
end
|
232
|
+
|
233
|
+
def build_order_by(args)
|
234
|
+
args.map do |arg|
|
235
|
+
case arg
|
236
|
+
when Symbol
|
237
|
+
mappings[arg].to_s
|
238
|
+
when Hash
|
239
|
+
arg.map { |key, value| "#{mappings[key]} #{order_type(value)}" }
|
240
|
+
else
|
241
|
+
arg
|
242
|
+
end
|
243
|
+
end.join(', ')
|
244
|
+
end
|
245
|
+
|
246
|
+
def order_type(type)
|
247
|
+
type == :desc ? 'DESC' : 'ASC'
|
248
|
+
end
|
249
|
+
|
149
250
|
end
|
150
251
|
end
|
@@ -11,10 +11,11 @@ module ActiveForce
|
|
11
11
|
@relation_name = relation_name
|
12
12
|
@options = options
|
13
13
|
define_relation_method
|
14
|
+
define_assignment_method
|
14
15
|
end
|
15
16
|
|
16
17
|
def relation_model
|
17
|
-
options[:model] || relation_name.to_s.singularize.camelcase.constantize
|
18
|
+
(options[:model] || relation_name.to_s.singularize.camelcase).to_s.constantize
|
18
19
|
end
|
19
20
|
|
20
21
|
def foreign_key
|
@@ -22,7 +23,7 @@ module ActiveForce
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def relationship_name
|
25
|
-
options[:relationship_name] || relation_model.table_name
|
26
|
+
options[:relationship_name] || relation_model.to_s.constantize.table_name
|
26
27
|
end
|
27
28
|
|
28
29
|
###
|
@@ -38,13 +39,56 @@ module ActiveForce
|
|
38
39
|
relationship_name.gsub /__c\z/, '__r'
|
39
40
|
end
|
40
41
|
|
42
|
+
def load_target(owner)
|
43
|
+
if loadable?(owner)
|
44
|
+
target(owner)
|
45
|
+
else
|
46
|
+
target_when_unloadable
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
41
50
|
private
|
42
51
|
|
52
|
+
attr_reader :parent
|
53
|
+
|
54
|
+
def loadable?(owner)
|
55
|
+
owner&.persisted?
|
56
|
+
end
|
57
|
+
|
58
|
+
def target(_owner)
|
59
|
+
raise NoMethodError, 'target must be implemented'
|
60
|
+
end
|
61
|
+
|
62
|
+
def target_when_unloadable
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def define_relation_method
|
67
|
+
association = self
|
68
|
+
method_name = relation_name
|
69
|
+
parent.send(:define_method, method_name) do
|
70
|
+
association_cache.fetch(method_name) { association_cache[method_name] = association.load_target(self) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def define_assignment_method
|
75
|
+
raise NoMethodError, 'define_assignment_method must be implemented'
|
76
|
+
end
|
77
|
+
|
43
78
|
def infer_foreign_key_from_model(model)
|
44
79
|
name = model.custom_table? ? model.name : model.table_name
|
45
80
|
name.foreign_key.to_sym
|
46
81
|
end
|
47
|
-
end
|
48
82
|
|
83
|
+
def apply_scope(query, owner)
|
84
|
+
return query unless (scope = options[:scoped_as])
|
85
|
+
|
86
|
+
if scope.arity.positive?
|
87
|
+
query.instance_exec(owner, &scope)
|
88
|
+
else
|
89
|
+
query.instance_exec(&scope)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
49
93
|
end
|
50
94
|
end
|
@@ -1,24 +1,38 @@
|
|
1
1
|
module ActiveForce
|
2
2
|
module Association
|
3
3
|
class BelongsToAssociation < Association
|
4
|
+
def relationship_name
|
5
|
+
options[:relationship_name] || default_relationship_name
|
6
|
+
end
|
7
|
+
|
4
8
|
private
|
5
9
|
|
10
|
+
def loadable?(owner)
|
11
|
+
foreign_key_value(owner).present?
|
12
|
+
end
|
13
|
+
|
14
|
+
def target(owner)
|
15
|
+
relation_model.find(foreign_key_value(owner))
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_relationship_name
|
19
|
+
parent.mappings[foreign_key].gsub(/__c\z/, '__r')
|
20
|
+
end
|
21
|
+
|
6
22
|
def default_foreign_key
|
7
23
|
infer_foreign_key_from_model relation_model
|
8
24
|
end
|
9
25
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
@parent.send :define_method, _method do
|
14
|
-
association_cache.fetch(_method) do
|
15
|
-
association_cache[_method] = association.relation_model.find(send association.foreign_key)
|
16
|
-
end
|
17
|
-
end
|
26
|
+
def foreign_key_value(owner)
|
27
|
+
owner&.public_send(foreign_key)
|
28
|
+
end
|
18
29
|
|
19
|
-
|
20
|
-
|
21
|
-
|
30
|
+
def define_assignment_method
|
31
|
+
association = self
|
32
|
+
method_name = relation_name
|
33
|
+
parent.send :define_method, "#{method_name}=" do |other|
|
34
|
+
send "#{association.foreign_key}=", other&.id
|
35
|
+
association_cache[method_name] = other
|
22
36
|
end
|
23
37
|
end
|
24
38
|
end
|
@@ -40,15 +40,21 @@ module ActiveForce
|
|
40
40
|
# relationship name. Per SFDC convention, the name needs
|
41
41
|
# to be pluralized
|
42
42
|
def projections
|
43
|
-
|
44
|
-
# pluralize the table name, and append '__r' if it was there to begin with
|
45
|
-
relationship_name = association.sfdc_association_field.sub(match.to_s, '').pluralize + match.to_s
|
43
|
+
relationship_name = association.sfdc_association_field
|
46
44
|
query = Query.new relationship_name
|
47
45
|
query.fields association.relation_model.fields
|
48
46
|
["(#{query.to_s})"]
|
49
47
|
end
|
50
48
|
end
|
51
49
|
|
50
|
+
class HasOneAssociationProjectionBuilder < AbstractProjectionBuilder
|
51
|
+
def projections
|
52
|
+
query = Query.new association.sfdc_association_field
|
53
|
+
query.fields association.relation_model.fields
|
54
|
+
["(#{query.to_s})"]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
52
58
|
class BelongsToAssociationProjectionBuilder < AbstractProjectionBuilder
|
53
59
|
def projections
|
54
60
|
association.relation_model.fields.map do |field|
|
@@ -1,31 +1,31 @@
|
|
1
1
|
module ActiveForce
|
2
2
|
module Association
|
3
3
|
class HasManyAssociation < Association
|
4
|
+
def sfdc_association_field
|
5
|
+
name = relationship_name.gsub(/__c\z/, '__r')
|
6
|
+
match = name.match(/__r\z/)
|
7
|
+
# pluralize the table name, and append '__r' if it was there to begin with
|
8
|
+
name.sub(match.to_s, '').pluralize + match.to_s
|
9
|
+
end
|
10
|
+
|
4
11
|
private
|
5
12
|
|
6
13
|
def default_foreign_key
|
7
|
-
infer_foreign_key_from_model
|
14
|
+
infer_foreign_key_from_model parent
|
8
15
|
end
|
9
16
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
if scope.arity > 0
|
18
|
-
query.instance_exec self, &scope
|
19
|
-
else
|
20
|
-
query.instance_exec &scope
|
21
|
-
end
|
22
|
-
end
|
23
|
-
association_cache[_method] = query.where association.foreign_key => self.id
|
24
|
-
end
|
25
|
-
end
|
17
|
+
def target(owner)
|
18
|
+
apply_scope(relation_model.query, owner).where(foreign_key => owner.id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def target_when_unloadable
|
22
|
+
relation_model.none
|
23
|
+
end
|
26
24
|
|
27
|
-
|
28
|
-
|
25
|
+
def define_assignment_method
|
26
|
+
method_name = relation_name
|
27
|
+
parent.send :define_method, "#{method_name}=" do |associated|
|
28
|
+
association_cache[method_name] = associated
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ActiveForce
|
2
|
+
module Association
|
3
|
+
class HasOneAssociation < Association
|
4
|
+
private
|
5
|
+
|
6
|
+
def target(owner)
|
7
|
+
apply_scope(relation_model.query, owner).find_by(foreign_key => owner.id)
|
8
|
+
end
|
9
|
+
|
10
|
+
def default_foreign_key
|
11
|
+
infer_foreign_key_from_model parent
|
12
|
+
end
|
13
|
+
|
14
|
+
def define_assignment_method
|
15
|
+
foreign_key = self.foreign_key
|
16
|
+
method_name = relation_name
|
17
|
+
parent.send :define_method, "#{method_name}=" do |new_target|
|
18
|
+
new_target = new_target.first if new_target.is_a?(Array)
|
19
|
+
if new_target.present?
|
20
|
+
new_target.public_send("#{foreign_key}=", id)
|
21
|
+
else
|
22
|
+
current_target = public_send(method_name)
|
23
|
+
current_target&.public_send("#{foreign_key}=", nil)
|
24
|
+
end
|
25
|
+
association_cache[method_name] = new_target
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -2,6 +2,7 @@ require 'active_force/association/association'
|
|
2
2
|
require 'active_force/association/eager_load_projection_builder'
|
3
3
|
require 'active_force/association/relation_model_builder'
|
4
4
|
require 'active_force/association/has_many_association'
|
5
|
+
require 'active_force/association/has_one_association'
|
5
6
|
require 'active_force/association/belongs_to_association'
|
6
7
|
|
7
8
|
module ActiveForce
|
@@ -10,17 +11,18 @@ module ActiveForce
|
|
10
11
|
@associations ||= {}
|
11
12
|
end
|
12
13
|
|
13
|
-
# i.e name = 'Quota__r'
|
14
14
|
def find_association name
|
15
|
-
associations.
|
16
|
-
association.represents_sfdc_table? name
|
17
|
-
end
|
15
|
+
associations[name.to_sym]
|
18
16
|
end
|
19
17
|
|
20
18
|
def has_many relation_name, options = {}
|
21
19
|
associations[relation_name] = HasManyAssociation.new(self, relation_name, options)
|
22
20
|
end
|
23
21
|
|
22
|
+
def has_one relation_name, options = {}
|
23
|
+
associations[relation_name] = HasOneAssociation.new(self, relation_name, options)
|
24
|
+
end
|
25
|
+
|
24
26
|
def belongs_to relation_name, options = {}
|
25
27
|
associations[relation_name] = BelongsToAssociation.new(self, relation_name, options)
|
26
28
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module ActiveForce
|
2
|
-
class
|
2
|
+
class Field
|
3
3
|
|
4
4
|
attr_accessor :local_name, :sfdc_name, :as
|
5
5
|
|
@@ -11,8 +11,8 @@ module ActiveForce
|
|
11
11
|
|
12
12
|
def value_for_hash value
|
13
13
|
case as
|
14
|
-
when :
|
15
|
-
value
|
14
|
+
when :datetime
|
15
|
+
value&.to_fs(:iso8601)
|
16
16
|
else
|
17
17
|
value
|
18
18
|
end
|
data/lib/active_force/mapping.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'active_force/
|
1
|
+
require 'active_force/field'
|
2
2
|
require 'active_force/table'
|
3
3
|
require 'forwardable'
|
4
4
|
|
@@ -6,12 +6,6 @@ module ActiveForce
|
|
6
6
|
class Mapping
|
7
7
|
extend Forwardable
|
8
8
|
|
9
|
-
STRINGLIKE_TYPES = [
|
10
|
-
nil, :string, :base64, :byte, :ID, :reference, :currency, :textarea,
|
11
|
-
:phone, :url, :email, :combobox, :picklist, :multipicklist, :anyType,
|
12
|
-
:location
|
13
|
-
]
|
14
|
-
|
15
9
|
def_delegators :table, :custom_table?, :table_name
|
16
10
|
|
17
11
|
def initialize model
|
@@ -19,7 +13,7 @@ module ActiveForce
|
|
19
13
|
end
|
20
14
|
|
21
15
|
def mappings
|
22
|
-
Hash[fields.map { |field, attr| [field, attr.sfdc_name] }]
|
16
|
+
@mappings ||= Hash[fields.map { |field, attr| [field, attr.sfdc_name] }]
|
23
17
|
end
|
24
18
|
|
25
19
|
def sfdc_names
|
@@ -27,7 +21,7 @@ module ActiveForce
|
|
27
21
|
end
|
28
22
|
|
29
23
|
def field name, options
|
30
|
-
fields.merge!({ name => ActiveForce::
|
24
|
+
fields.merge!({ name => ActiveForce::Field.new(name, options) })
|
31
25
|
end
|
32
26
|
|
33
27
|
def table
|
@@ -36,15 +30,15 @@ module ActiveForce
|
|
36
30
|
|
37
31
|
def translate_to_sf attributes
|
38
32
|
attrs = attributes.map do |attribute, value|
|
39
|
-
|
40
|
-
[
|
33
|
+
field = fields[attribute.to_sym]
|
34
|
+
[field.sfdc_name, field.value_for_hash(value)]
|
41
35
|
end
|
42
36
|
Hash[attrs]
|
43
37
|
end
|
44
38
|
|
45
39
|
def translate_value value, field_name
|
46
40
|
return value unless !!field_name
|
47
|
-
|
41
|
+
value
|
48
42
|
end
|
49
43
|
|
50
44
|
|
@@ -54,25 +48,5 @@ module ActiveForce
|
|
54
48
|
@fields ||= {}
|
55
49
|
end
|
56
50
|
|
57
|
-
# Handles Salesforce FieldTypes as described here:
|
58
|
-
# http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_describesobjects_describesobjectresult.htm#i1427700
|
59
|
-
def typecast_value value, type
|
60
|
-
case type
|
61
|
-
when *STRINGLIKE_TYPES
|
62
|
-
value
|
63
|
-
when :boolean
|
64
|
-
!['false','0','f'].include? value.downcase
|
65
|
-
when :int
|
66
|
-
value.to_i
|
67
|
-
when :double, :percent
|
68
|
-
value.to_f
|
69
|
-
when :date
|
70
|
-
Date.parse value
|
71
|
-
when :datetime
|
72
|
-
DateTime.parse value
|
73
|
-
else
|
74
|
-
value
|
75
|
-
end
|
76
|
-
end
|
77
51
|
end
|
78
52
|
end
|
data/lib/active_force/query.rb
CHANGED
@@ -35,7 +35,17 @@ module ActiveForce
|
|
35
35
|
self
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
38
|
+
def not condition
|
39
|
+
@conditions << "NOT ((#{ condition.join(') AND (') }))"
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def or query
|
44
|
+
@conditions = ["(#{ and_conditions }) OR (#{ query.and_conditions })"]
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def where condition = nil
|
39
49
|
@conditions << condition if condition
|
40
50
|
self
|
41
51
|
end
|
@@ -86,13 +96,22 @@ module ActiveForce
|
|
86
96
|
self
|
87
97
|
end
|
88
98
|
|
99
|
+
def sum field
|
100
|
+
@query_fields = ["sum(#{field})"]
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
89
104
|
protected
|
105
|
+
def and_conditions
|
106
|
+
"(#{@conditions.join(') AND (')})" unless @conditions.empty?
|
107
|
+
end
|
108
|
+
|
90
109
|
def build_select
|
91
110
|
@query_fields.compact.uniq.join(', ')
|
92
111
|
end
|
93
112
|
|
94
113
|
def build_where
|
95
|
-
"WHERE
|
114
|
+
"WHERE #{and_conditions}" unless @conditions.empty?
|
96
115
|
end
|
97
116
|
|
98
117
|
def build_limit
|