ahoy_matey 0.3.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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