soapforce 0.3.0 → 0.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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -1
- data/CHANGELOG.md +6 -0
- data/lib/soapforce.rb +1 -0
- data/lib/soapforce/client.rb +68 -40
- data/lib/soapforce/query_result.rb +40 -11
- data/lib/soapforce/result.rb +36 -0
- data/lib/soapforce/sobject.rb +36 -18
- data/lib/soapforce/version.rb +1 -1
- data/soapforce.gemspec +3 -3
- data/spec/lib/client_spec.rb +141 -120
- data/spec/lib/configuration_spec.rb +2 -2
- data/spec/lib/query_result_spec.rb +31 -19
- data/spec/lib/result_spec.rb +24 -0
- data/spec/lib/sobject_spec.rb +133 -17
- data/spec/support/fixture_helpers.rb +6 -6
- metadata +33 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92708d194ae913cf1c8b95690ba30e45baaa4a8d
|
4
|
+
data.tar.gz: 447d8ed94094138eb2ce5e61c823926acff32b1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d34fbfb9303880a6105d4b07e3cc00783de06355cf3f89537f26c9d8bdd529fd31ad2bff3eeb4afc2bad1e7efad0f518df4779c1f145649f49c0ebb430955725
|
7
|
+
data.tar.gz: 51e41d674182269bc3767224add6e7049ced6ef2adc3637aed1db29782a753ff3ab1358721fb3a3324920c7583e8ef6532437068793b5d1c5e41b693b33644d6
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/lib/soapforce.rb
CHANGED
data/lib/soapforce/client.rb
CHANGED
@@ -3,6 +3,7 @@ module Soapforce
|
|
3
3
|
|
4
4
|
attr_reader :client
|
5
5
|
attr_reader :headers
|
6
|
+
attr_reader :tag_style
|
6
7
|
attr_accessor :logger
|
7
8
|
|
8
9
|
# The partner.wsdl is used by default but can be changed by passing in a new :wsdl option.
|
@@ -26,13 +27,22 @@ module Soapforce
|
|
26
27
|
@login_url = "https://#{@host}/services/Soap/u/#{@version}"
|
27
28
|
|
28
29
|
@logger = options[:logger] || false
|
29
|
-
# Due to
|
30
|
-
@ssl_version = options[:ssl_version] || :
|
30
|
+
# Due to SSLv3 POODLE vulnerabilty and disabling of TLSv1, use TLSv1_2
|
31
|
+
@ssl_version = options[:ssl_version] || :TLSv1_2
|
32
|
+
|
33
|
+
if options[:tag_style] == :raw
|
34
|
+
@tag_style = :raw
|
35
|
+
@response_tags = lambda { |key| key }
|
36
|
+
else
|
37
|
+
@tag_style = :snakecase
|
38
|
+
@response_tags = lambda { |key| key.snakecase.to_sym }
|
39
|
+
end
|
31
40
|
|
32
41
|
@client = Savon.client(
|
33
42
|
wsdl: @wsdl,
|
34
43
|
soap_header: @headers,
|
35
44
|
convert_request_keys_to: :none,
|
45
|
+
convert_response_tags_to: @response_tags,
|
36
46
|
pretty_print_xml: true,
|
37
47
|
logger: @logger,
|
38
48
|
log: (@logger != false),
|
@@ -67,11 +77,10 @@ module Soapforce
|
|
67
77
|
locals.message :username => options[:username], :password => options[:password]
|
68
78
|
end
|
69
79
|
|
70
|
-
result = response.to_hash[:login_response][:result]
|
71
|
-
|
80
|
+
result = response.to_hash[key_name(:login_response)][key_name(:result)]
|
81
|
+
@session_id = result[key_name(:session_id)]
|
82
|
+
@server_url = result[key_name(:server_url)]
|
72
83
|
|
73
|
-
@session_id = result[:session_id]
|
74
|
-
@server_url = result[:server_url]
|
75
84
|
elsif options[:session_id] && options[:server_url]
|
76
85
|
@session_id = options[:session_id]
|
77
86
|
@server_url = options[:server_url]
|
@@ -85,6 +94,7 @@ module Soapforce
|
|
85
94
|
wsdl: @wsdl,
|
86
95
|
soap_header: @headers,
|
87
96
|
convert_request_keys_to: :none,
|
97
|
+
convert_response_tags_to: @response_tags,
|
88
98
|
logger: @logger,
|
89
99
|
log: (@logger != false),
|
90
100
|
endpoint: @server_url,
|
@@ -111,7 +121,7 @@ module Soapforce
|
|
111
121
|
# Returns an Array of String names for each SObject.
|
112
122
|
def list_sobjects
|
113
123
|
response = describe_global # method_missing
|
114
|
-
response[:sobjects].collect { |sobject| sobject[:name] }
|
124
|
+
response[key_name(:sobjects)].collect { |sobject| sobject[key_name(:name)] }
|
115
125
|
end
|
116
126
|
|
117
127
|
# Public: Get the current organization's Id.
|
@@ -123,10 +133,8 @@ module Soapforce
|
|
123
133
|
#
|
124
134
|
# Returns the String organization Id
|
125
135
|
def org_id
|
126
|
-
object = query('
|
127
|
-
|
128
|
-
return object[:id].is_a?(Array) ? object[:id].first : object[:id]
|
129
|
-
end
|
136
|
+
object = query('SELECT Id FROM Organization').first
|
137
|
+
object.Id if object
|
130
138
|
end
|
131
139
|
|
132
140
|
# Public: Returns a detailed describe result for the specified sobject
|
@@ -146,14 +154,11 @@ module Soapforce
|
|
146
154
|
# Returns the Hash representation of the describe call.
|
147
155
|
def describe(sobject_type)
|
148
156
|
if sobject_type.is_a?(Array)
|
149
|
-
|
150
|
-
{:sObjectType => type}
|
151
|
-
end
|
152
|
-
response = call_soap_api(:describe_s_objects, :sObjectType => sobject_type)
|
157
|
+
response = call_soap_api(:describe_s_objects, sObjectType: sobject_type)
|
153
158
|
else
|
154
159
|
# Cache objects to avoid repeat lookups.
|
155
160
|
if @describe_cache[sobject_type].nil?
|
156
|
-
response = call_soap_api(:describe_s_object, :
|
161
|
+
response = call_soap_api(:describe_s_object, sObjectType: sobject_type)
|
157
162
|
@describe_cache[sobject_type] = response
|
158
163
|
else
|
159
164
|
response = @describe_cache[sobject_type]
|
@@ -194,19 +199,16 @@ module Soapforce
|
|
194
199
|
end
|
195
200
|
|
196
201
|
def query(soql)
|
197
|
-
|
198
|
-
QueryResult.new(result)
|
202
|
+
call_soap_api(:query, {:queryString => soql})
|
199
203
|
end
|
200
204
|
|
201
205
|
# Includes deleted (isDeleted) or archived (isArchived) records
|
202
206
|
def query_all(soql)
|
203
|
-
|
204
|
-
QueryResult.new(result)
|
207
|
+
call_soap_api(:query_all, {:queryString => soql})
|
205
208
|
end
|
206
209
|
|
207
210
|
def query_more(locator)
|
208
|
-
|
209
|
-
QueryResult.new(result)
|
211
|
+
call_soap_api(:query_more, {:queryLocator => locator})
|
210
212
|
end
|
211
213
|
|
212
214
|
def search(sosl)
|
@@ -228,7 +230,7 @@ module Soapforce
|
|
228
230
|
# Returns false if something bad happens.
|
229
231
|
def create(sobject_type, properties)
|
230
232
|
create!(sobject_type, properties)
|
231
|
-
rescue
|
233
|
+
rescue
|
232
234
|
false
|
233
235
|
end
|
234
236
|
|
@@ -263,7 +265,7 @@ module Soapforce
|
|
263
265
|
# Returns false if there was an error.
|
264
266
|
def update(sobject_type, properties)
|
265
267
|
update!(sobject_type, properties)
|
266
|
-
rescue
|
268
|
+
rescue
|
267
269
|
false
|
268
270
|
end
|
269
271
|
|
@@ -316,7 +318,7 @@ module Soapforce
|
|
316
318
|
# Returns false if an error is returned from Salesforce.
|
317
319
|
def delete(id)
|
318
320
|
delete!(id)
|
319
|
-
rescue
|
321
|
+
rescue
|
320
322
|
false
|
321
323
|
end
|
322
324
|
alias_method :destroy, :delete
|
@@ -375,7 +377,7 @@ module Soapforce
|
|
375
377
|
# Raises an exception if an error is returned from Salesforce.
|
376
378
|
def merge(sobject_type, master_record_hash, ids)
|
377
379
|
merge!(sobject_type, master_record_hash, ids)
|
378
|
-
rescue
|
380
|
+
rescue
|
379
381
|
false
|
380
382
|
end
|
381
383
|
|
@@ -433,8 +435,8 @@ module Soapforce
|
|
433
435
|
field_names = select_fields
|
434
436
|
end
|
435
437
|
|
436
|
-
soql = "
|
437
|
-
|
438
|
+
soql = "SELECT #{field_names.join(", ")} FROM #{sobject} WHERE #{where_clause}"
|
439
|
+
query(soql)
|
438
440
|
end
|
439
441
|
|
440
442
|
# Public: Finds a single record and returns all fields.
|
@@ -449,14 +451,14 @@ module Soapforce
|
|
449
451
|
field_details = field_details(sobject, field_name)
|
450
452
|
field_names = field_list(sobject).join(", ")
|
451
453
|
|
452
|
-
if ["int", "currency", "double", "boolean", "percent"].include?(field_details[:type])
|
454
|
+
if ["int", "currency", "double", "boolean", "percent"].include?(field_details[key_name(:type)])
|
453
455
|
search_value = id
|
454
456
|
else
|
455
457
|
# default to quoted value
|
456
458
|
search_value = "'#{id}'"
|
457
459
|
end
|
458
460
|
|
459
|
-
soql = "
|
461
|
+
soql = "SELECT #{field_names} FROM #{sobject} WHERE #{field_name} = #{search_value}"
|
460
462
|
result = query(soql)
|
461
463
|
# Return first query result.
|
462
464
|
result ? result.first : nil
|
@@ -471,8 +473,7 @@ module Soapforce
|
|
471
473
|
# Returns Hash of sobject record.
|
472
474
|
def retrieve(sobject, id)
|
473
475
|
ids = id.is_a?(Array) ? id : [id]
|
474
|
-
|
475
|
-
sobject ? SObject.new(sobject) : nil
|
476
|
+
call_soap_api(:retrieve, {fieldList: field_list(sobject).join(","), sObjectType: sobject, ids: ids})
|
476
477
|
end
|
477
478
|
|
478
479
|
# ProcessSubmitRequest
|
@@ -515,13 +516,15 @@ module Soapforce
|
|
515
516
|
|
516
517
|
def field_list(sobject)
|
517
518
|
description = describe(sobject)
|
518
|
-
|
519
|
+
name_key = key_name(:name)
|
520
|
+
description[key_name(:fields)].collect {|c| c[name_key] }
|
519
521
|
end
|
520
522
|
|
521
523
|
def field_details(sobject, field_name)
|
522
524
|
description = describe(sobject)
|
523
|
-
fields = description[:fields]
|
524
|
-
|
525
|
+
fields = description[key_name(:fields)]
|
526
|
+
name_key = key_name(:name)
|
527
|
+
fields.find {|f| field_name.downcase == f[name_key].downcase }
|
525
528
|
end
|
526
529
|
|
527
530
|
# Supports the following No Argument methods:
|
@@ -535,26 +538,51 @@ module Soapforce
|
|
535
538
|
call_soap_api(method, *args)
|
536
539
|
end
|
537
540
|
|
541
|
+
def key_name(key)
|
542
|
+
if @tag_style == :snakecase
|
543
|
+
key.is_a?(Symbol) ? key : key.snakecase.to_sym
|
544
|
+
else
|
545
|
+
if key.to_s.include?('_')
|
546
|
+
camel_key = key.to_s.gsub(/\_(\w{1})/) {|cap| cap[1].upcase }
|
547
|
+
else
|
548
|
+
key.to_s
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
538
553
|
def call_soap_api(method, message_hash={})
|
539
554
|
|
540
555
|
response = @client.call(method.to_sym) do |locals|
|
541
556
|
locals.message message_hash
|
542
557
|
end
|
558
|
+
|
543
559
|
# Convert SOAP XML to Hash
|
544
560
|
response = response.to_hash
|
545
561
|
|
546
562
|
# Get Response Body
|
547
|
-
|
563
|
+
key = key_name("#{method}Response")
|
564
|
+
response_body = response[key]
|
548
565
|
|
549
566
|
# Grab result section if exists.
|
550
|
-
result = response_body ? response_body[:result] : nil
|
567
|
+
result = response_body ? response_body[key_name(:result)] : nil
|
551
568
|
|
552
569
|
# Raise error when response contains errors
|
553
|
-
if result
|
554
|
-
|
570
|
+
if result.is_a?(Hash)
|
571
|
+
xsi_type = result[key_name(:"@xsi:type")].to_s
|
572
|
+
|
573
|
+
if result[key_name(:success)] == false && result[key_name(:errors)]
|
574
|
+
errors = result[key_name(:errors)]
|
575
|
+
raise Savon::Error.new("#{errors[key_name(:status_code)]}: #{errors[key_name(:message)]}")
|
576
|
+
elsif xsi_type.include?("sObject")
|
577
|
+
result = SObject.new(result)
|
578
|
+
elsif xsi_type.include?("QueryResult")
|
579
|
+
result = QueryResult.new(result)
|
580
|
+
else
|
581
|
+
result = Result.new(result)
|
582
|
+
end
|
555
583
|
end
|
556
584
|
|
557
|
-
|
585
|
+
result
|
558
586
|
end
|
559
587
|
|
560
588
|
def sobjects_hash(sobject_type, sobject_hash)
|
@@ -4,39 +4,68 @@ module Soapforce
|
|
4
4
|
|
5
5
|
attr_reader :raw_result
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
|
7
|
+
def initialize(result={})
|
8
|
+
if result.is_a?(Soapforce::Result)
|
9
|
+
@raw_result = result.to_hash
|
10
|
+
else
|
11
|
+
@raw_result = result
|
12
|
+
end
|
13
|
+
|
9
14
|
@result_records = [] # Default for 0 size response.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
15
|
+
@key_type = @raw_result.key?(:size) ? :symbol : :string
|
16
|
+
|
17
|
+
records_key = key_name("records")
|
18
|
+
|
19
|
+
if @raw_result[records_key]
|
20
|
+
@result_records = @raw_result[records_key]
|
21
|
+
# Single records come back as a Hash. Wrap in array.
|
22
|
+
if @result_records.is_a?(Hash)
|
23
|
+
@result_records = [@result_records]
|
24
|
+
end
|
14
25
|
end
|
15
26
|
|
16
27
|
# Convert to SObject type.
|
17
28
|
@result_records.map! {|hash| SObject.new(hash) }
|
18
29
|
end
|
19
30
|
|
31
|
+
def records
|
32
|
+
@result_records
|
33
|
+
end
|
34
|
+
|
20
35
|
# Implmentation for Enumerable mix-in.
|
21
36
|
def each(&block)
|
22
37
|
@result_records.each(&block)
|
23
38
|
end
|
24
39
|
|
40
|
+
def map(&block)
|
41
|
+
@result_records.map(&block)
|
42
|
+
end
|
43
|
+
|
25
44
|
def size
|
26
|
-
@raw_result[
|
45
|
+
@raw_result[key_name("size")].to_i || 0
|
27
46
|
end
|
28
47
|
|
29
48
|
def done?
|
30
|
-
@raw_result[
|
49
|
+
@raw_result[key_name("done")] || true
|
31
50
|
end
|
32
51
|
|
33
52
|
def query_locator
|
34
|
-
@raw_result[
|
53
|
+
@raw_result[key_name("queryLocator")]
|
35
54
|
end
|
36
55
|
|
37
56
|
def method_missing(method, *args, &block)
|
38
|
-
@result_records.
|
57
|
+
if @result_records.respond_to?(method)
|
58
|
+
@result_records.send(method, *args, &block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def key_name(key)
|
63
|
+
if @key_type == :symbol
|
64
|
+
key.is_a?(String) ? key.snakecase.to_sym : key
|
65
|
+
else
|
66
|
+
key.to_s
|
67
|
+
end
|
39
68
|
end
|
40
69
|
end
|
41
70
|
|
42
|
-
end
|
71
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Soapforce
|
2
|
+
class Result
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_reader :raw_hash
|
6
|
+
|
7
|
+
def_delegators :@raw_hash, :key?, :has_key?, :each, :map, :to_hash
|
8
|
+
|
9
|
+
def initialize(result_hash={})
|
10
|
+
@raw_hash = result_hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](index)
|
14
|
+
# If index is a symbol, try :field_name, "fieldName", "field_name"
|
15
|
+
if index.is_a?(Symbol)
|
16
|
+
if @raw_hash.key?(index)
|
17
|
+
@raw_hash[index]
|
18
|
+
elsif index.to_s.include?('_')
|
19
|
+
camel_key = index.to_s.gsub(/\_(\w{1})/) {|cap| cap[1].upcase }
|
20
|
+
@raw_hash[camel_key]
|
21
|
+
else
|
22
|
+
@raw_hash[index.to_s]
|
23
|
+
end
|
24
|
+
elsif index.is_a?(String)
|
25
|
+
# If index is a String, try fieldName, :fieldName, :field_name
|
26
|
+
if @raw_hash.key?(index)
|
27
|
+
@raw_hash[index]
|
28
|
+
elsif @raw_hash.key?(index.to_sym)
|
29
|
+
@raw_hash[index.to_sym]
|
30
|
+
else
|
31
|
+
@raw_hash[index.snakecase.to_sym]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/soapforce/sobject.rb
CHANGED
@@ -4,32 +4,60 @@ module Soapforce
|
|
4
4
|
|
5
5
|
def initialize(hash)
|
6
6
|
@raw_hash = hash || {}
|
7
|
+
id_key = @raw_hash.key?(:id) ? :id : 'Id'
|
8
|
+
|
9
|
+
# For some reason the Id field is coming back twice and stored in an array.
|
10
|
+
if @raw_hash[id_key].is_a?(Array)
|
11
|
+
@raw_hash[id_key] = @raw_hash[id_key].compact.uniq
|
12
|
+
# Remove empty id array if nothing exists.
|
13
|
+
if @raw_hash[id_key].empty?
|
14
|
+
@raw_hash.delete(id_key)
|
15
|
+
elsif @raw_hash[id_key].size == 1
|
16
|
+
@raw_hash[id_key] = @raw_hash[id_key].first
|
17
|
+
end
|
18
|
+
end
|
7
19
|
end
|
8
20
|
|
9
|
-
# For some reason the Id field is coming back twice and stored in an array.
|
10
21
|
def Id
|
11
|
-
@raw_hash[:id]
|
22
|
+
@raw_hash[:id] || @raw_hash['Id']
|
12
23
|
end
|
13
24
|
|
14
25
|
def [](index)
|
15
|
-
@raw_hash[index
|
26
|
+
val = @raw_hash[index]
|
27
|
+
|
28
|
+
# When fetching a child relationship, wrap it in QueryResult
|
29
|
+
if val.is_a?(Hash) && (val.has_key?(:records) || val.has_key?("records"))
|
30
|
+
val = QueryResult.new(val)
|
31
|
+
end
|
32
|
+
val
|
16
33
|
end
|
17
34
|
|
18
35
|
def []=(index, value)
|
19
|
-
@raw_hash[index
|
36
|
+
@raw_hash[index] = value
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_key?(key)
|
40
|
+
@raw_hash.has_key?(key)
|
20
41
|
end
|
21
42
|
|
22
43
|
# Allows method-like access to the hash using camelcase field names.
|
23
44
|
def method_missing(method, *args, &block)
|
24
|
-
|
45
|
+
# Check string keys first, original and downcase
|
25
46
|
string_method = method.to_s
|
47
|
+
|
48
|
+
if raw_hash.key?(string_method)
|
49
|
+
return self[string_method]
|
50
|
+
elsif raw_hash.key?(string_method.downcase)
|
51
|
+
return self[string_method.downcase]
|
52
|
+
end
|
53
|
+
|
26
54
|
if string_method =~ /[A-Z+]/
|
27
|
-
string_method =
|
55
|
+
string_method = string_method.snakecase
|
28
56
|
end
|
29
57
|
|
30
58
|
index = string_method.downcase.to_sym
|
31
|
-
#
|
32
|
-
return
|
59
|
+
# Check symbol key and return local hash entry.
|
60
|
+
return self[index] if raw_hash.has_key?(index)
|
33
61
|
# Then delegate to hash object.
|
34
62
|
if raw_hash.respond_to?(method)
|
35
63
|
return raw_hash.send(method, *args)
|
@@ -38,15 +66,5 @@ module Soapforce
|
|
38
66
|
nil
|
39
67
|
end
|
40
68
|
|
41
|
-
protected
|
42
|
-
|
43
|
-
def underscore(str)
|
44
|
-
str.gsub(/::/, '/').
|
45
|
-
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
46
|
-
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
47
|
-
tr("-", "_").
|
48
|
-
downcase
|
49
|
-
end
|
50
|
-
|
51
69
|
end
|
52
70
|
end
|