salesforce_adapter 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e5db31804a0b4354b11b1e8cdd09ace23a9fc106
4
+ data.tar.gz: 15d3305e9a909da4ff4afed346144df47ddf8b7e
5
+ SHA512:
6
+ metadata.gz: 017c7c92736a178b66243ccbf7c8dbecf533dfc113a38b88d7c353117013d7e07040265c91b9c7c352d6d6db74ad31fbe560e3a731a7f1901951ea7c0324b14b
7
+ data.tar.gz: 28a734ff1747df86d47497c39cc5b33dab8fb35abe6ddc7bb63bc5bcb0f6b895c5ddb8c9501e2e59a0a5fbb0ee4415fdf5be568353d42290ffee05aedd1f6970
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # SalesforceAdapter
2
+
3
+ [![Build Status](https://travis-ci.org/mru2/salesforce_adapter.png?branch=master)](https://travis-ci.org/mru2/salesforce_adapter) [![Coverage Status](https://coveralls.io/repos/mru2/salesforce_adapter/badge.png)](https://coveralls.io/r/mru2/salesforce_adapter) [![Code Climate](https://codeclimate.com/github/mru2/salesforce_adapter.png)](https://codeclimate.com/github/mru2/salesforce_adapter)
4
+
5
+
6
+
7
+ Ruby client for the salesforce API
8
+ Actually, lightweight wrapper around the RForce gem
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'salesforce_adapter'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install salesforce_adapter
23
+
24
+ ## Usage
25
+
26
+ ```
27
+ require 'salesforce_adapter'
28
+
29
+ adapter = SalesforceAdapter::Base.new(
30
+ :url => 'https://test.salesforce.com/services/Soap/u/16.0',
31
+ :login => 'your_salesforce_login',
32
+ :password => 'your_salesforce_password'
33
+ )
34
+
35
+ > adapter.query("SELECT a.Id, a.Name FROM Account a WHERE Id='a_salesforce_id'")
36
+ => [ {:type => "Account", :Id => "a_salesforce_id", :Name => "an_account" } ]
37
+
38
+ > adapter.create(:Lead, {:type => 'Lead', :FirstName => 'Mace', :LastName => 'Windu', :Company => 'JEDI inc'})
39
+ => 'the_new_lead_id'
40
+
41
+ > adapter.update(:Lead, {:type => 'Lead', :Id => 'the_new_lead_id', :Company => 'One Arm Support Group'})
42
+ => 'the_new_lead_id'
43
+
44
+
45
+ > adapter.webservice('MyWebserviceClass').myMethod('foo')
46
+ => 'bar'
47
+ ```
48
+
49
+
50
+ ## Contributing
51
+
52
+ 1. Fork it ( http://github.com/<my-github-username>/salesforce_adapter/fork )
53
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
54
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
55
+ 4. Push to the branch (`git push origin my-new-feature`)
56
+ 5. Create new Pull Request
@@ -0,0 +1,90 @@
1
+ # Global helper methods used in the salesforce adapter
2
+
3
+ module SalesforceAdapter
4
+
5
+
6
+ # Exception : Salesforce API timeout
7
+ class SalesforceTimeout < StandardError ; end
8
+
9
+
10
+ module Helpers
11
+
12
+ class << self
13
+
14
+ # Attemps to run a salesforce operation a given number of times,
15
+ # Handle timeout errors and sleep between retries
16
+ def handle_timeout(opts= {}, &block)
17
+
18
+ max_tries = opts[:max_tries] || 1
19
+ sleep_between_tries = opts[:sleep_between_tries] || 1
20
+
21
+ counter = 0
22
+
23
+ begin
24
+ counter += 1
25
+ yield
26
+
27
+ rescue Timeout::Error => e
28
+ raise SalesforceAdapter::SalesforceTimeout.new(e.message) if counter >= max_tries
29
+
30
+ sleep sleep_between_tries
31
+ retry
32
+ end
33
+
34
+ end
35
+
36
+
37
+ # Format a fields hash in an array, to be sent with a create or update query
38
+ # Necessary to handle nil fields (must be submitted under the :fieldsToNull key, and there can be more than one)
39
+ def format_fields_for_create_or_update(fields)
40
+ fields_array = []
41
+ fields = fields.dup
42
+
43
+ # The type and Id have to be the first fields given
44
+ type = fields.delete(:type)
45
+ id = fields.delete(:Id)
46
+
47
+ fields_array << :type << type if type
48
+ fields_array << :Id << id if id
49
+
50
+ # Then append the other fields, handling the nil and boolean ones
51
+ fields.each do |key, value|
52
+ if value.nil?
53
+ fields_array << :fieldsToNull << key.to_s
54
+ else
55
+ fields_array << key << format_value_for_create_or_update(value)
56
+ end
57
+ end
58
+
59
+ fields_array
60
+ end
61
+
62
+
63
+ # Format a single value to be send in a create or update query
64
+ def format_value_for_create_or_update(value)
65
+ # Boolean have to be casted to strings
66
+ if !!value == value
67
+ value.to_s
68
+ else
69
+ value
70
+ end
71
+ end
72
+
73
+
74
+ # Format a hash for its display in logs / error messages
75
+ # Here for ruby 1.8's OrderedHash, to remove when migrated to ruby 2.0 +
76
+ def hash_to_s(h)
77
+ if h.class == Hash
78
+ h.inspect
79
+ elsif h.class < Hash
80
+ Hash[h.to_a].inspect
81
+ else
82
+ raise ArgumentError.new('h is not a Hash or a subclass of Hash')
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,80 @@
1
+ # Base class for performing rforce operations
2
+ # Template for formatting input, performing operation and validating results
3
+ # Implementation details defined in subclasses
4
+
5
+ # Responsible for validating, running and formatting the results of a query
6
+
7
+ module SalesforceAdapter
8
+
9
+ # Exception : Fault from the API, no results returned. The error code has its accessor : :code
10
+ class SalesforceApiFault < StandardError
11
+ attr_reader :code
12
+ def initialize(code, message = nil)
13
+ @code = code
14
+ super(message)
15
+ end
16
+ end
17
+
18
+
19
+ module Operations
20
+
21
+ class Base
22
+
23
+ attr_reader :rforce_binding
24
+
25
+
26
+ def initialize(rforce_binding)
27
+ @rforce_binding = rforce_binding
28
+ end
29
+
30
+
31
+ def run
32
+ validate_request!
33
+
34
+ Helpers.handle_timeout(:max_tries => 4, :sleep_between_tries => 5) do
35
+ @response = perform
36
+ end
37
+
38
+ validate_response!
39
+
40
+ format_response
41
+ end
42
+
43
+
44
+ private
45
+
46
+ def context
47
+ "No context given"
48
+ end
49
+
50
+ def validate_request!
51
+ # nothing by default...
52
+ end
53
+
54
+ def perform!
55
+ raise NotImplementedError.new("Must be defined in subclass")
56
+ end
57
+
58
+ def validate_response!
59
+ # Check for API errors and raise them
60
+ if @response[:Fault]
61
+
62
+ fault_code = @response[:Fault][:faultcode] # "sf:MALFORMED_QUERY"
63
+ fault_details = @response[:Fault][:faultstring]
64
+
65
+ message = "Salesforce API Fault : #{fault_details}.\nContext : #{context}"
66
+
67
+ raise SalesforceApiFault.new(fault_code), message
68
+ end
69
+
70
+ end
71
+
72
+ def format_response
73
+ # Nothing by default
74
+ @response
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,59 @@
1
+ # Responsible for validating, running and formatting the results of a salesforce creation
2
+
3
+ module SalesforceAdapter
4
+
5
+ # Exception : Creation failure, (still a response but no record created done)
6
+ class SalesforceFailedCreate < StandardError
7
+ attr_reader :code
8
+ def initialize(code, message = nil)
9
+ @code = code
10
+ super(message)
11
+ end
12
+ end
13
+
14
+
15
+ module Operations
16
+
17
+ class Create < Base
18
+
19
+ def initialize(rforce_binding, table_name, attributes)
20
+ @table_name = table_name
21
+ @attributes = attributes
22
+
23
+ super(rforce_binding)
24
+ end
25
+
26
+
27
+ private
28
+
29
+ def context
30
+ "creating salesforce #{@table_name} with attributes #{Helpers.hash_to_s(@attributes)}"
31
+ end
32
+
33
+ def validate_request!
34
+ raise ArgumentError.new("type must be specified for a Salesforce Create") unless @attributes[:type]
35
+ end
36
+
37
+ def perform
38
+ rforce_binding.create( @table_name => Helpers.format_fields_for_create_or_update(@attributes) )
39
+ end
40
+
41
+ def validate_response!
42
+ super
43
+
44
+ # Raise exception on update failure
45
+ if !@response[:createResponse][:result][:success]
46
+ sf_error_code = @response[:createResponse][:result][:errors][:statusCode]
47
+ sf_error_message = @response[:createResponse][:result][:errors][:message]
48
+ raise SalesforceFailedCreate.new(sf_error_code), "#{sf_error_message}\nContext : #{context}"
49
+ end
50
+ end
51
+
52
+ def format_response
53
+ # Return the id
54
+ @response[:createResponse][:result][:id]
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,43 @@
1
+ # Responsible for validating, running and formatting the results of a query
2
+
3
+ module SalesforceAdapter
4
+ module Operations
5
+
6
+ class Query < Base
7
+
8
+ def initialize(rforce_binding, query_string)
9
+ @query_string = query_string
10
+
11
+ super(rforce_binding)
12
+ end
13
+
14
+
15
+ private
16
+
17
+ def context
18
+ "querying salesforce with : #{@query_string}"
19
+ end
20
+
21
+ def perform
22
+ rforce_binding.query(:queryString => @query_string)
23
+ end
24
+
25
+ def format_response
26
+ # If no results, return an empty array
27
+ return [] if @response[:queryResponse][:result][:size] == "0"
28
+
29
+ # Otherwise return an array of the results (can be empty, or contain only one result)
30
+ records = @response[:queryResponse][:result][:records]
31
+
32
+ if records.is_a?(Array)
33
+ return records
34
+ else
35
+ return [records].compact # if nil => returning an empty array
36
+ end
37
+ end
38
+
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,61 @@
1
+ # Responsible for validating, running and formatting the results of a salesforce update
2
+
3
+ module SalesforceAdapter
4
+
5
+ # Exception : Update failure, (still a response but no update done)
6
+ class SalesforceFailedUpdate < StandardError
7
+ attr_reader :code
8
+ def initialize(code, message = nil)
9
+ @code = code
10
+ super(message)
11
+ end
12
+ end
13
+
14
+
15
+ module Operations
16
+
17
+ class Update < Base
18
+
19
+ def initialize(rforce_binding, table_name, attributes)
20
+ @table_name = table_name
21
+ @attributes = attributes
22
+
23
+ super(rforce_binding)
24
+ end
25
+
26
+
27
+ private
28
+
29
+ def context
30
+ "updating salesforce #{@table_name} with attributes #{Helpers.hash_to_s(@attributes)}"
31
+ end
32
+
33
+ def validate_request!
34
+ raise ArgumentError.new("Id must be specified for a Salesforce Update") unless @attributes[:Id]
35
+ raise ArgumentError.new("type must be specified for a Salesforce Update") unless @attributes[:type]
36
+ end
37
+
38
+ def perform
39
+ rforce_binding.update( @table_name => Helpers.format_fields_for_create_or_update(@attributes) )
40
+ end
41
+
42
+ def validate_response!
43
+ super
44
+
45
+ # Raise exception on update failure
46
+ if !@response[:updateResponse][:result][:success]
47
+ sf_error_code = @response[:updateResponse][:result][:errors][:statusCode]
48
+ sf_error_message = @response[:updateResponse][:result][:errors][:message]
49
+ raise SalesforceFailedUpdate.new(sf_error_code), "#{sf_error_message}\nContext : #{context}"
50
+ end
51
+ end
52
+
53
+ def format_response
54
+ # Return the id
55
+ @response[:updateResponse][:result][:id]
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,69 @@
1
+ # Responsible for validating, running and formatting the results of a webservice operation
2
+
3
+ module SalesforceAdapter
4
+ module Operations
5
+
6
+ class WebserviceCall < Base
7
+
8
+ def initialize(rforce_binding, method_name, arguments, schema_url, service_path)
9
+ @method_name = method_name.to_s
10
+ @arguments = arguments || {}
11
+ @schema_url = schema_url
12
+ @service_path = service_path
13
+
14
+ super(rforce_binding)
15
+ end
16
+
17
+
18
+ private
19
+
20
+ def context
21
+ "calling webservice method #{@method_name} with arguments #{@arguments.inspect}.\n Schema url: #{@schema_url}, service path: #{@service_path}"
22
+ end
23
+
24
+ def perform
25
+ rforce_binding.call_remote(@method_name, formatted_arguments){ [@schema_url, @service_path] }
26
+ end
27
+
28
+ def format_response
29
+ @response[:"#{@method_name}Response"][:result]
30
+ end
31
+
32
+ # Rescue missing methods by inspecting the content of the response
33
+ def validate_response!
34
+ begin
35
+ super
36
+ rescue SalesforceApiFault => e
37
+
38
+ # Change the exception to a NoMethodError if undefined on salesforce
39
+ if e.message =~ /No operation available for request/
40
+ raise NoMethodError.new(e.message)
41
+ else
42
+ raise e
43
+ end
44
+ end
45
+ end
46
+
47
+
48
+ # Formatted arguments for WS call : need to be like [:key1, value1, :key2, value2]
49
+ def formatted_arguments
50
+ [].tap do |formatted_arguments|
51
+ @arguments.each do |name, value|
52
+ formatted_arguments << name << cast(value)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Convert a value before sending it to salesforce
58
+ def cast(value)
59
+ if [TrueClass, FalseClass].include? value.class
60
+ value
61
+ else
62
+ value.to_s
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,55 @@
1
+ # Wrapper around the rforce binding, responsible to guarantee it is logged in
2
+
3
+ module SalesforceAdapter
4
+
5
+ class RforceBinding
6
+
7
+ def initialize(url, login, password)
8
+ @url = url
9
+ @login = login
10
+ @password = password
11
+
12
+ @rforce = RForce::Binding.new(@url)
13
+ end
14
+
15
+
16
+ # Attempts to login to salesforce
17
+ def login
18
+ Helpers.handle_timeout(:max_tries => 4, :sleep_between_tries => 2) do
19
+ @rforce.login( @login , @password )
20
+ end
21
+ end
22
+
23
+
24
+ # Is it currently logged in?
25
+ def logged_in?
26
+ # An unlogged rforce binding has a session_id defined as nil
27
+ # Does not handle session expiration however
28
+ !!@rforce.instance_variable_get(:@session_id)
29
+ end
30
+
31
+
32
+ # Delegate all these methods to the rforce binding
33
+ [:query, :create, :update, :call_remote].each do |method_name|
34
+
35
+ define_method method_name do |*args, &block|
36
+
37
+ begin
38
+
39
+ # Guarantee the binding is logged in
40
+ login unless logged_in?
41
+
42
+ @rforce.send method_name, *args, &block
43
+
44
+ # Handle timeouts from salesforce
45
+ rescue Errno::ECONNRESET => e
46
+ raise SalesforceAdapter::SalesforceTimeout.new(e.message)
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,3 @@
1
+ module SalesforceAdapter
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,20 @@
1
+ # Proxy for a salesforce webservice.
2
+ # Uses method_missing behind the scenes to forward methods to salesforce calls
3
+
4
+ module SalesforceAdapter
5
+
6
+ class WebserviceProxy
7
+
8
+ def initialize(adapter, webservice_class)
9
+ @adapter = adapter
10
+ @schema_url = "http://soap.sforce.com/schemas/class/#{webservice_class}"
11
+ @service_path = "/services/Soap/class/#{webservice_class}"
12
+ end
13
+
14
+ # Forward calls to salesforce
15
+ def method_missing(method_name, args = {})
16
+ @adapter.call_webservice(method_name, args, @schema_url, @service_path)
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,76 @@
1
+ require 'salesforce_adapter/version'
2
+
3
+ require 'salesforce_adapter/helpers'
4
+ require 'salesforce_adapter/rforce_binding'
5
+ require 'salesforce_adapter/webservice_proxy'
6
+
7
+ require 'salesforce_adapter/operations/base'
8
+ require 'salesforce_adapter/operations/query'
9
+ require 'salesforce_adapter/operations/create'
10
+ require 'salesforce_adapter/operations/update'
11
+ require 'salesforce_adapter/operations/webservice_call'
12
+
13
+
14
+ # Wrapper around a rforce binding
15
+ # Forwards the 'query' method, handling the logging and error raising
16
+
17
+ require 'rforce'
18
+
19
+
20
+ module SalesforceAdapter
21
+
22
+ class Base
23
+
24
+ attr_reader :rforce_binding
25
+ attr_reader :webservice
26
+
27
+
28
+ # Initialize with the credentials
29
+ def initialize(config)
30
+ @rforce_binding = RforceBinding.new(config[:url], config[:login], config[:password])
31
+ end
32
+
33
+ def webservice(webservice_class)
34
+ WebserviceProxy.new(self, webservice_class)
35
+ end
36
+
37
+ # Runs a query on salesforce and returns the result
38
+ def query( query_string )
39
+
40
+ # Perform the query
41
+ Operations::Query.new(@rforce_binding, query_string).run()
42
+
43
+ end
44
+
45
+
46
+ # Updates a salesforce record
47
+ def update(table_name, attributes)
48
+
49
+ # Perform the update and returns the id
50
+ Operations::Update.new(@rforce_binding, table_name, attributes).run()
51
+
52
+ end
53
+
54
+
55
+ # Creates a salesforce record
56
+ def create(table_name, attributes)
57
+
58
+ # Perform the creation and returns the id
59
+ Operations::Create.new(@rforce_binding, table_name, attributes).run()
60
+
61
+ end
62
+
63
+
64
+
65
+ # Queries a salesforce webservice
66
+ def call_webservice(method_name, arguments, schema_url, service_path)
67
+
68
+ # Perform the call to the webservice and returns the result
69
+ Operations::WebserviceCall.new(@rforce_binding, method_name, arguments, schema_url, service_path).run()
70
+
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: salesforce_adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - ClicRDV
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rforce
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0.11'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email:
57
+ - david.ruyer@clicrdv.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - README.md
63
+ - lib/salesforce_adapter.rb
64
+ - lib/salesforce_adapter/helpers.rb
65
+ - lib/salesforce_adapter/operations/base.rb
66
+ - lib/salesforce_adapter/operations/create.rb
67
+ - lib/salesforce_adapter/operations/query.rb
68
+ - lib/salesforce_adapter/operations/update.rb
69
+ - lib/salesforce_adapter/operations/webservice_call.rb
70
+ - lib/salesforce_adapter/rforce_binding.rb
71
+ - lib/salesforce_adapter/version.rb
72
+ - lib/salesforce_adapter/webservice_proxy.rb
73
+ homepage: ''
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Lightweight client for the salesforce API
97
+ test_files: []
98
+ has_rdoc: