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 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