ahoy_matey 0.3.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +324 -189
- data/ahoy_matey.gemspec +1 -0
- data/app/controllers/ahoy/base_controller.rb +5 -0
- data/app/controllers/ahoy/events_controller.rb +9 -7
- data/app/controllers/ahoy/visits_controller.rb +2 -1
- data/lib/ahoy.rb +37 -28
- data/lib/ahoy/controller.rb +12 -20
- data/lib/ahoy/deckhands/location_deckhand.rb +39 -0
- data/lib/ahoy/deckhands/request_deckhand.rb +41 -0
- data/lib/ahoy/deckhands/technology_deckhand.rb +49 -0
- data/lib/ahoy/deckhands/traffic_source_deckhand.rb +24 -0
- data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +24 -0
- data/lib/ahoy/engine.rb +3 -1
- data/lib/ahoy/model.rb +19 -82
- data/lib/ahoy/stores/active_record_store.rb +63 -0
- data/lib/ahoy/stores/active_record_token_store.rb +113 -0
- data/lib/ahoy/stores/base_store.rb +65 -0
- data/lib/ahoy/stores/log_store.rb +47 -0
- data/lib/ahoy/stores/mongoid_store.rb +59 -0
- data/lib/ahoy/tracker.rb +108 -67
- data/lib/ahoy/version.rb +1 -1
- data/lib/ahoy/visit_properties.rb +58 -0
- data/lib/ahoy/warden.rb +1 -10
- data/lib/generators/ahoy/stores/active_record_events_generator.rb +44 -0
- data/lib/generators/ahoy/stores/active_record_generator.rb +17 -0
- data/lib/generators/ahoy/{events/active_record_generator.rb → stores/active_record_visits_generator.rb} +14 -6
- data/lib/generators/ahoy/stores/custom_generator.rb +16 -0
- data/lib/generators/ahoy/stores/log_generator.rb +16 -0
- data/lib/generators/ahoy/stores/mongoid_events_generator.rb +20 -0
- data/lib/generators/ahoy/stores/mongoid_generator.rb +16 -0
- data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +20 -0
- data/{app/models/ahoy/event.rb → lib/generators/ahoy/stores/templates/active_record_event_model.rb} +2 -2
- data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +20 -0
- data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +3 -0
- data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +4 -0
- data/lib/generators/ahoy/{templates/install.rb → stores/templates/active_record_visits_migration.rb} +6 -8
- data/lib/generators/ahoy/stores/templates/custom_initializer.rb +12 -0
- data/lib/generators/ahoy/stores/templates/log_initializer.rb +3 -0
- data/lib/generators/ahoy/stores/templates/mongoid_event_model.rb +12 -0
- data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +3 -0
- data/lib/generators/ahoy/stores/templates/mongoid_visit_model.rb +41 -0
- data/vendor/assets/javascripts/ahoy.js +19 -8
- metadata +45 -8
- data/lib/generators/ahoy/events/templates/create_events.rb +0 -20
- data/lib/generators/ahoy/events/templates/initializer.rb +0 -1
- data/lib/generators/ahoy/install_generator.rb +0 -38
data/lib/ahoy/version.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Ahoy
|
2
|
+
class VisitProperties
|
3
|
+
|
4
|
+
REQUEST_KEYS = [:ip, :user_agent, :referrer, :landing_page, :platform, :app_version, :os_version]
|
5
|
+
TRAFFIC_SOURCE_KEYS = [:referring_domain, :search_keyword]
|
6
|
+
UTM_PARAMETER_KEYS = [:utm_source, :utm_medium, :utm_term, :utm_content, :utm_campaign]
|
7
|
+
TECHNOLOGY_KEYS = [:browser, :os, :device_type]
|
8
|
+
LOCATION_KEYS = [:country, :region, :city]
|
9
|
+
|
10
|
+
KEYS = REQUEST_KEYS + TRAFFIC_SOURCE_KEYS + UTM_PARAMETER_KEYS + TECHNOLOGY_KEYS + LOCATION_KEYS
|
11
|
+
|
12
|
+
delegate *REQUEST_KEYS, to: :request_deckhand
|
13
|
+
delegate *TRAFFIC_SOURCE_KEYS, to: :traffic_source_deckhand
|
14
|
+
delegate *(UTM_PARAMETER_KEYS + [:landing_params]), to: :utm_parameter_deckhand
|
15
|
+
delegate *TECHNOLOGY_KEYS, to: :technology_deckhand
|
16
|
+
delegate *LOCATION_KEYS, to: :location_deckhand
|
17
|
+
|
18
|
+
def initialize(request, options = {})
|
19
|
+
@request = request
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
send(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def keys
|
28
|
+
KEYS
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
keys.inject({}){|memo, key| memo[key] = send(key); memo }
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def request_deckhand
|
38
|
+
@request_deckhand ||= Deckhands::RequestDeckhand.new(@request, @options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def traffic_source_deckhand
|
42
|
+
@traffic_source_deckhand ||= Deckhands::TrafficSourceDeckhand.new(request_deckhand.referrer)
|
43
|
+
end
|
44
|
+
|
45
|
+
def utm_parameter_deckhand
|
46
|
+
@utm_parameter_deckhand ||= Deckhands::UtmParameterDeckhand.new(request_deckhand.landing_page)
|
47
|
+
end
|
48
|
+
|
49
|
+
def technology_deckhand
|
50
|
+
@technology_deckhand ||= Deckhands::TechnologyDeckhand.new(request_deckhand.user_agent)
|
51
|
+
end
|
52
|
+
|
53
|
+
def location_deckhand
|
54
|
+
@location_deckhand ||= Deckhands::LocationDeckhand.new(request_deckhand.ip)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
data/lib/ahoy/warden.rb
CHANGED
@@ -1,14 +1,5 @@
|
|
1
1
|
Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
|
2
2
|
request = ActionDispatch::Request.new(auth.env)
|
3
|
-
visit_token = request.cookies["ahoy_visit"] || request.headers["Ahoy-Visit"]
|
4
|
-
visit = nil
|
5
|
-
if visit_token
|
6
|
-
visit = Ahoy.visit_model.where(visit_token: visit_token).first
|
7
|
-
if visit and !visit.user
|
8
|
-
visit.user = user
|
9
|
-
visit.save!
|
10
|
-
end
|
11
|
-
end
|
12
3
|
ahoy = Ahoy::Tracker.new(request: request)
|
13
|
-
ahoy.
|
4
|
+
ahoy.authenticate(user)
|
14
5
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
|
2
|
+
require "rails/generators"
|
3
|
+
require "rails/generators/migration"
|
4
|
+
require "active_record"
|
5
|
+
require "rails/generators/active_record"
|
6
|
+
|
7
|
+
module Ahoy
|
8
|
+
module Stores
|
9
|
+
module Generators
|
10
|
+
class ActiveRecordEventsGenerator < Rails::Generators::Base
|
11
|
+
include Rails::Generators::Migration
|
12
|
+
source_root File.expand_path("../templates", __FILE__)
|
13
|
+
|
14
|
+
class_option :database, type: :string, aliases: "-d"
|
15
|
+
|
16
|
+
# Implement the required interface for Rails::Generators::Migration.
|
17
|
+
def self.next_migration_number(dirname) #:nodoc:
|
18
|
+
next_migration_number = current_migration_number(dirname) + 1
|
19
|
+
if ::ActiveRecord::Base.timestamped_migrations
|
20
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
21
|
+
else
|
22
|
+
"%.3d" % next_migration_number
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def copy_migration
|
27
|
+
unless options["database"].in?([nil, "postgresql"])
|
28
|
+
raise Thor::Error, "Unknown database option"
|
29
|
+
end
|
30
|
+
migration_template "active_record_events_migration.rb", "db/migrate/create_ahoy_events.rb"
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_model
|
34
|
+
template "active_record_event_model.rb", "app/models/ahoy/event.rb"
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_initializer
|
38
|
+
template "active_record_initializer.rb", "config/initializers/ahoy.rb"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Stores
|
5
|
+
module Generators
|
6
|
+
class ActiveRecordGenerator < Rails::Generators::Base
|
7
|
+
class_option :database, type: :string, aliases: "-d"
|
8
|
+
|
9
|
+
def boom
|
10
|
+
invoke "ahoy:stores:active_record_visits", nil, options
|
11
|
+
invoke "ahoy:stores:active_record_events", nil, options
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -5,17 +5,18 @@ require "active_record"
|
|
5
5
|
require "rails/generators/active_record"
|
6
6
|
|
7
7
|
module Ahoy
|
8
|
-
module
|
8
|
+
module Stores
|
9
9
|
module Generators
|
10
|
-
class
|
10
|
+
class ActiveRecordVisitsGenerator < Rails::Generators::Base
|
11
11
|
include Rails::Generators::Migration
|
12
|
-
|
13
12
|
source_root File.expand_path("../templates", __FILE__)
|
14
13
|
|
14
|
+
class_option :database, type: :string, aliases: "-d"
|
15
|
+
|
15
16
|
# Implement the required interface for Rails::Generators::Migration.
|
16
17
|
def self.next_migration_number(dirname) #:nodoc:
|
17
18
|
next_migration_number = current_migration_number(dirname) + 1
|
18
|
-
if ActiveRecord::Base.timestamped_migrations
|
19
|
+
if ::ActiveRecord::Base.timestamped_migrations
|
19
20
|
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
20
21
|
else
|
21
22
|
"%.3d" % next_migration_number
|
@@ -23,11 +24,18 @@ module Ahoy
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def copy_migration
|
26
|
-
|
27
|
+
unless options["database"].in?([nil, "postgresql"])
|
28
|
+
raise Thor::Error, "Unknown database option"
|
29
|
+
end
|
30
|
+
migration_template "active_record_visits_migration.rb", "db/migrate/create_visits.rb"
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_model
|
34
|
+
template "active_record_visit_model.rb", "app/models/visit.rb"
|
27
35
|
end
|
28
36
|
|
29
37
|
def create_initializer
|
30
|
-
template "
|
38
|
+
template "active_record_initializer.rb", "config/initializers/ahoy.rb"
|
31
39
|
end
|
32
40
|
|
33
41
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Stores
|
5
|
+
module Generators
|
6
|
+
class CustomGenerator < Rails::Generators::Base
|
7
|
+
source_root File.expand_path("../templates", __FILE__)
|
8
|
+
|
9
|
+
def create_initializer
|
10
|
+
template "custom_initializer.rb", "config/initializers/ahoy.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Stores
|
5
|
+
module Generators
|
6
|
+
class LogGenerator < Rails::Generators::Base
|
7
|
+
source_root File.expand_path("../templates", __FILE__)
|
8
|
+
|
9
|
+
def create_initializer
|
10
|
+
template "log_initializer.rb", "config/initializers/ahoy.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Stores
|
5
|
+
module Generators
|
6
|
+
class MongoidEventsGenerator < Rails::Generators::Base
|
7
|
+
source_root File.expand_path("../templates", __FILE__)
|
8
|
+
|
9
|
+
def generate_model
|
10
|
+
template "mongoid_event_model.rb", "app/models/ahoy/event.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_initializer
|
14
|
+
template "mongoid_initializer.rb", "config/initializers/ahoy.rb"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Stores
|
5
|
+
module Generators
|
6
|
+
class MongoidGenerator < Rails::Generators::Base
|
7
|
+
|
8
|
+
def boom
|
9
|
+
invoke "ahoy:stores:mongoid_visits"
|
10
|
+
invoke "ahoy:stores:mongoid_events"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Stores
|
5
|
+
module Generators
|
6
|
+
class MongoidVisitsGenerator < Rails::Generators::Base
|
7
|
+
source_root File.expand_path("../templates", __FILE__)
|
8
|
+
|
9
|
+
def generate_model
|
10
|
+
template "mongoid_visit_model.rb", "app/models/visit.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_initializer
|
14
|
+
template "mongoid_initializer.rb", "config/initializers/ahoy.rb"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :ahoy_events, id: false do |t|
|
4
|
+
t.uuid :id, primary_key: true
|
5
|
+
t.uuid :visit_id
|
6
|
+
|
7
|
+
# user
|
8
|
+
t.integer :user_id
|
9
|
+
# add t.string :user_type if polymorphic
|
10
|
+
|
11
|
+
t.string :name
|
12
|
+
t.<% if options["database"] == "postgresql" %>json<% else %>text<% end %> :properties
|
13
|
+
t.timestamp :time
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :ahoy_events, [:visit_id]
|
17
|
+
add_index :ahoy_events, [:user_id]
|
18
|
+
add_index :ahoy_events, [:time]
|
19
|
+
end
|
20
|
+
end
|
data/lib/generators/ahoy/{templates/install.rb → stores/templates/active_record_visits_migration.rb}
RENAMED
@@ -1,9 +1,8 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
2
|
def change
|
3
|
-
create_table :visits do |t|
|
4
|
-
|
5
|
-
t.
|
6
|
-
t.string :visitor_token
|
3
|
+
create_table :visits, id: false do |t|
|
4
|
+
t.uuid :id, primary_key: true
|
5
|
+
t.uuid :visitor_id
|
7
6
|
|
8
7
|
# the rest are recommended but optional
|
9
8
|
# simply remove the columns you don't want
|
@@ -16,7 +15,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
|
|
16
15
|
|
17
16
|
# user
|
18
17
|
t.integer :user_id
|
19
|
-
t.string :user_type
|
18
|
+
# add t.string :user_type if polymorphic
|
20
19
|
|
21
20
|
# traffic source
|
22
21
|
t.string :referring_domain
|
@@ -44,10 +43,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration
|
|
44
43
|
# t.string :app_version
|
45
44
|
# t.string :os_version
|
46
45
|
|
47
|
-
t.timestamp :
|
46
|
+
t.timestamp :started_at
|
48
47
|
end
|
49
48
|
|
50
|
-
add_index :visits, [:
|
51
|
-
add_index :visits, [:user_id, :user_type]
|
49
|
+
add_index :visits, [:user_id]
|
52
50
|
end
|
53
51
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Visit
|
2
|
+
include Mongoid::Document
|
3
|
+
|
4
|
+
# associations
|
5
|
+
belongs_to :user
|
6
|
+
|
7
|
+
# required
|
8
|
+
field :visitor_id, type: BSON::Binary
|
9
|
+
|
10
|
+
# the rest are recommended but optional
|
11
|
+
# simply remove the columns you don't want
|
12
|
+
|
13
|
+
# standard
|
14
|
+
field :ip, type: String
|
15
|
+
field :user_agent, type: String
|
16
|
+
field :referrer, type: String
|
17
|
+
field :landing_page, type: String
|
18
|
+
|
19
|
+
# traffic source
|
20
|
+
field :referring_domain, type: String
|
21
|
+
field :search_keyword, type: String
|
22
|
+
|
23
|
+
# technology
|
24
|
+
field :browser, type: String
|
25
|
+
field :os, type: String
|
26
|
+
field :device_type, type: String
|
27
|
+
|
28
|
+
# location
|
29
|
+
field :country, type: String
|
30
|
+
field :region, type: String
|
31
|
+
field :city, type: String
|
32
|
+
|
33
|
+
# utm parameters
|
34
|
+
field :utm_source, type: String
|
35
|
+
field :utm_medium, type: String
|
36
|
+
field :utm_term, type: String
|
37
|
+
field :utm_content, type: String
|
38
|
+
field :utm_campaign, type: String
|
39
|
+
|
40
|
+
field :started_at, type: Time
|
41
|
+
end
|
@@ -13,7 +13,7 @@
|
|
13
13
|
|
14
14
|
var ahoy = window.ahoy || window.Ahoy || {};
|
15
15
|
var $ = window.jQuery || window.Zepto || window.$;
|
16
|
-
var visitId, visitorId;
|
16
|
+
var visitId, visitorId, track;
|
17
17
|
var visitTtl = 4 * 60; // 4 hours
|
18
18
|
var visitorTtl = 2 * 365 * 24 * 60; // 2 years
|
19
19
|
var isReady = false;
|
@@ -21,6 +21,8 @@
|
|
21
21
|
var canStringify = typeof(JSON) !== "undefined" && typeof(JSON.stringify) !== "undefined";
|
22
22
|
var eventQueue = [];
|
23
23
|
var page = ahoy.page || window.location.pathname;
|
24
|
+
var visitsUrl = ahoy.visitsUrl || "/ahoy/visits"
|
25
|
+
var eventsUrl = ahoy.eventsUrl || "/ahoy/events"
|
24
26
|
|
25
27
|
// cookies
|
26
28
|
|
@@ -102,7 +104,7 @@
|
|
102
104
|
if (canStringify) {
|
103
105
|
$.ajax({
|
104
106
|
type: "POST",
|
105
|
-
url:
|
107
|
+
url: eventsUrl,
|
106
108
|
data: JSON.stringify([event]),
|
107
109
|
contentType: "application/json; charset=utf-8",
|
108
110
|
dataType: "json",
|
@@ -136,14 +138,21 @@
|
|
136
138
|
|
137
139
|
visitId = getCookie("ahoy_visit");
|
138
140
|
visitorId = getCookie("ahoy_visitor");
|
141
|
+
track = getCookie("ahoy_track");
|
139
142
|
|
140
|
-
if (visitId && visitorId) {
|
143
|
+
if (visitId && visitorId && !track) {
|
141
144
|
// TODO keep visit alive?
|
142
145
|
log("Active visit");
|
143
146
|
setReady();
|
144
147
|
} else {
|
145
|
-
|
146
|
-
|
148
|
+
if (track) {
|
149
|
+
destroyCookie("ahoy_track");
|
150
|
+
}
|
151
|
+
|
152
|
+
if (!visitId) {
|
153
|
+
visitId = generateId();
|
154
|
+
setCookie("ahoy_visit", visitId, visitTtl);
|
155
|
+
}
|
147
156
|
|
148
157
|
// make sure cookies are enabled
|
149
158
|
if (getCookie("ahoy_visit")) {
|
@@ -170,24 +179,26 @@
|
|
170
179
|
|
171
180
|
log(data);
|
172
181
|
|
173
|
-
$.post(
|
182
|
+
$.post(visitsUrl, data, setReady, "json");
|
174
183
|
} else {
|
175
184
|
log("Cookies disabled");
|
176
185
|
setReady();
|
177
186
|
}
|
178
187
|
}
|
179
188
|
|
180
|
-
ahoy.getVisitToken = function () {
|
189
|
+
ahoy.getVisitId = ahoy.getVisitToken = function () {
|
181
190
|
return visitId;
|
182
191
|
};
|
183
192
|
|
184
|
-
ahoy.getVisitorToken = function () {
|
193
|
+
ahoy.getVisitorId = ahoy.getVisitorToken = function () {
|
185
194
|
return visitorId;
|
186
195
|
};
|
187
196
|
|
188
197
|
ahoy.reset = function () {
|
189
198
|
destroyCookie("ahoy_visit");
|
190
199
|
destroyCookie("ahoy_visitor");
|
200
|
+
destroyCookie("ahoy_events");
|
201
|
+
destroyCookie("ahoy_track");
|
191
202
|
return true;
|
192
203
|
};
|
193
204
|
|