activesalesforce 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/active_salesforce.rb +18 -0
- data/lib/column_definition.rb +22 -6
- data/lib/rforce.rb +7 -2
- data/lib/salesforce_connection_adapter.rb +196 -58
- data/test/unit/account_test.rb +71 -54
- metadata +2 -5
- data/lib/salesforce_active_record.rb +0 -151
- data/lib/sobject_attributes.rb +0 -132
- data/test/unit/sobject_attributes_test.rb +0 -38
data/lib/active_salesforce.rb
CHANGED
@@ -22,3 +22,21 @@
|
|
22
22
|
=end
|
23
23
|
|
24
24
|
require 'salesforce_connection_adapter'
|
25
|
+
|
26
|
+
module ActionView
|
27
|
+
module Helpers
|
28
|
+
# Provides a set of methods for making easy links and getting urls that depend on the controller and action. This means that
|
29
|
+
# you can use the same format for links in the views that you do in the controller. The different methods are even named
|
30
|
+
# synchronously, so link_to uses that same url as is generated by url_for, which again is the same url used for
|
31
|
+
# redirection in redirect_to.
|
32
|
+
module UrlHelper
|
33
|
+
def link_to_asf(active_record, column)
|
34
|
+
if column.reference_to
|
35
|
+
link_to(column.reference_to, { :action => 'show', :controller => column.reference_to.pluralize, :id => active_record.send(column.name) } )
|
36
|
+
else
|
37
|
+
active_record.send(column.name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/column_definition.rb
CHANGED
@@ -28,12 +28,22 @@ require 'pp'
|
|
28
28
|
|
29
29
|
|
30
30
|
module ActiveRecord
|
31
|
+
module StringHelper
|
32
|
+
def column_nameize(s)
|
33
|
+
#s ? s.first.downcase + s[1 .. s.length] : nil
|
34
|
+
s.underscore
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
31
38
|
module ConnectionAdapters
|
32
39
|
class SalesforceColumn < Column
|
33
|
-
|
40
|
+
include StringHelper
|
41
|
+
|
42
|
+
attr_reader :api_name, :label, :readonly, :reference_to
|
34
43
|
|
35
44
|
def initialize(field)
|
36
|
-
@
|
45
|
+
@api_name = field[:name]
|
46
|
+
@name = column_nameize(@api_name)
|
37
47
|
@type = get_type(field[:type])
|
38
48
|
@limit = field[:length]
|
39
49
|
@label = field[:label]
|
@@ -82,28 +92,34 @@ module ActiveRecord
|
|
82
92
|
end
|
83
93
|
|
84
94
|
class SalesforceRelationship
|
85
|
-
|
95
|
+
include StringHelper
|
96
|
+
|
97
|
+
attr_reader :name, :api_name, :foreign_key, :label, :reference_to, :one_to_many, :cascade_delete
|
86
98
|
|
87
99
|
def initialize(source)
|
88
100
|
if source[:childSObject]
|
89
101
|
relationship = source
|
90
102
|
|
91
|
-
@
|
103
|
+
@api_name = relationship[:relationshipName] ? relationship[:relationshipName] : relationship[:field].chop.chop
|
92
104
|
@one_to_many = relationship[:relationshipName] != nil
|
93
105
|
@cascade_delete = relationship[:cascadeDelete] == "true"
|
94
106
|
@reference_to = relationship[:childSObject]
|
95
107
|
@label = @name
|
96
|
-
@foreign_key = relationship[:field]
|
108
|
+
@foreign_key = column_nameize(relationship[:field])
|
97
109
|
else
|
98
110
|
field = source
|
99
111
|
|
100
|
-
@
|
112
|
+
@api_name = field[:name].chop.chop
|
101
113
|
@label = field[:label]
|
102
114
|
@readonly = (field[:updateable] != "true" or field[:createable] != "true")
|
103
115
|
@reference_to = field[:referenceTo]
|
104
116
|
@one_to_many = false
|
105
117
|
@cascade_delete = false
|
118
|
+
@foreign_key = column_nameize(field[:name])
|
106
119
|
end
|
120
|
+
|
121
|
+
@name = column_nameize(@api_name)
|
122
|
+
|
107
123
|
end
|
108
124
|
end
|
109
125
|
|
data/lib/rforce.rb
CHANGED
@@ -202,13 +202,16 @@ module RForce
|
|
202
202
|
request = encode(request)
|
203
203
|
|
204
204
|
headers = {
|
205
|
-
'Accept-Encoding' => 'gzip',
|
206
205
|
'Connection' => 'Keep-Alive',
|
207
|
-
'Content-Encoding' => 'gzip',
|
208
206
|
'Content-Type' => 'text/xml',
|
209
207
|
'SOAPAction' => '""',
|
210
208
|
'User-Agent' => 'ActiveSalesforce RForce'
|
211
209
|
}
|
210
|
+
|
211
|
+
unless $DEBUG
|
212
|
+
headers['Accept-Encoding'] = 'gzip'
|
213
|
+
headers['Content-Encoding'] = 'gzip'
|
214
|
+
end
|
212
215
|
|
213
216
|
#Send the request to the server and read the response.
|
214
217
|
response = @server.post2(@url.path, request, headers)
|
@@ -255,6 +258,8 @@ module RForce
|
|
255
258
|
|
256
259
|
# encode gzip
|
257
260
|
def encode(request)
|
261
|
+
return request if $DEBUG
|
262
|
+
|
258
263
|
begin
|
259
264
|
ostream = StringIO.new
|
260
265
|
gzw = Zlib::GzipWriter.new(ostream)
|
@@ -27,14 +27,8 @@ require_gem 'rails', ">= 1.0.0"
|
|
27
27
|
require 'thread'
|
28
28
|
|
29
29
|
require File.dirname(__FILE__) + '/salesforce_login'
|
30
|
-
require File.dirname(__FILE__) + '/sobject_attributes'
|
31
|
-
require File.dirname(__FILE__) + '/salesforce_active_record'
|
32
30
|
require File.dirname(__FILE__) + '/column_definition'
|
33
31
|
|
34
|
-
ActiveRecord::Base.class_eval do
|
35
|
-
include ActiveRecord::SalesforceRecord
|
36
|
-
end
|
37
|
-
|
38
32
|
|
39
33
|
module ActiveRecord
|
40
34
|
class Base
|
@@ -75,6 +69,8 @@ module ActiveRecord
|
|
75
69
|
end
|
76
70
|
|
77
71
|
class SalesforceAdapter < AbstractAdapter
|
72
|
+
include StringHelper
|
73
|
+
|
78
74
|
attr_accessor :batch_size
|
79
75
|
|
80
76
|
def initialize(connection, logger, connection_options, config)
|
@@ -84,7 +80,7 @@ module ActiveRecord
|
|
84
80
|
|
85
81
|
@columns_map = {}
|
86
82
|
@columns_name_map = {}
|
87
|
-
|
83
|
+
|
88
84
|
@relationships_map = {}
|
89
85
|
end
|
90
86
|
|
@@ -109,7 +105,8 @@ module ActiveRecord
|
|
109
105
|
end
|
110
106
|
|
111
107
|
def quote_column_name(name) #:nodoc:
|
112
|
-
|
108
|
+
# Mark the column name to make it easier to find later
|
109
|
+
"@@#{name}"
|
113
110
|
end
|
114
111
|
|
115
112
|
def quote_string(string) #:nodoc:
|
@@ -138,45 +135,124 @@ module ActiveRecord
|
|
138
135
|
|
139
136
|
# DATABASE STATEMENTS ======================================
|
140
137
|
|
141
|
-
def select_all(
|
138
|
+
def select_all(sql, name = nil) #:nodoc:
|
139
|
+
table_name = table_name_from_sql(sql)
|
140
|
+
entity_name = entity_name_from_table(table_name)
|
141
|
+
column_names = api_column_names(table_name)
|
142
|
+
|
143
|
+
soql = sql.sub(/SELECT \* FROM/, "SELECT #{column_names.join(', ')} FROM")
|
144
|
+
|
145
|
+
# Look for a LIMIT clause
|
146
|
+
soql.sub!(/LIMIT 1/, "")
|
147
|
+
|
148
|
+
# Fixup column references to use api names
|
149
|
+
columns = columns_map(table_name)
|
150
|
+
while soql =~ /@@(\w+)/
|
151
|
+
column = columns[$~[1]]
|
152
|
+
soql = $~.pre_match + column.api_name + $~.post_match
|
153
|
+
end
|
154
|
+
|
142
155
|
log(soql, name)
|
143
156
|
|
144
157
|
@connection.batch_size = @batch_size if @batch_size
|
145
158
|
@batch_size = nil
|
146
159
|
|
147
160
|
records = get_result(@connection.query(:queryString => soql), :query).records
|
148
|
-
|
161
|
+
|
149
162
|
result = []
|
150
163
|
return result unless records
|
151
164
|
|
152
165
|
records = [ records ] unless records.is_a?(Array)
|
153
166
|
|
154
167
|
records.each do |record|
|
155
|
-
|
156
|
-
|
168
|
+
row = {}
|
169
|
+
|
170
|
+
record.each do |name, value|
|
171
|
+
name = column_nameize(name.to_s)
|
172
|
+
|
173
|
+
# Replace nil element with nil
|
174
|
+
value = nil if value.respond_to?(:xmlattr_nil) and value.xmlattr_nil
|
175
|
+
|
176
|
+
# Ids are returned in an array with 2 duplicate entries...
|
177
|
+
value = value[0] if name == "id"
|
178
|
+
|
179
|
+
row[name] = value
|
180
|
+
end
|
181
|
+
|
182
|
+
result << row
|
157
183
|
end
|
158
184
|
|
159
185
|
result
|
160
186
|
end
|
161
187
|
|
162
188
|
def select_one(sql, name = nil) #:nodoc:
|
189
|
+
@connection.batch_size = 1
|
163
190
|
result = select_all(sql, name)
|
164
191
|
result.nil? ? nil : result.first
|
165
192
|
end
|
166
193
|
|
167
|
-
def
|
168
|
-
|
169
|
-
|
194
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
195
|
+
tokens = sql.scan(/[0-9A-Za-z._]+/)
|
196
|
+
|
197
|
+
# Convert sql to sobject
|
198
|
+
table_name = tokens[2]
|
199
|
+
entity_name = entity_name_from_table(table_name)
|
200
|
+
columns = columns_map(table_name)
|
201
|
+
|
202
|
+
# Extract array of [column_name, value] pairs
|
203
|
+
names = tokens[3 .. tokens.length / 2]
|
204
|
+
values = tokens[(tokens.length / 2) + 2 .. tokens.length]
|
205
|
+
|
206
|
+
fields = {}
|
207
|
+
names.length.times do | n |
|
208
|
+
name = names[n]
|
209
|
+
value = values[n]
|
210
|
+
column = columns[name]
|
211
|
+
|
212
|
+
fields[column.api_name] = value if not column.readonly and value != "NULL"
|
213
|
+
end
|
214
|
+
|
215
|
+
sobject = create_sobject(entity_name, nil, fields)
|
216
|
+
|
217
|
+
check_result(get_result(@connection.create(:sObjects => sobject), :create))[0][:id]
|
218
|
+
end
|
170
219
|
|
171
|
-
def update(
|
172
|
-
|
220
|
+
def update(sql, name = nil) #:nodoc:
|
221
|
+
# Convert sql to sobject
|
222
|
+
table_name = sql.match(/UPDATE (\w+) /)[1]
|
223
|
+
entity_name = entity_name_from_table(table_name)
|
224
|
+
columns = columns_map(table_name)
|
225
|
+
|
226
|
+
# Extract array of [column_name, value] pairs
|
227
|
+
raw_fields = sql.scan(/(\w+) = '([^']*)'/)
|
228
|
+
|
229
|
+
fields = {}
|
230
|
+
raw_fields.each do | name, value |
|
231
|
+
column = columns[name]
|
232
|
+
fields[column.api_name] = value if not column.readonly or name == "id"
|
233
|
+
end
|
234
|
+
|
235
|
+
id = fields["Id"].match(/[a-zA-Z0-9]+/)[0]
|
236
|
+
fields.delete("Id")
|
237
|
+
|
238
|
+
sobject = create_sobject(entity_name, id, fields)
|
239
|
+
|
240
|
+
check_result(get_result(@connection.update(:sObjects => sobject), :update))
|
173
241
|
end
|
242
|
+
|
243
|
+
def delete(sql, name = nil)
|
244
|
+
# Extract the ids from the IN () clause
|
245
|
+
match = sql.match(/IN \(([^\)]*)\)/)
|
174
246
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
247
|
+
# If the IN clause was not found fall back to WHERE id = 'blah'
|
248
|
+
ids = match ? match[1].scan(/\w+/) : [ sql.match(/WHERE id = '([^\']*)'/)[1] ]
|
249
|
+
|
250
|
+
ids_element = []
|
251
|
+
ids.each { |id| ids_element << :ids << id }
|
179
252
|
|
253
|
+
check_result(get_result(@connection.delete(ids_element), :delete))
|
254
|
+
end
|
255
|
+
|
180
256
|
def get_result(response, method)
|
181
257
|
responseName = (method.to_s + "Response").to_sym
|
182
258
|
finalResponse = response[responseName]
|
@@ -187,21 +263,28 @@ module ActiveRecord
|
|
187
263
|
end
|
188
264
|
|
189
265
|
def check_result(result)
|
190
|
-
|
266
|
+
result = [ result ] unless result.is_a?(Array)
|
267
|
+
|
268
|
+
result.each do |r|
|
269
|
+
raise SalesforceError.new(r[:errors], r[:errors].Message) unless r[:success] == "true"
|
270
|
+
end
|
271
|
+
|
191
272
|
result
|
192
273
|
end
|
193
|
-
|
274
|
+
|
194
275
|
def columns(table_name, name = nil)
|
195
|
-
|
276
|
+
entity_name = entity_name_from_table(table_name)
|
277
|
+
|
278
|
+
cached_columns = @columns_map[entity_name]
|
196
279
|
return cached_columns if cached_columns
|
197
280
|
|
198
281
|
cached_columns = []
|
199
|
-
@columns_map[
|
200
|
-
|
282
|
+
@columns_map[entity_name] = cached_columns
|
283
|
+
|
201
284
|
cached_relationships = []
|
202
|
-
@relationships_map[
|
203
|
-
|
204
|
-
metadata = get_result(@connection.describeSObject(:sObjectType =>
|
285
|
+
@relationships_map[entity_name] = cached_relationships
|
286
|
+
|
287
|
+
metadata = get_result(@connection.describeSObject(:sObjectType => entity_name), :describeSObject)
|
205
288
|
|
206
289
|
metadata.fields.each do |field|
|
207
290
|
column = SalesforceColumn.new(field)
|
@@ -209,54 +292,109 @@ module ActiveRecord
|
|
209
292
|
|
210
293
|
cached_relationships << SalesforceRelationship.new(field) if field[:type] =~ /reference/i
|
211
294
|
end
|
212
|
-
|
295
|
+
|
213
296
|
if metadata.childRelationships
|
214
297
|
metadata.childRelationships.each do |relationship|
|
215
|
-
|
298
|
+
|
299
|
+
if relationship[:childSObject].casecmp(entity_name) == 0
|
300
|
+
r = SalesforceRelationship.new(relationship)
|
301
|
+
cached_relationships << r
|
302
|
+
end
|
216
303
|
end
|
217
304
|
end
|
218
|
-
|
305
|
+
|
306
|
+
configure_active_record entity_name
|
307
|
+
|
219
308
|
cached_columns
|
220
309
|
end
|
221
310
|
|
311
|
+
def configure_active_record(entity_name)
|
312
|
+
klass = entity_name.constantize
|
313
|
+
|
314
|
+
klass.table_name = entity_name
|
315
|
+
klass.pluralize_table_names = false
|
316
|
+
klass.set_inheritance_column nil
|
317
|
+
klass.lock_optimistically = false
|
318
|
+
klass.record_timestamps = false
|
319
|
+
klass.default_timezone = :utc
|
320
|
+
|
321
|
+
# Create relationships for any reference field
|
322
|
+
@relationships_map[entity_name].each do |relationship|
|
323
|
+
referenceName = relationship.name
|
324
|
+
unless self.respond_to? referenceName.to_sym or relationship.reference_to == "Profile"
|
325
|
+
one_to_many = relationship.one_to_many
|
326
|
+
foreign_key = relationship.foreign_key
|
327
|
+
|
328
|
+
if one_to_many
|
329
|
+
klass.has_many referenceName.to_sym, :class_name => relationship.reference_to, :foreign_key => foreign_key, :dependent => false
|
330
|
+
else
|
331
|
+
klass.belongs_to referenceName.to_sym, :class_name => relationship.reference_to, :foreign_key => foreign_key, :dependent => false
|
332
|
+
end
|
333
|
+
|
334
|
+
puts "Created one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{entity_name} to #{relationship.reference_to} using #{foreign_key}"
|
335
|
+
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
|
222
341
|
def relationships(table_name)
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
342
|
+
entity_name = entity_name_from_table(table_name)
|
343
|
+
|
344
|
+
cached_relationships = @relationships_map[entity_name]
|
345
|
+
|
346
|
+
unless cached_relationships
|
347
|
+
# This will load column and relationship metadata
|
348
|
+
columns(table_name)
|
349
|
+
end
|
350
|
+
|
351
|
+
@relationships_map[entity_name]
|
230
352
|
end
|
231
353
|
|
232
354
|
def columns_map(table_name, name = nil)
|
233
|
-
|
234
|
-
return columns_map if columns_map
|
355
|
+
entity_name = entity_name_from_table(table_name)
|
235
356
|
|
236
|
-
columns_map =
|
237
|
-
|
238
|
-
|
239
|
-
|
357
|
+
columns_map = @columns_name_map[entity_name]
|
358
|
+
unless columns_map
|
359
|
+
columns_map = {}
|
360
|
+
@columns_name_map[entity_name] = columns_map
|
361
|
+
|
362
|
+
columns(entity_name).each { |column| columns_map[column.name] = column }
|
363
|
+
end
|
240
364
|
|
241
365
|
columns_map
|
242
366
|
end
|
243
367
|
|
244
|
-
|
368
|
+
def entity_name_from_table(table_name)
|
369
|
+
return table_name.singularize.camelize
|
370
|
+
end
|
371
|
+
|
245
372
|
|
246
|
-
def
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
all_fields = result.fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
|
255
|
-
result.each_hash { |row| rows << all_fields.dup.update(row) }
|
373
|
+
def create_sobject(entity_name, id, fields)
|
374
|
+
sobj = [ 'type { :xmlns => "urn:sobject.partner.soap.sforce.com" }', entity_name ]
|
375
|
+
sobj << 'Id { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << id if id
|
376
|
+
|
377
|
+
# now add any changed fields
|
378
|
+
fieldValues = {}
|
379
|
+
fields.each do | name, value |
|
380
|
+
sobj << name.to_sym << value if value
|
256
381
|
end
|
257
|
-
|
258
|
-
|
382
|
+
|
383
|
+
sobj
|
384
|
+
end
|
385
|
+
|
386
|
+
def table_name_from_sql(sql)
|
387
|
+
sql.match(/FROM (\w+) /)[1]
|
388
|
+
end
|
389
|
+
|
390
|
+
def column_names(table_name)
|
391
|
+
columns(table_name).map { |column| column.name }
|
392
|
+
end
|
393
|
+
|
394
|
+
def api_column_names(table_name)
|
395
|
+
columns(table_name).map { |column| column.api_name }
|
259
396
|
end
|
260
397
|
end
|
398
|
+
|
261
399
|
end
|
262
400
|
end
|
data/test/unit/account_test.rb
CHANGED
@@ -8,85 +8,102 @@ class AccountTest < Test::Unit::TestCase
|
|
8
8
|
ActiveRecord::Base.allow_concurrency = true
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
if false
|
12
|
+
def test_create_account
|
13
|
+
dutchCo = Account.new
|
14
|
+
dutchCo.name = "DutchCo"
|
15
|
+
dutchCo.website = "www.dutchco.com"
|
16
|
+
dutchCo.save
|
17
|
+
|
18
|
+
dutchCo2 = Account.new(:name => "DutchCo2", :website => "www.dutchco2.com")
|
19
|
+
dutchCo2.save
|
20
|
+
|
21
|
+
dutchCo3 = Account.create(:name => "DutchCo3", :website => "www.dutchco3.com")
|
22
|
+
|
23
|
+
accounts = Account.create([
|
24
|
+
{ :name => "DutchCo4", :website => "www.dutchco4.com" },
|
25
|
+
{ :name => "DutchCo5", :website => "www.dutchco5.com" }])
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_create_a_contact
|
29
|
+
contact = Contact.find_by_id("0033000000B1LKpAAN")
|
30
|
+
contact.first_name = "DutchieBoy"
|
31
|
+
contact.save
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def test_create_a_contact
|
36
|
+
contact = Contact.new
|
37
|
+
end
|
38
|
+
|
39
|
+
|
12
40
|
def test_get_a_case_comment
|
13
|
-
comment = CaseComment.
|
41
|
+
comment = CaseComment.find_by_parent_id('500300000011inJAAQ')
|
14
42
|
end
|
15
43
|
|
16
|
-
|
44
|
+
|
17
45
|
def test_one_to_many_relationship
|
18
|
-
contact = Contact.
|
46
|
+
contact = Contact.find_by_id('0033000000B1LKrAAN')
|
19
47
|
|
20
|
-
cases = contact.
|
48
|
+
cases = contact.cases
|
21
49
|
|
22
50
|
cases.each do |c|
|
23
|
-
puts "Case('#{c.
|
24
|
-
|
25
|
-
comments = c.CaseComments
|
51
|
+
puts "Case('#{c.id}', '#{c.subject}')"
|
26
52
|
|
53
|
+
comments = c.case_comments
|
27
54
|
comments.each do |comment|
|
28
|
-
puts " CaseComment('#{comment.
|
55
|
+
puts " CaseComment('#{comment.id}', '#{comment.comment_body}')"
|
29
56
|
end
|
30
|
-
|
31
57
|
end
|
32
58
|
end
|
33
|
-
|
34
|
-
|
59
|
+
|
35
60
|
def test_get_account
|
36
61
|
accounts = Account.find(:all)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
acme = Account.find(:first, :conditions => ["
|
41
|
-
|
42
|
-
acme = Account.
|
43
|
-
|
44
|
-
acme = Account.
|
62
|
+
|
63
|
+
accounts.each { |account| puts "#{account.name}, #{account.id}, #{account.last_modified_by_id}" }
|
64
|
+
|
65
|
+
acme = Account.find(:first, :conditions => ["name = 'Acme'"])
|
66
|
+
|
67
|
+
acme = Account.find_by_id(acme.id)
|
68
|
+
|
69
|
+
acme = Account.find_by_name_and_last_modified_by_id('salesforce.com', acme.last_modified_by_id)
|
45
70
|
end
|
46
|
-
|
47
|
-
|
71
|
+
|
48
72
|
def test_update_account
|
49
73
|
acme = Account.new
|
50
|
-
acme.
|
74
|
+
acme.name = "Acme"
|
51
75
|
acme.save
|
52
76
|
|
53
|
-
acme = Account.
|
54
|
-
|
55
|
-
acme.
|
56
|
-
acme.
|
77
|
+
acme = Account.find_by_name('Acme')
|
78
|
+
|
79
|
+
acme.website = "http://www.dutchforce.com/#{Time.now}.jpg"
|
80
|
+
acme.last_modified_date = Time.now
|
57
81
|
|
58
82
|
acme.save
|
59
83
|
end
|
60
|
-
|
61
|
-
|
62
|
-
def test_create_account
|
63
|
-
dutchCo = Account.new
|
64
|
-
dutchCo.Name = "DutchCo"
|
65
|
-
dutchCo.Website = "www.dutchco.com"
|
66
|
-
dutchCo.save
|
67
|
-
|
68
|
-
dutchCo2 = Account.new(:Name => "DutchCo2", :Website => "www.dutchco2.com")
|
69
|
-
dutchCo2.save
|
70
|
-
|
71
|
-
dutchCo3 = Account.create(:Name => "DutchCo3", :Website => "www.dutchco3.com")
|
72
|
-
|
73
|
-
accounts = Account.create([
|
74
|
-
{ :Name => "DutchCo4", :Website => "www.dutchco4.com" },
|
75
|
-
{ :Name => "DutchCo5", :Website => "www.dutchco5.com" }])
|
76
|
-
end
|
77
|
-
|
84
|
+
end
|
85
|
+
|
78
86
|
def test_destroy_account
|
79
|
-
|
80
|
-
account = Account.find_by_Id(account.Id)
|
87
|
+
Account.new
|
81
88
|
|
82
|
-
|
89
|
+
account = Account.create(:name => "DutchADelete", :website => "www.dutchcodelete.com")
|
90
|
+
account2 = Account.create(:name => "DutchADelete2", :website => "www.dutchcodelete2.com")
|
91
|
+
|
92
|
+
#pp account
|
93
|
+
|
94
|
+
account = Account.find_by_id(account.id)
|
95
|
+
|
96
|
+
pp account.parent
|
83
97
|
|
84
98
|
puts "Getting CreatedBy"
|
85
|
-
createdBy = account.
|
86
|
-
createdBy = User.
|
87
|
-
puts createdBy.
|
88
|
-
|
89
|
-
Account.delete(account.
|
99
|
+
createdBy = account.created_by
|
100
|
+
createdBy = User.find_by_id(account.created_by_id);
|
101
|
+
puts createdBy.email
|
102
|
+
|
103
|
+
Account.delete([account.id, account2.id])
|
104
|
+
|
105
|
+
account3 = Account.create(:name => "DutchADelete3", :website => "www.dutchcodelete3.com")
|
106
|
+
account3.destroy
|
90
107
|
end
|
91
|
-
|
108
|
+
|
92
109
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: activesalesforce
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.1.2
|
7
|
+
date: 2006-02-02 00:00:00 -05:00
|
8
8
|
summary: ActiveSalesforce is an extension to the Rails Framework that allows for the dynamic creation and management of ActiveRecord objects through the use of Salesforce meta-data and uses a Salesforce.com organization as the backing store.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,14 +29,11 @@ authors:
|
|
29
29
|
- Doug Chasman
|
30
30
|
files:
|
31
31
|
- lib/salesforce_login.rb
|
32
|
-
- lib/salesforce_active_record.rb
|
33
32
|
- lib/column_definition.rb
|
34
33
|
- lib/active_salesforce.rb
|
35
34
|
- lib/rforce.rb
|
36
|
-
- lib/sobject_attributes.rb
|
37
35
|
- lib/salesforce_connection_adapter.rb
|
38
36
|
- test/unit
|
39
|
-
- test/unit/sobject_attributes_test.rb
|
40
37
|
- test/unit/account_test.rb
|
41
38
|
- README
|
42
39
|
test_files: []
|
@@ -1,151 +0,0 @@
|
|
1
|
-
=begin
|
2
|
-
ActiveSalesforce
|
3
|
-
Copyright (c) 2006 Doug Chasman
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
13
|
-
all copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
SOFTWARE.
|
22
|
-
=end
|
23
|
-
|
24
|
-
require File.dirname(__FILE__) + '/sobject_attributes'
|
25
|
-
|
26
|
-
|
27
|
-
module ActiveRecord
|
28
|
-
# Active Records will automatically record creation and/or update timestamps of database objects
|
29
|
-
# if fields of the names created_at/created_on or updated_at/updated_on are present. This module is
|
30
|
-
# automatically included, so you don't need to do that manually.
|
31
|
-
#
|
32
|
-
# This behavior can be turned off by setting <tt>ActiveRecord::Base.record_timestamps = false</tt>.
|
33
|
-
# This behavior can use GMT by setting <tt>ActiveRecord::Base.timestamps_gmt = true</tt>
|
34
|
-
module SalesforceRecord
|
35
|
-
|
36
|
-
def self.append_features(base) # :nodoc:
|
37
|
-
super
|
38
|
-
|
39
|
-
base.class_eval do
|
40
|
-
alias_method :create, :create_with_sforce_api
|
41
|
-
alias_method :update, :update_with_sforce_api
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def create_with_sforce_api
|
46
|
-
return if not @attributes.changed?
|
47
|
-
puts "create_with_sforce_api creating #{self.class}"
|
48
|
-
id = connection.create(:sObjects => create_sobject())
|
49
|
-
self.Id = id
|
50
|
-
@attributes.clear_changed!
|
51
|
-
end
|
52
|
-
|
53
|
-
def update_with_sforce_api
|
54
|
-
return if not @attributes.changed?
|
55
|
-
puts "update_with_sforce_api updating #{self.class}('#{self.Id}')"
|
56
|
-
connection.update(:sObjects => create_sobject())
|
57
|
-
@attributes.clear_changed!
|
58
|
-
end
|
59
|
-
|
60
|
-
def create_sobject()
|
61
|
-
fields = @attributes.changed_fields
|
62
|
-
|
63
|
-
sobj = [ 'type { :xmlns => "urn:sobject.partner.soap.sforce.com" }', self.class.table_name ]
|
64
|
-
sobj << 'Id { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << self.Id if self.Id
|
65
|
-
|
66
|
-
# now add any changed fields
|
67
|
-
fieldValues = {}
|
68
|
-
fields.each do |fieldName|
|
69
|
-
value = @attributes[fieldName]
|
70
|
-
sobj << fieldName.to_sym << value if value
|
71
|
-
end
|
72
|
-
|
73
|
-
sobj
|
74
|
-
end
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
class Base
|
79
|
-
set_inheritance_column nil
|
80
|
-
lock_optimistically = false
|
81
|
-
record_timestamps = false
|
82
|
-
default_timezone = :utc
|
83
|
-
|
84
|
-
def after_initialize()
|
85
|
-
sfdcObjectName = self.class.table_name
|
86
|
-
if not @attributes.is_a?(Salesforce::SObjectAttributes)
|
87
|
-
# Insure that SObjectAttributes is always used for our attributes
|
88
|
-
originalAttributes = @attributes
|
89
|
-
|
90
|
-
@attributes = Salesforce::SObjectAttributes.new(connection.columns_map(sfdcObjectName))
|
91
|
-
|
92
|
-
originalAttributes.each { |name, value| self[name] = value }
|
93
|
-
end
|
94
|
-
|
95
|
-
# Create relationships for any reference field
|
96
|
-
connection.relationships(sfdcObjectName).each do |relationship|
|
97
|
-
referenceName = relationship.name
|
98
|
-
unless self.respond_to? referenceName.to_sym or relationship.reference_to == "Profile"
|
99
|
-
one_to_many = relationship.one_to_many
|
100
|
-
|
101
|
-
puts "Creating one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{sfdcObjectName} to #{relationship.reference_to}"
|
102
|
-
|
103
|
-
if one_to_many
|
104
|
-
self.class.has_many referenceName.to_sym, :class_name => relationship.reference_to, :foreign_key => relationship.foreign_key, :dependent => false
|
105
|
-
else
|
106
|
-
self.class.belongs_to referenceName.to_sym, :class_name => relationship.reference_to, :foreign_key => relationship.name, :dependent => false
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def self.table_name
|
113
|
-
# Undo weird camilization that messes with custom object names
|
114
|
-
name = self.name
|
115
|
-
name.last(6) == "Custom" ? name.first(name.length - 6) << "__c" : name
|
116
|
-
end
|
117
|
-
|
118
|
-
def self.primary_key
|
119
|
-
"Id"
|
120
|
-
end
|
121
|
-
|
122
|
-
def self.construct_finder_sql(options)
|
123
|
-
soql = "SELECT #{column_names.join(', ')} FROM #{table_name} "
|
124
|
-
add_conditions!(soql, options[:conditions])
|
125
|
-
soql
|
126
|
-
end
|
127
|
-
|
128
|
-
def self.construct_conditions_from_arguments(attribute_names, arguments)
|
129
|
-
conditions = []
|
130
|
-
attribute_names.each_with_index { |name, idx| conditions << "#{name} #{attribute_condition(arguments[idx])} " }
|
131
|
-
[ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
|
132
|
-
end
|
133
|
-
|
134
|
-
def self.count(conditions = nil, joins = nil)
|
135
|
-
soql = "SELECT Id FROM #{table_name} "
|
136
|
-
add_conditions!(soql, conditions)
|
137
|
-
|
138
|
-
count_by_sql(soql)
|
139
|
-
end
|
140
|
-
|
141
|
-
def self.count_by_sql(soql)
|
142
|
-
connection.batch_size = 1
|
143
|
-
connection.select_all(soql, "#{name} Count").length
|
144
|
-
end
|
145
|
-
|
146
|
-
def self.delete(ids)
|
147
|
-
connection.delete(ids)
|
148
|
-
end
|
149
|
-
|
150
|
-
end
|
151
|
-
end
|
data/lib/sobject_attributes.rb
DELETED
@@ -1,132 +0,0 @@
|
|
1
|
-
=begin
|
2
|
-
ActiveSalesforce
|
3
|
-
Copyright (c) 2006 Doug Chasman
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
13
|
-
all copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
SOFTWARE.
|
22
|
-
=end
|
23
|
-
|
24
|
-
require 'set'
|
25
|
-
|
26
|
-
|
27
|
-
module Salesforce
|
28
|
-
|
29
|
-
class SObjectAttributes
|
30
|
-
include Enumerable
|
31
|
-
|
32
|
-
def initialize(columns, record = nil)
|
33
|
-
@columns = columns
|
34
|
-
@values = {}
|
35
|
-
|
36
|
-
if record
|
37
|
-
record.each do |name, value|
|
38
|
-
# Replace nil element with nil
|
39
|
-
value = nil if value.respond_to?(:xmlattr_nil) and value.xmlattr_nil
|
40
|
-
|
41
|
-
# Ids are returned in an array with 2 duplicate entries...
|
42
|
-
value = value[0] if name == :Id
|
43
|
-
|
44
|
-
self[name.to_s] = value
|
45
|
-
end
|
46
|
-
else
|
47
|
-
columns.values.each { |column| self[column.name] = nil }
|
48
|
-
end
|
49
|
-
|
50
|
-
clear_changed!
|
51
|
-
end
|
52
|
-
|
53
|
-
def [](key)
|
54
|
-
@values[key].freeze
|
55
|
-
end
|
56
|
-
|
57
|
-
def []=(key, value)
|
58
|
-
column = @columns[key]
|
59
|
-
return unless column
|
60
|
-
|
61
|
-
value = nil if value == ""
|
62
|
-
|
63
|
-
return if @values[key] == value and @values.include?(key)
|
64
|
-
|
65
|
-
originalClass = value.class
|
66
|
-
originalValue = value
|
67
|
-
|
68
|
-
if value
|
69
|
-
# Convert strings representation of dates and datetimes to date and time objects
|
70
|
-
case column.type
|
71
|
-
when :date
|
72
|
-
value = value.is_a?(Date) ? value : Date.parse(value)
|
73
|
-
when :datetime
|
74
|
-
value = value.is_a?(Time) ? value : Time.parse(value)
|
75
|
-
else
|
76
|
-
value = column.type_cast(value)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
@values[key] = value
|
81
|
-
|
82
|
-
#puts "setting #{key} = #{value} [#{originalValue}] (#{originalClass}, #{value.class})"
|
83
|
-
|
84
|
-
if not column.readonly
|
85
|
-
@changed = Set.new unless @changed
|
86
|
-
@changed.add(key)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def include?(key)
|
91
|
-
@values.include?(key)
|
92
|
-
end
|
93
|
-
|
94
|
-
def has_key?(key)
|
95
|
-
@values.has_key?(key)
|
96
|
-
end
|
97
|
-
|
98
|
-
def length
|
99
|
-
@values.length
|
100
|
-
end
|
101
|
-
|
102
|
-
def keys
|
103
|
-
@values.keys
|
104
|
-
end
|
105
|
-
|
106
|
-
def clear
|
107
|
-
@values.clear
|
108
|
-
clear_changed
|
109
|
-
end
|
110
|
-
|
111
|
-
def clear_changed!
|
112
|
-
@changed = nil
|
113
|
-
end
|
114
|
-
|
115
|
-
def changed?
|
116
|
-
@changed != nil
|
117
|
-
end
|
118
|
-
|
119
|
-
def changed_fields
|
120
|
-
@changed
|
121
|
-
end
|
122
|
-
|
123
|
-
|
124
|
-
# Enumerable support
|
125
|
-
|
126
|
-
def each(&block)
|
127
|
-
@values.each(&block)
|
128
|
-
end
|
129
|
-
|
130
|
-
end
|
131
|
-
|
132
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
puts "Yahoo"
|
2
|
-
|
3
|
-
require 'test/unit'
|
4
|
-
require File.dirname(__FILE__) + '/../../src/sobject_attributes'
|
5
|
-
|
6
|
-
class SobjectAttributesTest < Test::Unit::TestCase
|
7
|
-
|
8
|
-
def setup
|
9
|
-
@attributes = Salesforce::SObjectAttributes.new
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_add_values()
|
13
|
-
assert((not @attributes.changed?))
|
14
|
-
|
15
|
-
@attributes['name'] = 'value'
|
16
|
-
assert(@attributes.changed?)
|
17
|
-
|
18
|
-
assert_equal('value', @attributes['name'])
|
19
|
-
|
20
|
-
assert_equal(Set.new('name'), @attributes.changed_fields)
|
21
|
-
|
22
|
-
@attributes.clear_changed!
|
23
|
-
assert((not @attributes.changed?))
|
24
|
-
|
25
|
-
assert_equal('value', @attributes['name'])
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_enumeration
|
29
|
-
10.times { |n| @attributes["name_#{n}"] = "value_#{n}" }
|
30
|
-
|
31
|
-
assert_equal(10, @attributes.length)
|
32
|
-
|
33
|
-
5.times { |n| @attributes["name_#{n + 10}"] = "value_#{n + 10}" }
|
34
|
-
|
35
|
-
@attributes.each { |name, value| assert_equal(name[/_\d/], value[/_\d/]) }
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|