customerio 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ ## Customerio 0.2.0 - Nov 21, 2012 ##
2
+
3
+ * Allow raw hashes to be passed into `identify` and `track` methods rather than a customer object.
4
+ * Passing a customer object has been depreciated.
5
+ * Customizing ids with `Customerio::Client.id` block is deprecated.
6
+
7
+ ## Customerio 0.1.0 - Nov 15, 2012 ##
8
+
9
+ * Allow tracking of anonymous events.
10
+
11
+ ## Customerio 0.0.3 - Nov 5, 2012 ##
12
+
13
+ * Bump httparty dependency to the latest version.
14
+
15
+ ## Customerio 0.0.2 - May 22, 2012 ##
16
+
17
+ * First release.
data/Gemfile.lock CHANGED
@@ -1,17 +1,22 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- customerio (0.0.3)
4
+ customerio (0.2.0)
5
+ activesupport
5
6
  httparty (~> 0.9.0)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
11
+ activesupport (3.2.9)
12
+ i18n (~> 0.6)
13
+ multi_json (~> 1.0)
10
14
  diff-lcs (1.1.3)
11
15
  fakeweb (1.3.0)
12
16
  httparty (0.9.0)
13
17
  multi_json (~> 1.0)
14
18
  multi_xml
19
+ i18n (0.6.1)
15
20
  multi_json (1.3.7)
16
21
  multi_xml (0.5.1)
17
22
  rake (0.9.2.2)
data/customerio.gemspec CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.version = Customerio::VERSION
17
17
 
18
18
  gem.add_dependency('httparty', '~> 0.9.0')
19
+ gem.add_dependency('activesupport')
19
20
 
20
21
  gem.add_development_dependency('rake')
21
22
  gem.add_development_dependency('rspec')
@@ -1,13 +1,20 @@
1
1
  require 'httparty'
2
+ require 'active_support/hash_with_indifferent_access'
2
3
 
3
4
  module Customerio
4
5
  class Client
5
6
  include HTTParty
6
7
  base_uri 'https://app.customer.io'
7
8
 
9
+ CustomerProxy = Struct.new("Customer", :id)
10
+
11
+ class MissingIdAttributeError < RuntimeError; end
12
+ class InvalidResponse < RuntimeError; end
13
+
8
14
  @@id_block = nil
9
15
 
10
16
  def self.id(&block)
17
+ warn "[DEPRECATION] Customerio::Client.id customization is deprecated."
11
18
  @@id_block = block
12
19
  end
13
20
 
@@ -19,40 +26,47 @@ module Customerio
19
26
  @auth = { :username => site_id, :password => secret_key }
20
27
  end
21
28
 
22
- def identify(customer, attributes = {})
23
- create_or_update(customer, attributes)
29
+ def identify(*args)
30
+ attributes = extract_attributes(args)
31
+
32
+ if args.any?
33
+ customer = args.first
34
+ attributes = attributes_from(customer).merge(attributes)
35
+ end
36
+
37
+ create_or_update(attributes)
24
38
  end
25
39
 
26
40
  def track(*args)
27
- hash = args.last.is_a?(Hash) ? args.pop : {}
41
+ attributes = extract_attributes(args)
28
42
 
29
43
  if args.length == 1
30
44
  # Only passed in an event name, create an anonymous event
31
- create_anonymous_event(args.first, hash)
45
+ event_name = args.first
46
+ create_anonymous_event(event_name, attributes)
32
47
  else
33
48
  # Passed in a customer and an event name.
34
49
  # Track the event for the given customer
35
50
  customer, event_name = args
36
51
 
37
- identify(customer)
38
- create_customer_event(customer, event_name, hash)
52
+ identify(attributes_from(customer))
53
+ create_customer_event(id_from(customer), event_name, attributes)
39
54
  end
40
55
  end
41
56
 
42
57
  private
43
58
 
44
- def create_or_update(customer, attributes = {})
45
- body = {
46
- :id => id(customer),
47
- :email => customer.email,
48
- :created_at => customer.created_at.to_i
49
- }.merge(attributes)
59
+ def create_or_update(attributes = {})
60
+ raise MissingIdAttributeError.new("Must provide an customer id") unless attributes[:id]
61
+
62
+ url = customer_path(attributes[:id])
63
+ attributes[:id] = custom_id(attributes[:id])
50
64
 
51
- self.class.put(customer_path(customer), options.merge(:body => body))
65
+ verify_response(self.class.put(url, options.merge(:body => attributes)))
52
66
  end
53
67
 
54
- def create_customer_event(customer, event_name, attributes = {})
55
- create_event("#{customer_path(customer)}/events", event_name, attributes)
68
+ def create_customer_event(customer_id, event_name, attributes = {})
69
+ create_event("#{customer_path(customer_id)}/events", event_name, attributes)
56
70
  end
57
71
 
58
72
  def create_anonymous_event(event_name, attributes = {})
@@ -61,17 +75,54 @@ module Customerio
61
75
 
62
76
  def create_event(url, event_name, attributes = {})
63
77
  body = { :name => event_name, :data => attributes }
64
- self.class.post(url, options.merge(:body => body))
78
+ verify_response(self.class.post(url, options.merge(:body => body)))
65
79
  end
66
80
 
67
- def customer_path(customer)
68
- "/api/v1/customers/#{id(customer)}"
81
+ def customer_path(id)
82
+ "/api/v1/customers/#{custom_id(id)}"
69
83
  end
70
84
 
71
- def id(customer)
72
- @@id_block ? @@id_block.call(customer) : customer.id
85
+ def verify_response(response)
86
+ if response.code >= 200 && response.code < 300
87
+ response
88
+ else
89
+ raise InvalidResponse.new("Customer.io API returned an invalid response: #{response.code}")
90
+ end
91
+ end
92
+
93
+ def extract_attributes(args)
94
+ HashWithIndifferentAccess.new(args.last.is_a?(Hash) ? args.pop : {})
95
+ end
96
+
97
+ def attributes_from(customer)
98
+ if id?(customer)
99
+ HashWithIndifferentAccess.new(:id => customer)
100
+ else
101
+ HashWithIndifferentAccess.new(
102
+ :id => id_from(customer),
103
+ :email => customer.email,
104
+ :created_at => customer.created_at.to_i
105
+ )
106
+ end
107
+ end
108
+
109
+ def id_from(customer)
110
+ if id?(customer)
111
+ customer
112
+ else
113
+ warn "[DEPRECATION] Passing a customer object to Customerio::Client is deprecated. Just pass a hash with an id key."
114
+ customer.id
115
+ end
116
+ end
117
+
118
+ def custom_id(id)
119
+ @@id_block ? @@id_block.call(id) : id
73
120
  end
74
121
 
122
+ def id?(object)
123
+ object.is_a?(Integer) || object.is_a?(String)
124
+ end
125
+
75
126
  def options
76
127
  { :basic_auth => @auth }
77
128
  end
@@ -1,3 +1,3 @@
1
1
  module Customerio
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/readme.markdown CHANGED
@@ -49,12 +49,6 @@ If you're using Rails, create an initializer `config/initializers/customerio.rb`
49
49
 
50
50
  $customerio = Customerio::Client.new("YOUR SITE ID", "YOUR API SECRET KEY")
51
51
 
52
- By default, this gem identifies customers by just their `id`. However, a common approach is to use `production_2342` as the id attribute for the javascript snippet. You'll want to use the same format by customizing the id in `config/initializers/customerio.rb`:
53
-
54
- Customerio::Client.id do |customer|
55
- "#{Rails.env}_#{customer.id}"
56
- end
57
-
58
52
  ### Identify logged in customers
59
53
 
60
54
  Tracking data of logged in customers is a key part of [Customer.io](http://customer.io). In order to
@@ -77,16 +71,17 @@ You'll want to indentify your customers when they sign up for your app and any t
77
71
  key information changes. This keeps [Customer.io](http://customer.io) up to date with your customer information.
78
72
 
79
73
  # Arguments
80
- # customer (required) - a customer object which responds to a few key methods:
81
- # id - a unique identifier for the customer
82
- # email - the customer's current email address
83
- # created_at - a timestamp which represents when the
84
- # customer was first created.
85
- #
86
- # attributes (optional) - a hash of information about the customer. You can pass any
87
- # information that would be useful in your triggers.
74
+ # attributes (required) - a hash of information about the customer. You can pass any
75
+ # information that would be useful in your triggers. You
76
+ # must at least pass in an id, email, and created_at timestamp.
88
77
 
89
- $customerio.identify(customer, first_name: "Bob", plan: "basic")
78
+ $customerio.identify(
79
+ id: 5,
80
+ email: "bob@example.com,
81
+ created_at: customer.created_at.to_i,
82
+ first_name: "Bob",
83
+ plan: "basic"
84
+ )
90
85
 
91
86
 
92
87
  ### Tracking a custom event
@@ -97,13 +92,13 @@ with automated emails, and track conversions when you're sending automated email
97
92
  encourage your customers to perform an action.
98
93
 
99
94
  # Arguments
100
- # customer (required) - the customer who you want to associate with the event.
101
- # name (required) - the name of the event you want to track.
102
- # attributes (optional) - any related information you'd like to attach to this
103
- # event. These attributes can be used in your triggers to control who should
104
- # receive the triggered email. You can set any number of data values.
95
+ # customer_id (required) - the id of the customer who you want to associate with the event.
96
+ # name (required) - the name of the event you want to track.
97
+ # attributes (optional) - any related information you'd like to attach to this
98
+ # event. These attributes can be used in your triggers to control who should
99
+ # receive the triggered email. You can set any number of data values.
105
100
 
106
- $customerio.track(user, "purchase", type: "socks", price: "13.99")
101
+ $customerio.track(5, "purchase", type: "socks", price: "13.99")
107
102
 
108
103
  ## Contributing
109
104
 
data/spec/client_spec.rb CHANGED
@@ -3,6 +3,13 @@ require 'spec_helper'
3
3
  describe Customerio::Client do
4
4
  let(:client) { Customerio::Client.new("SITE_ID", "API_KEY") }
5
5
  let(:customer) { mock("Customer", :id => 5, :email => "customer@example.com", :created_at => Time.now) }
6
+ let(:response) { mock("Response", code: 200) }
7
+
8
+ before do
9
+ # Dont call out to customer.io
10
+ Customerio::Client.stub(:post).and_return(response)
11
+ Customerio::Client.stub(:put).and_return(response)
12
+ end
6
13
 
7
14
  describe ".base_uri" do
8
15
  it "should be set to customer.io's api" do
@@ -11,52 +18,78 @@ describe Customerio::Client do
11
18
  end
12
19
 
13
20
  describe "#identify" do
14
- it "sends a PUT request to customer.io's customer API" do
15
- Customerio::Client.should_receive(:put).with("/api/v1/customers/5", anything())
16
- client.identify(customer)
17
- end
21
+ it "sends a PUT request to customer.io's customer API" do
22
+ Customerio::Client.should_receive(:put).with("/api/v1/customers/5", anything()).and_return(response)
23
+ client.identify(:id => 5)
24
+ end
18
25
 
19
- it "uses the site_id and api key for basic auth" do
20
- Customerio::Client.should_receive(:put).with("/api/v1/customers/5", {
21
- :basic_auth => { :username => "SITE_ID", :password => "API_KEY" },
22
- :body => anything()
23
- })
26
+ it "raises an error if PUT doesn't return a 2xx response code" do
27
+ Customerio::Client.should_receive(:put).and_return(mock("Response", code: 500))
28
+ lambda { client.identify(:id => 5) }.should raise_error(Customerio::Client::InvalidResponse)
29
+ end
24
30
 
25
- client.identify(customer)
26
- end
31
+ it "uses the site_id and api key for basic auth" do
32
+ Customerio::Client.should_receive(:put).with("/api/v1/customers/5", {
33
+ :basic_auth => { :username => "SITE_ID", :password => "API_KEY" },
34
+ :body => anything()
35
+ }).and_return(response)
27
36
 
28
- it "sends the customer's id, email, and created_at timestamp" do
29
- Customerio::Client.should_receive(:put).with("/api/v1/customers/5", {
30
- :basic_auth => anything(),
31
- :body => {
32
- :id => 5,
33
- :email => "customer@example.com",
34
- :created_at => Time.now.to_i
35
- }
36
- })
37
+ client.identify(:id => 5)
38
+ end
37
39
 
38
- client.identify(customer)
39
- end
40
+ it "sends along all attributes" do
41
+ Customerio::Client.should_receive(:put).with("/api/v1/customers/5", {
42
+ :basic_auth => anything(),
43
+ :body => {
44
+ :id => 5,
45
+ :email => "customer@example.com",
46
+ :created_at => Time.now.to_i,
47
+ :first_name => "Bob",
48
+ :plan => "basic"
49
+ }.stringify_keys
50
+ }).and_return(response)
51
+
52
+ client.identify(:id => 5, :email => "customer@example.com", :created_at => Time.now.to_i, :first_name => "Bob", :plan => "basic")
53
+ end
40
54
 
41
- it "sends any optional attributes" do
42
- Customerio::Client.should_receive(:put).with("/api/v1/customers/5", {
43
- :basic_auth => anything(),
44
- :body => {
45
- :id => 5,
46
- :email => "customer@example.com",
47
- :created_at => Time.now.to_i,
48
- :first_name => "Bob",
49
- :plan => "basic"
50
- }
51
- })
55
+ it "requires an id attribute" do
56
+ lambda { client.identify(:email => "customer@example.com") }.should raise_error(Customerio::Client::MissingIdAttributeError)
57
+ end
52
58
 
53
- client.identify(customer, :first_name => "Bob", :plan => "basic")
54
- end
59
+ context "customer object passed in" do
60
+ it "sends the customer's id, email, and created_at timestamp" do
61
+ Customerio::Client.should_receive(:put).with("/api/v1/customers/5", {
62
+ :basic_auth => anything(),
63
+ :body => {
64
+ :id => 5,
65
+ :email => "customer@example.com",
66
+ :created_at => Time.now.to_i
67
+ }.stringify_keys
68
+ }).and_return(response)
69
+
70
+ client.identify(customer)
71
+ end
72
+
73
+ it "sends any optional attributes" do
74
+ Customerio::Client.should_receive(:put).with("/api/v1/customers/5", {
75
+ :basic_auth => anything(),
76
+ :body => {
77
+ :id => 5,
78
+ :email => "customer@example.com",
79
+ :created_at => Time.now.to_i,
80
+ :first_name => "Bob",
81
+ :plan => "basic"
82
+ }.stringify_keys
83
+ }).and_return(response)
84
+
85
+ client.identify(customer, :first_name => "Bob", :plan => "basic")
86
+ end
87
+ end
55
88
 
56
89
  context "client has customized identities" do
57
90
  before do
58
- Customerio::Client.id do |customer|
59
- "production_#{customer.id}"
91
+ Customerio::Client.id do |customer_id|
92
+ "production_#{customer_id}"
60
93
  end
61
94
  end
62
95
 
@@ -67,28 +100,40 @@ describe Customerio::Client do
67
100
  :id => "production_5",
68
101
  :email => "customer@example.com",
69
102
  :created_at => Time.now.to_i
70
- }
71
- })
103
+ }.stringify_keys
104
+ }).and_return(response)
72
105
 
73
106
  client.identify(customer)
74
107
  end
108
+
109
+ it "uses custom identity when using a pure hash" do
110
+ Customerio::Client.should_receive(:put).with("/api/v1/customers/production_5", {
111
+ :basic_auth => anything(),
112
+ :body => {
113
+ :id => "production_5",
114
+ :email => "customer@example.com",
115
+ :created_at => Time.now.to_i
116
+ }.stringify_keys
117
+ }).and_return(response)
118
+
119
+ client.identify(:id => 5, :email => "customer@example.com", :created_at => Time.now.to_i)
120
+ end
75
121
  end
76
122
  end
77
123
 
78
124
  describe "#track" do
79
- before do
80
- # Don't actually send identify requests
81
- Customerio::Client.stub(:put)
82
- end
83
-
84
125
  it "sends a POST request to the customer.io's event API" do
85
- Customerio::Client.should_receive(:post).with("/api/v1/customers/5/events", anything())
126
+ Customerio::Client.should_receive(:post).with("/api/v1/customers/5/events", anything()).and_return(response)
86
127
  client.track(customer, "purchase")
87
128
  end
88
129
 
89
- it "calls identify with the user to ensure they've been properly identified" do
90
- Customerio::Client.stub(:post) # don't send the request
91
- client.should_receive(:identify).with(customer)
130
+ it "raises an error if POST doesn't return a 2xx response code" do
131
+ Customerio::Client.should_receive(:post).and_return(mock("Response", code: 500))
132
+ lambda { client.track(customer, "purchase") }.should raise_error(Customerio::Client::InvalidResponse)
133
+ end
134
+
135
+ it "calls identify with the user's attributes to ensure they've been properly identified" do
136
+ client.should_receive(:identify).with({ :id => 5, :email => "customer@example.com", :created_at => Time.now.to_i }.stringify_keys).and_return(response)
92
137
  client.track(customer, "purchase")
93
138
  end
94
139
 
@@ -105,7 +150,7 @@ describe Customerio::Client do
105
150
  Customerio::Client.should_receive(:post).with("/api/v1/customers/5/events", {
106
151
  :basic_auth => anything(),
107
152
  :body => { :name => "purchase", :data => {} }
108
- })
153
+ }).and_return(response)
109
154
 
110
155
  client.track(customer, "purchase")
111
156
  end
@@ -115,33 +160,54 @@ describe Customerio::Client do
115
160
  :basic_auth => anything(),
116
161
  :body => {
117
162
  :name => "purchase",
118
- :data => { :type => "socks", :price => "13.99" }
163
+ :data => { :type => "socks", :price => "13.99" }.stringify_keys
119
164
  }
120
- })
165
+ }).and_return(response)
121
166
 
122
167
  client.track(customer, "purchase", :type => "socks", :price => "13.99")
123
168
  end
124
169
 
170
+ it "allows tracking by customer id as well" do
171
+ Customerio::Client.should_receive(:post).with("/api/v1/customers/5/events", {
172
+ :basic_auth => anything(),
173
+ :body => {
174
+ :name => "purchase",
175
+ :data => { :type => "socks", :price => "13.99" }.stringify_keys
176
+ }
177
+ }).and_return(response)
178
+
179
+ client.track(5, "purchase", :type => "socks", :price => "13.99")
180
+ end
181
+
125
182
  context "client has customized identities" do
126
183
  before do
127
- Customerio::Client.id do |customer|
128
- "production_#{customer.id}"
184
+ Customerio::Client.id do |customer_id|
185
+ "production_#{customer_id}"
129
186
  end
130
187
  end
131
188
 
132
189
  it "identifies the customer with the identification method" do
133
190
  Customerio::Client.should_receive(:post).with("/api/v1/customers/production_5/events", {
134
- :basic_auth => anything(),
135
- :body => anything()
136
- })
191
+ :basic_auth => anything(),
192
+ :body => anything()
193
+ }).and_return(response)
137
194
 
138
- client.track(customer, "purchase")
195
+ client.track(customer, "purchase")
196
+ end
197
+
198
+ it "uses the identification method when tracking by id" do
199
+ Customerio::Client.should_receive(:post).with("/api/v1/customers/production_5/events", {
200
+ :basic_auth => anything(),
201
+ :body => anything()
202
+ }).and_return(response)
203
+
204
+ client.track(5, "purchase")
139
205
  end
140
206
  end
141
207
 
142
208
  context "tracking an anonymous event" do
143
209
  it "sends a POST request to the customer.io's anonymous event API" do
144
- Customerio::Client.should_receive(:post).with("/api/v1/events", anything())
210
+ Customerio::Client.should_receive(:post).with("/api/v1/events", anything()).and_return(response)
145
211
  client.track("purchase")
146
212
  end
147
213
 
@@ -149,7 +215,7 @@ describe Customerio::Client do
149
215
  Customerio::Client.should_receive(:post).with("/api/v1/events", {
150
216
  :basic_auth => { :username => "SITE_ID", :password => "API_KEY" },
151
217
  :body => anything()
152
- })
218
+ }).and_return(response)
153
219
 
154
220
  client.track("purchase")
155
221
  end
@@ -158,7 +224,7 @@ describe Customerio::Client do
158
224
  Customerio::Client.should_receive(:post).with("/api/v1/events", {
159
225
  :basic_auth => anything(),
160
226
  :body => { :name => "purchase", :data => {} }
161
- })
227
+ }).and_return(response)
162
228
 
163
229
  client.track("purchase")
164
230
  end
@@ -168,9 +234,9 @@ describe Customerio::Client do
168
234
  :basic_auth => anything(),
169
235
  :body => {
170
236
  :name => "purchase",
171
- :data => { :type => "socks", :price => "13.99" }
237
+ :data => { :type => "socks", :price => "13.99" }.stringify_keys
172
238
  }
173
- })
239
+ }).and_return(response)
174
240
 
175
241
  client.track("purchase", :type => "socks", :price => "13.99")
176
242
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: customerio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-15 00:00:00.000000000 Z
12
+ date: 2012-11-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
29
  version: 0.9.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: rake
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -84,6 +100,7 @@ extra_rdoc_files: []
84
100
  files:
85
101
  - .gitignore
86
102
  - .travis.yml
103
+ - CHANGELOG.markdown
87
104
  - Gemfile
88
105
  - Gemfile.lock
89
106
  - LICENSE