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