Class: Databasedotcom::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/databasedotcom/client.rb

Overview

Interface for operating the Force.com REST API

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Client

Returns a new client object. options can be one of the following

  • A String containing the name of a YAML file formatted like:

    client_id: <your_salesforce_client_id> client_secret: <your_salesforce_client_secret> host: login.salesforce.com debugging: true version: 23.0 sobject_module: My::Module ca_file: some/ca/file.cert verify_mode: OpenSSL::SSL::VERIFY_PEER proxy: http://web-proxy.example.com proxy_username: someuser proxy_password: password
  • A Hash containing the following keys: client_id client_secret host debugging version sobject_module ca_file verify_mode proxy proxy_username proxy_password If the environment variables DATABASEDOTCOM_CLIENT_ID, DATABASEDOTCOM_CLIENT_SECRET, DATABASEDOTCOM_HOST, DATABASEDOTCOM_DEBUGGING, DATABASEDOTCOM_VERSION, DATABASEDOTCOM_SOBJECT_MODULE, DATABASEDOTCOM_CA_FILE, DATABASEDOTCOM_VERIFY_MODE and/or HTTP_PROXY are present, they override any other values provided


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/databasedotcom/client.rb', line 76

def initialize(options = {})
  if options.is_a?(String)
    @options = YAML.load_file(options)
    @options["verify_mode"] = @options["verify_mode"].constantize if @options["verify_mode"] && @options["verify_mode"].is_a?(String)
  else
    @options = options
  end
  @options.symbolize_keys!

  if ENV['DATABASE_COM_URL']
    url = URI.parse(ENV['DATABASE_COM_URL'])
    url_options = Hash[url.query.split("&").map{|q| q.split("=")}].symbolize_keys!
    self.host = url.host
    self.client_id = url_options[:oauth_key]
    self.client_secret = url_options[:oauth_secret]
    self.username = url_options[:user]
    self.password = url_options[:password]
  else
    self.client_id = ENV['DATABASEDOTCOM_CLIENT_ID'] || @options[:client_id]
    self.client_secret = ENV['DATABASEDOTCOM_CLIENT_SECRET'] || @options[:client_secret]
    self.host = ENV['DATABASEDOTCOM_HOST'] || @options[:host] || "login.salesforce.com"
  end

  self.debugging = ENV['DATABASEDOTCOM_DEBUGGING'] || @options[:debugging]
  self.version = ENV['DATABASEDOTCOM_VERSION'] || @options[:version]
  self.version = self.version.to_s if self.version
  self.sobject_module = ENV['DATABASEDOTCOM_SOBJECT_MODULE'] || @options[:sobject_module]
  self.ca_file = ENV['DATABASEDOTCOM_CA_FILE'] || @options[:ca_file]
  self.verify_mode = ENV['DATABASEDOTCOM_VERIFY_MODE'] || @options[:verify_mode]
  self.verify_mode = self.verify_mode.to_i if self.verify_mode
  self.proxy = ENV['HTTP_PROXY'] || @options[:proxy]
  self.proxy_username = @options[:proxy_username]
  self.proxy_password = @options[:proxy_password]
end

Instance Attribute Details

#ca_file ⇒ Object

The CA file configured for this instance, if any



36
37
38
# File 'lib/databasedotcom/client.rb', line 36

def ca_file
  @ca_file
end

#client_id ⇒ Object

The client id (aka "Consumer Key") to use for OAuth2 authentication



10
11
12
# File 'lib/databasedotcom/client.rb', line 10

def client_id
  @client_id
end

#client_secret ⇒ Object

The client secret (aka "Consumer Secret" to use for OAuth2 authentication)



12
13
14
# File 'lib/databasedotcom/client.rb', line 12

def client_secret
  @client_secret
end

#debugging ⇒ Object

If true, print API debugging information to stdout. Defaults to false.



20
21
22
# File 'lib/databasedotcom/client.rb', line 20

def debugging
  @debugging
end

#host ⇒ Object

The host to use for OAuth2 authentication. Defaults to login.salesforce.com



22
23
24
# File 'lib/databasedotcom/client.rb', line 22

def host
  @host
end

#instance_url ⇒ Object

The base URL to the authenticated user's SalesForce instance



18
19
20
# File 'lib/databasedotcom/client.rb', line 18

def instance_url
  @instance_url
end

#oauth_token ⇒ Object

The OAuth access token in use by the client



14
15
16
# File 'lib/databasedotcom/client.rb', line 14

def oauth_token
  @oauth_token
end

#org_id ⇒ Object (readonly)

The SalesForce organization id for the authenticated user's Salesforce instance



34
35
36
# File 'lib/databasedotcom/client.rb', line 34

def org_id
  @org_id
end

#password ⇒ Object

The SalesForce password



32
33
34
# File 'lib/databasedotcom/client.rb', line 32

def password
  @password
end

#proxy ⇒ Object

The HTTP proxy host to connect through, if any



40
41
42
# File 'lib/databasedotcom/client.rb', line 40

def proxy
  @proxy
end

#proxy_password ⇒ Object

The password to use when connecting through an HTTP proxy, if any



44
45
46
# File 'lib/databasedotcom/client.rb', line 44

def proxy_password
  @proxy_password
end

#proxy_username ⇒ Object

The username to use when connecting through an HTTP proxy, if any



42
43
44
# File 'lib/databasedotcom/client.rb', line 42

def proxy_username
  @proxy_username
end

#refresh_token ⇒ Object

The OAuth refresh token in use by the client



16
17
18
# File 'lib/databasedotcom/client.rb', line 16

def refresh_token
  @refresh_token
end

#sobject_module ⇒ Object

A Module in which to materialize Sobject classes. Defaults to the global module (Object)



26
27
28
# File 'lib/databasedotcom/client.rb', line 26

def sobject_module
  @sobject_module
end

#user_id ⇒ Object (readonly)

The SalesForce user id of the authenticated user



28
29
30
# File 'lib/databasedotcom/client.rb', line 28

def user_id
  @user_id
end

#username ⇒ Object

The SalesForce username



30
31
32
# File 'lib/databasedotcom/client.rb', line 30

def username
  @username
end

#verify_mode ⇒ Object

The SSL verify mode configured for this instance, if any



38
39
40
# File 'lib/databasedotcom/client.rb', line 38

def verify_mode
  @verify_mode
end

#version ⇒ Object

The API version the client is using. Defaults to 23.0



24
25
26
# File 'lib/databasedotcom/client.rb', line 24

def version
  @version
end

Instance Method Details

#authenticate(options = nil) ⇒ Object

Authenticate to the Force.com API. options is a Hash, interpreted as follows:

  • If options contains the keys :username and :password, those credentials are used to authenticate. In this case, the value of :password may need to include a concatenated security token, if required by your Salesforce org
  • If options contains the key :provider, it is assumed to be the hash returned by Omniauth from a successful web-based OAuth2 authentication
  • If options contains the keys :token and :instance_url, those are assumed to be a valid OAuth2 token and instance URL for a Salesforce account, obtained from an external source. options may also optionally contain the key :refresh_token

Raises SalesForceError if an error occurs



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/databasedotcom/client.rb', line 118

def authenticate(options = nil)
  if user_and_pass?(options)
    req = https_request(self.host)
    user = self.username || options[:username]
    pass = self.password || options[:password]
    path = encode_path_with_params('/services/oauth2/token', :grant_type => 'password', :client_id => self.client_id, :client_secret => self.client_secret, :username => user, :password => pass)
    log_request("https://#{self.host}/#{path}")
    result = req.post(path, "")
    log_response(result)
    raise SalesForceError.new(result) unless result.is_a?(Net::HTTPOK)
    self.username = user
    self.password = pass
    parse_auth_response(result.body)
  elsif options.is_a?(Hash)
    if options.has_key?("provider")
      parse_user_id_and_org_id_from_identity_url(options["uid"])
      self.instance_url = options["credentials"]["instance_url"]
      self.oauth_token = options["credentials"]["token"]
      self.refresh_token = options["credentials"]["refresh_token"]
    else
      raise ArgumentError unless options.has_key?(:token) && options.has_key?(:instance_url)
      self.instance_url = options[:instance_url]
      self.oauth_token = options[:token]
      self.refresh_token = options[:refresh_token]
    end
  end

  self.version = "22.0" unless self.version

  self.oauth_token
end

#create(class_or_classname, object_attrs) ⇒ Object

Returns a new instance of class_or_classname (which can be passed in as either a String or a Class) with the specified attributes.

client.create("Car", {"Color" => "Blue", "Year" => "2011"}) #=> #<Car @Id="recordid", @Color="Blue", @Year="2011">


246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/databasedotcom/client.rb', line 246

def create(class_or_classname, object_attrs)
  class_or_classname = find_or_materialize(class_or_classname)
  json_for_assignment = coerced_json(object_attrs, class_or_classname)
  result = http_post("/services/data/v#{self.version}/sobjects/#{class_or_classname.sobject_name}", json_for_assignment)
  new_object = class_or_classname.new
  JSON.parse(json_for_assignment).each do |property, value|
    set_value(new_object, property, value, class_or_classname.type_map[property][:type])
  end
  id = JSON.parse(result.body)["id"]
  set_value(new_object, "Id", id, "id")
  new_object
end

#delete(class_or_classname, record_id) ⇒ Object

Deletes the record of type class_or_classname with id of record_id. class_or_classname can be a String or a Class.

client.delete(Car, "rid")


281
282
283
284
# File 'lib/databasedotcom/client.rb', line 281

def delete(class_or_classname, record_id)
  clazz = find_or_materialize(class_or_classname)
  http_delete("/services/data/v#{self.version}/sobjects/#{clazz.sobject_name}/#{record_id}")
end

#describe_sobject(class_name) ⇒ Object

Returns a description of the Sobject specified by class_name. The description includes all fields and their properties for the Sobject.



195
196
197
198
# File 'lib/databasedotcom/client.rb', line 195

def describe_sobject(class_name)
  result = http_get("/services/data/v#{self.version}/sobjects/#{class_name}/describe")
  JSON.parse(result.body)
end

#describe_sobjects ⇒ Object

Returns an Array of Hashes listing the properties for every type of Sobject in the database. Raises SalesForceError if an error occurs.



189
190
191
192
# File 'lib/databasedotcom/client.rb', line 189

def describe_sobjects
  result = http_get("/services/data/v#{self.version}/sobjects")
  JSON.parse(result.body)["sobjects"]
end

#find(class_or_classname, record_id) ⇒ Object

Returns an instance of the Sobject specified by class_or_classname (which can be either a String or a Class) populated with the values of the Force.com record specified by record_id. If given a Class that is not defined, it will attempt to materialize the class on demand.

client.find(Account, "recordid") #=> #<Account @Id="recordid", ...>


204
205
206
207
208
209
210
211
212
213
# File 'lib/databasedotcom/client.rb', line 204

def find(class_or_classname, record_id)
  class_or_classname = find_or_materialize(class_or_classname)
  result = http_get("/services/data/v#{self.version}/sobjects/#{class_or_classname.sobject_name}/#{record_id}")
  response = JSON.parse(result.body)
  new_record = class_or_classname.new
  class_or_classname.description["fields"].each do |field|
    set_value(new_record, field["name"], response[key_from_label(field["label"])] || response[field["name"]], field["type"])
  end
  new_record
end

#http_delete(path, parameters = {}, headers = {}) ⇒ Object

Performs an HTTP DELETE request to the specified path (relative to self.instance_url). Query parameters are included from parameters. The required Authorization header is automatically included, as are any additional headers specified in headers. Returns the HTTPResult if it is of type HTTPSuccess- raises SalesForceError otherwise.



312
313
314
315
316
# File 'lib/databasedotcom/client.rb', line 312

def http_delete(path, parameters={}, headers={})
  with_encoded_path_and_checked_response(path, parameters, {:expected_result_class => Net::HTTPNoContent}) do |encoded_path|
    https_request.delete(encoded_path, {"Authorization" => "OAuth #{self.oauth_token}"}.merge(headers))
  end
end

#http_get(path, parameters = {}, headers = {}) ⇒ Object

Performs an HTTP GET request to the specified path (relative to self.instance_url). Query parameters are included from parameters. The required Authorization header is automatically included, as are any additional headers specified in headers. Returns the HTTPResult if it is of type HTTPSuccess- raises SalesForceError otherwise.



302
303
304
305
306
# File 'lib/databasedotcom/client.rb', line 302

def http_get(path, parameters={}, headers={})
  with_encoded_path_and_checked_response(path, parameters) do |encoded_path|
    https_request.get(encoded_path, {"Authorization" => "OAuth #{self.oauth_token}"}.merge(headers))
  end
end

#http_multipart_post(path, parts, parameters = {}, headers = {}) ⇒ Object

Performs an HTTP POST request to the specified path (relative to self.instance_url), using Content-Type multiplart/form-data. The parts of the body of the request are taken from parts_. Query parameters are included from parameters. The required Authorization header is automatically included, as are any additional headers specified in headers. Returns the HTTPResult if it is of type HTTPSuccess- raises SalesForceError otherwise.



340
341
342
343
344
# File 'lib/databasedotcom/client.rb', line 340

def http_multipart_post(path, parts, parameters={}, headers={})
  with_encoded_path_and_checked_response(path, parameters) do |encoded_path|
    https_request.request(Net::HTTP::Post::Multipart.new(encoded_path, parts, {"Authorization" => "OAuth #{self.oauth_token}"}.merge(headers)))
  end
end

#http_patch(path, data = nil, parameters = {}, headers = {}) ⇒ Object

Performs an HTTP PATCH request to the specified path (relative to self.instance_url). The body of the request is taken from data. Query parameters are included from parameters. The required Authorization header is automatically included, as are any additional headers specified in headers. Returns the HTTPResult if it is of type HTTPSuccess- raises SalesForceError otherwise.



330
331
332
333
334
# File 'lib/databasedotcom/client.rb', line 330

def http_patch(path, data=nil, parameters={}, headers={})
  with_encoded_path_and_checked_response(path, parameters, {:data => data}) do |encoded_path|
    https_request.send_request("PATCH", encoded_path, data, {"Content-Type" => data ? "application/json" : "text/plain", "Authorization" => "OAuth #{self.oauth_token}"}.merge(headers))
  end
end

#http_post(path, data = nil, parameters = {}, headers = {}) ⇒ Object

Performs an HTTP POST request to the specified path (relative to self.instance_url). The body of the request is taken from data. Query parameters are included from parameters. The required Authorization header is automatically included, as are any additional headers specified in headers. Returns the HTTPResult if it is of type HTTPSuccess- raises SalesForceError otherwise.



321
322
323
324
325
# File 'lib/databasedotcom/client.rb', line 321

def http_post(path, data=nil, parameters={}, headers={})
  with_encoded_path_and_checked_response(path, parameters, {:data => data}) do |encoded_path|
    https_request.post(encoded_path, data, {"Content-Type" => data ? "application/json" : "text/plain", "Authorization" => "OAuth #{self.oauth_token}"}.merge(headers))
  end
end

#list_sobjects ⇒ Object

Returns an Array of Strings listing the class names for every type of Sobject in the database. Raises SalesForceError if an error occurs.



156
157
158
159
160
161
162
163
# File 'lib/databasedotcom/client.rb', line 156

def list_sobjects
  result = http_get("/services/data/v#{self.version}/sobjects")
  if result.is_a?(Net::HTTPOK)
    JSON.parse(result.body)["sobjects"].collect { |sobject| sobject["name"] }
  elsif result.is_a?(Net::HTTPBadRequest)
    raise SalesForceError.new(result)
  end
end

#materialize(classnames) ⇒ Object

Dynamically defines classes for Force.com class names. classnames can be a single String or an Array of Strings. Returns the class or Array of classes defined.

client.materialize("Contact") #=> Contact
client.materialize(%w(Contact Company)) #=> [Contact, Company]

The classes defined by materialize derive from Sobject, and have getters and setters defined for all the attributes defined by the associated Force.com Sobject.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/databasedotcom/client.rb', line 171

def materialize(classnames)
  classes = (classnames.is_a?(Array) ? classnames : [classnames]).collect do |clazz|
    original_classname = clazz
    clazz = original_classname[0,1].capitalize + original_classname[1..-1]
    unless const_defined_in_module(module_namespace, clazz)
      new_class = module_namespace.const_set(clazz, Class.new(Databasedotcom::Sobject::Sobject))
      new_class.client = self
      new_class.materialize(original_classname)
      new_class
    else
      module_namespace.const_get(clazz)
    end
  end

  classes.length == 1 ? classes.first : classes
end

#next_page(path) ⇒ Object

Used by Collection objects. Returns a Collection of Sobjects from the specified URL path that represents the next page of paginated results.



232
233
234
235
# File 'lib/databasedotcom/client.rb', line 232

def next_page(path)
  result = http_get(path)
  collection_from(result.body)
end

#previous_page(path) ⇒ Object

Used by Collection objects. Returns a Collection of Sobjects from the specified URL path that represents the previous page of paginated results.



238
239
240
241
# File 'lib/databasedotcom/client.rb', line 238

def previous_page(path)
  result = http_get(path)
  collection_from(result.body)
end

#query(soql_expr) ⇒ Object

Returns a Collection of Sobjects of the class specified in the soql_expr, which is a valid SOQL expression. The objects will only be populated with the values of attributes specified in the query.

client.query("SELECT Name FROM Account") #=> [#<Account @Id=nil, @Name="Foo", ...>, #<Account @Id=nil, @Name="Bar", ...> ...]


218
219
220
221
# File 'lib/databasedotcom/client.rb', line 218

def query(soql_expr)
  result = http_get("/services/data/v#{self.version}/query", :q => soql_expr)
  collection_from(result.body)
end

#recent ⇒ Object

Returns a Collection of recently touched items. The Collection contains Sobject instances that are fully populated with their correct values.



287
288
289
290
# File 'lib/databasedotcom/client.rb', line 287

def recent
  result = http_get("/services/data/v#{self.version}/recent")
  collection_from(result.body)
end

#search(sosl_expr) ⇒ Object

Returns a Collection of Sobject instances form the results of the SOSL search.

client.search("FIND {bar}") #=> [#<Account @Name="foobar", ...>, #<Account @Name="barfoo", ...> ...]


226
227
228
229
# File 'lib/databasedotcom/client.rb', line 226

def search(sosl_expr)
  result = http_get("/services/data/v#{self.version}/search", :q => sosl_expr)
  collection_from(result.body)
end

Returns an array of trending topic names.



293
294
295
296
297
# File 'lib/databasedotcom/client.rb', line 293

def trending_topics
  result = http_get("/services/data/v#{self.version}/chatter/topics/trending")
  result = JSON.parse(result.body)
  result["topics"].collect { |topic| topic["name"] }
end

#update(class_or_classname, record_id, new_attrs) ⇒ Object

Updates the attributes of the record of type class_or_classname and specified by record_id with the values of new_attrs in the Force.com database. new_attrs is a hash of attribute => value

client.update("Car", "rid", {"Color" => "Red"})


262
263
264
265
266
# File 'lib/databasedotcom/client.rb', line 262

def update(class_or_classname, record_id, new_attrs)
  class_or_classname = find_or_materialize(class_or_classname)
  json_for_update = coerced_json(new_attrs, class_or_classname)
  http_patch("/services/data/v#{self.version}/sobjects/#{class_or_classname.sobject_name}/#{record_id}", json_for_update)
end

#upsert(class_or_classname, field, value, attrs) ⇒ Object

Attempts to find the record on Force.com of type class_or_classname with attribute field set as value. If found, it will update the record with the attrs hash. If not found, it will create a new record with attrs.

client.upsert(Car, "Color", "Blue", {"Year" => "2012"})


272
273
274
275
276
# File 'lib/databasedotcom/client.rb', line 272

def upsert(class_or_classname, field, value, attrs)
  clazz = find_or_materialize(class_or_classname)
  json_for_update = coerced_json(attrs, clazz)
  http_patch("/services/data/v#{self.version}/sobjects/#{clazz.sobject_name}/#{field}/#{value}", json_for_update)
end