Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Changes made for CloudSpokes Challenge #1751.

  • Loading branch information...
commit 0f2c03f107ba6348f19915423420cac0eeb2be47 1 parent 1c97c73
@rrojas-chariot authored
View
93 README-Changes.txt
@@ -0,0 +1,93 @@
+This readme file explains the changes made for the challenge 1751.
+
+Overview
+
+The current version of the databasedotcom ruby gem contains a bug that prevents it from
+updating records if you are using a Database.com org (it works fine with a Force.com org).
+We want you to fix this bug and submit a pull request to our repo.
+We've put together a small databasedotcom rails app that you can use for testing as well as a short video to
+help you get a Database.com org up and running quickly. The error occurs on line 44 of the accounts_controller
+when updating the record and throws Net::HTTPBadRequest.
+This is the stack trace of the error as well as some additional information buried at the end of this thread.
+
+
+Bug Research and Changes:
+As I was trying to figure out what was causing this error I came across this link:
+ http://www.salesforce.com/us/developer/docs/api_rest/index_Left.htm#StartTopic=Content/dome_sobject_create.htm?SearchType=Stem
+
+
+Basically, the approach is that instead of using the PATCH request directly, the request could be
+performed using POST requests. Well, I decided to try it out and that ended up fixing the problem.
+The idea is to append the http parameter '_HttpMethod=PATCH' to the URL of the POST request.
+
+What I did then was to add an attribute to the Databasedotcom::Client class (patch_using_post).
+This attribute would allow the developer using this code to switch between performing real PATCH request
+and performing PATCH requests using the POST method.
+
+I'm not completely sure that this is the way to fix this issue, but at least it solves it and it could
+help in other situation like those where proxy servers don't support PATCH requests.
+
+
+The only change to for the sample rails test application https://github.com/cloudspokes/databasedotcom-rails-test
+would be patch_using_post: true to the databasedotcom.yml file.
+Of course, installation of this new version of the GEM needs to be done first.
+
+
+Here is a quick list of the changes:
+====================================
+
+ lib/databasedotcom/client.rb:
+ This file has been changed to add the patch_using_post attribute and use it for biz logic in the
+ Databasedotcom::Client.http_patch method.
+
+ spec/fixtures/databasedotcom.yml:
+ Added the patch_using_post attribute for testing purposes.
+
+ spec/lib/client_spec.rb:
+ Added/Modified the corresponding tests for coverage of the addition of the patch_using_post attribute.
+ Note: The file diff.txt details the changes made.
+
+ Modified tests:
+ Databasedotcom::Client
+ configuration from environment variables
+ takes configuration information from the environment, if present
+ takes configuration information from a URL
+ from a yaml file
+ takes configuration information from the specified file
+ from a hash
+ takes configuration information from the hash
+ accepts symbols in the hash
+
+ Databasedotcom::Client precedence
+ prefers the environment configuration to the YAML configuration
+
+ Added tests:
+ Databasedotcom::Client defaults
+ defaults to using real patch requests
+
+ Databasedotcom::Client authentiation with a materialized class
+ #update
+ with proper fields performing patch request using post
+ with attributes from a hash
+ persists the updated changes with names as symbols
+ applies type coercions before serializing
+ applies type coercions with Dates represented as Strings
+
+ #upsert
+ with a valid external field performing patch request using post
+ with a non-existent external id
+ creates a new record with the external id and the specified attributes
+ with an existing external id
+ updates attributes in the existing object
+ with multiple choice
+ raises an Databasedotcom::SalesForceError
+
+ with an invalid external field performing patch request using post
+ raises an Databasedotcom::SalesForceError
+
+ #http_patch performing patch request using post
+ upserts the data to the specified path
+ puts parameters into the path
+ includes the headers in the request
+ raises SalesForceError
+
View
338 diff.txt
@@ -0,0 +1,338 @@
+diff --git a/lib/databasedotcom/client.rb b/lib/databasedotcom/client.rb
+index 428b80e..cf9fa83 100644
+--- a/lib/databasedotcom/client.rb
++++ b/lib/databasedotcom/client.rb
+@@ -36,6 +36,8 @@ module Databasedotcom
+ attr_accessor :ca_file
+ # The SSL verify mode configured for this instance, if any
+ attr_accessor :verify_mode
++ # The flag to use post to perform patch requests for this instance, if any
++ attr_accessor :patch_using_post
+
+ # Returns a new client object. _options_ can be one of the following
+ #
+@@ -49,6 +51,7 @@ module Databasedotcom
+ # sobject_module: My::Module
+ # ca_file: some/ca/file.cert
+ # verify_mode: OpenSSL::SSL::VERIFY_PEER
++ # patch_using_post: true
+ # * A Hash containing the following keys:
+ # client_id
+ # client_secret
+@@ -58,9 +61,11 @@ module Databasedotcom
+ # sobject_module
+ # ca_file
+ # verify_mode
++ # patch_using_post
+ # 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
++ # DATABASEDOTCOM_DEBUGGING, DATABASEDOTCOM_VERSION, DATABASEDOTCOM_SOBJECT_MODULE, DATABASEDOTCOM_CA_FILE,
++ # DATABASEDOTCOM_PATCH_USING_POST and/or DATABASEDOTCOM_VERIFY_MODE are present, they override any other
++ # values provided
+ def initialize(options = {})
+ if options.is_a?(String)
+ @options = YAML.load_file(options)
+@@ -90,6 +95,7 @@ module Databasedotcom
+ 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.patch_using_post = ENV['DATABASEDOTCOM_PATCH_USING_POST'] || @options[:patch_using_post]
+ self.verify_mode = self.verify_mode.to_i if self.verify_mode
+ end
+
+@@ -313,8 +319,13 @@ module Databasedotcom
+ # 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.
+ 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))
++ if (self.patch_using_post)
++ (parameters || {}).merge!({"_HttpMethod" => "PATCH"})
++ http_post(path, data, parameters, headers)
++ else
++ 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
+ end
+
+diff --git a/spec/fixtures/databasedotcom.yml b/spec/fixtures/databasedotcom.yml
+index 515423e..969d23e 100644
+--- a/spec/fixtures/databasedotcom.yml
++++ b/spec/fixtures/databasedotcom.yml
+@@ -5,4 +5,5 @@ debugging: true
+ host: bro.baz
+ version: 88
+ ca_file: other/ca/file.cert
+-verify_mode: OpenSSL::SSL::VERIFY_PEER
+\ No newline at end of file
++verify_mode: OpenSSL::SSL::VERIFY_PEER
++patch_using_post: true
+\ No newline at end of file
+diff --git a/spec/lib/client_spec.rb b/spec/lib/client_spec.rb
+index fd50c66..97a98d8 100644
+--- a/spec/lib/client_spec.rb
++++ b/spec/lib/client_spec.rb
+@@ -15,6 +15,7 @@ describe Databasedotcom::Client do
+ ENV['DATABASEDOTCOM_SOBJECT_MODULE'] = "Databasedotcom::Sobject"
+ ENV['DATABASEDOTCOM_CA_FILE'] = "ca/file.cert"
+ ENV['DATABASEDOTCOM_VERIFY_MODE'] = "1"
++ ENV['DATABASEDOTCOM_PATCH_USING_POST'] = "true"
+ @client = Databasedotcom::Client.new
+ end
+
+@@ -28,6 +29,7 @@ describe Databasedotcom::Client do
+ ENV.delete "DATABASE_COM_URL"
+ ENV.delete "DATABASEDOTCOM_CA_FILE"
+ ENV.delete "DATABASEDOTCOM_VERIFY_MODE"
++ ENV.delete "DATABASEDOTCOM_PATCH_USING_POST"
+ end
+
+ it "takes configuration information from the environment, if present" do
+@@ -39,6 +41,7 @@ describe Databasedotcom::Client do
+ @client.sobject_module.should == "Databasedotcom::Sobject"
+ @client.ca_file.should == "ca/file.cert"
+ @client.verify_mode.should == 1
++ @client.patch_using_post.should be_true
+ end
+
+ it "takes configuration information from a URL" do
+@@ -52,6 +55,7 @@ describe Databasedotcom::Client do
+ @client.sobject_module.should == "Databasedotcom::Sobject"
+ @client.ca_file.should == "ca/file.cert"
+ @client.verify_mode.should == 1
++ @client.patch_using_post.should be_true
+ end
+ end
+
+@@ -65,12 +69,13 @@ describe Databasedotcom::Client do
+ client.version.should == '88'
+ client.ca_file.should == "other/ca/file.cert"
+ client.verify_mode.should == 1
++ client.patch_using_post.should be_true
+ end
+ end
+
+ context "from a hash" do
+ it "takes configuration information from the hash" do
+- client = Databasedotcom::Client.new("client_id" => "client_id", "client_secret" => "client_secret", "debugging" => true, "host" => "foo.baz", "version" => "77", "ca_file" => "alt/ca/file.cert", "verify_mode" => 3)
++ client = Databasedotcom::Client.new("client_id" => "client_id", "client_secret" => "client_secret", "debugging" => true, "host" => "foo.baz", "version" => "77", "ca_file" => "alt/ca/file.cert", "verify_mode" => 3, "patch_using_post" => true)
+ client.client_id.should == "client_id"
+ client.client_secret.should == "client_secret"
+ client.debugging.should be_true
+@@ -78,10 +83,11 @@ describe Databasedotcom::Client do
+ client.version.should == "77"
+ client.ca_file.should == "alt/ca/file.cert"
+ client.verify_mode.should == 3
++ client.patch_using_post.should be_true
+ end
+
+ it "accepts symbols in the hash" do
+- client = Databasedotcom::Client.new(:client_id => "client_id", :client_secret => "client_secret", :debugging => true, :host => "foo.baz", :version => "77", :ca_file => "alt/ca/file.cert", :verify_mode => 3)
++ client = Databasedotcom::Client.new(:client_id => "client_id", :client_secret => "client_secret", :debugging => true, :host => "foo.baz", :version => "77", :ca_file => "alt/ca/file.cert", :verify_mode => 3, :patch_using_post => true)
+ client.client_id.should == "client_id"
+ client.client_secret.should == "client_secret"
+ client.debugging.should be_true
+@@ -89,6 +95,7 @@ describe Databasedotcom::Client do
+ client.version.should == "77"
+ client.ca_file.should == "alt/ca/file.cert"
+ client.verify_mode.should == 3
++ client.patch_using_post.should be_true
+ end
+ end
+
+@@ -104,6 +111,10 @@ describe Databasedotcom::Client do
+ it "defaults to no debugging output" do
+ @client.debugging.should be_false
+ end
++
++ it "defaults to using real patch requests" do
++ @client.patch_using_post.should be_false
++ end
+
+ it "defaults to no special ca file" do
+ @client.ca_file.should be_nil
+@@ -121,6 +132,7 @@ describe Databasedotcom::Client do
+ ENV['DATABASEDOTCOM_DEBUGGING'] = "foo"
+ ENV['DATABASEDOTCOM_HOST'] = "foo.bar"
+ ENV['DATABASEDOTCOM_VERSION'] = '99'
++ ENV['DATABASEDOTCOM_PATCH_USING_POST'] = "foo"
+ end
+
+ after do
+@@ -129,6 +141,7 @@ describe Databasedotcom::Client do
+ ENV.delete 'DATABASEDOTCOM_DEBUGGING'
+ ENV.delete 'DATABASEDOTCOM_HOST'
+ ENV.delete 'DATABASEDOTCOM_VERSION'
++ ENV.delete 'DATABASEDOTCOM_PATCH_USING_POST'
+ end
+
+ it "prefers the environment configuration to the YAML configuration" do
+@@ -138,6 +151,7 @@ describe Databasedotcom::Client do
+ client.debugging.should == "foo"
+ client.host.should == "foo.bar"
+ client.version.should == '99'
++ client.patch_using_post.should == "foo"
+ end
+ end
+ end
+@@ -860,6 +874,9 @@ describe Databasedotcom::Client do
+
+ describe "#update" do
+ context "with proper fields" do
++ before do
++ @client.patch_using_post = false
++ end
+ context "with attributes from a JSON" do
+ it "persists the updated changes" do
+ stub_request(:patch, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid").to_return(:body => nil, :status => 204)
+@@ -894,9 +911,48 @@ describe Databasedotcom::Client do
+ end
+ end
+ end
++
++ context "with proper fields performing patch request using post" do
++ context "with attributes from a JSON" do
++ it "persists the updated changes" do
++ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
++ @client.update("Whizbang", "rid", "{\"Name\":\"update\"}")
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH")
++ end
++ end
++
++ context "with attributes from a hash" do
++ it "persists the updated changes" do
++ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
++ @client.update("Whizbang", "rid", {"Name" => "update"})
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH")
++ end
++
++ it "persists the updated changes with names as symbols" do
++ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
++ @client.update("Whizbang", "rid", {:Name => "update"})
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH")
++ end
++
++ it "applies type coercions before serializing" do
++ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
++ @client.update("Whizbang", "rid", "Date_Field" => Date.civil(2011, 1, 1), "DateTime_Field" => DateTime.civil(2011, 2, 1, 12), "Picklist_Multiselect_Field" => %w(a b))
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").with(:body => {"Date_Field" => "2011-01-01", "DateTime_Field" => "2011-02-01T12:00:00.000+0000", "Picklist_Multiselect_Field" => "a;b"})
++ end
++
++ it "applies type coercions with Dates represented as Strings" do
++ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
++ @client.update("Whizbang", "rid", "Date_Field" => Date.civil(2011, 1, 1).to_s, "DateTime_Field" => DateTime.civil(2011, 2, 1, 12).to_s, "Picklist_Multiselect_Field" => %w(a b))
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").with(:body => {"Date_Field" => "2011-01-01", "DateTime_Field" => "2011-02-01T12:00:00.000+0000", "Picklist_Multiselect_Field" => "a;b"})
++ end
++ end
++ end
+
+ context "with improper fields" do
+ before do
++ @client.patch_using_post = false
++ end
++ before do
+ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/write_error_response.json"))
+ stub_request(:patch, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid").to_return(:body => @response_body, :status => 400)
+ end
+@@ -911,6 +967,9 @@ describe Databasedotcom::Client do
+
+ describe "#upsert" do
+ context "with a valid external field" do
++ before do
++ @client.patch_using_post = false
++ end
+ context "with a non-existent external id" do
+ it "creates a new record with the external id and the specified attributes" do
+ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_created_success_response.json"))
+@@ -942,9 +1001,43 @@ describe Databasedotcom::Client do
+ end
+ end
+ end
++
++ context "with a valid external field performing patch request using post" do
++ context "with a non-existent external id" do
++ it "creates a new record with the external id and the specified attributes" do
++ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_created_success_response.json"))
++ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/somename?_HttpMethod=PATCH").to_return(:body => @response_body, :status => 201)
++ @client.upsert("Whizbang", "Name", "somename", "Name" => "newname")
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/somename?_HttpMethod=PATCH").with(:body => %|{"Name":"newname"}|)
++ end
++ end
++
++ context "with an existing external id" do
++ it "updates attributes in the existing object" do
++ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_updated_success_response.json"))
++ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/somename?_HttpMethod=PATCH").to_return(:body => @response_body, :status => 201)
++ @client.upsert("Whizbang", "Name", "somename", "Name" => "newname")
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/somename?_HttpMethod=PATCH").with(:body => %|{"Name":"newname"}|)
++ end
++ end
++
++ context "with multiple choice" do
++ before do
++ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_multiple_error_response.json"))
++ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/ambiguous?_HttpMethod=PATCH").to_return(:body => @response_body, :status => 300)
++ end
++
++ it "raises an Databasedotcom::SalesForceError" do
++ lambda {
++ @client.upsert("Whizbang", "Name", "ambiguous", "Name" => "newname")
++ }.should raise_error(Databasedotcom::SalesForceError)
++ end
++ end
++ end
+
+ context "with an invalid external field" do
+ before do
++ @client.patch_using_post = false
+ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_error_response.json"))
+ stub_request(:patch, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Namez/fakename").to_return(:body => @response_body, :status => 404)
+ end
+@@ -1160,7 +1253,11 @@ describe Databasedotcom::Client do
+ it_should_behave_like "a request that can refresh the oauth token", :post, "multipart_post", "https://na1.salesforce.com/my/path", 201
+ end
+
+- describe "#http_patch" do
++ describe "#http_patch using patch request" do
++ before do
++ @client.patch_using_post = false
++ end
++
+ it "upserts the data to the specified path" do
+ stub_request(:patch, "https://na1.salesforce.com/my/path").to_return(:body => "", :status => 201)
+ @client.http_patch("/my/path", "data")
+@@ -1186,5 +1283,32 @@ describe Databasedotcom::Client do
+ }.should raise_error(Databasedotcom::SalesForceError)
+ end
+ end
++ describe "#http_patch performing patch request using post" do
++
++ it "upserts the data to the specified path" do
++ stub_request(:post, "https://na1.salesforce.com/my/path?_HttpMethod=PATCH").to_return(:body => "", :status => 201)
++ @client.http_patch("/my/path", "data")
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/my/path?_HttpMethod=PATCH").with(:data => "data")
++ end
++
++ it "puts parameters into the path" do
++ stub_request(:post, "https://na1.salesforce.com/my/path?_HttpMethod=PATCH&foo=bar&bro=baz%20bap").to_return(:body => "", :status => 201)
++ @client.http_patch("/my/path", "data", :foo => "bar", "bro" => "baz bap")
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/my/path?_HttpMethod=PATCH&foo=bar&bro=baz%20bap")
++ end
++
++ it "includes the headers in the request" do
++ stub_request(:post, "https://na1.salesforce.com/my/path").to_return(:body => "", :status => 201)
++ @client.http_patch("/my/path", "data", nil, {"Something" => "Header"})
++ WebMock.should have_requested(:post, "https://na1.salesforce.com/my/path").with(:headers => {"Something" => "Header"})
++ end
++
++ it "raises SalesForceError" do
++ stub_request(:post, "https://na1.salesforce.com/my/path").to_return(:body => "", :status => 400)
++ lambda {
++ @client.http_patch("/my/path", "data", nil, {"Something" => "Header"})
++ }.should raise_error(Databasedotcom::SalesForceError)
++ end
++ end
+ end
+ end
View
19 lib/databasedotcom/client.rb
@@ -36,6 +36,8 @@ class Client
attr_accessor :ca_file
# The SSL verify mode configured for this instance, if any
attr_accessor :verify_mode
+ # The flag to use post to perform patch requests for this instance, if any
+ attr_accessor :patch_using_post
# Returns a new client object. _options_ can be one of the following
#
@@ -49,6 +51,7 @@ class Client
# sobject_module: My::Module
# ca_file: some/ca/file.cert
# verify_mode: OpenSSL::SSL::VERIFY_PEER
+ # patch_using_post: true
# * A Hash containing the following keys:
# client_id
# client_secret
@@ -58,9 +61,11 @@ class Client
# sobject_module
# ca_file
# verify_mode
+ # patch_using_post
# 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
+ # DATABASEDOTCOM_DEBUGGING, DATABASEDOTCOM_VERSION, DATABASEDOTCOM_SOBJECT_MODULE, DATABASEDOTCOM_CA_FILE,
+ # DATABASEDOTCOM_PATCH_USING_POST and/or DATABASEDOTCOM_VERIFY_MODE are present, they override any other
+ # values provided
def initialize(options = {})
if options.is_a?(String)
@options = YAML.load_file(options)
@@ -90,6 +95,7 @@ def initialize(options = {})
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.patch_using_post = ENV['DATABASEDOTCOM_PATCH_USING_POST'] || @options[:patch_using_post]
self.verify_mode = self.verify_mode.to_i if self.verify_mode
end
@@ -313,8 +319,13 @@ def http_post(path, data=nil, parameters={}, headers={})
# 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.
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))
+ if (self.patch_using_post)
+ (parameters || {}).merge!({"_HttpMethod" => "PATCH"})
+ http_post(path, data, parameters, headers)
+ else
+ 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
end
View
2  lib/databasedotcom/version.rb
@@ -1,3 +1,3 @@
module Databasedotcom
- VERSION = "1.3.1"
+ VERSION = "1.3.2"
end
View
3  spec/fixtures/databasedotcom.yml
@@ -5,4 +5,5 @@ debugging: true
host: bro.baz
version: 88
ca_file: other/ca/file.cert
-verify_mode: OpenSSL::SSL::VERIFY_PEER
+verify_mode: OpenSSL::SSL::VERIFY_PEER
+patch_using_post: true
View
143 spec/lib/client_spec.rb
@@ -15,6 +15,7 @@
ENV['DATABASEDOTCOM_SOBJECT_MODULE'] = "Databasedotcom::Sobject"
ENV['DATABASEDOTCOM_CA_FILE'] = "ca/file.cert"
ENV['DATABASEDOTCOM_VERIFY_MODE'] = "1"
+ ENV['DATABASEDOTCOM_PATCH_USING_POST'] = "true"
@client = Databasedotcom::Client.new
end
@@ -28,6 +29,7 @@
ENV.delete "DATABASE_COM_URL"
ENV.delete "DATABASEDOTCOM_CA_FILE"
ENV.delete "DATABASEDOTCOM_VERIFY_MODE"
+ ENV.delete "DATABASEDOTCOM_PATCH_USING_POST"
end
it "takes configuration information from the environment, if present" do
@@ -39,6 +41,7 @@
@client.sobject_module.should == "Databasedotcom::Sobject"
@client.ca_file.should == "ca/file.cert"
@client.verify_mode.should == 1
+ @client.patch_using_post.should be_true
end
it "takes configuration information from a URL" do
@@ -52,6 +55,7 @@
@client.sobject_module.should == "Databasedotcom::Sobject"
@client.ca_file.should == "ca/file.cert"
@client.verify_mode.should == 1
+ @client.patch_using_post.should be_true
end
end
@@ -65,12 +69,13 @@
client.version.should == '88'
client.ca_file.should == "other/ca/file.cert"
client.verify_mode.should == 1
+ client.patch_using_post.should be_true
end
end
context "from a hash" do
it "takes configuration information from the hash" do
- client = Databasedotcom::Client.new("client_id" => "client_id", "client_secret" => "client_secret", "debugging" => true, "host" => "foo.baz", "version" => "77", "ca_file" => "alt/ca/file.cert", "verify_mode" => 3)
+ client = Databasedotcom::Client.new("client_id" => "client_id", "client_secret" => "client_secret", "debugging" => true, "host" => "foo.baz", "version" => "77", "ca_file" => "alt/ca/file.cert", "verify_mode" => 3, "patch_using_post" => true)
client.client_id.should == "client_id"
client.client_secret.should == "client_secret"
client.debugging.should be_true
@@ -78,10 +83,11 @@
client.version.should == "77"
client.ca_file.should == "alt/ca/file.cert"
client.verify_mode.should == 3
+ client.patch_using_post.should be_true
end
it "accepts symbols in the hash" do
- client = Databasedotcom::Client.new(:client_id => "client_id", :client_secret => "client_secret", :debugging => true, :host => "foo.baz", :version => "77", :ca_file => "alt/ca/file.cert", :verify_mode => 3)
+ client = Databasedotcom::Client.new(:client_id => "client_id", :client_secret => "client_secret", :debugging => true, :host => "foo.baz", :version => "77", :ca_file => "alt/ca/file.cert", :verify_mode => 3, :patch_using_post => true)
client.client_id.should == "client_id"
client.client_secret.should == "client_secret"
client.debugging.should be_true
@@ -89,6 +95,7 @@
client.version.should == "77"
client.ca_file.should == "alt/ca/file.cert"
client.verify_mode.should == 3
+ client.patch_using_post.should be_true
end
end
@@ -104,6 +111,10 @@
it "defaults to no debugging output" do
@client.debugging.should be_false
end
+
+ it "defaults to using real patch requests" do
+ @client.patch_using_post.should be_false
+ end
it "defaults to no special ca file" do
@client.ca_file.should be_nil
@@ -121,6 +132,7 @@
ENV['DATABASEDOTCOM_DEBUGGING'] = "foo"
ENV['DATABASEDOTCOM_HOST'] = "foo.bar"
ENV['DATABASEDOTCOM_VERSION'] = '99'
+ ENV['DATABASEDOTCOM_PATCH_USING_POST'] = "foo"
end
after do
@@ -129,6 +141,7 @@
ENV.delete 'DATABASEDOTCOM_DEBUGGING'
ENV.delete 'DATABASEDOTCOM_HOST'
ENV.delete 'DATABASEDOTCOM_VERSION'
+ ENV.delete 'DATABASEDOTCOM_PATCH_USING_POST'
end
it "prefers the environment configuration to the YAML configuration" do
@@ -138,6 +151,7 @@
client.debugging.should == "foo"
client.host.should == "foo.bar"
client.version.should == '99'
+ client.patch_using_post.should == "foo"
end
end
end
@@ -860,6 +874,9 @@ class Something;
describe "#update" do
context "with proper fields" do
+ before do
+ @client.patch_using_post = false
+ end
context "with attributes from a JSON" do
it "persists the updated changes" do
stub_request(:patch, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid").to_return(:body => nil, :status => 204)
@@ -894,9 +911,49 @@ class Something;
end
end
end
+
+ context "with proper fields performing patch request using post" do
+ context "with attributes from a JSON" do
+ it "persists the updated changes" do
+ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
+ @client.update("Whizbang", "rid", "{\"Name\":\"update\"}")
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH")
+ end
+ end
+
+ context "with attributes from a hash" do
+ it "persists the updated changes" do
+ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
+ @client.update("Whizbang", "rid", {"Name" => "update"})
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH")
+ end
+
+ it "persists the updated changes with names as symbols" do
+ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
+ @client.update("Whizbang", "rid", {:Name => "update"})
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH")
+ end
+
+ it "applies type coercions before serializing" do
+ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
+ @client.update("Whizbang", "rid", "Date_Field" => Date.civil(2011, 1, 1), "DateTime_Field" => DateTime.civil(2011, 2, 1, 12), "Picklist_Multiselect_Field" => %w(a b))
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").with(:body => {"Date_Field" => "2011-01-01", "DateTime_Field" => "2011-02-01T12:00:00.000+0000", "Picklist_Multiselect_Field" => "a;b"})
+ end
+
+ it "applies type coercions with Dates represented as Strings" do
+ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").to_return(:body => nil, :status => 204)
+ @client.update("Whizbang", "rid", "Date_Field" => Date.civil(2011, 1, 1).to_s, "DateTime_Field" => DateTime.civil(2011, 2, 1, 12).to_s, "Picklist_Multiselect_Field" => %w(a b))
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid?_HttpMethod=PATCH").with(:body => {"Date_Field" => "2011-01-01", "DateTime_Field" => "2011-02-01T12:00:00.000+0000", "Picklist_Multiselect_Field" => "a;b"})
+ end
+ end
+ end
+
context "with improper fields" do
before do
+ @client.patch_using_post = false
+ end
+ before do
@response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/write_error_response.json"))
stub_request(:patch, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/rid").to_return(:body => @response_body, :status => 400)
end
@@ -911,6 +968,9 @@ class Something;
describe "#upsert" do
context "with a valid external field" do
+ before do
+ @client.patch_using_post = false
+ end
context "with a non-existent external id" do
it "creates a new record with the external id and the specified attributes" do
@response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_created_success_response.json"))
@@ -942,9 +1002,43 @@ class Something;
end
end
end
+
+ context "with a valid external field performing patch request using post" do
+ context "with a non-existent external id" do
+ it "creates a new record with the external id and the specified attributes" do
+ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_created_success_response.json"))
+ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/somename?_HttpMethod=PATCH").to_return(:body => @response_body, :status => 201)
+ @client.upsert("Whizbang", "Name", "somename", "Name" => "newname")
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/somename?_HttpMethod=PATCH").with(:body => %|{"Name":"newname"}|)
+ end
+ end
+
+ context "with an existing external id" do
+ it "updates attributes in the existing object" do
+ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_updated_success_response.json"))
+ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/somename?_HttpMethod=PATCH").to_return(:body => @response_body, :status => 201)
+ @client.upsert("Whizbang", "Name", "somename", "Name" => "newname")
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/somename?_HttpMethod=PATCH").with(:body => %|{"Name":"newname"}|)
+ end
+ end
+
+ context "with multiple choice" do
+ before do
+ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_multiple_error_response.json"))
+ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Name/ambiguous?_HttpMethod=PATCH").to_return(:body => @response_body, :status => 300)
+ end
+
+ it "raises an Databasedotcom::SalesForceError" do
+ lambda {
+ @client.upsert("Whizbang", "Name", "ambiguous", "Name" => "newname")
+ }.should raise_error(Databasedotcom::SalesForceError)
+ end
+ end
+ end
context "with an invalid external field" do
before do
+ @client.patch_using_post = false
@response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_error_response.json"))
stub_request(:patch, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Namez/fakename").to_return(:body => @response_body, :status => 404)
end
@@ -955,6 +1049,18 @@ class Something;
}.should raise_error(Databasedotcom::SalesForceError)
end
end
+ context "with an invalid external field performing patch request using post" do
+ before do
+ @response_body = File.read(File.join(File.dirname(__FILE__), "../fixtures/sobject/upsert_error_response.json"))
+ stub_request(:post, "https://na1.salesforce.com/services/data/v23.0/sobjects/Whizbang/Namez/fakename?_HttpMethod=PATCH").to_return(:body => @response_body, :status => 404)
+ end
+
+ it "raises an Databasedotcom::SalesForceError" do
+ lambda {
+ @client.upsert("Whizbang", "Namez", "fakename", "{\"Name\":\"update\"}")
+ }.should raise_error(Databasedotcom::SalesForceError)
+ end
+ end
end
describe "#delete" do
@@ -1160,7 +1266,11 @@ class Something;
it_should_behave_like "a request that can refresh the oauth token", :post, "multipart_post", "https://na1.salesforce.com/my/path", 201
end
- describe "#http_patch" do
+ describe "#http_patch using patch request" do
+ before do
+ @client.patch_using_post = false
+ end
+
it "upserts the data to the specified path" do
stub_request(:patch, "https://na1.salesforce.com/my/path").to_return(:body => "", :status => 201)
@client.http_patch("/my/path", "data")
@@ -1186,5 +1296,32 @@ class Something;
}.should raise_error(Databasedotcom::SalesForceError)
end
end
+ describe "#http_patch performing patch request using post" do
+
+ it "upserts the data to the specified path" do
+ stub_request(:post, "https://na1.salesforce.com/my/path?_HttpMethod=PATCH").to_return(:body => "", :status => 201)
+ @client.http_patch("/my/path", "data")
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/my/path?_HttpMethod=PATCH").with(:data => "data")
+ end
+
+ it "puts parameters into the path" do
+ stub_request(:post, "https://na1.salesforce.com/my/path?_HttpMethod=PATCH&foo=bar&bro=baz%20bap").to_return(:body => "", :status => 201)
+ @client.http_patch("/my/path", "data", :foo => "bar", "bro" => "baz bap")
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/my/path?_HttpMethod=PATCH&foo=bar&bro=baz%20bap")
+ end
+
+ it "includes the headers in the request" do
+ stub_request(:post, "https://na1.salesforce.com/my/path").to_return(:body => "", :status => 201)
+ @client.http_patch("/my/path", "data", nil, {"Something" => "Header"})
+ WebMock.should have_requested(:post, "https://na1.salesforce.com/my/path").with(:headers => {"Something" => "Header"})
+ end
+
+ it "raises SalesForceError" do
+ stub_request(:post, "https://na1.salesforce.com/my/path").to_return(:body => "", :status => 400)
+ lambda {
+ @client.http_patch("/my/path", "data", nil, {"Something" => "Header"})
+ }.should raise_error(Databasedotcom::SalesForceError)
+ end
+ end
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.