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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +324 -189
  4. data/ahoy_matey.gemspec +1 -0
  5. data/app/controllers/ahoy/base_controller.rb +5 -0
  6. data/app/controllers/ahoy/events_controller.rb +9 -7
  7. data/app/controllers/ahoy/visits_controller.rb +2 -1
  8. data/lib/ahoy.rb +37 -28
  9. data/lib/ahoy/controller.rb +12 -20
  10. data/lib/ahoy/deckhands/location_deckhand.rb +39 -0
  11. data/lib/ahoy/deckhands/request_deckhand.rb +41 -0
  12. data/lib/ahoy/deckhands/technology_deckhand.rb +49 -0
  13. data/lib/ahoy/deckhands/traffic_source_deckhand.rb +24 -0
  14. data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +24 -0
  15. data/lib/ahoy/engine.rb +3 -1
  16. data/lib/ahoy/model.rb +19 -82
  17. data/lib/ahoy/stores/active_record_store.rb +63 -0
  18. data/lib/ahoy/stores/active_record_token_store.rb +113 -0
  19. data/lib/ahoy/stores/base_store.rb +65 -0
  20. data/lib/ahoy/stores/log_store.rb +47 -0
  21. data/lib/ahoy/stores/mongoid_store.rb +59 -0
  22. data/lib/ahoy/tracker.rb +108 -67
  23. data/lib/ahoy/version.rb +1 -1
  24. data/lib/ahoy/visit_properties.rb +58 -0
  25. data/lib/ahoy/warden.rb +1 -10
  26. data/lib/generators/ahoy/stores/active_record_events_generator.rb +44 -0
  27. data/lib/generators/ahoy/stores/active_record_generator.rb +17 -0
  28. data/lib/generators/ahoy/{events/active_record_generator.rb → stores/active_record_visits_generator.rb} +14 -6
  29. data/lib/generators/ahoy/stores/custom_generator.rb +16 -0
  30. data/lib/generators/ahoy/stores/log_generator.rb +16 -0
  31. data/lib/generators/ahoy/stores/mongoid_events_generator.rb +20 -0
  32. data/lib/generators/ahoy/stores/mongoid_generator.rb +16 -0
  33. data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +20 -0
  34. data/{app/models/ahoy/event.rb → lib/generators/ahoy/stores/templates/active_record_event_model.rb} +2 -2
  35. data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +20 -0
  36. data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +3 -0
  37. data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +4 -0
  38. data/lib/generators/ahoy/{templates/install.rb → stores/templates/active_record_visits_migration.rb} +6 -8
  39. data/lib/generators/ahoy/stores/templates/custom_initializer.rb +12 -0
  40. data/lib/generators/ahoy/stores/templates/log_initializer.rb +3 -0
  41. data/lib/generators/ahoy/stores/templates/mongoid_event_model.rb +12 -0
  42. data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +3 -0
  43. data/lib/generators/ahoy/stores/templates/mongoid_visit_model.rb +41 -0
  44. data/vendor/assets/javascripts/ahoy.js +19 -8
  45. metadata +45 -8
  46. data/lib/generators/ahoy/events/templates/create_events.rb +0 -20
  47. data/lib/generators/ahoy/events/templates/initializer.rb +0 -1
  48. data/lib/generators/ahoy/install_generator.rb +0 -38
@@ -1,3 +1,3 @@
1
1
  module Ahoy
2
- VERSION = "0.3.2"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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
@@ -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.track "$authenticate", {}, user: user, visit: visit
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 Events
8
+ module Stores
9
9
  module Generators
10
- class ActiveRecordGenerator < Rails::Generators::Base
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
- migration_template "create_events.rb", "db/migrate/create_ahoy_events.rb"
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 "initializer.rb", "config/initializers/ahoy.rb"
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
@@ -3,8 +3,8 @@ module Ahoy
3
3
  self.table_name = "ahoy_events"
4
4
 
5
5
  belongs_to :visit
6
- belongs_to :user, polymorphic: true
6
+ belongs_to :user
7
7
 
8
- serialize :properties, JSON
8
+ <% if options["database"] != "postgresql" %>serialize :properties, JSON<% end %>
9
9
  end
10
10
  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
@@ -0,0 +1,3 @@
1
+ class Ahoy::Store < Ahoy::Stores::ActiveRecordStore
2
+ # customize here
3
+ end
@@ -0,0 +1,4 @@
1
+ class Visit < ActiveRecord::Base
2
+ has_many :ahoy_events, class_name: "Ahoy::Event"
3
+ belongs_to :user
4
+ end
@@ -1,9 +1,8 @@
1
1
  class <%= migration_class_name %> < ActiveRecord::Migration
2
2
  def change
3
- create_table :visits do |t|
4
- # cookies
5
- t.string :visit_token
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 :created_at
46
+ t.timestamp :started_at
48
47
  end
49
48
 
50
- add_index :visits, [:visit_token], unique: true
51
- add_index :visits, [:user_id, :user_type]
49
+ add_index :visits, [:user_id]
52
50
  end
53
51
  end
@@ -0,0 +1,12 @@
1
+ class Ahoy::Store < Ahoy::Stores::BaseStore
2
+
3
+ def track_visit(options)
4
+ end
5
+
6
+ def track_event(name, properties, options)
7
+ end
8
+
9
+ def current_visit
10
+ end
11
+
12
+ end
@@ -0,0 +1,3 @@
1
+ class Ahoy::Store < Ahoy::Stores::LogStore
2
+ # customize here
3
+ end
@@ -0,0 +1,12 @@
1
+ class Ahoy::Event
2
+ include Mongoid::Document
3
+
4
+ # associations
5
+ belongs_to :visit
6
+ belongs_to :user
7
+
8
+ # fields
9
+ field :name, type: String
10
+ field :properties, type: Hash
11
+ field :time, type: Time
12
+ end
@@ -0,0 +1,3 @@
1
+ class Ahoy::Store < Ahoy::Stores::MongoidStore
2
+ # customize here
3
+ 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: "/ahoy/events",
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
- visitId = generateId();
146
- setCookie("ahoy_visit", visitId, visitTtl);
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("/ahoy/visits", data, setReady, "json");
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