activeforce 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/Gemfile +15 -0
  2. data/Gemfile.lock +128 -0
  3. data/LICENSE.txt +20 -0
  4. data/README.md +112 -0
  5. data/Rakefile +47 -0
  6. data/VERSION +1 -0
  7. data/activeforce.gemspec +151 -0
  8. data/app/models/salesforce/account.rb +4 -0
  9. data/app/models/salesforce/activity_history.rb +4 -0
  10. data/app/models/salesforce/approval.rb +4 -0
  11. data/app/models/salesforce/campaign.rb +4 -0
  12. data/app/models/salesforce/campaign_feed.rb +4 -0
  13. data/app/models/salesforce/campaign_member.rb +4 -0
  14. data/app/models/salesforce/case.rb +4 -0
  15. data/app/models/salesforce/case_comment.rb +4 -0
  16. data/app/models/salesforce/case_contact_role.rb +4 -0
  17. data/app/models/salesforce/case_feed.rb +4 -0
  18. data/app/models/salesforce/case_history.rb +4 -0
  19. data/app/models/salesforce/case_share.rb +4 -0
  20. data/app/models/salesforce/case_solution.rb +4 -0
  21. data/app/models/salesforce/case_status.rb +4 -0
  22. data/app/models/salesforce/case_team_member.rb +4 -0
  23. data/app/models/salesforce/community.rb +4 -0
  24. data/app/models/salesforce/contact.rb +4 -0
  25. data/app/models/salesforce/contact_feed.rb +4 -0
  26. data/app/models/salesforce/contact_history.rb +4 -0
  27. data/app/models/salesforce/contract.rb +4 -0
  28. data/app/models/salesforce/document.rb +4 -0
  29. data/app/models/salesforce/event.rb +4 -0
  30. data/app/models/salesforce/feed_item.rb +4 -0
  31. data/app/models/salesforce/group.rb +4 -0
  32. data/app/models/salesforce/group_member.rb +4 -0
  33. data/app/models/salesforce/idea.rb +4 -0
  34. data/app/models/salesforce/lead.rb +4 -0
  35. data/app/models/salesforce/lead_status.rb +4 -0
  36. data/app/models/salesforce/name.rb +4 -0
  37. data/app/models/salesforce/note.rb +4 -0
  38. data/app/models/salesforce/open_activity.rb +4 -0
  39. data/app/models/salesforce/opportunity.rb +4 -0
  40. data/app/models/salesforce/organization.rb +4 -0
  41. data/app/models/salesforce/partner.rb +4 -0
  42. data/app/models/salesforce/period.rb +4 -0
  43. data/app/models/salesforce/product2.rb +4 -0
  44. data/app/models/salesforce/product2_feed.rb +4 -0
  45. data/app/models/salesforce/profile.rb +4 -0
  46. data/app/models/salesforce/quote.rb +4 -0
  47. data/app/models/salesforce/solution.rb +4 -0
  48. data/app/models/salesforce/task.rb +4 -0
  49. data/app/models/salesforce/task_feed.rb +4 -0
  50. data/app/models/salesforce/task_priority.rb +4 -0
  51. data/app/models/salesforce/task_status.rb +4 -0
  52. data/app/models/salesforce/user.rb +4 -0
  53. data/app/models/salesforce/user_role.rb +4 -0
  54. data/app/models/salesforce/vote.rb +4 -0
  55. data/lib/activeforce.rb +29 -0
  56. data/lib/salesforce/attributes.rb +13 -0
  57. data/lib/salesforce/authentication.rb +25 -0
  58. data/lib/salesforce/base.rb +240 -0
  59. data/lib/salesforce/bulk/batch.rb +77 -0
  60. data/lib/salesforce/bulk/job.rb +103 -0
  61. data/lib/salesforce/bulk/operations.rb +25 -0
  62. data/lib/salesforce/bulk/update_job.rb +24 -0
  63. data/lib/salesforce/column.rb +98 -0
  64. data/lib/salesforce/columns.rb +60 -0
  65. data/lib/salesforce/config.rb +110 -0
  66. data/lib/salesforce/connection.rb +33 -0
  67. data/lib/salesforce/connection/async.rb +36 -0
  68. data/lib/salesforce/connection/conversion.rb +37 -0
  69. data/lib/salesforce/connection/http_methods.rb +72 -0
  70. data/lib/salesforce/connection/rest_api.rb +52 -0
  71. data/lib/salesforce/connection/soap_api.rb +74 -0
  72. data/lib/salesforce/engine.rb +6 -0
  73. data/lib/salesforce/errors.rb +33 -0
  74. data/test/salesforce/authentication_test.rb +52 -0
  75. data/test/salesforce/base_test.rb +440 -0
  76. data/test/salesforce/bulk/batch_test.rb +79 -0
  77. data/test/salesforce/bulk/update_job_test.rb +125 -0
  78. data/test/salesforce/column_test.rb +115 -0
  79. data/test/salesforce/config_test.rb +163 -0
  80. data/test/salesforce/connection/async_test.rb +138 -0
  81. data/test/salesforce/connection/http_methods_test.rb +242 -0
  82. data/test/salesforce/connection/rest_api_test.rb +61 -0
  83. data/test/salesforce/connection/soap_api_test.rb +148 -0
  84. data/test/salesforce/connection_test.rb +148 -0
  85. data/test/test_helper.rb +79 -0
  86. metadata +295 -0
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Account < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class ActivityHistory < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Approval < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Campaign < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CampaignFeed < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CampaignMember < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Case < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CaseComment < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CaseContactRole < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CaseFeed < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CaseHistory < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CaseShare < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CaseSolution < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CaseStatus < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class CaseTeamMember < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Community < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Contact < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class ContactFeed < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class ContactHistory < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Contract < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Document < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Event < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class FeedItem < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Group < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class GroupMember < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Idea < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Lead < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class LeadStatus < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Name < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Note < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class OpenActivity < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Opportunity < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Organization < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Partner < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Period < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Product2 < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Product2Feed < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Profile < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Quote < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Solution < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Task < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class TaskFeed < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class TaskPriority < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class TaskStatus < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class User < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class UserRole < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Salesforce
2
+ class Vote < Base
3
+ end
4
+ end
@@ -0,0 +1,29 @@
1
+ require 'pp'
2
+ require 'blockenspiel'
3
+ require 'savon'
4
+ require 'rest-client'
5
+ require 'fastercsv'
6
+ require 'active_support/all'
7
+ require 'salesforce/config'
8
+ require 'salesforce/engine'
9
+ require 'salesforce/authentication'
10
+ require 'salesforce/attributes'
11
+ require 'salesforce/errors'
12
+ require 'salesforce/connection'
13
+ require 'salesforce/columns'
14
+ require 'salesforce/column'
15
+ require 'salesforce/bulk/operations'
16
+ require 'salesforce/base'
17
+ require 'salesforce/bulk/job'
18
+ require 'salesforce/bulk/update_job'
19
+ require 'salesforce/bulk/batch'
20
+
21
+ module Salesforce
22
+ def self.configure(&block)
23
+ Blockenspiel.invoke(block, Config.instance)
24
+ end
25
+
26
+ def self.connection
27
+ Connection
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ module Salesforce
2
+ module Attributes
3
+ extend ActiveSupport::Concern
4
+
5
+ module InstanceMethods
6
+ def assign_attributes!(hash)
7
+ hash.each do |key, value|
8
+ send("#{key}=", value) if respond_to?("#{key}=")
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ module Salesforce
2
+ class Authentication
3
+ cattr_accessor :username
4
+ cattr_accessor :password
5
+
6
+ def self.session_id
7
+ raise InvalidCredentials.new("No credentials provided.") if Config.username.blank? || Config.password.blank?
8
+ Config.session_id || generate_new_session_id
9
+ end
10
+
11
+ def self.generate_new_session_id
12
+ result = Connection.login
13
+ Config.instance.soap_endpoint_url result[:server_url]
14
+ Config.instance.session_id result[:session_id]
15
+ Config.instance.server_instance URI.parse(result[:server_url]).host.split("-").first
16
+ Config.instance.user_id result[:user_id]
17
+ Config.session_id
18
+ end
19
+
20
+ def self.logout
21
+ Config.instance.session_id nil
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,240 @@
1
+ module Salesforce
2
+ class Base
3
+ class_attribute :custom_object, :custom_table_name, :cached_columns, :attributes
4
+ include ::Salesforce::Bulk::Operations
5
+
6
+ self.custom_object = false
7
+
8
+ def initialize(attrs = {})
9
+ self.class.columns
10
+ assign_attributes(attrs)
11
+ end
12
+
13
+ def assign_attributes(attrs)
14
+ attrs.delete("attributes")
15
+ attrs.each do |k,v|
16
+ send("#{k}=", v)
17
+ end
18
+ end
19
+
20
+ def self.table_name
21
+ return self.custom_table_name if self.custom_table_name
22
+ name.demodulize + (self.custom_object ? "__c" : '')
23
+ end
24
+
25
+ def self.columns
26
+ self.cached_columns ||= begin
27
+ cols = Columns.new(table_name)
28
+ cols.all.each do |col|
29
+ attr_reader col.name
30
+
31
+ define_method "#{col.original_name}=" do |value|
32
+ instance_variable_set(:"@#{col.name}", Column.typecast(col.type, value))
33
+ end
34
+ end
35
+
36
+ cols.editable.each do |col|
37
+ define_method "#{col.name}=" do |value|
38
+ if (new_record? && col.createable?) || (!new_record? && col.updateable?)
39
+ instance_variable_set(:"@#{col.name}", Column.typecast(col.type, value))
40
+ else
41
+ raise ArgumentError.new("#{self.class.table_name}##{col.name} is not editable.")
42
+ end
43
+ end
44
+ end
45
+ cols
46
+ end
47
+ end
48
+
49
+ ## Attributes
50
+
51
+ def attributes
52
+ self.class.columns.names.inject(ActiveSupport::HashWithIndifferentAccess.new) do |hash,name|
53
+ hash[name] = self.send(name)
54
+ hash
55
+ end
56
+ end
57
+
58
+ def createable_attributes
59
+ self.class.columns.createable.inject(ActiveSupport::HashWithIndifferentAccess.new) do |hash,col|
60
+ value = self.send(col.name)
61
+ hash[col.original_name] = value unless value.nil?
62
+ hash
63
+ end
64
+ end
65
+
66
+ def updateable_attributes
67
+ self.class.columns.updateable.inject(ActiveSupport::HashWithIndifferentAccess.new) do |hash,col|
68
+ value = self.send(col.name)
69
+ hash[col.original_name] = value unless value.nil?
70
+ hash
71
+ end
72
+ end
73
+
74
+ def attributes=(attrs)
75
+ assign_attributes(attrs)
76
+ end
77
+
78
+ ## Finders
79
+
80
+ def self.find(*args)
81
+ if args.first == :all
82
+ find_all(*args)
83
+ else
84
+ find_by_id(*args)
85
+ end
86
+ end
87
+
88
+ def self.find_by_id(object_id)
89
+ with_invalid_column_handling do
90
+ to_object(connection(:find_object_by_id, table_name, object_id, columns.soql_selector))
91
+ end
92
+ end
93
+
94
+ def self.find_all(*args)
95
+ with_invalid_column_handling do
96
+ options = args.extract_options!
97
+ connection(:soql, query_string(options)).map { |result| to_object(result) }
98
+ end
99
+ end
100
+
101
+ def self.all; find_all; end
102
+
103
+ def self.find_by_column(column, value)
104
+ connection(:soql, "#{select_clause} FROM #{table_name} WHERE #{column.original_name}=#{Column.to_soql_value(value)}").map { |result| to_object(result) }
105
+ end
106
+
107
+ def self.select_values(options)
108
+ connection(:soql, query_string(options)).map do |result|
109
+ {}.tap do |hash|
110
+ Array.wrap(options[:select] || columns.names).each do |col|
111
+ column = columns.find_by_name(col)
112
+ hash[col] = Column.typecast(column.type, result[column.original_name])
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ def self.with_invalid_column_handling
119
+ _count = 0
120
+ begin
121
+ yield
122
+ rescue InvalidRequest => e
123
+ if e.error_code == 'INVALID_FIELD' && _count == 0
124
+ clear_cached_columns!
125
+ _count += 1
126
+ retry
127
+ else
128
+ raise e
129
+ end
130
+ end
131
+ end
132
+
133
+ def self.clear_cached_columns!
134
+ self.cached_columns = nil
135
+ end
136
+
137
+ def save!
138
+ raise_if_destroyed
139
+ new_record? ? create : update
140
+ end
141
+
142
+ def create
143
+ raise_if_destroyed
144
+ self.Id = self.class.connection(:create, self.class.table_name, createable_attributes)
145
+ end
146
+
147
+ def update
148
+ self.class.with_invalid_column_handling do
149
+ raise_if_destroyed
150
+ self.class.connection(:update, self.class.table_name, self.id, updateable_attributes)
151
+ end
152
+ end
153
+
154
+ def destroy
155
+ raise_if_destroyed
156
+ self.class.connection(:destroy, self.class.table_name, self.id) unless new_record?
157
+ @destroyed = true
158
+ end
159
+
160
+ def destroyed?
161
+ @destroyed || false
162
+ end
163
+
164
+ def raise_if_destroyed
165
+ raise ObjectDestroyed.new if destroyed?
166
+ end
167
+
168
+ def new_record?
169
+ self.id.blank?
170
+ end
171
+
172
+ def self.select_clause(select = nil)
173
+ "SELECT #{selector(select)}"
174
+ end
175
+
176
+ def self.selector(select = nil)
177
+ select ||= :all
178
+ return columns.soql_selector if select == :all
179
+ Array.wrap(select).map { |var| columns.find_by_name(var).original_name }.join(',')
180
+ end
181
+
182
+ def self.query_string(options)
183
+ str = [].tap do |string|
184
+ string << "SELECT #{selector(options[:select])} FROM #{table_name}"
185
+ string << "WHERE #{soql_clause(options[:conditions])}" if options[:conditions]
186
+ string << "LIMIT #{soql_clause(options[:limit])}" if options[:limit]
187
+ string << "WITH #{soql_clause(options[:with])}" if options[:with]
188
+ string << "GROUP BY #{soql_clause(options[:group_by])}" if options[:group_by]
189
+ string << "HAVING #{soql_clause(options[:having])}" if options[:having]
190
+ string << "ORDER BY #{soql_clause(options[:order])}" if options[:order]
191
+ end.join(' ')
192
+ sanitized_soql(str, options)
193
+ end
194
+
195
+ def self.soql_clause(sub_clause)
196
+ return sub_clause if sub_clause.is_a?(String)
197
+ Array.wrap(sub_clause).map { |col| columns.find_by_name(col).original_name }.join(',')
198
+ end
199
+
200
+ def self.sanitized_soql(string, values = nil)
201
+ values ||= {}
202
+ # Substitute columns
203
+ columns.each do |col|
204
+ string.gsub!(":#{col.name}", col.original_name)
205
+ end
206
+
207
+ # Substitute Values
208
+ values.each do |key, value|
209
+ next if [ :select, :conditions, :limit, :with, :group_by, :having, :order ].include?(key.to_sym)
210
+ string.gsub!(":#{key}", Column.to_soql_value(value))
211
+ end
212
+ string
213
+ end
214
+
215
+
216
+ def self.connection(method, *args)
217
+ columns
218
+ Connection.send method, *args
219
+ end
220
+
221
+ def self.method_missing(method_name, *args)
222
+ if method_name.to_s =~ /^find_by_([_a-zA-Z]\w*)$/ && column = columns.find { |column| column.name.to_s == $1 }
223
+ self.find_by_column(column, *args)
224
+ else
225
+ super
226
+ end
227
+ end
228
+
229
+ def self.to_object(result)
230
+ result.delete("attributes")
231
+ new.tap do |record|
232
+ result.each do |k, v|
233
+ if record.respond_to?(:"#{k}=")
234
+ record.send("#{k}=", v)
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end