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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9ad9594a32904ff7305f9fed654cc0a71148e7e0
4
- data.tar.gz: 0fb2ff0b93f97172a1e6ce0eb53591e91e9c3441
3
+ metadata.gz: 92708d194ae913cf1c8b95690ba30e45baaa4a8d
4
+ data.tar.gz: 447d8ed94094138eb2ce5e61c823926acff32b1d
5
5
  SHA512:
6
- metadata.gz: 56920bf8ee1aa09ef84d071011b39c8a9a791cb183659bad3de9924adb9f1dd5599f5373db8e64fcd8ca42437e96a42c17c16ad61373f7db916457bf51deeeca
7
- data.tar.gz: fda094cc5420a8a8848549937f5207d31b9fbdf1cb786b8eb2361fe3fb41aea2c4005872fd909224344ba300904e3116dc9fa1fd0c8314d245edf5d96e4b8d3f
6
+ metadata.gz: d34fbfb9303880a6105d4b07e3cc00783de06355cf3f89537f26c9d8bdd529fd31ad2bff3eeb4afc2bad1e7efad0f518df4779c1f145649f49c0ebb430955725
7
+ data.tar.gz: 51e41d674182269bc3767224add6e7049ced6ef2adc3637aed1db29782a753ff3ab1358721fb3a3324920c7583e8ef6532437068793b5d1c5e41b693b33644d6
@@ -1,6 +1,9 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
3
- - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.5
6
+ - 2.2.4
4
7
  notifications:
5
8
  email:
6
9
  recipients:
@@ -1,3 +1,9 @@
1
+ ## 0.5.0 (Mar 4, 2016)
2
+
3
+ * Default ssl_version to TLSv1_2
4
+ * Switch to Ruby 2, updates rspec syntax, and update dependencies. #8
5
+ * Add support for original message tag names #7
6
+
1
7
  ## 0.3.0 (Mar 25, 2015)
2
8
 
3
9
  * Adds merge command.
@@ -4,6 +4,7 @@ require "soapforce/version"
4
4
  require "soapforce/configuration"
5
5
 
6
6
  require "soapforce/client"
7
+ require "soapforce/result"
7
8
  require "soapforce/query_result"
8
9
  require "soapforce/sobject"
9
10
 
@@ -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 recent SSLv3 POODLE vulnerabilty we default to TLSv1
30
- @ssl_version = options[:ssl_version] || :TLSv1
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
- returned_endpoint = result[:server_url]
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('select id from Organization').first
127
- if object && object[:id]
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
- list = sobject_type.map do |type|
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, :sObjectType => sobject_type)
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
- result = call_soap_api(:query, {:queryString => soql})
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
- result = call_soap_api(:query_all, {:queryString => soql})
204
- QueryResult.new(result)
207
+ call_soap_api(:query_all, {:queryString => soql})
205
208
  end
206
209
 
207
210
  def query_more(locator)
208
- result = call_soap_api(:query_more, {:queryLocator => locator})
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 => e
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 => e
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 => e
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 => e
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 = "Select #{field_names.join(", ")} From #{sobject} Where #{where_clause}"
437
- result = query(soql)
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 = "Select #{field_names} From #{sobject} Where #{field_name} = #{search_value}"
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
- sobject = call_soap_api(:retrieve, {fieldList: field_list(sobject).join(","), sObjectType: sobject, ids: ids})
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
- field_list = description[:fields].collect {|c| c[:name] }
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
- fields.find {|f| field_name.downcase == f[:name].downcase }
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
- response_body = response["#{method}_response".to_sym]
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 && result.is_a?(Hash) && result[:success] == false && result[:errors]
554
- raise Savon::Error.new("#{result[:errors][:status_code]}: #{result[:errors][:message]}")
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
- return result
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(result_hash={})
8
- @raw_result = result_hash
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
- if @raw_result[:size].to_i == 1
11
- @result_records = [@raw_result[:records]]
12
- elsif @raw_result[:records]
13
- @result_records = @raw_result[:records]
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[:size].to_i || 0
45
+ @raw_result[key_name("size")].to_i || 0
27
46
  end
28
47
 
29
48
  def done?
30
- @raw_result[:done] || true
49
+ @raw_result[key_name("done")] || true
31
50
  end
32
51
 
33
52
  def query_locator
34
- @raw_result[:query_locator]
53
+ @raw_result[key_name("queryLocator")]
35
54
  end
36
55
 
37
56
  def method_missing(method, *args, &block)
38
- @result_records.send(method, *args, &block)
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
@@ -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].is_a?(Array) ? @raw_hash[:id].first : @raw_hash[:id]
22
+ @raw_hash[:id] || @raw_hash['Id']
12
23
  end
13
24
 
14
25
  def [](index)
15
- @raw_hash[index.to_sym]
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.to_sym] = value
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 = underscore(string_method)
55
+ string_method = string_method.snakecase
28
56
  end
29
57
 
30
58
  index = string_method.downcase.to_sym
31
- # First return local hash entry.
32
- return raw_hash[index] if raw_hash.has_key?(index)
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