From 62927260821c92086a502bb94446928d59074fcd Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Fri, 8 Dec 2023 17:37:56 +0000 Subject: [PATCH 1/6] Allow customer_id to be specified in identify URL (based on work by jrbeck) --- README.md | 24 ++++++++++++++++++++- lib/customerio/client.rb | 12 +++++++++++ spec/client_spec.rb | 46 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bcfd5a2..d41b443 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,29 @@ $customerio.identify( ) ``` -### Updating customers +### Updating customers: Using other IDs + +If you wish to specify a customer ID that is different than the one used in the `id` attribute, you can do so by using the `identify_customer_id` method: + +```ruby +# Arguments +# customer_id (required) - the customer ID to use for this customer, may be an id, email address, or the cio_id. +# This will be used to construct the URL but not sent in the body attributes. +# attributes (required) - a hash of information about the customer. You can pass any +# information that would be useful in your triggers. You +# must at least pass in an id, email, and created_at timestamp. + +$customerio.identify_customer_id( + :customer_id => "bob@example.com", + :id => 5, + :email => "bob@example.com", + :created_at => customer.created_at.to_i, + :first_name => "Bob", + :plan => "basic" +) +``` + +### Updating customers: Changing identifiers You can use the identify operation to update customers. If you need to change the `id` or `email` identifiers for a customer, diff --git a/lib/customerio/client.rb b/lib/customerio/client.rb index e49a6cc..f3a5dcd 100644 --- a/lib/customerio/client.rb +++ b/lib/customerio/client.rb @@ -29,6 +29,10 @@ def identify(attributes) create_or_update(attributes) end + def identify_customer_id(customer_id: nil, **attributes) + create_or_update_customer_id(customer_id, **attributes) + end + def delete(customer_id) raise ParamError.new("customer_id must be a non-empty string") if is_empty?(customer_id) @client.request_and_verify_response(:delete, customer_path(customer_id)) @@ -161,6 +165,14 @@ def create_or_update(attributes = {}) if !is_empty?(attributes[:cio_id]) customer_id = "cio_" + attributes[:cio_id] end + create_or_update_customer_id(customer_id, attributes) + end + + def create_or_update_customer_id(customer_id, attributes = {}) + attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }] + if is_empty?(customer_id) + raise MissingIdAttributeError.new("Must provide a customer id") + end url = customer_path(customer_id) @client.request_and_verify_response(:put, url, attributes) end diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 41df3cd..b59f9a0 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -225,6 +225,50 @@ def json(data) end end + describe "#identify_customer_id" do + it "uses provided id rather than id" do + stub_request(:put, api_uri('/api/v1/customers/1234')). + with(body: json(id: "5")). + to_return(status: 200, body: "", headers: {}) + + client.identify_customer_id( + customer_id: "1234", + id: "5" + ) + end + + it "uses provided cio_id rather than id" do + stub_request(:put, api_uri('/api/v1/customers/cio_5')). + with(body: json(id: "5")). + to_return(status: 200, body: "", headers: {}) + + client.identify_customer_id( + customer_id: "cio_5", + id: "5" + ) + end + + it "uses provided email rather than id" do + stub_request(:put, api_uri('/api/v1/customers/customer@example.com')). + with(body: json(id: "5")). + to_return(status: 200, body: "", headers: {}) + + client.identify_customer_id( + customer_id: "customer@example.com", + id: "5" + ) + end + + it "requires a customer_id attribute" do + lambda { client.identify_customer_id() }.should raise_error(Customerio::Client::MissingIdAttributeError) + lambda { client.identify_customer_id(customer_id: "") }.should raise_error(Customerio::Client::MissingIdAttributeError) + # None of these are customer_id + lambda { client.identify_customer_id(id: "5") }.should raise_error(Customerio::Client::MissingIdAttributeError) + lambda { client.identify_customer_id(cio_id: "cio_5") }.should raise_error(Customerio::Client::MissingIdAttributeError) + lambda { client.identify_customer_id(email: "customer@example.com") }.should raise_error(Customerio::Client::MissingIdAttributeError) + end + end + describe "#delete" do it "sends a DELETE request to the customer.io's event API" do stub_request(:delete, api_uri('/api/v1/customers/5')). @@ -650,7 +694,7 @@ def json(data) }.to raise_error(Customerio::Client::ParamError, 'timestamp must be a valid timestamp') end end - + describe "#merge_customers" do before(:each) do @client = Customerio::Client.new("SITE_ID", "API_KEY", :json => true) From 97050af89e1c4acce7c90d4d56efc792bcf50e7c Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Fri, 8 Dec 2023 17:45:20 +0000 Subject: [PATCH 2/6] Bump for release 5.3.0 --- CHANGELOG.markdown | 4 ++++ lib/customerio/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index aef6fcc..75a270e 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,3 +1,7 @@ +## Customerio 5.3.0 - December 8, 2023 +### Added +- The `identify_customer_id` method has been added. This allows the customer ID to be specified separately from the attributes. This allows a person to be updated by identifying them by e.g.: their email address. Thanks to trwalzer, jrbeck and jeremyw for these changes. + ## Customerio 5.2.0 - December 8, 2023 ### Changed - The `identify` method will now automatically use the `cio_id` attribute as the customer ID when calling the track service. This allows a customer to be updated using `identify` to modify the `id` and `email` attributes. diff --git a/lib/customerio/version.rb b/lib/customerio/version.rb index 82b8bbb..ec93c97 100644 --- a/lib/customerio/version.rb +++ b/lib/customerio/version.rb @@ -1,3 +1,3 @@ module Customerio - VERSION = "5.2.0" + VERSION = "5.3.0" end From 6362c8004ac0b69b8f45cdb295f82eb40d90d470 Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Fri, 8 Dec 2023 18:15:36 +0000 Subject: [PATCH 3/6] Re-order the code docs and clarify usage of cio_id --- README.md | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index d41b443..0c526f1 100644 --- a/README.md +++ b/README.md @@ -95,9 +95,33 @@ $customerio.identify( ) ``` -### Updating customers: Using other IDs +### Updating customers: Changing identifiers + +You can use the identify operation to update customers. +If you need to change the `id` or `email` identifiers for a customer, +you will need to pass in the `cio_id` identifier. +`cio_id` is a unique identifier set by Customer.io, used to reference a person, +and cannot be changed. -If you wish to specify a customer ID that is different than the one used in the `id` attribute, you can do so by using the `identify_customer_id` method: +E.g.: if the customer created in the identify operation above was given the `cio_id` of `"f000000d"`, you could change its ID and email address using: + +```ruby +$customerio.identify( + :cio_id => "f000000d", + :id => 1005, + :email => "bob.fullname@example.com" +) +``` + +This method requires either the `id` or `cio_id` for the person. It does not work with email addresses. + +You can also use this method to make other updates to the person using the `cio_id`. + +### Updating customers: Using email address + +If you need to identify a person using their email address, then you can do so +by using the `identify_customer_id` method. This allows you to specify +a customer ID that is different than the one used in the `id` attribute. E.g.: ```ruby # Arguments @@ -117,23 +141,7 @@ $customerio.identify_customer_id( ) ``` -### Updating customers: Changing identifiers - -You can use the identify operation to update customers. -If you need to change the `id` or `email` identifiers for a customer, -you will need to pass in the `cio_id` identifier. -`cio_id` is a unique identifier set by Customer.io, used to reference a person, -and cannot be changed. - -E.g.: if the customer created in the identify operation above was given the `cio_id` of `"f000000d"`, you could change its ID and email address using: - -```ruby -$customerio.identify( - :cio_id => "f000000d", - :id => 1005, - :email => "bob.fullname@example.com" -) -``` +Note: If you want to use the `cio_id` in the `customer_id` field of `identify_customer_id`, you will need to prefix it with `"cio_"`. E.g.: `"cio_f000000d"` for a `cio_id` of `f000000d`. ### Deleting customers From a1f783203285d55859d2bd0f0f39e49b4b036e12 Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Fri, 8 Dec 2023 20:44:07 +0000 Subject: [PATCH 4/6] Move the support for :customer_id into the base identify method --- README.md | 15 +++++++-------- lib/customerio/client.rb | 29 +++++++++++++++++------------ spec/client_spec.rb | 18 ++++-------------- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 0c526f1..71ff845 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ You can also use this method to make other updates to the person using the `cio_ ### Updating customers: Using email address If you need to identify a person using their email address, then you can do so -by using the `identify_customer_id` method. This allows you to specify +by passing in a customer ID to the `identify` method. This allows you to specify a customer ID that is different than the one used in the `id` attribute. E.g.: ```ruby @@ -131,17 +131,16 @@ a customer ID that is different than the one used in the `id` attribute. E.g.: # information that would be useful in your triggers. You # must at least pass in an id, email, and created_at timestamp. -$customerio.identify_customer_id( +$customerio.identify( :customer_id => "bob@example.com", - :id => 5, - :email => "bob@example.com", - :created_at => customer.created_at.to_i, - :first_name => "Bob", - :plan => "basic" + :location => "Australia" ) ``` -Note: If you want to use the `cio_id` in the `customer_id` field of `identify_customer_id`, you will need to prefix it with `"cio_"`. E.g.: `"cio_f000000d"` for a `cio_id` of `f000000d`. +Note: + + * If you want to use the `cio_id` in the `customer_id` field of `identify_customer_id`, you will need to prefix it with `"cio_"`. E.g.: `"cio_f000000d"` for a `cio_id` of `f000000d`. + * The `identify` method can identify the person using one of `customer_id`, `cio_id` or `id`. The order of precedence is `customer_id` > `cio_id` > `id`. ### Deleting customers diff --git a/lib/customerio/client.rb b/lib/customerio/client.rb index f3a5dcd..41fb315 100644 --- a/lib/customerio/client.rb +++ b/lib/customerio/client.rb @@ -29,10 +29,6 @@ def identify(attributes) create_or_update(attributes) end - def identify_customer_id(customer_id: nil, **attributes) - create_or_update_customer_id(customer_id, **attributes) - end - def delete(customer_id) raise ParamError.new("customer_id must be a non-empty string") if is_empty?(customer_id) @client.request_and_verify_response(:delete, customer_path(customer_id)) @@ -155,24 +151,33 @@ def merge_customers_path def create_or_update(attributes = {}) attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }] - if is_empty?(attributes[:id]) && is_empty?(attributes[:cio_id]) + if is_empty?(attributes[:id]) && is_empty?(attributes[:cio_id]) && is_empty?(attributes[:customer_id]) raise MissingIdAttributeError.new("Must provide a customer id") end # Use cio_id as the identifier, if present, # to allow the id and email identifiers to be updated. + # The person is identified by a customer ID, which is included + # in the path to the Track v1 API. Choose the ID in this order + # from highest to lowest precedence: + # + # 1. customer_id: "id", an email address, or "cio_id" value. + # Any "cio_id" values need to be prefixed "cio_" + # so that the Track v1 API knows it's a cio_id. + # + # 2. cio_id: The cio_id value (no prefix required). + # + # 3. id: The id value. customer_id = attributes[:id] if !is_empty?(attributes[:cio_id]) customer_id = "cio_" + attributes[:cio_id] end - create_or_update_customer_id(customer_id, attributes) - end - - def create_or_update_customer_id(customer_id, attributes = {}) - attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }] - if is_empty?(customer_id) - raise MissingIdAttributeError.new("Must provide a customer id") + if !is_empty?(attributes[:customer_id]) + customer_id = attributes[:customer_id] end + # customer_id is not an attribute, so remove it. + attributes.delete(:customer_id) + url = customer_path(customer_id) @client.request_and_verify_response(:put, url, attributes) end diff --git a/spec/client_spec.rb b/spec/client_spec.rb index b59f9a0..27ba3db 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -173,6 +173,7 @@ def json(data) lambda { client.identify(email: "customer@example.com") }.should raise_error(Customerio::Client::MissingIdAttributeError) lambda { client.identify(id: "") }.should raise_error(Customerio::Client::MissingIdAttributeError) lambda { client.identify(cio_id: "") }.should raise_error(Customerio::Client::MissingIdAttributeError) + lambda { client.identify(customer_id: "") }.should raise_error(Customerio::Client::MissingIdAttributeError) end it 'should not raise errors when attribute keys are strings' do @@ -223,15 +224,13 @@ def json(data) location: "here" }) end - end - describe "#identify_customer_id" do it "uses provided id rather than id" do stub_request(:put, api_uri('/api/v1/customers/1234')). with(body: json(id: "5")). to_return(status: 200, body: "", headers: {}) - client.identify_customer_id( + client.identify( customer_id: "1234", id: "5" ) @@ -242,7 +241,7 @@ def json(data) with(body: json(id: "5")). to_return(status: 200, body: "", headers: {}) - client.identify_customer_id( + client.identify( customer_id: "cio_5", id: "5" ) @@ -253,20 +252,11 @@ def json(data) with(body: json(id: "5")). to_return(status: 200, body: "", headers: {}) - client.identify_customer_id( + client.identify( customer_id: "customer@example.com", id: "5" ) end - - it "requires a customer_id attribute" do - lambda { client.identify_customer_id() }.should raise_error(Customerio::Client::MissingIdAttributeError) - lambda { client.identify_customer_id(customer_id: "") }.should raise_error(Customerio::Client::MissingIdAttributeError) - # None of these are customer_id - lambda { client.identify_customer_id(id: "5") }.should raise_error(Customerio::Client::MissingIdAttributeError) - lambda { client.identify_customer_id(cio_id: "cio_5") }.should raise_error(Customerio::Client::MissingIdAttributeError) - lambda { client.identify_customer_id(email: "customer@example.com") }.should raise_error(Customerio::Client::MissingIdAttributeError) - end end describe "#delete" do From 6aa44d6e9852f15e8c56389e55815a59e0c2c5d0 Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Fri, 8 Dec 2023 20:46:31 +0000 Subject: [PATCH 5/6] Update changelog --- CHANGELOG.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 75a270e..b900c9e 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,6 +1,6 @@ ## Customerio 5.3.0 - December 8, 2023 -### Added -- The `identify_customer_id` method has been added. This allows the customer ID to be specified separately from the attributes. This allows a person to be updated by identifying them by e.g.: their email address. Thanks to trwalzer, jrbeck and jeremyw for these changes. +### Changed +- The `identify` method has been updated to allow the customer ID to be specified separately from the attributes, using the `customer_id` attribute. This allows a person to be updated by identifying them by e.g.: their email address. Thanks to trwalzer, jrbeck and jeremyw for the original changes that this is based on. ## Customerio 5.2.0 - December 8, 2023 ### Changed From 6a4841688850e5c092b8e08aeb55e13b392ed87d Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Mon, 11 Dec 2023 09:56:59 +0000 Subject: [PATCH 6/6] Note that customer_id attribute can't be set anymore --- CHANGELOG.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index b900c9e..68eacc9 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,6 +1,7 @@ ## Customerio 5.3.0 - December 8, 2023 ### Changed - The `identify` method has been updated to allow the customer ID to be specified separately from the attributes, using the `customer_id` attribute. This allows a person to be updated by identifying them by e.g.: their email address. Thanks to trwalzer, jrbeck and jeremyw for the original changes that this is based on. +- It is no longer possible to set the `customer_id` attribute on a person. This is a side-effect of the changes to the `identify` method. ## Customerio 5.2.0 - December 8, 2023 ### Changed