activeforce 1.5.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.
- data/Gemfile +15 -0
- data/Gemfile.lock +128 -0
- data/LICENSE.txt +20 -0
- data/README.md +112 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/activeforce.gemspec +151 -0
- data/app/models/salesforce/account.rb +4 -0
- data/app/models/salesforce/activity_history.rb +4 -0
- data/app/models/salesforce/approval.rb +4 -0
- data/app/models/salesforce/campaign.rb +4 -0
- data/app/models/salesforce/campaign_feed.rb +4 -0
- data/app/models/salesforce/campaign_member.rb +4 -0
- data/app/models/salesforce/case.rb +4 -0
- data/app/models/salesforce/case_comment.rb +4 -0
- data/app/models/salesforce/case_contact_role.rb +4 -0
- data/app/models/salesforce/case_feed.rb +4 -0
- data/app/models/salesforce/case_history.rb +4 -0
- data/app/models/salesforce/case_share.rb +4 -0
- data/app/models/salesforce/case_solution.rb +4 -0
- data/app/models/salesforce/case_status.rb +4 -0
- data/app/models/salesforce/case_team_member.rb +4 -0
- data/app/models/salesforce/community.rb +4 -0
- data/app/models/salesforce/contact.rb +4 -0
- data/app/models/salesforce/contact_feed.rb +4 -0
- data/app/models/salesforce/contact_history.rb +4 -0
- data/app/models/salesforce/contract.rb +4 -0
- data/app/models/salesforce/document.rb +4 -0
- data/app/models/salesforce/event.rb +4 -0
- data/app/models/salesforce/feed_item.rb +4 -0
- data/app/models/salesforce/group.rb +4 -0
- data/app/models/salesforce/group_member.rb +4 -0
- data/app/models/salesforce/idea.rb +4 -0
- data/app/models/salesforce/lead.rb +4 -0
- data/app/models/salesforce/lead_status.rb +4 -0
- data/app/models/salesforce/name.rb +4 -0
- data/app/models/salesforce/note.rb +4 -0
- data/app/models/salesforce/open_activity.rb +4 -0
- data/app/models/salesforce/opportunity.rb +4 -0
- data/app/models/salesforce/organization.rb +4 -0
- data/app/models/salesforce/partner.rb +4 -0
- data/app/models/salesforce/period.rb +4 -0
- data/app/models/salesforce/product2.rb +4 -0
- data/app/models/salesforce/product2_feed.rb +4 -0
- data/app/models/salesforce/profile.rb +4 -0
- data/app/models/salesforce/quote.rb +4 -0
- data/app/models/salesforce/solution.rb +4 -0
- data/app/models/salesforce/task.rb +4 -0
- data/app/models/salesforce/task_feed.rb +4 -0
- data/app/models/salesforce/task_priority.rb +4 -0
- data/app/models/salesforce/task_status.rb +4 -0
- data/app/models/salesforce/user.rb +4 -0
- data/app/models/salesforce/user_role.rb +4 -0
- data/app/models/salesforce/vote.rb +4 -0
- data/lib/activeforce.rb +29 -0
- data/lib/salesforce/attributes.rb +13 -0
- data/lib/salesforce/authentication.rb +25 -0
- data/lib/salesforce/base.rb +240 -0
- data/lib/salesforce/bulk/batch.rb +77 -0
- data/lib/salesforce/bulk/job.rb +103 -0
- data/lib/salesforce/bulk/operations.rb +25 -0
- data/lib/salesforce/bulk/update_job.rb +24 -0
- data/lib/salesforce/column.rb +98 -0
- data/lib/salesforce/columns.rb +60 -0
- data/lib/salesforce/config.rb +110 -0
- data/lib/salesforce/connection.rb +33 -0
- data/lib/salesforce/connection/async.rb +36 -0
- data/lib/salesforce/connection/conversion.rb +37 -0
- data/lib/salesforce/connection/http_methods.rb +72 -0
- data/lib/salesforce/connection/rest_api.rb +52 -0
- data/lib/salesforce/connection/soap_api.rb +74 -0
- data/lib/salesforce/engine.rb +6 -0
- data/lib/salesforce/errors.rb +33 -0
- data/test/salesforce/authentication_test.rb +52 -0
- data/test/salesforce/base_test.rb +440 -0
- data/test/salesforce/bulk/batch_test.rb +79 -0
- data/test/salesforce/bulk/update_job_test.rb +125 -0
- data/test/salesforce/column_test.rb +115 -0
- data/test/salesforce/config_test.rb +163 -0
- data/test/salesforce/connection/async_test.rb +138 -0
- data/test/salesforce/connection/http_methods_test.rb +242 -0
- data/test/salesforce/connection/rest_api_test.rb +61 -0
- data/test/salesforce/connection/soap_api_test.rb +148 -0
- data/test/salesforce/connection_test.rb +148 -0
- data/test/test_helper.rb +79 -0
- metadata +295 -0
data/lib/activeforce.rb
ADDED
@@ -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,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
|