active_force 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5fc8bc2425de35aa52c303eb28a9165a2601c5f5
4
- data.tar.gz: b397002aa1906d23b92d4d20ff08745d349c6cb4
3
+ metadata.gz: 36f3376b1b60bba8f226a7b811550b16fc4fccf2
4
+ data.tar.gz: 8c5f72016eb4139055f141858d7377d6dc93a84c
5
5
  SHA512:
6
- metadata.gz: ef355901a29bd4ff95bf593400f3a52e32e53cd4bec886b5d057984a31092f832b0ce0cf4608462f6379652ad91120d4437100177d4f3912e0051a85abbfa9fb
7
- data.tar.gz: 8ce4758557b84eef8662c8f53ff3e301ecdc077d1f824ef2850cb17636d6dde5cab9ed564ca57d3b02ed9a600a188b6e133ebb6eefa8f536803d3a767abcf672
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: rbx-2
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
  [![Code Climate](http://img.shields.io/codeclimate/github/ionia-corporation/active_force.svg)](https://codeclimate.com/github/ionia-corporation/active_force)
4
4
  [![Dependency Status](http://img.shields.io/gemnasium/ionia-corporation/active_force.svg)](https://gemnasium.com/ionia-corporation/active_force)
5
5
  [![Test Coverage](https://codeclimate.com/github/ionia-corporation/active_force/badges/coverage.svg)](https://codeclimate.com/github/ionia-corporation/active_force)
6
-
6
+ [![Chat](http://img.shields.io/badge/chat-gitter-brightgreen.svg)](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 Page < ActiveForce::SObject
41
+ class Medication < ActiveForce::SObject
44
42
 
45
- end
46
- ```
43
+ field :name, from: 'Name'
47
44
 
48
- ### Add Attributes
45
+ field :max_dossage # from defaults to "Max_Dossage__c"
46
+ field :updated_from
49
47
 
50
- ```ruby
51
- class Page < ActiveForce::SObject
52
- #field, :attribute_name, from: 'Name_In_Salesforce_Database'
53
- field :id, from: 'Id'
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
- ### Validations
53
+ ##
54
+ # Validations
55
+ #
56
+ validates :name, :login, :email, presence: true
61
57
 
62
- You can use any validation that active record has (except for validates_associated), just by adding them to your class:
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
- ```ruby
65
- validates :name, :login, :email, presence: true
66
- validates :text, length: { minimum: 2 }
67
- validates :text, format: { with: /\A[a-zA-Z]+\z/, message: "only allows letters" }
68
- validates :size, inclusion: { in: %w(small medium large),
69
- message: "%{value} is not a valid size" }
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
- you could send a option parameter in the declaration.
90
+ # Use option parameters in the declaration.
87
91
 
88
- ```ruby
89
- class Account < ActiveForce::SObject
90
92
  has_many :medications,
91
- where: "(Date_Discontinued__c > #{ Date.today.strftime("%Y-%m-%d") } or Date_Discontinued__c = NULL)"
93
+ where: "Discontinued__c > #{ Date.today.strftime("%Y-%m-%d") }" \
94
+ "OR Discontinued__c = NULL"
92
95
 
93
- has_many :today_log_entrys,
96
+ has_many :today_log_entries,
94
97
  model: DailyLogEntry,
95
- where: "Date__c = #{ Time.now.in_time_zone.strftime("%Y-%m-%d") }"
98
+ where: { date: Time.now.in_time_zone.strftime("%Y-%m-%d") }
96
99
 
97
100
  has_many :labs,
98
- where: "Category__c = 'EMR' And Date__c <> NULL",
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 Pull Request so we can talk about it.
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 = '~> 2.0'
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'
@@ -17,10 +17,5 @@ module ActiveAttr
17
17
  end
18
18
  end
19
19
 
20
- def initialize(attributes = nil, options = {})
21
- super(attributes, options)
22
- changed_attributes.clear
23
- end
24
-
25
20
  end
26
21
  end
@@ -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 conditions
36
- return super unless conditions.is_a? Hash
37
- conditions.each do |key, value|
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
- if value.is_a? String
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
@@ -25,6 +25,10 @@ module ActiveForce
25
25
  define_relation_method
26
26
  end
27
27
 
28
+ def infer_foreign_key_from_model(model)
29
+ name = model.custom_table_name? ? model.name : model.table_name
30
+ "#{name.downcase}_id".to_sym
31
+ end
28
32
  end
29
33
 
30
34
  end
@@ -6,13 +6,21 @@ module ActiveForce
6
6
  private
7
7
 
8
8
  def default_foreign_key
9
- "#{ relation_model.name.downcase }_id".to_sym
9
+ infer_foreign_key_from_model relation_model
10
10
  end
11
11
 
12
12
  def define_relation_method
13
13
  association = self
14
- @parent.send :define_method, @relation_name do
15
- association.relation_model.find(send association.foreign_key)
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
- "#{ @parent.name.downcase }_id".to_sym
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
- query = association.relation_model.query
15
- query.options association.options
16
- query.where association.foreign_key => self.id
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
@@ -30,6 +30,11 @@ module ActiveForce
30
30
  SOQL
31
31
  end
32
32
 
33
+ def select *columns
34
+ @query_fields = columns
35
+ self
36
+ end
37
+
33
38
  def where condition
34
39
  @conditions << condition if condition
35
40
  self
@@ -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
- STANDARD_TYPES = %w[ Account Contact Opportunity Campaign]
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
- @table_name ||= custom_table_name || "#{ self.name.split('::').last }__c"
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, attributes_for_sfdb_update
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
- update_attributes! attributes
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, attributes_for_sfdb_create
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
- if persisted?
111
- update
112
- else
113
- create
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
- if persisted?
119
- update_attributes!
120
- else
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 attributes_for_sfdb_create
172
+ def attributes_for_sfdb
154
173
  attrs = mappings.map do |attr, sf_field|
155
- value = read_attribute(attr)
174
+ value = read_value(attr)
156
175
  [sf_field, value] if value
157
176
  end
158
- Hash.new(attrs.compact)
159
- end
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][:sf_tpye]
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