sfdc 0.0.1 → 1.0.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 +8 -8
- data/lib/sfdc.rb +39 -1
- data/lib/sfdc/attachment.rb +23 -0
- data/lib/sfdc/chatter/comment.rb +10 -0
- data/lib/sfdc/chatter/conversation.rb +100 -0
- data/lib/sfdc/chatter/feed.rb +64 -0
- data/lib/sfdc/chatter/feed_item.rb +40 -0
- data/lib/sfdc/chatter/feeds.rb +5 -0
- data/lib/sfdc/chatter/filter_feed.rb +14 -0
- data/lib/sfdc/chatter/group.rb +45 -0
- data/lib/sfdc/chatter/group_membership.rb +9 -0
- data/lib/sfdc/chatter/like.rb +9 -0
- data/lib/sfdc/chatter/message.rb +29 -0
- data/lib/sfdc/chatter/photo_methods.rb +55 -0
- data/lib/sfdc/chatter/record.rb +122 -0
- data/lib/sfdc/chatter/subscription.rb +9 -0
- data/lib/sfdc/chatter/user.rb +153 -0
- data/lib/sfdc/client.rb +98 -0
- data/lib/sfdc/client/api.rb +320 -0
- data/lib/sfdc/client/authentication.rb +40 -0
- data/lib/sfdc/client/caching.rb +26 -0
- data/lib/sfdc/client/canvas.rb +12 -0
- data/lib/sfdc/client/connection.rb +74 -0
- data/lib/sfdc/client/picklists.rb +90 -0
- data/lib/sfdc/client/streaming.rb +31 -0
- data/lib/sfdc/client/verbs.rb +68 -0
- data/lib/sfdc/collection.rb +40 -0
- data/lib/sfdc/config.rb +136 -0
- data/lib/sfdc/mash.rb +65 -0
- data/lib/sfdc/middleware.rb +29 -0
- data/lib/sfdc/middleware/authentication.rb +63 -0
- data/lib/sfdc/middleware/authentication/password.rb +20 -0
- data/lib/sfdc/middleware/authentication/token.rb +15 -0
- data/lib/sfdc/middleware/authorization.rb +19 -0
- data/lib/sfdc/middleware/caching.rb +24 -0
- data/lib/sfdc/middleware/gzip.rb +31 -0
- data/lib/sfdc/middleware/instance_url.rb +18 -0
- data/lib/sfdc/middleware/logger.rb +40 -0
- data/lib/sfdc/middleware/mashify.rb +18 -0
- data/lib/sfdc/middleware/multipart.rb +53 -0
- data/lib/sfdc/middleware/raise_error.rb +23 -0
- data/lib/sfdc/signed_request.rb +48 -0
- data/lib/sfdc/sobject.rb +64 -0
- data/lib/sfdc/upload_io.rb +20 -0
- data/lib/sfdc/version.rb +1 -1
- metadata +59 -4
- data/lib/sfdc/sfdc.rb +0 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
module Sfdc
|
2
|
+
class Client
|
3
|
+
module Authentication
|
4
|
+
|
5
|
+
# Public: Force an authentication
|
6
|
+
def authenticate!
|
7
|
+
raise AuthenticationError, 'No authentication middleware present' unless authentication_middleware
|
8
|
+
middleware = authentication_middleware.new nil, self, @options
|
9
|
+
middleware.authenticate!
|
10
|
+
end
|
11
|
+
|
12
|
+
# Internal: Determines what middleware will be used based on the options provided
|
13
|
+
def authentication_middleware
|
14
|
+
if username_password?
|
15
|
+
Sfdc::Middleware::Authentication::Password
|
16
|
+
elsif oauth_refresh?
|
17
|
+
Sfdc::Middleware::Authentication::Token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Internal: Returns true if username/password (autonomous) flow should be used for
|
22
|
+
# authentication.
|
23
|
+
def username_password?
|
24
|
+
@options[:username] &&
|
25
|
+
@options[:password] &&
|
26
|
+
@options[:client_id] &&
|
27
|
+
@options[:client_secret]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Internal: Returns true if oauth token refresh flow should be used for
|
31
|
+
# authentication.
|
32
|
+
def oauth_refresh?
|
33
|
+
@options[:refresh_token] &&
|
34
|
+
@options[:client_id] &&
|
35
|
+
@options[:client_secret]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Sfdc
|
2
|
+
class Client
|
3
|
+
module Caching
|
4
|
+
|
5
|
+
# Public: Runs the block with caching disabled.
|
6
|
+
#
|
7
|
+
# block - A query/describe/etc.
|
8
|
+
#
|
9
|
+
# Returns the result of the block
|
10
|
+
def without_caching(&block)
|
11
|
+
@options[:use_cache] = false
|
12
|
+
block.call
|
13
|
+
ensure
|
14
|
+
@options.delete(:use_cache)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Internal: Cache to use for the caching middleware
|
20
|
+
def cache
|
21
|
+
@options[:cache]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Sfdc
|
2
|
+
class Client
|
3
|
+
module Connection
|
4
|
+
|
5
|
+
# Public: The Faraday::Builder instance used for the middleware stack. This
|
6
|
+
# can be used to insert an custom middleware.
|
7
|
+
#
|
8
|
+
# Examples
|
9
|
+
#
|
10
|
+
# # Add the instrumentation middleware for Rails.
|
11
|
+
# client.middleware.use FaradayMiddleware::Instrumentation
|
12
|
+
#
|
13
|
+
# Returns the Faraday::Builder for the Faraday connection.
|
14
|
+
def middleware
|
15
|
+
connection.builder
|
16
|
+
end
|
17
|
+
alias_method :builder, :middleware
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Internal: Internal faraday connection where all requests go through
|
22
|
+
def connection
|
23
|
+
@connection ||= Faraday.new(@options[:instance_url], connection_options) do |builder|
|
24
|
+
# Parses JSON into Hashie::Mash structures.
|
25
|
+
builder.use Sfdc::Middleware::Mashify, self, @options
|
26
|
+
# Handles multipart file uploads for blobs.
|
27
|
+
builder.use Sfdc::Middleware::Multipart
|
28
|
+
# Converts the request into JSON.
|
29
|
+
builder.request :json
|
30
|
+
# Handles reauthentication for 403 responses.
|
31
|
+
builder.use authentication_middleware, self, @options if authentication_middleware
|
32
|
+
# Sets the oauth token in the headers.
|
33
|
+
builder.use Sfdc::Middleware::Authorization, self, @options
|
34
|
+
# Ensures the instance url is set.
|
35
|
+
builder.use Sfdc::Middleware::InstanceURL, self, @options
|
36
|
+
# Parses returned JSON response into a hash.
|
37
|
+
builder.response :json, :content_type => /\bjson$/
|
38
|
+
# Caches GET requests.
|
39
|
+
builder.use Sfdc::Middleware::Caching, cache, @options if cache
|
40
|
+
# Follows 30x redirects.
|
41
|
+
builder.use FaradayMiddleware::FollowRedirects
|
42
|
+
# Raises errors for 40x responses.
|
43
|
+
builder.use Sfdc::Middleware::RaiseError
|
44
|
+
# Log request/responses
|
45
|
+
builder.use Sfdc::Middleware::Logger, Sfdc.configuration.logger, @options if Sfdc.log?
|
46
|
+
# Compress/Decompress the request/response
|
47
|
+
builder.use Sfdc::Middleware::Gzip, self, @options
|
48
|
+
|
49
|
+
builder.adapter adapter
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def adapter
|
54
|
+
Sfdc.configuration.adapter
|
55
|
+
end
|
56
|
+
|
57
|
+
# Internal: Faraday Connection options
|
58
|
+
def connection_options
|
59
|
+
{ :request => {
|
60
|
+
:timeout => @options[:timeout],
|
61
|
+
:open_timeout => @options[:timeout] },
|
62
|
+
:proxy => @options[:proxy_uri]
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
# Internal: Returns true if the middlware stack includes the
|
67
|
+
# Sfdc::Middleware::Mashify middleware.
|
68
|
+
def mashify?
|
69
|
+
middleware.handlers.index(Sfdc::Middleware::Mashify)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Sfdc
|
2
|
+
class Client
|
3
|
+
module Picklists
|
4
|
+
|
5
|
+
# Public: Get the available picklist values for a picklist or multipicklist field.
|
6
|
+
#
|
7
|
+
# sobject - The String name of the sobject.
|
8
|
+
# field - The String name of the picklist/multipicklist field.
|
9
|
+
# options - A hash of options. (default: {}).
|
10
|
+
# :valid_for - If specified, will only return picklist values
|
11
|
+
# that are valid for the controlling picklist
|
12
|
+
# value
|
13
|
+
#
|
14
|
+
# Examples
|
15
|
+
#
|
16
|
+
# client.picklist_values('Account', 'Type')
|
17
|
+
# # => [#<Sfdc::Mash label="Prospect" value="Prospect">]
|
18
|
+
#
|
19
|
+
# # Given a custom object named Automobile__c with picklist fields
|
20
|
+
# # Model__c and Make__c, where Model__c depends on the value of
|
21
|
+
# # Make__c.
|
22
|
+
# client.picklist_values('Automobile__c', 'Model__c', :valid_for => 'Honda')
|
23
|
+
# # => [#<Sfdc::Mash label="Civic" value="Civic">, ... ]
|
24
|
+
#
|
25
|
+
# Returns an array of Sfdc::Mash objects.
|
26
|
+
def picklist_values(sobject, field, options = {})
|
27
|
+
PicklistValues.new(describe(sobject)['fields'], field, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
class PicklistValues < Array
|
33
|
+
|
34
|
+
def initialize(fields, field, options = {})
|
35
|
+
@fields, @field = fields, field
|
36
|
+
@valid_for = options.delete(:valid_for)
|
37
|
+
raise "#{field} is not a dependent picklist" if @valid_for && !dependent?
|
38
|
+
replace(picklist_values)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :fields
|
44
|
+
|
45
|
+
def picklist_values
|
46
|
+
if valid_for?
|
47
|
+
field['picklistValues'].select { |picklist_entry| valid? picklist_entry }
|
48
|
+
else
|
49
|
+
field['picklistValues']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns true of the given field is dependent on another field.
|
54
|
+
def dependent?
|
55
|
+
!!field['dependentPicklist']
|
56
|
+
end
|
57
|
+
|
58
|
+
def valid_for?
|
59
|
+
!!@valid_for
|
60
|
+
end
|
61
|
+
|
62
|
+
def controlling_picklist
|
63
|
+
@_controlling_picklist ||= controlling_field['picklistValues'].find { |picklist_entry| picklist_entry['value'] == @valid_for }
|
64
|
+
end
|
65
|
+
|
66
|
+
def index
|
67
|
+
@_index ||= controlling_field['picklistValues'].index(controlling_picklist)
|
68
|
+
end
|
69
|
+
|
70
|
+
def controlling_field
|
71
|
+
@_controlling_field ||= fields.find { |f| f['name'] == field['controllerName'] }
|
72
|
+
end
|
73
|
+
|
74
|
+
def field
|
75
|
+
@_field ||= fields.find { |f| f['name'] == @field }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns true if the picklist entry is valid for the controlling picklist.
|
79
|
+
#
|
80
|
+
# See http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_describesobjects_describesobjectresult.htm
|
81
|
+
def valid?(picklist_entry)
|
82
|
+
valid_for = picklist_entry['validFor'].ljust(16, 'A').unpack('m').first.unpack('q*')
|
83
|
+
(valid_for[index >> 3] & (0x80 >> index % 8)) != 0
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Sfdc
|
2
|
+
class Client
|
3
|
+
module Streaming
|
4
|
+
|
5
|
+
# Public: Subscribe to a PushTopic
|
6
|
+
#
|
7
|
+
# channels - The name of the PushTopic channel(s) to subscribe to.
|
8
|
+
# block - A block to run when a new message is received.
|
9
|
+
#
|
10
|
+
# Returns a Faye::Subscription
|
11
|
+
def subscribe(channels, &block)
|
12
|
+
faye.subscribe Array(channels).map { |channel| "/topic/#{channel}" }, &block
|
13
|
+
end
|
14
|
+
|
15
|
+
# Public: Faye client to use for subscribing to PushTopics
|
16
|
+
def faye
|
17
|
+
raise 'Instance URL missing. Call .authenticate! first.' unless @options[:instance_url]
|
18
|
+
@faye ||= Faye::Client.new("#{@options[:instance_url]}/cometd/#{@options[:api_version]}").tap do |client|
|
19
|
+
client.bind 'transport:down' do
|
20
|
+
Sfdc.log "[COMETD DOWN]"
|
21
|
+
client.set_header 'Authorization', "OAuth #{authenticate!.access_token}"
|
22
|
+
end
|
23
|
+
client.bind 'transport:up' do
|
24
|
+
Sfdc.log "[COMETD UP]"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Sfdc
|
2
|
+
class Client
|
3
|
+
module Verbs
|
4
|
+
|
5
|
+
# Internal: Define methods to handle a verb.
|
6
|
+
#
|
7
|
+
# verbs - A list of verbs to define methods for.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# define_verbs :get, :post
|
12
|
+
#
|
13
|
+
# Returns nil.
|
14
|
+
def define_verbs(*verbs)
|
15
|
+
verbs.each do |verb|
|
16
|
+
define_verb(verb)
|
17
|
+
define_api_verb(verb)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Internal: Defines a method to handle HTTP requests with the passed in
|
22
|
+
# verb.
|
23
|
+
#
|
24
|
+
# verb - Symbol name of the verb (e.g. :get).
|
25
|
+
#
|
26
|
+
# Examples
|
27
|
+
#
|
28
|
+
# define_verb :get
|
29
|
+
# # => get '/services/data/v24.0/sobjects'
|
30
|
+
#
|
31
|
+
# Returns nil.
|
32
|
+
def define_verb(verb)
|
33
|
+
define_method verb do |*args, &block|
|
34
|
+
retries = @options[:authentication_retries]
|
35
|
+
begin
|
36
|
+
connection.send(verb, *args, &block)
|
37
|
+
rescue Sfdc::UnauthorizedError
|
38
|
+
if retries > 0
|
39
|
+
retries -= 1
|
40
|
+
connection.url_prefix = @options[:instance_url]
|
41
|
+
retry
|
42
|
+
end
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Internal: Defines a method to handle HTTP requests with the passed in
|
49
|
+
# verb to a salesforce api endpoint.
|
50
|
+
#
|
51
|
+
# verb - Symbol name of the verb (e.g. :get).
|
52
|
+
#
|
53
|
+
# Examples
|
54
|
+
#
|
55
|
+
# define_api_verb :get
|
56
|
+
# # => api_get 'sobjects'
|
57
|
+
#
|
58
|
+
# Returns nil.
|
59
|
+
def define_api_verb(verb)
|
60
|
+
define_method :"api_#{verb}" do |*args, &block|
|
61
|
+
args[0] = api_path(args[0])
|
62
|
+
send(verb, *args, &block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Sfdc
|
2
|
+
class Collection
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
# Given a hash and client, will create an Enumerator that will lazily
|
6
|
+
# request Salesforce for the next page of results.
|
7
|
+
def initialize(hash, client)
|
8
|
+
@client = client
|
9
|
+
@raw_page = hash
|
10
|
+
end
|
11
|
+
|
12
|
+
# Yield each value on each page.
|
13
|
+
def each
|
14
|
+
@raw_page['records'].each { |record| yield Sfdc::Mash.build(record, @client) }
|
15
|
+
|
16
|
+
next_page.each { |record| yield record } if has_next_page?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return the size of the Collection without making any additional requests.
|
20
|
+
def size
|
21
|
+
@raw_page['totalSize']
|
22
|
+
end
|
23
|
+
alias_method :length, :size
|
24
|
+
|
25
|
+
# Return the current and all of the following pages.
|
26
|
+
def pages
|
27
|
+
[self] + (has_next_page? ? next_page.pages : [])
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns true if there is a pointer to the next page.
|
31
|
+
def has_next_page?
|
32
|
+
!@raw_page['nextRecordsUrl'].nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the next page as a Sfdc::Collection if it's available, nil otherwise.
|
36
|
+
def next_page
|
37
|
+
@next_page ||= @client.get(@raw_page['nextRecordsUrl']).body if has_next_page?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/sfdc/config.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Sfdc
|
4
|
+
class << self
|
5
|
+
attr_writer :log
|
6
|
+
|
7
|
+
# Returns the current Configuration
|
8
|
+
#
|
9
|
+
# Example
|
10
|
+
#
|
11
|
+
# Sfdc.configuration.username = "username"
|
12
|
+
# Sfdc.configuration.password = "password"
|
13
|
+
def configuration
|
14
|
+
@configuration ||= Configuration.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# Yields the Configuration
|
18
|
+
#
|
19
|
+
# Example
|
20
|
+
#
|
21
|
+
# Sfdc.configure do |config|
|
22
|
+
# config.username = "username"
|
23
|
+
# config.password = "password"
|
24
|
+
# end
|
25
|
+
def configure
|
26
|
+
yield configuration
|
27
|
+
end
|
28
|
+
|
29
|
+
def log?
|
30
|
+
@log ||= false
|
31
|
+
end
|
32
|
+
|
33
|
+
def log(message)
|
34
|
+
return unless Sfdc.log?
|
35
|
+
Sfdc.configuration.logger.send :debug, message
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Configuration
|
40
|
+
class Option
|
41
|
+
attr_reader :configuration, :name, :options
|
42
|
+
|
43
|
+
def self.define(*args)
|
44
|
+
new(*args).define
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(configuration, name, options = {})
|
48
|
+
@configuration, @name, @options = configuration, name, options
|
49
|
+
@default = options.fetch(:default, nil)
|
50
|
+
end
|
51
|
+
|
52
|
+
def define
|
53
|
+
write_attribute
|
54
|
+
define_method if default_provided?
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
attr_reader :default
|
60
|
+
alias_method :default_provided?, :default
|
61
|
+
|
62
|
+
def write_attribute
|
63
|
+
configuration.send :attr_accessor, name
|
64
|
+
end
|
65
|
+
|
66
|
+
def define_method
|
67
|
+
our_default = default
|
68
|
+
our_name = name
|
69
|
+
configuration.send :define_method, our_name do
|
70
|
+
instance_variable_get(:"@#{our_name}") ||
|
71
|
+
instance_variable_set(:"@#{our_name}", our_default.respond_to?(:call) ? our_default.call : our_default)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class << self
|
77
|
+
attr_accessor :options
|
78
|
+
|
79
|
+
def option(*args)
|
80
|
+
option = Option.define(self, *args)
|
81
|
+
(self.options ||= []) << option.name
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
option :api_version, :default => '27.0'
|
86
|
+
|
87
|
+
# The username to use during login.
|
88
|
+
option :username, :default => lambda { ENV['SALESFORCE_USERNAME'] }
|
89
|
+
|
90
|
+
# The password to use during login.
|
91
|
+
option :password, :default => lambda { ENV['SALESFORCE_PASSWORD'] }
|
92
|
+
|
93
|
+
# The security token to use during login.
|
94
|
+
option :security_token, :default => lambda { ENV['SALESFORCE_SECURITY_TOKEN'] }
|
95
|
+
|
96
|
+
# The OAuth client id
|
97
|
+
option :client_id, :default => lambda { ENV['SALESFORCE_CLIENT_ID'] }
|
98
|
+
|
99
|
+
# The OAuth client secret
|
100
|
+
option :client_secret, :default => lambda { ENV['SALESFORCE_CLIENT_SECRET'] }
|
101
|
+
|
102
|
+
# Set this to true if you're authenticating with a Sandbox instance.
|
103
|
+
# Defaults to false.
|
104
|
+
option :host, :default => 'login.salesforce.com'
|
105
|
+
|
106
|
+
option :oauth_token
|
107
|
+
option :refresh_token
|
108
|
+
option :instance_url
|
109
|
+
|
110
|
+
# Set this to an object that responds to read, write and fetch and all GET
|
111
|
+
# requests will be cached.
|
112
|
+
option :cache
|
113
|
+
|
114
|
+
# The number of times reauthentication should be tried before failing.
|
115
|
+
option :authentication_retries, :default => 3
|
116
|
+
|
117
|
+
# Set to true if you want responses from Salesforce to be gzip compressed.
|
118
|
+
option :compress
|
119
|
+
|
120
|
+
# Faraday request read/open timeout.
|
121
|
+
option :timeout
|
122
|
+
|
123
|
+
# Faraday adapter to use. Defaults to Faraday.default_adapter.
|
124
|
+
option :adapter, :default => lambda { Faraday.default_adapter }
|
125
|
+
|
126
|
+
option :proxy_uri, :default => lambda { ENV['PROXY_URI'] }
|
127
|
+
|
128
|
+
def logger
|
129
|
+
@logger ||= ::Logger.new STDOUT
|
130
|
+
end
|
131
|
+
|
132
|
+
def options
|
133
|
+
self.class.options
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|