aarrr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +16 -0
- data/Gemfile.lock +67 -0
- data/Guardfile +6 -0
- data/LICENSE +69 -0
- data/README.md +286 -0
- data/Rakefile +43 -0
- data/aarrr.gemspec +35 -0
- data/lib/aarrr.rb +63 -0
- data/lib/aarrr/config.rb +68 -0
- data/lib/aarrr/middleware.rb +21 -0
- data/lib/aarrr/railtie.rb +3 -0
- data/lib/aarrr/session.rb +130 -0
- data/spec/functional/aarrr/config_spec.rb +2 -0
- data/spec/functional/aarrr/session_spec.rb +55 -0
- data/spec/functional/aarrr_spec.rb +27 -0
- data/spec/spec_helper.rb +21 -0
- metadata +113 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
aarrr (0.0.1)
|
5
|
+
mongo (>= 1.3.1)
|
6
|
+
rack (>= 1.2.2)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
archive-tar-minitar (0.5.2)
|
12
|
+
bson (1.3.1)
|
13
|
+
bson_ext (1.3.1)
|
14
|
+
columnize (0.3.2)
|
15
|
+
diff-lcs (1.1.2)
|
16
|
+
growl (1.0.3)
|
17
|
+
guard (0.3.4)
|
18
|
+
thor (~> 0.14.6)
|
19
|
+
guard-rspec (0.3.1)
|
20
|
+
guard (>= 0.2.2)
|
21
|
+
linecache (0.43)
|
22
|
+
linecache19 (0.5.12)
|
23
|
+
ruby_core_source (>= 0.1.4)
|
24
|
+
mongo (1.3.1)
|
25
|
+
bson (>= 1.3.1)
|
26
|
+
rack (1.3.0)
|
27
|
+
rake (0.8.7)
|
28
|
+
rb-fsevent (0.4.0)
|
29
|
+
rspec (2.6.0)
|
30
|
+
rspec-core (~> 2.6.0)
|
31
|
+
rspec-expectations (~> 2.6.0)
|
32
|
+
rspec-mocks (~> 2.6.0)
|
33
|
+
rspec-core (2.6.3)
|
34
|
+
rspec-expectations (2.6.0)
|
35
|
+
diff-lcs (~> 1.1.2)
|
36
|
+
rspec-mocks (2.6.0)
|
37
|
+
ruby-debug (0.10.4)
|
38
|
+
columnize (>= 0.1)
|
39
|
+
ruby-debug-base (~> 0.10.4.0)
|
40
|
+
ruby-debug-base (0.10.4)
|
41
|
+
linecache (>= 0.3)
|
42
|
+
ruby-debug-base19 (0.11.25)
|
43
|
+
columnize (>= 0.3.1)
|
44
|
+
linecache19 (>= 0.5.11)
|
45
|
+
ruby_core_source (>= 0.1.4)
|
46
|
+
ruby-debug19 (0.11.6)
|
47
|
+
columnize (>= 0.3.1)
|
48
|
+
linecache19 (>= 0.5.11)
|
49
|
+
ruby-debug-base19 (>= 0.11.19)
|
50
|
+
ruby_core_source (0.1.5)
|
51
|
+
archive-tar-minitar (>= 0.5.2)
|
52
|
+
thor (0.14.6)
|
53
|
+
|
54
|
+
PLATFORMS
|
55
|
+
ruby
|
56
|
+
|
57
|
+
DEPENDENCIES
|
58
|
+
aarrr!
|
59
|
+
bson_ext
|
60
|
+
growl
|
61
|
+
guard
|
62
|
+
guard-rspec
|
63
|
+
rake (= 0.8.7)
|
64
|
+
rb-fsevent
|
65
|
+
rspec (>= 2.0)
|
66
|
+
ruby-debug
|
67
|
+
ruby-debug19
|
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
Copyright (c) 2011 Jacques Crocker
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
|
23
|
+
Parts taken from Mongoid library:
|
24
|
+
|
25
|
+
Copyright (c) 2009 Durran Jordan
|
26
|
+
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
28
|
+
a copy of this software and associated documentation files (the
|
29
|
+
"Software"), to deal in the Software without restriction, including
|
30
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
31
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
32
|
+
permit persons to whom the Software is furnished to do so, subject to
|
33
|
+
the following conditions:
|
34
|
+
|
35
|
+
The above copyright notice and this permission notice shall be
|
36
|
+
included in all copies or substantial portions of the Software.
|
37
|
+
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
39
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
40
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
41
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
42
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
43
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
44
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
Parts taken from Vanity library:
|
49
|
+
|
50
|
+
Copyright (c) 2010 Assaf Arkin
|
51
|
+
|
52
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
53
|
+
a copy of this software and associated documentation files (the
|
54
|
+
"Software"), to deal in the Software without restriction, including
|
55
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
56
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
57
|
+
permit persons to whom the Software is furnished to do so, subject to
|
58
|
+
the following conditions:
|
59
|
+
|
60
|
+
The above copyright notice and this permission notice shall be
|
61
|
+
included in all copies or substantial portions of the Software.
|
62
|
+
|
63
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
64
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
65
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
66
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
67
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
68
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
69
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
AARRR - metrics for Pirates
|
2
|
+
-----------------------------
|
3
|
+
|
4
|
+
AARRR is a MongoDB backed ruby library that helps you track **user lifecycle** metrics for your web apps (with cohorts!).
|
5
|
+
|
6
|
+
The name comes from an acronym coined by Dave McClure that represents the five most important early metrics for any web startup: Acquisition, Activation, Revenue, Retention, and Referral.
|
7
|
+
|
8
|
+
A quick 5 min video:<br>
|
9
|
+
<http://500hats.typepad.com/500blogs/2007/09/startup-metrics.html>
|
10
|
+
|
11
|
+
Learn more about startup metrics for Pirates:<br>
|
12
|
+
<http://www.slideshare.net/dmc500hats/startup-metrics-for-pirates-long-version>
|
13
|
+
|
14
|
+
![mascot](https://github.com/railsjedi/aarrr/raw/master/aarrr.png "Mascot")
|
15
|
+
|
16
|
+
## Why you should use AARRR:
|
17
|
+
|
18
|
+
AARRR is meant to quickly get you started and provide a framework for collecting and displaying data. It's goal is to help you learn what to measure, and quickly get results. It's long term goal is to really get you to think very hard about how to measure and track your users in order to provide actionable metrics about your web app.
|
19
|
+
|
20
|
+
While it has support for split testing, it's main goal is to provide macro metrics for your users. For more micro experiment driven metrics, you should check out tools such as [Vanity](https://github.com/assaf/vanity) and [ABingo](http://www.bingocardcreator.com/abingo)
|
21
|
+
|
22
|
+
|
23
|
+
## Features:
|
24
|
+
|
25
|
+
* Easily define custom [cohorts](http://www.avc.com/a_vc/2009/10/the-cohort-analysis.html) (defaults to weekly cohorts). You can also define cohorts for specific deploys, or by traffic source.
|
26
|
+
|
27
|
+
* Uses MongoDB for storing analytics (no schema needed, and super crazy fast writes)
|
28
|
+
|
29
|
+
* Automatic configuration for Mongoid and MongoMapper users
|
30
|
+
|
31
|
+
* Easily hooks into Devise (to capture User Acquisition event)
|
32
|
+
|
33
|
+
|
34
|
+
## How to Install:
|
35
|
+
|
36
|
+
* Add `gem "aarrr"` to your Gemfile and `bundle`
|
37
|
+
|
38
|
+
* Run `rails g aarrr:install` to generate your initializer
|
39
|
+
|
40
|
+
* If you'd like to customize the MongoDB connection (or if you aren't using Mongoid/MongoMapper, then edit `config/initializers/aarrr.rb` and set `AARRR.connection` to a valid `Mongo::Connection`. See the [mongo gem tutorial](http://api.mongodb.org/ruby/current/file.TUTORIAL.html) for quick an easy instructions on how to set up a `Mongo::Connection`
|
41
|
+
|
42
|
+
AARRR is now set up, and will add an `around` filter to each request so it can handle user tracking. When a user first shows up at your site, we'll add in a "_utmarr" permanent cookie to them that uniquely identifies the user. You can configure this cookie in `config/initializers/aarrr.rb`.
|
43
|
+
|
44
|
+
|
45
|
+
## How to add tracking
|
46
|
+
|
47
|
+
AARRR defines a helper method `AARRR()` that returns a "session" object. actually just an alias to AARRR::Session.new(). The session object's purpose is to define a user uniquely. All tracking events should be called from the session object
|
48
|
+
|
49
|
+
You can get a session in a few different ways:
|
50
|
+
|
51
|
+
# from an env hash (we'll pull out the right cookie tracking code)
|
52
|
+
AARRR(request.env)
|
53
|
+
|
54
|
+
# directly from a user (if they're already logged in)
|
55
|
+
# use this if you are doing tracking from model objects (you just need a reference to the user)
|
56
|
+
AARRR(current_user)
|
57
|
+
|
58
|
+
# pass in the tracking code directly
|
59
|
+
AARRR(cookies["_utmarrr"])
|
60
|
+
|
61
|
+
You should then save the session to cookie: `AARRR(request.env).save(response)`
|
62
|
+
|
63
|
+
### Tracking vs Completion events
|
64
|
+
|
65
|
+
For each category of events, you can track multiple events leading up to the actual "completion" step for a category, use the option `:in_progress => true`
|
66
|
+
|
67
|
+
|
68
|
+
### Acquisition
|
69
|
+
|
70
|
+
You'll probably want to track customer acquisition at the time of user signup. We can automatically hook into Devise so this event is triggered as soon as your user signs up.
|
71
|
+
|
72
|
+
If you'd rather define Acquisition events manually, just use:
|
73
|
+
|
74
|
+
AARRR(request.env).acquisition!(:viewed_homepage)
|
75
|
+
|
76
|
+
To track the funnel leading up to the acquisition event, use:
|
77
|
+
|
78
|
+
AARRR(request.env).acquisition!(:opened_signup_popup, :in_progress => true)
|
79
|
+
|
80
|
+
|
81
|
+
### Activation
|
82
|
+
|
83
|
+
Activation events should be tracked as soon as your user interacts "sucessfully" with your app. You'll need to define this for your own app, however if your app is built to do something specific then you should add an activation event whenever that thing happens.
|
84
|
+
|
85
|
+
AARRR(request.env).activation!(:built_page)
|
86
|
+
|
87
|
+
AARRR(request.env).activation!(:finished_game)
|
88
|
+
|
89
|
+
|
90
|
+
### Retention
|
91
|
+
|
92
|
+
Retention is defined by how often your user keeps coming back to the app. You can define retention rules separately in reports (e.g. 5 times in 2 months)
|
93
|
+
|
94
|
+
AARRR(request.env).retention!(:logged_in)
|
95
|
+
|
96
|
+
|
97
|
+
### Referral
|
98
|
+
|
99
|
+
Referral should be triggered whenever someone gets someone else to sign up to your app. It's used to calculate a Virality coefficient which is. Learn more about the virality coefficient [here](http://andrewchenblog.com/2008/04/17/viral-coefficient-what-it-does-and-does-not-measure/).
|
100
|
+
|
101
|
+
|
102
|
+
Referrals are done in 2 parts. First you can track when someone decides to refer someone. This would be an "invite" link or something similar.
|
103
|
+
|
104
|
+
# generate a referral
|
105
|
+
referral_code = AARRR(request.env).sent_referral!(:sent => {email: "someone@somewhere.com"})
|
106
|
+
|
107
|
+
# email out the url with this referral code in the query param
|
108
|
+
# "?_a=x71n5"
|
109
|
+
|
110
|
+
# after accepting the code
|
111
|
+
AARRR(request.env).accept_referral!(code)
|
112
|
+
|
113
|
+
When someone enters the site without an activated session and a referral code shows up, then we track the referral event as soon as the user signs up.
|
114
|
+
|
115
|
+
|
116
|
+
### Track
|
117
|
+
|
118
|
+
Track allows you to trigger multiple events at a time. (defaults to :activation event)
|
119
|
+
|
120
|
+
AARRR(request.env).track!(:built_page, :event_type => :activate, :in_progress => true)
|
121
|
+
|
122
|
+
|
123
|
+
### Revenue
|
124
|
+
|
125
|
+
Whenever you capture a dollar from user, then you should track that intake event.
|
126
|
+
|
127
|
+
# customer paid you 55.00
|
128
|
+
AARRR(request.env).revenue!(55.00)
|
129
|
+
|
130
|
+
# can also pass in the cents
|
131
|
+
AARRR(request.env).revenue_cents!(5500)
|
132
|
+
|
133
|
+
# it's also useful to pass in a unique code here (receipt / invoice number or something) so you don't double track someone's revenue
|
134
|
+
AARRR(request.env).revenue!(55.00, :unique => "x8175m1o58113")
|
135
|
+
|
136
|
+
|
137
|
+
## Cohorts
|
138
|
+
|
139
|
+
Cohorts are ways to slice up reports so you can see the results for these 5 metrics for groups of specific users. Some useful examples are:
|
140
|
+
|
141
|
+
### Date (by day, week, month)
|
142
|
+
|
143
|
+
slice up the metrics based on when users first came to your site (session creation). This is useful to see if what your building is actually improving your metrics
|
144
|
+
|
145
|
+
|
146
|
+
# assigns a cohort based on the week
|
147
|
+
AARRR.define_cohort :weekly do |user|
|
148
|
+
user["created_at"].beginning_of_week.strftime("%B %d, %Y")
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
### By Traffic Source
|
153
|
+
|
154
|
+
slice up the metrics based on where your users are coming from. This allows you to see what sources of traffic are most value and target your marketing efforts on these.
|
155
|
+
|
156
|
+
# assigns a cohort based on the traffic source
|
157
|
+
AARRR.define_cohort :source do |user|
|
158
|
+
case user["referrer"]
|
159
|
+
when /google.com/, /gmail.com/
|
160
|
+
"google"
|
161
|
+
when /facebook.com/
|
162
|
+
"facebook"
|
163
|
+
else
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
### By Keyword
|
169
|
+
|
170
|
+
slice up the users by the keyword (or groups of keyword) in order to classify users by market segment.
|
171
|
+
|
172
|
+
AARRR.define_cohort :keyword do |user, data|
|
173
|
+
# define a `extract_keywords` method to pull out the keywords from the referrer url
|
174
|
+
keywords = extract_keywords(user["referrer"])
|
175
|
+
if keywords.include?("ruby") or keywords.include?("rails")
|
176
|
+
"rails"
|
177
|
+
else
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
### By gender (or other custom data attributes)
|
184
|
+
|
185
|
+
assuming you've captured it via `AARRR(request.env).set_data(:gender => "m")`
|
186
|
+
|
187
|
+
AARRR.define_cohort :gender do |user, data|
|
188
|
+
if data["gender"].to_s.upcase == "M"
|
189
|
+
"male"
|
190
|
+
elsif data["gender"].to_s.upcase == "F"
|
191
|
+
"female"
|
192
|
+
else
|
193
|
+
nil
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
### By Location
|
198
|
+
|
199
|
+
AARRR.define_cohort :location do |user, data|
|
200
|
+
# define get_city_for method to get the city for a particular ip address
|
201
|
+
get_city_for(user["ip_address"])
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
## Split Testing
|
206
|
+
|
207
|
+
You can set up split testing by using:
|
208
|
+
|
209
|
+
AARRR.define_split :landing_redesign, :options => {
|
210
|
+
:v1_layout => 0.9,
|
211
|
+
:v2_layout => 0.1
|
212
|
+
}
|
213
|
+
|
214
|
+
To use these split tests, just add the following to your views:
|
215
|
+
|
216
|
+
AARRR(request.env).split?(:landing_redesign, :v1_layout) #=> true
|
217
|
+
|
218
|
+
The first argument is a reference to the split test that you defined in your initializer. The second argument is the split option to return.
|
219
|
+
|
220
|
+
This will attach the session with a randomly selected version of the split test and return whether it matches the second argument.
|
221
|
+
|
222
|
+
You can also just return the split option currently assigned to the user by using:
|
223
|
+
|
224
|
+
AARRR(request.env).split(:landing_redesign) #=> :v1_layout
|
225
|
+
|
226
|
+
Split test results can be accessed via the reports, and you can slice up the user metrics based on which users saw what split. People who saw neither splits will not be included in the results.
|
227
|
+
|
228
|
+
|
229
|
+
## Ignored Cohorts
|
230
|
+
|
231
|
+
When you start seeing screwy data (spammers, seo, scrapers) you can selectively remove these people by configuring Ignored Cohorts. This just excludes data before running the report calculations.
|
232
|
+
|
233
|
+
This allows you to identify the AARRR users that are likely "spam" and removes them from most report results.
|
234
|
+
|
235
|
+
# pass it a mongo query that defines the users you want to ignore
|
236
|
+
AARRR.ignored_cohort :googlebot, "data.useragent" => /googlebot/
|
237
|
+
|
238
|
+
|
239
|
+
## Pulling the Data out (generating reports)
|
240
|
+
|
241
|
+
AARRR provides some simple views that allow you to generate some basic reports. Reports are generated via a cron job `rake aarrr:generate`.
|
242
|
+
|
243
|
+
You can also generate the reports manually by running AARRR.generate!, however I'd advise you to run it via Resque or Delayed Job as it may take a long time to generate.
|
244
|
+
|
245
|
+
Once you have the reports, you can use the AARRR view helpers in order to render your reports to a web page.
|
246
|
+
|
247
|
+
Our report views probably aren't going to be exactly what you want, so we encourage you to cycle through the `AAARR.report_results` (returns the latest generated report results) and build up your own graphs and charts.
|
248
|
+
|
249
|
+
|
250
|
+
## Data Model
|
251
|
+
|
252
|
+
This section describes how AARRR stores your data within the MongoDB collections (raw and reports).
|
253
|
+
|
254
|
+
### Raw Events
|
255
|
+
|
256
|
+
AARRR tracks the raw metric data in a 2 main tables:
|
257
|
+
|
258
|
+
`aarrr_users`: tracks the unique identities of each user
|
259
|
+
|
260
|
+
* `_id`: generated aarrr user id
|
261
|
+
* `user_id`: optional tie in to your database's user_id (for drill down)
|
262
|
+
* `data`: hash that stores any data that's passed for the user on creation. main use is for analyzing the cohort data
|
263
|
+
* `splits`: hash that maps split testing rules to assigned splits for the user
|
264
|
+
* `cohorts`: a hash that maps cohort rules with the results
|
265
|
+
* `ignore_reason`: a string that represents the reason this user is ignored in reports
|
266
|
+
* `referrer`: referrer url that user came from
|
267
|
+
* `ip_address`: ip address for the user
|
268
|
+
* `last_event_at`: date that the user last interacted with the site
|
269
|
+
|
270
|
+
`aarrr_events`: tracks each event that the user is engaged in
|
271
|
+
|
272
|
+
* `_id`: generated aarrr event id
|
273
|
+
* `aarrr_user_id`: id that maps event back to the aarrr users table
|
274
|
+
* `event_name`: name for the event that was tracked
|
275
|
+
* `event_type`: category of event type you are tracking
|
276
|
+
* `in_progress`: true/false whether or not this event_type is in progress (not yet completed)
|
277
|
+
* `data`: data that should be tracked along with the event
|
278
|
+
* `revenue`: revenue the was generated on this event
|
279
|
+
* `referral_code`: referral code that was generated for this event
|
280
|
+
|
281
|
+
|
282
|
+
### Reports
|
283
|
+
|
284
|
+
... TBD ...
|
285
|
+
|
286
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
|
7
|
+
gemspec = eval(File.read('aarrr.gemspec'))
|
8
|
+
Rake::GemPackageTask.new(gemspec) do |pkg|
|
9
|
+
pkg.gem_spec = gemspec
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "build the gem and release it to rubygems.org"
|
13
|
+
task :release => :gem do
|
14
|
+
puts "Tagging #{gemspec.version}..."
|
15
|
+
system "git tag -a #{gemspec.version} -m 'Tagging #{gemspec.version}'"
|
16
|
+
puts "Pushing to Github..."
|
17
|
+
system "git push --tags"
|
18
|
+
puts "Pushing to rubygems.org..."
|
19
|
+
system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
20
|
+
end
|
21
|
+
|
22
|
+
require "rspec"
|
23
|
+
require "rspec/core/rake_task"
|
24
|
+
|
25
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
26
|
+
spec.pattern = "spec/**/*_spec.rb"
|
27
|
+
end
|
28
|
+
|
29
|
+
RSpec::Core::RakeTask.new('spec:progress') do |spec|
|
30
|
+
spec.rspec_opts = %w(--format progress)
|
31
|
+
spec.pattern = "spec/**/*_spec.rb"
|
32
|
+
end
|
33
|
+
|
34
|
+
require "rake/rdoctask"
|
35
|
+
Rake::RDocTask.new do |rdoc|
|
36
|
+
rdoc.rdoc_dir = "rdoc"
|
37
|
+
rdoc.title = "AARRR #{gemspec.version}"
|
38
|
+
rdoc.rdoc_files.include("README*")
|
39
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
task :default => :spec
|
data/aarrr.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "aarrr"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
|
5
|
+
s.authors = ["Jacques Crocker"]
|
6
|
+
s.summary = "metrics for pirates"
|
7
|
+
s.description = "AARRR helps track user lifecycle metrics via MongoDB. It also provides cohort and reporting tools."
|
8
|
+
|
9
|
+
s.email = "railsjedi@gmail.com"
|
10
|
+
s.homepage = "http://github.com/railsjedi/aarrr"
|
11
|
+
s.rubyforge_project = "none"
|
12
|
+
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
s.files = Dir['lib/**/*',
|
15
|
+
'spec/**/*',
|
16
|
+
'aarrr.gemspec',
|
17
|
+
'Gemfile',
|
18
|
+
'Gemfile.lock',
|
19
|
+
'Guardfile',
|
20
|
+
'LICENSE',
|
21
|
+
'Rakefile',
|
22
|
+
'README.md']
|
23
|
+
|
24
|
+
s.test_files = Dir['spec/**/*']
|
25
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
26
|
+
s.extra_rdoc_files = [
|
27
|
+
"LICENSE",
|
28
|
+
"README.md"
|
29
|
+
]
|
30
|
+
|
31
|
+
s.add_runtime_dependency "rack", ">= 1.2.2"
|
32
|
+
s.add_runtime_dependency "mongo", ">= 1.3.1"
|
33
|
+
s.add_development_dependency "rspec", ">= 2.0"
|
34
|
+
end
|
35
|
+
|
data/lib/aarrr.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "mongo"
|
4
|
+
require "aarrr/config"
|
5
|
+
require "aarrr/session"
|
6
|
+
|
7
|
+
require "rack"
|
8
|
+
require "aarrr/middleware"
|
9
|
+
|
10
|
+
# add railtie
|
11
|
+
if defined?(Rails)
|
12
|
+
require "aarrr/railtie"
|
13
|
+
end
|
14
|
+
|
15
|
+
# helper method to initialize an AARRR session
|
16
|
+
def AARRR(env_or_model)
|
17
|
+
if env_or_model.is_a?(Hash) and env_or_model["aarrr.session"]
|
18
|
+
env_or_model["aarrr.session"]
|
19
|
+
else
|
20
|
+
session = AARRR::Session.new(env_or_model)
|
21
|
+
|
22
|
+
# add to the rack env (if applicable)
|
23
|
+
env_or_model["aarrr.session"] = session if env_or_model.is_a?(Hash)
|
24
|
+
|
25
|
+
session
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
module AARRR
|
31
|
+
|
32
|
+
class << self
|
33
|
+
|
34
|
+
# Sets the Mongoid configuration options. Best used by passing a block.
|
35
|
+
#
|
36
|
+
# @example Set up configuration options.
|
37
|
+
#
|
38
|
+
# AARRR.configure do |config|
|
39
|
+
# config.database = Mongo::Connection.new.db("metrics")
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @return [ Config ] The configuration obejct.
|
43
|
+
def configure
|
44
|
+
config = AARRR::Config
|
45
|
+
block_given? ? yield(config) : config
|
46
|
+
end
|
47
|
+
alias :config :configure
|
48
|
+
end
|
49
|
+
|
50
|
+
# Take all the public instance methods from the Config singleton and allow
|
51
|
+
# them to be accessed through the AARRR module directly.
|
52
|
+
#
|
53
|
+
# @example Delegate the configuration methods.
|
54
|
+
# AARRR.database = Mongo::Connection.new.db("test")
|
55
|
+
AARRR::Config.public_instance_methods(false).each do |name|
|
56
|
+
(class << self; self; end).class_eval <<-EOT
|
57
|
+
def #{name}(*args)
|
58
|
+
configure.send("#{name}", *args)
|
59
|
+
end
|
60
|
+
EOT
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
data/lib/aarrr/config.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AARRR
|
4
|
+
|
5
|
+
# Configures AARRR
|
6
|
+
module Config
|
7
|
+
extend self
|
8
|
+
@settings = {}
|
9
|
+
|
10
|
+
@database = nil
|
11
|
+
@connection = nil
|
12
|
+
|
13
|
+
# Define a configuration option with a default.
|
14
|
+
#
|
15
|
+
# @example Define the option.
|
16
|
+
# Config.option(:persist_in_safe_mode, :default => false)
|
17
|
+
#
|
18
|
+
# @param [ Symbol ] name The name of the configuration option.
|
19
|
+
# @param [ Hash ] options Extras for the option.
|
20
|
+
#
|
21
|
+
# @option options [ Object ] :default The default value.
|
22
|
+
#
|
23
|
+
def option(name, options = {})
|
24
|
+
define_method(name) do
|
25
|
+
@settings.has_key?(name) ? @settings[name] : options[:default]
|
26
|
+
end
|
27
|
+
define_method("#{name}=") { |value| @settings[name] = value }
|
28
|
+
define_method("#{name}?") { send(name) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# default some options with defaults
|
32
|
+
option :database_name, :default => "metrics"
|
33
|
+
option :cookie_name, :default => "_utmarr"
|
34
|
+
option :cookie_expiration, :default => 60*24*60*60
|
35
|
+
option :user_collection_name, :default => "aarrr_users"
|
36
|
+
option :event_collection_name, :default => "aarrr_events"
|
37
|
+
|
38
|
+
# Get the Mongo::Connection to use to pull the AARRR metrics data
|
39
|
+
def connection
|
40
|
+
@connection || Mongo::Connection.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set the Mongo::Connection to use to pull the AARRR metrics data
|
44
|
+
def connection=(connection)
|
45
|
+
@connection = connection
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get the Mongo::Database associated with the AARRR metrics data
|
49
|
+
def database
|
50
|
+
@database || connection.db(database_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set the Mongo::Database associated with the AARRR metrics data
|
54
|
+
def database=(database)
|
55
|
+
@database = database
|
56
|
+
end
|
57
|
+
|
58
|
+
def users
|
59
|
+
database[user_collection_name]
|
60
|
+
end
|
61
|
+
|
62
|
+
def events
|
63
|
+
database[event_collection_name]
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module AARRR
|
2
|
+
class Middleware
|
3
|
+
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
aarrr_session = AARRR(env)
|
10
|
+
|
11
|
+
status, headers, body = @app.call(env)
|
12
|
+
|
13
|
+
# sets a tracking cookie on the response
|
14
|
+
response = Rack::Response.new body, status, headers
|
15
|
+
aarrr_session.save(response)
|
16
|
+
|
17
|
+
response.finish
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AARRR
|
4
|
+
|
5
|
+
# an AARR session is used to identify a particular user in order to track events
|
6
|
+
class Session
|
7
|
+
attr_accessor :id
|
8
|
+
|
9
|
+
# find or creates a session in the db based on the env or object
|
10
|
+
def initialize(env_or_object = nil, attributes = nil)
|
11
|
+
self.id = parse_id(env_or_object) || BSON::ObjectId.new.to_s
|
12
|
+
|
13
|
+
# perform upsert
|
14
|
+
update({"$set" => build_attributes(env_or_object).merge(attributes || {})}, :upsert => true)
|
15
|
+
end
|
16
|
+
|
17
|
+
# returns a reference the othe AARRR user
|
18
|
+
def user
|
19
|
+
AARRR.users.find(id)
|
20
|
+
end
|
21
|
+
|
22
|
+
# sets some additional data
|
23
|
+
def set_data(data)
|
24
|
+
update({"data" => {"$set" => data}})
|
25
|
+
end
|
26
|
+
|
27
|
+
# save a cookie to the response
|
28
|
+
def save(response)
|
29
|
+
response.set_cookie(AARRR::Config.cookie_name, {
|
30
|
+
:value => self.id,
|
31
|
+
:path => "/",
|
32
|
+
:expires => Time.now+AARRR::Config.cookie_expiration
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
# track event name
|
37
|
+
def track!(event_name, options = {})
|
38
|
+
|
39
|
+
# add event tracking
|
40
|
+
result = AARRR.events.insert({
|
41
|
+
"aarrr_user_id" => self.id,
|
42
|
+
"event_name" => event_name,
|
43
|
+
"event_type" => options[:event_type],
|
44
|
+
"in_progress" => options[:in_progress] || false,
|
45
|
+
"data" => options[:data],
|
46
|
+
"revenue" => options[:revenue],
|
47
|
+
"referral_code" => options[:referral_code]
|
48
|
+
})
|
49
|
+
|
50
|
+
# update user with last updated time
|
51
|
+
update({
|
52
|
+
"$set" => {
|
53
|
+
"last_event_at" => Time.now.getutc
|
54
|
+
}
|
55
|
+
})
|
56
|
+
|
57
|
+
result
|
58
|
+
end
|
59
|
+
|
60
|
+
# more helpers
|
61
|
+
|
62
|
+
def acquisition!(event_name, options = {})
|
63
|
+
options[:event_type] = :acquisition
|
64
|
+
track!(event_name, options)
|
65
|
+
end
|
66
|
+
|
67
|
+
def activation!(event_name, options = {})
|
68
|
+
options[:event_type] = :activation
|
69
|
+
track!(event_name, options)
|
70
|
+
end
|
71
|
+
|
72
|
+
def retention!(event_name, options = {})
|
73
|
+
options[:event_type] = :retention
|
74
|
+
track!(event_name, options)
|
75
|
+
end
|
76
|
+
|
77
|
+
# TODO: referral and revenue
|
78
|
+
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
# mark update
|
83
|
+
def update(attributes, options = {})
|
84
|
+
AARRR.users.update({"_id" => id}, attributes, options)
|
85
|
+
end
|
86
|
+
|
87
|
+
# returns id
|
88
|
+
def parse_id(env_or_object)
|
89
|
+
# check for empty case or string
|
90
|
+
|
91
|
+
# if it's a hash, then process like a request and pull out the cookie
|
92
|
+
if env_or_object.is_a?(Hash)
|
93
|
+
|
94
|
+
request = Rack::Request.new(env_or_object)
|
95
|
+
request.cookies[AARRR::Config.cookie_name]
|
96
|
+
|
97
|
+
# if it's an object with an id, then return that
|
98
|
+
elsif env_or_object.respond_to?(:id) and env_or_object.id.is_a?(BSON::ObjectId)
|
99
|
+
env_or_object.id.to_s
|
100
|
+
|
101
|
+
# if it's a string
|
102
|
+
elsif env_or_object.is_a?(String)
|
103
|
+
env_or_object
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# returns updates
|
109
|
+
def build_attributes(env_or_object)
|
110
|
+
if env_or_object.is_a?(Hash)
|
111
|
+
user_attributes = {}
|
112
|
+
|
113
|
+
# referrer: HTTP_REFERER
|
114
|
+
referrer = env_or_object["HTTP_REFERER"]
|
115
|
+
user_attributes["referrer"] = referrer if referrer
|
116
|
+
|
117
|
+
# ip_address: HTTP_X_REAL_IP || REMOTE_ADDR
|
118
|
+
ip_address = env_or_object["HTTP_X_REAL_IP"] || env_or_object["REMOTE_ADDR"]
|
119
|
+
user_attributes["ip_address"] = ip_address if ip_address
|
120
|
+
|
121
|
+
# TODO: additional data from the env for the user
|
122
|
+
|
123
|
+
user_attributes
|
124
|
+
else
|
125
|
+
{}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module AARRR
|
6
|
+
describe Session do
|
7
|
+
|
8
|
+
describe "#new" do
|
9
|
+
it "should create a session with no data" do
|
10
|
+
session = Session.new
|
11
|
+
AARRR.users.count.should eq(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should create a session with a request env" do
|
15
|
+
session = Session.new
|
16
|
+
Session.new({
|
17
|
+
"HTTP_COOKIE" => "_utmarr=#{session.id}; path=/;"
|
18
|
+
})
|
19
|
+
AARRR.users.count.should eq(1)
|
20
|
+
|
21
|
+
session = Session.new({
|
22
|
+
"HTTP_COOKIE" => "_utmarr=x83y1; path=/;"
|
23
|
+
})
|
24
|
+
|
25
|
+
AARRR.users.count.should eq(2)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "tracking" do
|
30
|
+
before(:each) do
|
31
|
+
@session = Session.new
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should track a custom event" do
|
35
|
+
@session.track!(:something)
|
36
|
+
|
37
|
+
AARRR.events.count.should eq(1)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "saving" do
|
42
|
+
it "should save the session to cookie" do
|
43
|
+
@session = Session.new
|
44
|
+
@session.track!(:some_event)
|
45
|
+
|
46
|
+
# save session to response
|
47
|
+
response = Rack::Response.new "some body", 200, {}
|
48
|
+
@session.save(response)
|
49
|
+
|
50
|
+
response.header["Set-Cookie"].should include("_utmarr=#{@session.id}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "spec setup" do
|
4
|
+
it "should run the specs without error" do
|
5
|
+
true.should be_true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "AARRR()" do
|
10
|
+
|
11
|
+
context "with an request.env" do
|
12
|
+
before(:each) do
|
13
|
+
@env = {}
|
14
|
+
AARRR(@env)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create a session" do
|
18
|
+
AARRR.users.count.should eq(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set the aarrr.session env variable" do
|
22
|
+
user_attributes = AARRR.users.find_one
|
23
|
+
@env["aarrr.session"].id.should eq(user_attributes["_id"])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'aarrr'
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
|
6
|
+
config.before(:suite) do
|
7
|
+
# setup the AARRR test database
|
8
|
+
AARRR.configure do |c|
|
9
|
+
puts "configured to use aarrr_metrics_test db"
|
10
|
+
c.database_name = "aarrr_metrics_test"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
config.before(:each) do
|
15
|
+
AARRR.database.collections.select {|c| c.name !~ /system/ }.each(&:drop)
|
16
|
+
end
|
17
|
+
|
18
|
+
# add helper methods here
|
19
|
+
|
20
|
+
end
|
21
|
+
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aarrr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jacques Crocker
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-02 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rack
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.2
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mongo
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.3.1
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rspec
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "2.0"
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
description: AARRR helps track user lifecycle metrics via MongoDB. It also provides cohort and reporting tools.
|
50
|
+
email: railsjedi@gmail.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files:
|
56
|
+
- LICENSE
|
57
|
+
- README.md
|
58
|
+
files:
|
59
|
+
- lib/aarrr/config.rb
|
60
|
+
- lib/aarrr/middleware.rb
|
61
|
+
- lib/aarrr/railtie.rb
|
62
|
+
- lib/aarrr/session.rb
|
63
|
+
- lib/aarrr.rb
|
64
|
+
- spec/functional/aarrr/config_spec.rb
|
65
|
+
- spec/functional/aarrr/session_spec.rb
|
66
|
+
- spec/functional/aarrr_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
- aarrr.gemspec
|
69
|
+
- Gemfile
|
70
|
+
- Gemfile.lock
|
71
|
+
- Guardfile
|
72
|
+
- LICENSE
|
73
|
+
- Rakefile
|
74
|
+
- README.md
|
75
|
+
has_rdoc: true
|
76
|
+
homepage: http://github.com/railsjedi/aarrr
|
77
|
+
licenses: []
|
78
|
+
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options:
|
81
|
+
- --charset=UTF-8
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: -4480489648285763335
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: -4480489648285763335
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
requirements: []
|
103
|
+
|
104
|
+
rubyforge_project: none
|
105
|
+
rubygems_version: 1.6.2
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: metrics for pirates
|
109
|
+
test_files:
|
110
|
+
- spec/functional/aarrr/config_spec.rb
|
111
|
+
- spec/functional/aarrr/session_spec.rb
|
112
|
+
- spec/functional/aarrr_spec.rb
|
113
|
+
- spec/spec_helper.rb
|