class Databasedotcom::Client

Interface for operating the Force.com REST API

Attributes

ca_file[RW]

The CA file configured for this instance, if any

client_id[RW]

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

client_secret[RW]

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

debugging[RW]

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

host[RW]

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

instance_url[RW]

The base URL to the authenticated user’s SalesForce instance

oauth_token[RW]

The OAuth access token in use by the client

org_id[R]

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

password[RW]

The SalesForce password

refresh_token[RW]

The OAuth refresh token in use by the client

sobject_module[RW]

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

user_id[R]

The SalesForce user id of the authenticated user

username[RW]

The SalesForce username

verify_mode[RW]

The SSL verify mode configured for this instance, if any

version[RW]

The API version the client is using. Defaults to 23.0

Public Class Methods

new(options = {}) click to toggle source

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
  • A Hash containing the following keys:

    client_id
    client_secret
    host
    debugging
    version
    sobject_module
    ca_file
    verify_mode
    

If the environment variables DATABASEDOTCOM_CLIENT_ID, DATABASEDOTCOM_CLIENT_SECRET, DATABASEDOTCOM_HOST, DATABASEDOTCOM_DEBUGGING, DATABASEDOTCOM_VERSION, DATABASEDOTCOM_SOBJECT_MODULE, DATABASEDOTCOM_CA_FILE, and/or DATABASEDOTCOM_VERIFY_MODE are present, they override any other values provided

# File lib/databasedotcom/client.rb, line 64
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
end

Public Instance Methods

authenticate(options = nil) click to toggle source

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

# File lib/databasedotcom/client.rb, line 103
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) click to toggle source

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">
# File lib/databasedotcom/client.rb, line 231
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) click to toggle source

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")
# File lib/databasedotcom/client.rb, line 266
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) click to toggle source

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

# File lib/databasedotcom/client.rb, line 180
def describe_sobject(class_name)
  result = http_get("/services/data/v#{self.version}/sobjects/#{class_name}/describe")
  JSON.parse(result.body)
end
describe_sobjects() click to toggle source

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

# File lib/databasedotcom/client.rb, line 174
def describe_sobjects
  result = http_get("/services/data/v#{self.version}/sobjects")
  JSON.parse(result.body)["sobjects"]
end
find(class_or_classname, record_id) click to toggle source

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", ...>
# File lib/databasedotcom/client.rb, line 189
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={}) click to toggle source

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.

# File lib/databasedotcom/client.rb, line 297
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={}) click to toggle source

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.

# File lib/databasedotcom/client.rb, line 287
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={}) click to toggle source

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.

# File lib/databasedotcom/client.rb, line 325
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={}) click to toggle source

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.

# File lib/databasedotcom/client.rb, line 315
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={}) click to toggle source

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.

# File lib/databasedotcom/client.rb, line 306
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() click to toggle source

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

# File lib/databasedotcom/client.rb, line 141
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) click to toggle source

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.

# File lib/databasedotcom/client.rb, line 156
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) click to toggle source

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

# File lib/databasedotcom/client.rb, line 217
def next_page(path)
  result = http_get(path)
  collection_from(result.body)
end
previous_page(path) click to toggle source

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

# File lib/databasedotcom/client.rb, line 223
def previous_page(path)
  result = http_get(path)
  collection_from(result.body)
end
query(soql_expr) click to toggle source

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", ...> ...]
# File lib/databasedotcom/client.rb, line 203
def query(soql_expr)
  result = http_get("/services/data/v#{self.version}/query", :q => soql_expr)
  collection_from(result.body)
end
recent() click to toggle source

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

# File lib/databasedotcom/client.rb, line 272
def recent
  result = http_get("/services/data/v#{self.version}/recent")
  collection_from(result.body)
end
update(class_or_classname, record_id, new_attrs) click to toggle source

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"})
# File lib/databasedotcom/client.rb, line 247
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) click to toggle source

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"})
# File lib/databasedotcom/client.rb, line 257
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

Private Instance Methods

coerced_json(attrs, clazz) click to toggle source
# File lib/databasedotcom/client.rb, line 493
def coerced_json(attrs, clazz)
  if attrs.is_a?(Hash)
    coerced_attrs = {}
    attrs.keys.each do |key|
      case clazz.field_type(key.to_s)
        when "multipicklist"
          coerced_attrs[key] = (attrs[key] || []).join(';')
        when "datetime"
          begin
            attrs[key] = DateTime.parse(attrs[key]) if attrs[key].is_a?(String)
            coerced_attrs[key] = attrs[key].strftime(RUBY_VERSION.match(/^1.8/) ? "%Y-%m-%dT%H:%M:%S.000%z" : "%Y-%m-%dT%H:%M:%S.%L%z")
          rescue
            nil
          end
        when "date"
          if attrs[key]
            coerced_attrs[key] = attrs[key].respond_to?(:strftime) ? attrs[key].strftime("%Y-%m-%d") : attrs[key]
          else
            coerced_attrs[key] = nil
          end
        else
          coerced_attrs[key] = attrs[key]
      end
    end
    coerced_attrs.to_json
  else
    attrs
  end
end
collection_from(response) click to toggle source
# File lib/databasedotcom/client.rb, line 431
def collection_from(response)
  response = JSON.parse(response)
  collection_from_hash( response )
end
collection_from_hash(data) click to toggle source
# File lib/databasedotcom/client.rb, line 463
def collection_from_hash(data)
  array_response = data.is_a?(Array)
  if array_response
    records = data.collect { |rec| self.find(rec["attributes"]["type"], rec["Id"]) }
  else
    records = data["records"].collect do |record|
      record_from_hash( record )
    end
  end

  Databasedotcom::Collection.new(self, array_response ? records.length : data["totalSize"], array_response ? nil : data["nextRecordsUrl"]).concat(records)
end
const_defined_in_module(mod, const) click to toggle source
# File lib/databasedotcom/client.rb, line 548
def const_defined_in_module(mod, const)
  mod.method(:const_defined?).arity == 1 ? mod.const_defined?(const) : mod.const_defined?(const, false)
end
encode_parameters(parameters={}) click to toggle source
# File lib/databasedotcom/client.rb, line 394
def encode_parameters(parameters={})
  (parameters || {}).collect { |k, v| "#{uri_escape(k)}=#{uri_escape(v)}" }.join('&')
end
encode_path_with_params(path, parameters={}) click to toggle source
# File lib/databasedotcom/client.rb, line 390
def encode_path_with_params(path, parameters={})
  [URI.escape(path), encode_parameters(parameters)].reject{|el| el.empty?}.join('?')
end
ensure_expected_response(expected_result_class) { || ... } click to toggle source
# File lib/databasedotcom/client.rb, line 348
def ensure_expected_response(expected_result_class)
  response = yield

  unless response.is_a?(expected_result_class || Net::HTTPSuccess)
    if response.is_a?(Net::HTTPUnauthorized)
      if self.refresh_token
        response = with_encoded_path_and_checked_response("/services/oauth2/token", { :grant_type => "refresh_token", :refresh_token => self.refresh_token, :client_id => self.client_id, :client_secret => self.client_secret}, :host => self.host) do |encoded_path|
          response = https_request(self.host).post(encoded_path, nil)
          if response.is_a?(Net::HTTPOK)
            parse_auth_response(response.body)
          end
          response
        end
      elsif self.username && self.password
        response = with_encoded_path_and_checked_response("/services/oauth2/token", { :grant_type => "password", :username => self.username, :password => self.password, :client_id => self.client_id, :client_secret => self.client_secret}, :host => self.host) do |encoded_path|
          response = https_request(self.host).post(encoded_path, nil)
          if response.is_a?(Net::HTTPOK)
            parse_auth_response(response.body)
          end
          response
        end
      end

      if response.is_a?(Net::HTTPSuccess)
        response = yield
      end
    end

    raise SalesForceError.new(response) unless response.is_a?(expected_result_class ||  Net::HTTPSuccess)
  end

  response
end
find_or_materialize(class_or_classname) click to toggle source
# File lib/databasedotcom/client.rb, line 411
def find_or_materialize(class_or_classname)
  if class_or_classname.is_a?(Class)
    clazz = class_or_classname
  else
    match = class_or_classname.match(/(?:(.+)::)?(\w+)$/)
    preceding_namespace = match[1]
    classname = match[2]
    raise ArgumentError if preceding_namespace && preceding_namespace != module_namespace.name
    clazz = module_namespace.const_get(classname.to_sym) rescue nil
    clazz ||= self.materialize(classname)
  end
  clazz
end
https_request(host=nil) click to toggle source
# File lib/databasedotcom/client.rb, line 382
def https_request(host=nil)
  Net::HTTP.new(host || URI.parse(self.instance_url).host, 443).tap do |http|
    http.use_ssl = true
    http.ca_file = self.ca_file if self.ca_file
    http.verify_mode = self.verify_mode if self.verify_mode
  end
end
key_from_label(label) click to toggle source
# File lib/databasedotcom/client.rb, line 523
def key_from_label(label)
  label.gsub(' ', '_')
end
log_request(path, options={}) click to toggle source
# File lib/databasedotcom/client.rb, line 398
def log_request(path, options={})
  base_url = options[:host] ? "https://#{options[:host]}" : self.instance_url
  puts "***** REQUEST: #{path.include?(':') ? path : URI.join(base_url, path)}#{options[:data] ? " => #{options[:data]}" : ''}" if self.debugging
end
log_response(result) click to toggle source
# File lib/databasedotcom/client.rb, line 407
def log_response(result)
  puts "***** RESPONSE: #{result.class.name} -> #{result.body}" if self.debugging
end
module_namespace() click to toggle source
# File lib/databasedotcom/client.rb, line 425
def module_namespace
  _module = self.sobject_module
  _module = _module.constantize if _module.is_a? String
  _module || Object
end
parse_auth_response(body) click to toggle source
# File lib/databasedotcom/client.rb, line 537
def parse_auth_response(body)
  json = JSON.parse(body)
  parse_user_id_and_org_id_from_identity_url(json["id"])
  self.instance_url = json["instance_url"]
  self.oauth_token = json["access_token"]
end
parse_user_id_and_org_id_from_identity_url(identity_url) click to toggle source
# File lib/databasedotcom/client.rb, line 531
def parse_user_id_and_org_id_from_identity_url(identity_url)
  m = identity_url.match(/\/id\/([^\/]+)\/([^\/]+)$/)
  @org_id = m[1] rescue nil
  @user_id = m[2] rescue nil
end
query_org_id() click to toggle source
# File lib/databasedotcom/client.rb, line 544
def query_org_id
  query("select id from Organization")[0]["Id"]
end
record_from_hash(data) click to toggle source

Converts a Hash of object data into a concrete SObject

# File lib/databasedotcom/client.rb, line 437
def record_from_hash(data)
  attributes = data.delete('attributes')
  new_record = find_or_materialize(attributes["type"]).new
  data.each do |name, value|
    field = new_record.description['fields'].find do |field|
      key_from_label(field["label"]) == name || field["name"] == name || field["relationshipName"] == name
    end

    # Field not found
    if field == nil
      break
    end

    # If reference/lookup field data was fetched, recursively build the child record and apply
    if value.is_a?(Hash) and field['type'] == 'reference' and field["relationshipName"]
      relation = record_from_hash( value )
      set_value( new_record, field["relationshipName"], relation, 'reference' )

    # Apply the raw value for all other field types
    else
      set_value(new_record, field["name"], value, field["type"]) if field
    end
  end
  new_record
end
set_value(record, attr, value, attr_type) click to toggle source
# File lib/databasedotcom/client.rb, line 476
def set_value(record, attr, value, attr_type)
  value_to_set = value

  case attr_type
    when "datetime"
      value_to_set = DateTime.parse(value) rescue nil

    when "date"
      value_to_set = Date.parse(value) rescue nil

    when "multipicklist"
      value_to_set = value.split(";") rescue []
  end

  record.send("#{attr}=", value_to_set)
end
uri_escape(str) click to toggle source
# File lib/databasedotcom/client.rb, line 403
def uri_escape(str)
  URI.escape(str.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
end
user_and_pass?(options) click to toggle source
# File lib/databasedotcom/client.rb, line 527
def user_and_pass?(options)
  (self.username && self.password) || (options && options[:username] && options[:password])
end
with_encoded_path_and_checked_response(path, parameters, options = {}) { |encoded_path| ... } click to toggle source
# File lib/databasedotcom/client.rb, line 333
def with_encoded_path_and_checked_response(path, parameters, options = {})
  ensure_expected_response(options[:expected_result_class]) do
    with_logging(encode_path_with_params(path, parameters), options) do |encoded_path|
      yield(encoded_path)
    end
  end
end
with_logging(encoded_path, options) { |encoded_path| ... } click to toggle source
# File lib/databasedotcom/client.rb, line 341
def with_logging(encoded_path, options)
  log_request(encoded_path, options)
  response = yield encoded_path
  log_response(response)
  response
end