active_force 0.5.0 → 0.6.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 +4 -4
- data/.travis.yml +9 -2
- data/CHANGELOG.md +6 -0
- data/README.md +43 -41
- data/active_force.gemspec +1 -1
- data/lib/active_attr/dirty.rb +0 -5
- data/lib/active_force/active_query.rb +66 -7
- data/lib/active_force/association/association.rb +4 -0
- data/lib/active_force/association/belongs_to_association.rb +11 -3
- data/lib/active_force/association/has_many_association.rb +6 -4
- data/lib/active_force/query.rb +5 -0
- data/lib/active_force/sobject.rb +45 -32
- data/lib/active_force/standard_types.rb +357 -0
- data/lib/active_force/table.rb +33 -0
- data/lib/active_force/version.rb +1 -1
- data/lib/generators/active_force/{active_force_model → model}/USAGE +0 -0
- data/lib/generators/active_force/model/model_generator.rb +61 -0
- data/lib/generators/active_force/model/templates/model.rb.erb +7 -0
- data/spec/active_force/active_query_spec.rb +79 -12
- data/spec/active_force/association_spec.rb +94 -16
- data/spec/active_force/callbacks_spec.rb +42 -0
- data/spec/active_force/query_spec.rb +10 -0
- data/spec/active_force/sobject/table_name_spec.rb +15 -4
- data/spec/active_force/sobject_spec.rb +72 -3
- data/spec/active_force/table_spec.rb +28 -0
- metadata +13 -8
- data/lib/generators/active_force/active_force_model/active_force_model_generator.rb +0 -47
- data/lib/generators/active_force/active_force_model/templates/model.rb.erb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36f3376b1b60bba8f226a7b811550b16fc4fccf2
|
4
|
+
data.tar.gz: 8c5f72016eb4139055f141858d7377d6dc93a84c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ec85db056728c1365e39f2934726daa912d67b6fae0865dcfbe1b88313c991dac416a24ba0c6bf516955125cc2471550ca38d427485d894078d477e0bf05c4a
|
7
|
+
data.tar.gz: 1b9263f3bdaab14b9ce0abe6c366684032dbeb7d01f5a1718003f5d7b0c640163722849ec69eebff1e2f57f48a1c9fda9ffc36a145ec87938b5e5173476a0457
|
data/.travis.yml
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
+
- 1.9.3
|
3
4
|
- 2.0.0
|
4
5
|
- 2.1.1
|
6
|
+
- rbx-2
|
5
7
|
matrix:
|
6
8
|
allow_failures:
|
7
|
-
- rvm:
|
9
|
+
- rvm:
|
10
|
+
- rbx-2
|
8
11
|
fast_finish: true
|
9
12
|
env:
|
10
|
-
- CODECLIMATE_REPO_TOKEN=1c5ed259429e0bfc5412974d69cfba2780962a87ca8c543d684e33fea37f7e71
|
13
|
+
- CODECLIMATE_REPO_TOKEN=1c5ed259429e0bfc5412974d69cfba2780962a87ca8c543d684e33fea37f7e71
|
14
|
+
notifications:
|
15
|
+
webhooks:
|
16
|
+
urls:
|
17
|
+
- https://webhooks.gitter.im/e/a790a8fc07d033f05e00
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
## Not released
|
4
4
|
|
5
|
+
* Add select statement functionality. ([Pablo Oldani][], [#33][])
|
6
|
+
* Add callback functionality ([Pablo Oldani][], [#20][])
|
7
|
+
* Support bind parameters. ([Dan Olson][], [#29][])
|
8
|
+
* Fix when passing nil value in a :where condition. ([Armando Andini][])
|
9
|
+
* Model generator complete ([Armando Andini][], [#19][])
|
10
|
+
|
5
11
|
## 0.5.0
|
6
12
|
|
7
13
|
* Provide a default id field for all SObject subclassees ([Dan Olson][], [#30][])
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
[](https://codeclimate.com/github/ionia-corporation/active_force)
|
4
4
|
[](https://gemnasium.com/ionia-corporation/active_force)
|
5
5
|
[](https://codeclimate.com/github/ionia-corporation/active_force)
|
6
|
-
|
6
|
+
[](https://gitter.im/ionia-corporation/active_force)
|
7
7
|
|
8
8
|
# ActiveForce
|
9
9
|
|
@@ -37,38 +37,48 @@ Restforce.log = true if Rails.env.development?
|
|
37
37
|
|
38
38
|
## Usage
|
39
39
|
|
40
|
-
### Define a class
|
41
|
-
|
42
40
|
```ruby
|
43
|
-
class
|
41
|
+
class Medication < ActiveForce::SObject
|
44
42
|
|
45
|
-
|
46
|
-
```
|
43
|
+
field :name, from: 'Name'
|
47
44
|
|
48
|
-
|
45
|
+
field :max_dossage # from defaults to "Max_Dossage__c"
|
46
|
+
field :updated_from
|
49
47
|
|
50
|
-
|
51
|
-
|
52
|
-
#
|
53
|
-
|
54
|
-
field :name, from: 'Medication__c'
|
55
|
-
#set SalesForce table name.
|
56
|
-
self.table_name = 'Patient_Medication__c'
|
57
|
-
end
|
58
|
-
```
|
48
|
+
##
|
49
|
+
# Table name is infered from class name.
|
50
|
+
#
|
51
|
+
# self.table_name = 'Medication__c' # default one.
|
59
52
|
|
60
|
-
|
53
|
+
##
|
54
|
+
# Validations
|
55
|
+
#
|
56
|
+
validates :name, :login, :email, presence: true
|
61
57
|
|
62
|
-
|
58
|
+
# Use any validation from active record.
|
59
|
+
# validates :text, length: { minimum: 2 }
|
60
|
+
# validates :text, format: { with: /\A[a-zA-Z]+\z/, message: "only allows letters" }
|
61
|
+
# validates :size, inclusion: { in: %w(small medium large),
|
62
|
+
# message: "%{value} is not a valid size" }
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
##
|
65
|
+
# Callbacks
|
66
|
+
#
|
67
|
+
before_save :set_as_updated_from_rails
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def set_as_updated_from_rails
|
72
|
+
self.updated_from = 'Rails'
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
70
76
|
```
|
71
77
|
|
78
|
+
Altenative you can try the generator. (requires setting up the connection)
|
79
|
+
|
80
|
+
rails generate active_force_model Medication__c
|
81
|
+
|
72
82
|
### Relationships
|
73
83
|
|
74
84
|
#### Has Many
|
@@ -76,38 +86,30 @@ validates :size, inclusion: { in: %w(small medium large),
|
|
76
86
|
```ruby
|
77
87
|
class Account < ActiveForce::SObject
|
78
88
|
has_many :pages
|
79
|
-
end
|
80
|
-
|
81
|
-
class Page < ActiveForce::SObject
|
82
|
-
field :account_id, from: 'Account__c'
|
83
|
-
end
|
84
|
-
```
|
85
89
|
|
86
|
-
|
90
|
+
# Use option parameters in the declaration.
|
87
91
|
|
88
|
-
```ruby
|
89
|
-
class Account < ActiveForce::SObject
|
90
92
|
has_many :medications,
|
91
|
-
where: "
|
93
|
+
where: "Discontinued__c > #{ Date.today.strftime("%Y-%m-%d") }" \
|
94
|
+
"OR Discontinued__c = NULL"
|
92
95
|
|
93
|
-
has_many :
|
96
|
+
has_many :today_log_entries,
|
94
97
|
model: DailyLogEntry,
|
95
|
-
where:
|
98
|
+
where: { date: Time.now.in_time_zone.strftime("%Y-%m-%d") }
|
96
99
|
|
97
100
|
has_many :labs,
|
98
|
-
where: "Category__c = 'EMR'
|
101
|
+
where: "Category__c = 'EMR' AND Date__c <> NULL",
|
99
102
|
order: 'Date__c DESC'
|
103
|
+
|
100
104
|
end
|
101
105
|
```
|
102
106
|
|
103
107
|
#### Belongs to
|
104
108
|
|
105
109
|
```ruby
|
106
|
-
class Account < ActiveForce::SObject
|
107
|
-
end
|
108
|
-
|
109
110
|
class Page < ActiveForce::SObject
|
110
111
|
field :account_id, from: 'Account__c'
|
112
|
+
|
111
113
|
belongs_to :account
|
112
114
|
end
|
113
115
|
```
|
@@ -118,6 +120,6 @@ end
|
|
118
120
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
119
121
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
120
122
|
4. Push to the branch (`git push origin my-new-feature`)
|
121
|
-
5. Create new
|
123
|
+
5. Create new pull request so we can talk about it.
|
122
124
|
6. Once accepted, please add an entry in the CHANGELOG and rebase your changes
|
123
125
|
to squash typos or corrections.
|
data/active_force.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.required_ruby_version = '
|
21
|
+
spec.required_ruby_version = '>= 1.9.3'
|
22
22
|
|
23
23
|
spec.add_dependency 'active_attr', '~> 0.8'
|
24
24
|
spec.add_dependency 'restforce', '~> 1.4'
|
data/lib/active_attr/dirty.rb
CHANGED
@@ -2,6 +2,7 @@ require 'active_force/query'
|
|
2
2
|
require 'forwardable'
|
3
3
|
|
4
4
|
module ActiveForce
|
5
|
+
class PreparedStatementInvalid < ArgumentError; end
|
5
6
|
class ActiveQuery < Query
|
6
7
|
extend Forwardable
|
7
8
|
|
@@ -17,7 +18,7 @@ module ActiveForce
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def to_a
|
20
|
-
result.to_a.map { |mash| build mash }
|
21
|
+
@records ||= result.to_a.map { |mash| build mash }
|
21
22
|
end
|
22
23
|
|
23
24
|
alias_method :all, :to_a
|
@@ -32,23 +33,81 @@ module ActiveForce
|
|
32
33
|
limit == 1 ? to_a.first : self
|
33
34
|
end
|
34
35
|
|
35
|
-
def where
|
36
|
-
return
|
37
|
-
|
38
|
-
super "#{ mappings[key] } = #{ enclose_value value }"
|
39
|
-
end
|
36
|
+
def where args=nil, *rest
|
37
|
+
return self if args.nil?
|
38
|
+
super build_condition args, rest
|
40
39
|
self
|
41
40
|
end
|
42
41
|
|
42
|
+
def select *fields
|
43
|
+
fields.map! { |field| mappings[field] }
|
44
|
+
super *fields
|
45
|
+
end
|
46
|
+
|
43
47
|
def find_by conditions
|
44
48
|
where(conditions).limit 1
|
45
49
|
end
|
46
50
|
|
47
51
|
private
|
48
52
|
|
53
|
+
def build_condition(args, other=[])
|
54
|
+
case args
|
55
|
+
when String, Array
|
56
|
+
build_condition_from_array other.empty? ? args : ([args] + other)
|
57
|
+
when Hash
|
58
|
+
build_conditions_from_hash args
|
59
|
+
else
|
60
|
+
args
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_condition_from_array(ary)
|
65
|
+
statement, *bind_parameters = ary
|
66
|
+
return statement if bind_parameters.empty?
|
67
|
+
if bind_parameters.first.is_a? Hash
|
68
|
+
replace_named_bind_parameters statement, bind_parameters.first
|
69
|
+
else
|
70
|
+
replace_bind_parameters statement, bind_parameters
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def replace_named_bind_parameters(statement, bind_parameters)
|
75
|
+
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
|
76
|
+
key = $2.to_sym
|
77
|
+
if bind_parameters.has_key? key
|
78
|
+
enclose_value bind_parameters[key]
|
79
|
+
else
|
80
|
+
raise PreparedStatementInvalid, "missing value for :#{key} in #{statement}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def replace_bind_parameters(statement, values)
|
86
|
+
raise_if_bind_arity_mismatch statement.count('?'), values.size
|
87
|
+
bound = values.dup
|
88
|
+
statement.gsub('?') do
|
89
|
+
enclose_value bound.shift
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def raise_if_bind_arity_mismatch(expected_var_count, actual_var_count)
|
94
|
+
if expected_var_count != actual_var_count
|
95
|
+
raise PreparedStatementInvalid, "wrong number of bind variables (#{actual_var_count} for #{expected_var_count})"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_conditions_from_hash(hash)
|
100
|
+
hash.map do |key, value|
|
101
|
+
"#{mappings[key]} = #{enclose_value value}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
49
105
|
def enclose_value value
|
50
|
-
|
106
|
+
case value
|
107
|
+
when String
|
51
108
|
"'#{value}'"
|
109
|
+
when NilClass
|
110
|
+
'NULL'
|
52
111
|
else
|
53
112
|
value.to_s
|
54
113
|
end
|
@@ -6,13 +6,21 @@ module ActiveForce
|
|
6
6
|
private
|
7
7
|
|
8
8
|
def default_foreign_key
|
9
|
-
|
9
|
+
infer_foreign_key_from_model relation_model
|
10
10
|
end
|
11
11
|
|
12
12
|
def define_relation_method
|
13
13
|
association = self
|
14
|
-
|
15
|
-
|
14
|
+
_method = @relation_name
|
15
|
+
@parent.send :define_method, _method do
|
16
|
+
association_cache.fetch(_method) do
|
17
|
+
association_cache[_method] = association.relation_model.find(send association.foreign_key)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
@parent.send :define_method, "#{_method}=" do |other|
|
22
|
+
send "#{ association.foreign_key }=", other.id
|
23
|
+
association_cache[_method] = other
|
16
24
|
end
|
17
25
|
end
|
18
26
|
end
|
@@ -5,15 +5,17 @@ module ActiveForce
|
|
5
5
|
private
|
6
6
|
|
7
7
|
def default_foreign_key
|
8
|
-
|
8
|
+
infer_foreign_key_from_model @parent
|
9
9
|
end
|
10
10
|
|
11
11
|
def define_relation_method
|
12
12
|
association = self
|
13
13
|
@parent.send :define_method, @relation_name do
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
association_cache.fetch __method__ do
|
15
|
+
query = association.relation_model.query
|
16
|
+
query.options association.options
|
17
|
+
association_cache[__method__] = query.where association.foreign_key => self.id
|
18
|
+
end
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/lib/active_force/query.rb
CHANGED
data/lib/active_force/sobject.rb
CHANGED
@@ -3,23 +3,26 @@ require 'active_attr'
|
|
3
3
|
require 'active_attr/dirty'
|
4
4
|
require 'active_force/active_query'
|
5
5
|
require 'active_force/association'
|
6
|
+
require 'active_force/table'
|
6
7
|
require 'yaml'
|
7
8
|
require 'forwardable'
|
8
9
|
require 'logger'
|
9
10
|
|
10
|
-
|
11
11
|
module ActiveForce
|
12
12
|
class SObject
|
13
13
|
include ActiveAttr::Model
|
14
14
|
include ActiveAttr::Dirty
|
15
15
|
include ActiveForce::Association
|
16
|
-
|
16
|
+
extend ActiveModel::Callbacks
|
17
|
+
|
18
|
+
define_model_callbacks :save, :create, :update
|
17
19
|
|
18
20
|
class_attribute :mappings, :table_name
|
19
21
|
|
20
22
|
class << self
|
21
23
|
extend Forwardable
|
22
24
|
def_delegators :query, :where, :first, :last, :all, :find, :find_by, :count
|
25
|
+
def_delegators :table, :custom_table_name?
|
23
26
|
|
24
27
|
private
|
25
28
|
|
@@ -33,10 +36,6 @@ module ActiveForce
|
|
33
36
|
String(attribute).split('_').map(&:capitalize).join('_') << '__c'
|
34
37
|
end
|
35
38
|
|
36
|
-
def custom_table_name
|
37
|
-
self.name if STANDARD_TYPES.include? self.name
|
38
|
-
end
|
39
|
-
|
40
39
|
###
|
41
40
|
# Provide each subclass with a default id field. Can be overridden
|
42
41
|
# in the subclass if needed
|
@@ -48,7 +47,11 @@ module ActiveForce
|
|
48
47
|
# The table name to used to make queries.
|
49
48
|
# It is derived from the class name adding the "__c" when needed.
|
50
49
|
def self.table_name
|
51
|
-
|
50
|
+
table.name
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.table
|
54
|
+
@table ||= ActiveForce::Table.new name
|
52
55
|
end
|
53
56
|
|
54
57
|
def self.fields
|
@@ -72,13 +75,15 @@ module ActiveForce
|
|
72
75
|
def update_attributes! attributes = {}
|
73
76
|
assign_attributes attributes
|
74
77
|
return false unless valid?
|
75
|
-
sfdc_client.update! table_name,
|
78
|
+
sfdc_client.update! table_name, attributes_for_sfdb
|
76
79
|
changed_attributes.clear
|
77
80
|
self
|
78
81
|
end
|
79
82
|
|
80
83
|
def update_attributes attributes = {}
|
81
|
-
|
84
|
+
run_callbacks :update do
|
85
|
+
update_attributes! attributes
|
86
|
+
end
|
82
87
|
rescue Faraday::Error::ClientError => error
|
83
88
|
logger_output __method__
|
84
89
|
end
|
@@ -87,13 +92,15 @@ module ActiveForce
|
|
87
92
|
|
88
93
|
def create!
|
89
94
|
return false unless valid?
|
90
|
-
self.id = sfdc_client.create! table_name,
|
95
|
+
self.id = sfdc_client.create! table_name, attributes_for_sfdb
|
91
96
|
changed_attributes.clear
|
92
97
|
self
|
93
98
|
end
|
94
99
|
|
95
100
|
def create
|
96
|
-
create
|
101
|
+
run_callbacks :create do
|
102
|
+
create!
|
103
|
+
end
|
97
104
|
rescue Faraday::Error::ClientError => error
|
98
105
|
logger_output __method__
|
99
106
|
end
|
@@ -107,19 +114,19 @@ module ActiveForce
|
|
107
114
|
end
|
108
115
|
|
109
116
|
def save
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
117
|
+
run_callbacks :save do
|
118
|
+
if persisted?
|
119
|
+
update
|
120
|
+
else
|
121
|
+
create
|
122
|
+
end
|
114
123
|
end
|
115
124
|
end
|
116
125
|
|
117
126
|
def save!
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
create!
|
122
|
-
end
|
127
|
+
save
|
128
|
+
rescue Faraday::Error::ClientError => error
|
129
|
+
logger_output __method__
|
123
130
|
end
|
124
131
|
|
125
132
|
def to_param
|
@@ -141,8 +148,20 @@ module ActiveForce
|
|
141
148
|
@mappings ||= {}
|
142
149
|
end
|
143
150
|
|
151
|
+
def reload
|
152
|
+
association_cache.clear
|
153
|
+
reloaded = self.class.find(id)
|
154
|
+
self.attributes = reloaded.attributes
|
155
|
+
changed_attributes.clear
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
144
159
|
private
|
145
160
|
|
161
|
+
def association_cache
|
162
|
+
@association_cache ||= {}
|
163
|
+
end
|
164
|
+
|
146
165
|
def logger_output action
|
147
166
|
logger = Logger.new(STDOUT)
|
148
167
|
logger.info("[SFDC] [#{self.class.model_name}] [#{self.class.table_name}] Error while #{ action }, params: #{hash}, error: #{error.inspect}")
|
@@ -150,20 +169,13 @@ module ActiveForce
|
|
150
169
|
false
|
151
170
|
end
|
152
171
|
|
153
|
-
def
|
172
|
+
def attributes_for_sfdb
|
154
173
|
attrs = mappings.map do |attr, sf_field|
|
155
|
-
value =
|
174
|
+
value = read_value(attr)
|
156
175
|
[sf_field, value] if value
|
157
176
|
end
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
def attributes_for_sfdb_update
|
163
|
-
attrs = changed_mappings.map do |attr, sf_field|
|
164
|
-
[sf_field, read_attribute(attr)]
|
165
|
-
end
|
166
|
-
Hash.new(attrs).merge('Id' => id)
|
177
|
+
attrs << ['Id', id] if persisted?
|
178
|
+
Hash[attrs.compact]
|
167
179
|
end
|
168
180
|
|
169
181
|
def changed_mappings
|
@@ -180,7 +192,7 @@ module ActiveForce
|
|
180
192
|
end
|
181
193
|
|
182
194
|
def sf_field_type field
|
183
|
-
self.class.attributes[field][:
|
195
|
+
self.class.attributes[field][:sf_type]
|
184
196
|
end
|
185
197
|
|
186
198
|
def self.picklist field
|
@@ -198,4 +210,5 @@ module ActiveForce
|
|
198
210
|
self.class.sfdc_client
|
199
211
|
end
|
200
212
|
end
|
213
|
+
|
201
214
|
end
|