aarrr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem "bson_ext"
6
+
7
+ gem "ruby-debug", :platforms => :mri_18
8
+ gem "ruby-debug19", :platforms => :mri_19
9
+
10
+ gem "rb-fsevent"
11
+ gem "growl"
12
+
13
+ gem "guard"
14
+ gem "guard-rspec"
15
+
16
+ gem "rake", "= 0.8.7"
@@ -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
@@ -0,0 +1,6 @@
1
+ guard 'rspec' do
2
+ watch('spec/spec_helper.rb') { "spec" }
3
+ watch(%r{^spec/.+_spec\.rb})
4
+ watch(%r{^lib/(.+)\.rb}) { "spec" } # { |m| "spec/lib/#{m[1]}_spec.rb" }
5
+ end
6
+
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.
@@ -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
+
@@ -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
@@ -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
+
@@ -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
@@ -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,3 @@
1
+ # encoding: utf-8
2
+
3
+ # nothing yet...
@@ -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,2 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
@@ -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
@@ -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