soapforce 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|