Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions lib/basecrm/services/contacts_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,28 @@ def update(contact)
Contact.new(root[:data])
end

# Upsert a contact
#
# post '/contacts/upsert?filter_param=filter_value
#
# Create a new contact or update an existing, based on a value of a filter or a set of filters.
# At least a single filter - query parameter - is required. If no parameters are present, the request will return an error.
# See full docs https://developers.getbase.com/docs/rest/reference/contacts
#
# @param filters [Hash] - hash contain filters, one level deep e.g. { name: 'string', 'custom_fields[field]': 'value' }
# @param contact [Contact, Hash] - This object's attributes describe the object to be updated or created
# @return [Contact] The resulting object representing updated or created resource.
def upsert(filters, contact)
validate_upsert_filters!(filters)
validate_type!(contact)

attributes = sanitize(contact)
query_string = URI.encode_www_form(filters)
_, _, root = @client.post("/contacts/upsert?#{query_string}", attributes)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 If the intention of upsert is to be idempotent, then you should use a PUT.

The difference between PUT and POST is that PUT is idempotent: calling it once or several times successively has the same effect (that is no side effect), where successive identical POST may have additional effects, like passing an order several times.

MDN Docs

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That may be so, but according to the docs it Zendesk Sell's case its a POST https://developers.getbase.com/docs/rest/reference/deals


Contact.new(root[:data])
end


# Delete a contact
#
Expand All @@ -125,6 +147,11 @@ def validate_type!(contact)
raise TypeError unless contact.is_a?(Contact) || contact.is_a?(Hash)
end

def validate_upsert_filters!(filters)
raise TypeError unless filters.is_a?(Hash)
raise ArgumentError, "at least one filter is required" if filters.empty?
end

def extract_params!(contact, *args)
params = contact.to_h.select{ |k, _| args.include?(k) }
raise ArgumentError, "one of required attributes is missing. Expected: #{args.join(',')}" if params.count != args.length
Expand Down
27 changes: 27 additions & 0 deletions lib/basecrm/services/deals_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ def update(deal)
Deal.new(root[:data])
end

# Upsert a deal
#
# post '/deals/upsert?filter_param=filter_value
#
# Create a new deal or update an existing, based on a value of a filter or a set of filters.
# At least a single filter - query parameter - is required. If no parameters are present, the request will return an error.
# See full docs https://developers.getbase.com/docs/rest/reference/deals
#
# @param filters [Hash] - hash contain filters, one level deep e.g. { name: 'string', 'custom_fields[field]': 'value' }
# @param deal [Deal, Hash] - This object's attributes describe the object to be updated or created
# @return [Deal] The resulting object representing updated or created resource.
def upsert(filters, deal)
validate_upsert_filters!(filters)
validate_type!(deal)

attributes = sanitize(deal)
query_string = URI.encode_www_form(filters)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was trying to find a way on encoding the hash without using Rails only methods (like Object.to_query) or a new gem like Addressable, this seems to do the trick, but other opinions welcome

_, _, root = @client.post("/deals/upsert?#{query_string}", attributes)

Deal.new(root[:data])
end


# Delete a deal
#
Expand All @@ -127,6 +149,11 @@ def validate_type!(deal)
raise TypeError unless deal.is_a?(Deal) || deal.is_a?(Hash)
end

def validate_upsert_filters!(filters)
raise TypeError unless filters.is_a?(Hash)
raise ArgumentError, "at least one filter is required" if filters.empty?
end

def extract_params!(deal, *args)
params = deal.to_h.select{ |k, _| args.include?(k) }
raise ArgumentError, "one of required attributes is missing. Expected: #{args.join(',')}" if params.count != args.length
Expand Down
17 changes: 17 additions & 0 deletions spec/services/contacts_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
it { should respond_to :destroy }
it { should respond_to :find }
it { should respond_to :update }
it { should respond_to :upsert }
it { should respond_to :where }
end

Expand Down Expand Up @@ -48,6 +49,22 @@
end
end

describe :upsert do
it 'raises a TypeError if filters is nil' do
expect(client.contacts.upsert(nil, { name: 'unique_name' }).to raise_error(TypeError)
end

it 'raises an ArgumentError if filters is empty' do
expect(client.contacts.upsert({}, { name: 'unique_name' }).to raise_error(ArgumentError)
end

it 'calls the upsert route with encoded filters' do
filters = { last_name: 'unique_name', 'custom_fields[external_id]': 'unique-1' }
contact = create(:contact, last_name: 'unique_name', custom_fields: { external_id: 'unique-1'})
expect(client.contacts.upsert(filters, contact)).to be_instance_of BaseCRM::Contact
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 This test seems to cover the "update" path, but we are missing the "insert" path.
If the endpoint does an upsert both scenarios should be tested

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, waiting on some guidance on the test suite in general here #83 (comment) before i improve this but i agree

end

describe :destroy do
it "returns true on success" do
@contact = create(:contact)
Expand Down
17 changes: 17 additions & 0 deletions spec/services/deals_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
it { should respond_to :destroy }
it { should respond_to :find }
it { should respond_to :update }
it { should respond_to :upsert }
it { should respond_to :where }

end
Expand Down Expand Up @@ -59,6 +60,22 @@
end
end

describe :upsert do
it 'raises a TypeError if filters is nil' do
expect(client.deals.upsert(nil, { name: 'unique_name' }).to raise_error(TypeError)
end

it 'raises an ArgumentError if filters is empty' do
expect(client.deals.upsert({}, { name: 'unique_name' }).to raise_error(ArgumentError)
end

it 'calls the upsert route with encoded filters' do
filters = { name: 'unique_name', 'custom_fields[external_id]': 'unique-1' }
deal = create(:deal, name: 'unique_name', custom_fields: { external_id: 'unique-1'})
expect(client.deals.upsert(filters, deal)).to be_instance_of BaseCRM::Deal
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above

end

describe :destroy do
it "returns true on success" do
@deal = create(:deal)
Expand Down