ahoy_matey 1.6.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE.md +7 -0
- data/CHANGELOG.md +22 -0
- data/CONTRIBUTING.md +40 -0
- data/LICENSE.txt +1 -1
- data/README.md +210 -489
- data/Rakefile +1 -0
- data/ahoy_matey.gemspec +6 -8
- data/app/controllers/ahoy/base_controller.rb +2 -6
- data/app/controllers/ahoy/events_controller.rb +7 -1
- data/app/controllers/ahoy/visits_controller.rb +7 -1
- data/app/jobs/ahoy/geocode_job.rb +10 -0
- data/app/jobs/ahoy/geocode_v2_job.rb +29 -0
- data/config/routes.rb +1 -1
- data/docs/Ahoy-2-Upgrade.md +147 -0
- data/docs/Data-Store-Examples.md +240 -0
- data/lib/ahoy.rb +30 -88
- data/lib/ahoy/base_store.rb +72 -0
- data/lib/ahoy/controller.rb +4 -10
- data/lib/ahoy/database_store.rb +72 -0
- data/lib/ahoy/engine.rb +5 -7
- data/lib/ahoy/model.rb +4 -26
- data/lib/ahoy/{properties.rb → query_methods.rb} +18 -4
- data/lib/ahoy/tracker.rb +60 -38
- data/lib/ahoy/version.rb +1 -1
- data/lib/ahoy/visit_properties.rb +65 -39
- data/lib/generators/ahoy/activerecord_generator.rb +58 -0
- data/lib/generators/ahoy/base_generator.rb +13 -0
- data/lib/generators/ahoy/install_generator.rb +44 -0
- data/lib/generators/ahoy/mongoid_generator.rb +20 -0
- data/lib/generators/ahoy/templates/active_record_event_model.rb +10 -0
- data/lib/generators/ahoy/{stores/templates/active_record_visits_migration.rb → templates/active_record_migration.rb} +19 -21
- data/lib/generators/ahoy/templates/active_record_visit_model.rb +6 -0
- data/lib/generators/ahoy/templates/base_store_initializer.rb +17 -0
- data/lib/generators/ahoy/templates/database_store_initializer.rb +5 -0
- data/lib/generators/ahoy/{stores/templates → templates}/mongoid_event_model.rb +4 -2
- data/lib/generators/ahoy/{stores/templates → templates}/mongoid_visit_model.rb +7 -3
- data/test/query_methods/mongoid_test.rb +23 -0
- data/test/{properties → query_methods}/mysql_json_test.rb +1 -1
- data/test/{properties → query_methods}/mysql_text_test.rb +1 -1
- data/test/{properties → query_methods}/postgresql_hstore_test.rb +1 -1
- data/test/{properties → query_methods}/postgresql_json_test.rb +1 -1
- data/test/{properties → query_methods}/postgresql_jsonb_test.rb +1 -1
- data/test/{properties → query_methods}/postgresql_text_test.rb +1 -1
- data/test/test_helper.rb +4 -3
- data/vendor/assets/javascripts/ahoy.js +551 -325
- metadata +67 -112
- data/lib/ahoy/deckhands/location_deckhand.rb +0 -49
- data/lib/ahoy/deckhands/request_deckhand.rb +0 -52
- data/lib/ahoy/deckhands/technology_deckhand.rb +0 -47
- data/lib/ahoy/deckhands/traffic_source_deckhand.rb +0 -22
- data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +0 -23
- data/lib/ahoy/geocode_job.rb +0 -13
- data/lib/ahoy/logger_silencer.rb +0 -75
- data/lib/ahoy/stores/active_record_store.rb +0 -61
- data/lib/ahoy/stores/active_record_token_store.rb +0 -114
- data/lib/ahoy/stores/base_store.rb +0 -88
- data/lib/ahoy/stores/bunny_store.rb +0 -33
- data/lib/ahoy/stores/fluentd_store.rb +0 -17
- data/lib/ahoy/stores/kafka_store.rb +0 -42
- data/lib/ahoy/stores/kinesis_firehose_store.rb +0 -42
- data/lib/ahoy/stores/log_store.rb +0 -53
- data/lib/ahoy/stores/mongoid_store.rb +0 -63
- data/lib/ahoy/stores/nats_store.rb +0 -34
- data/lib/ahoy/stores/nsq_store.rb +0 -36
- data/lib/ahoy/subscribers/active_record.rb +0 -19
- data/lib/ahoy/throttle.rb +0 -17
- data/lib/generators/ahoy/stores/active_record_events_generator.rb +0 -59
- data/lib/generators/ahoy/stores/active_record_generator.rb +0 -16
- data/lib/generators/ahoy/stores/active_record_visits_generator.rb +0 -49
- data/lib/generators/ahoy/stores/bunny_generator.rb +0 -15
- data/lib/generators/ahoy/stores/custom_generator.rb +0 -15
- data/lib/generators/ahoy/stores/fluentd_generator.rb +0 -15
- data/lib/generators/ahoy/stores/kafka_generator.rb +0 -15
- data/lib/generators/ahoy/stores/kinesis_firehose_generator.rb +0 -15
- data/lib/generators/ahoy/stores/log_generator.rb +0 -15
- data/lib/generators/ahoy/stores/mongoid_events_generator.rb +0 -19
- data/lib/generators/ahoy/stores/mongoid_generator.rb +0 -14
- data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +0 -27
- data/lib/generators/ahoy/stores/nats_generator.rb +0 -15
- data/lib/generators/ahoy/stores/nsq_generator.rb +0 -15
- data/lib/generators/ahoy/stores/templates/active_record_event_model.rb +0 -12
- data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +0 -20
- data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +0 -3
- data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +0 -4
- data/lib/generators/ahoy/stores/templates/bunny_initializer.rb +0 -9
- data/lib/generators/ahoy/stores/templates/custom_initializer.rb +0 -10
- data/lib/generators/ahoy/stores/templates/fluentd_initializer.rb +0 -3
- data/lib/generators/ahoy/stores/templates/kafka_initializer.rb +0 -9
- data/lib/generators/ahoy/stores/templates/kinesis_firehose_initializer.rb +0 -17
- data/lib/generators/ahoy/stores/templates/log_initializer.rb +0 -3
- data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +0 -3
- data/lib/generators/ahoy/stores/templates/nats_initializer.rb +0 -9
- data/lib/generators/ahoy/stores/templates/nsq_initializer.rb +0 -9
- data/test/visit_properties_test.rb +0 -44
data/lib/ahoy/version.rb
CHANGED
@@ -1,60 +1,86 @@
|
|
1
|
+
require "browser"
|
2
|
+
require "referer-parser"
|
3
|
+
require "user_agent_parser"
|
4
|
+
|
1
5
|
module Ahoy
|
2
6
|
class VisitProperties
|
3
|
-
|
4
|
-
TRAFFIC_SOURCE_KEYS = [:referring_domain, :search_keyword]
|
5
|
-
UTM_PARAMETER_KEYS = [:utm_source, :utm_medium, :utm_term, :utm_content, :utm_campaign]
|
6
|
-
TECHNOLOGY_KEYS = [:browser, :os, :device_type]
|
7
|
-
LOCATION_KEYS = [:country, :region, :city, :postal_code, :latitude, :longitude]
|
8
|
-
|
9
|
-
KEYS = REQUEST_KEYS + TRAFFIC_SOURCE_KEYS + UTM_PARAMETER_KEYS + TECHNOLOGY_KEYS + LOCATION_KEYS
|
10
|
-
|
11
|
-
delegate(*REQUEST_KEYS, to: :request_deckhand)
|
12
|
-
delegate(*TRAFFIC_SOURCE_KEYS, to: :traffic_source_deckhand)
|
13
|
-
delegate(*(UTM_PARAMETER_KEYS + [:landing_params]), to: :utm_parameter_deckhand)
|
14
|
-
delegate(*TECHNOLOGY_KEYS, to: :technology_deckhand)
|
15
|
-
delegate(*LOCATION_KEYS, to: :location_deckhand)
|
7
|
+
attr_reader :request, :params, :referrer, :landing_page
|
16
8
|
|
17
|
-
def initialize(request,
|
9
|
+
def initialize(request, api:)
|
18
10
|
@request = request
|
19
|
-
@
|
11
|
+
@params = request.params
|
12
|
+
@referrer = api ? params["referrer"] : request.referer
|
13
|
+
@landing_page = api ? params["landing_page"] : request.original_url
|
20
14
|
end
|
21
15
|
|
22
|
-
def
|
23
|
-
|
16
|
+
def generate
|
17
|
+
@generate ||= request_properties.merge(tech_properties).merge(traffic_properties).merge(utm_properties)
|
24
18
|
end
|
25
19
|
|
26
|
-
|
27
|
-
if Ahoy.geocode == true # no location keys for :async
|
28
|
-
KEYS
|
29
|
-
else
|
30
|
-
KEYS - LOCATION_KEYS
|
31
|
-
end
|
32
|
-
end
|
20
|
+
private
|
33
21
|
|
34
|
-
def
|
35
|
-
|
22
|
+
def utm_properties
|
23
|
+
landing_uri = Addressable::URI.parse(landing_page) rescue nil
|
24
|
+
landing_params = (landing_uri && landing_uri.query_values) || {}
|
25
|
+
|
26
|
+
props = {}
|
27
|
+
%w(utm_source utm_medium utm_term utm_content utm_campaign).each do |name|
|
28
|
+
props[name.to_sym] = params[name] || landing_params[name]
|
29
|
+
end
|
30
|
+
props
|
36
31
|
end
|
37
32
|
|
38
|
-
|
33
|
+
def traffic_properties
|
34
|
+
# cache for performance
|
35
|
+
@@referrer_parser ||= RefererParser::Parser.new
|
39
36
|
|
40
|
-
|
41
|
-
|
37
|
+
{
|
38
|
+
referring_domain: (Addressable::URI.parse(referrer).host.first(255) rescue nil),
|
39
|
+
search_keyword: (@@referrer_parser.parse(@referrer)[:term][0..255] rescue nil).presence
|
40
|
+
}
|
42
41
|
end
|
43
42
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
43
|
+
def tech_properties
|
44
|
+
# cache for performance
|
45
|
+
@@user_agent_parser ||= UserAgentParser::Parser.new
|
47
46
|
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
user_agent = request.user_agent
|
48
|
+
agent = @@user_agent_parser.parse(user_agent)
|
49
|
+
browser = Browser.new(user_agent)
|
50
|
+
device_type =
|
51
|
+
if browser.bot?
|
52
|
+
"Bot"
|
53
|
+
elsif browser.device.tv?
|
54
|
+
"TV"
|
55
|
+
elsif browser.device.console?
|
56
|
+
"Console"
|
57
|
+
elsif browser.device.tablet?
|
58
|
+
"Tablet"
|
59
|
+
elsif browser.device.mobile?
|
60
|
+
"Mobile"
|
61
|
+
else
|
62
|
+
"Desktop"
|
63
|
+
end
|
51
64
|
|
52
|
-
|
53
|
-
|
65
|
+
{
|
66
|
+
browser: agent.name,
|
67
|
+
os: agent.os.name,
|
68
|
+
device_type: device_type,
|
69
|
+
}
|
54
70
|
end
|
55
71
|
|
56
|
-
def
|
57
|
-
|
72
|
+
def request_properties
|
73
|
+
{
|
74
|
+
ip: request.remote_ip,
|
75
|
+
user_agent: request.user_agent,
|
76
|
+
referrer: referrer,
|
77
|
+
landing_page: landing_page,
|
78
|
+
platform: params["platform"],
|
79
|
+
app_version: params["app_version"],
|
80
|
+
os_version: params["os_version"],
|
81
|
+
screen_height: params["screen_height"],
|
82
|
+
screen_width: params["screen_width"]
|
83
|
+
}
|
58
84
|
end
|
59
85
|
end
|
60
86
|
end
|
@@ -0,0 +1,58 @@
|
|
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 Generators
|
9
|
+
class ActiverecordGenerator < Rails::Generators::Base
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
source_root File.expand_path("../templates", __FILE__)
|
12
|
+
|
13
|
+
class_option :database, type: :string, aliases: "-d"
|
14
|
+
|
15
|
+
# Implement the required interface for Rails::Generators::Migration.
|
16
|
+
def self.next_migration_number(dirname) #:nodoc:
|
17
|
+
next_migration_number = current_migration_number(dirname) + 1
|
18
|
+
if ::ActiveRecord::Base.timestamped_migrations
|
19
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
20
|
+
else
|
21
|
+
"%.3d" % next_migration_number
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def copy_templates
|
26
|
+
template "database_store_initializer.rb", "config/initializers/ahoy.rb"
|
27
|
+
template "active_record_visit_model.rb", "app/models/ahoy/visit.rb"
|
28
|
+
template "active_record_event_model.rb", "app/models/ahoy/event.rb"
|
29
|
+
migration_template "active_record_migration.rb", "db/migrate/create_ahoy_visits_and_events.rb", migration_version: migration_version
|
30
|
+
migrate_command = rails5? ? "rails" : "rake"
|
31
|
+
puts "\nAlmost set! Last, run:\n\n #{migrate_command} db:migrate"
|
32
|
+
end
|
33
|
+
|
34
|
+
def properties_type
|
35
|
+
# use connection_config instead of connection.adapter
|
36
|
+
# so database connection isn't needed
|
37
|
+
case ActiveRecord::Base.connection_config[:adapter].to_s
|
38
|
+
when /postg/i # postgres, postgis
|
39
|
+
"jsonb"
|
40
|
+
when /mysql/i
|
41
|
+
"json"
|
42
|
+
else
|
43
|
+
"text"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def rails5?
|
48
|
+
Rails::VERSION::MAJOR >= 5
|
49
|
+
end
|
50
|
+
|
51
|
+
def migration_version
|
52
|
+
if rails5?
|
53
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Generators
|
5
|
+
class BaseGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
def copy_templates
|
9
|
+
template "base_store_initializer.rb", "config/initializers/ahoy.rb"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
def copy_templates
|
9
|
+
activerecord = defined?(ActiveRecord)
|
10
|
+
mongoid = defined?(Mongoid)
|
11
|
+
|
12
|
+
selection =
|
13
|
+
if activerecord && mongoid
|
14
|
+
puts <<-MSG
|
15
|
+
|
16
|
+
Which data store would you like to use?
|
17
|
+
1. ActiveRecord (default)
|
18
|
+
2. Mongoid
|
19
|
+
3. Neither
|
20
|
+
MSG
|
21
|
+
|
22
|
+
ask(">")
|
23
|
+
elsif activerecord
|
24
|
+
"1"
|
25
|
+
elsif mongoid
|
26
|
+
"2"
|
27
|
+
else
|
28
|
+
"3"
|
29
|
+
end
|
30
|
+
|
31
|
+
case selection
|
32
|
+
when "", "1"
|
33
|
+
invoke "ahoy:activerecord"
|
34
|
+
when "2"
|
35
|
+
invoke "ahoy:mongoid"
|
36
|
+
when "3"
|
37
|
+
invoke "ahoy:base"
|
38
|
+
else
|
39
|
+
abort "Error: must enter a number [1-3]"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Generators
|
5
|
+
class MongoidGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
def copy_templates
|
9
|
+
template "database_store_initializer.rb", "config/initializers/ahoy.rb"
|
10
|
+
template "mongoid_visit_model.rb", "app/models/ahoy/visit.rb"
|
11
|
+
template "mongoid_event_model.rb", "app/models/ahoy/event.rb"
|
12
|
+
puts "\nAlmost set! Last, run:\n\n rake db:mongoid:create_indexes"
|
13
|
+
end
|
14
|
+
|
15
|
+
def rails5?
|
16
|
+
Rails::VERSION::MAJOR >= 5
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Ahoy::Event < <%= rails5? ? "ApplicationRecord" : "ActiveRecord::Base" %>
|
2
|
+
include Ahoy::QueryMethods
|
3
|
+
|
4
|
+
self.table_name = "ahoy_events"
|
5
|
+
|
6
|
+
belongs_to :visit
|
7
|
+
belongs_to :user<%= rails5? ? ", optional: true" : nil %><% if properties_type == "text" %>
|
8
|
+
|
9
|
+
serialize :properties, JSON<% end %>
|
10
|
+
end
|
@@ -1,40 +1,32 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
|
3
|
+
create_table :ahoy_visits do |t|
|
4
4
|
t.string :visit_token
|
5
5
|
t.string :visitor_token
|
6
6
|
|
7
7
|
# the rest are recommended but optional
|
8
|
-
# simply remove
|
8
|
+
# simply remove any you don't want
|
9
|
+
|
10
|
+
# user
|
11
|
+
t.references :user
|
9
12
|
|
10
13
|
# standard
|
11
14
|
t.string :ip
|
12
15
|
t.text :user_agent
|
13
16
|
t.text :referrer
|
14
|
-
t.text :landing_page
|
15
|
-
|
16
|
-
# user
|
17
|
-
t.integer :user_id
|
18
|
-
# add t.string :user_type if polymorphic
|
19
|
-
|
20
|
-
# traffic source
|
21
17
|
t.string :referring_domain
|
22
18
|
t.string :search_keyword
|
19
|
+
t.text :landing_page
|
23
20
|
|
24
21
|
# technology
|
25
22
|
t.string :browser
|
26
23
|
t.string :os
|
27
24
|
t.string :device_type
|
28
|
-
t.integer :screen_height
|
29
|
-
t.integer :screen_width
|
30
25
|
|
31
26
|
# location
|
32
27
|
t.string :country
|
33
28
|
t.string :region
|
34
29
|
t.string :city
|
35
|
-
t.string :postal_code
|
36
|
-
t.decimal :latitude
|
37
|
-
t.decimal :longitude
|
38
30
|
|
39
31
|
# utm parameters
|
40
32
|
t.string :utm_source
|
@@ -43,15 +35,21 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
|
|
43
35
|
t.string :utm_content
|
44
36
|
t.string :utm_campaign
|
45
37
|
|
46
|
-
# native apps
|
47
|
-
# t.string :platform
|
48
|
-
# t.string :app_version
|
49
|
-
# t.string :os_version
|
50
|
-
|
51
38
|
t.timestamp :started_at
|
52
39
|
end
|
53
40
|
|
54
|
-
add_index :
|
55
|
-
|
41
|
+
add_index :ahoy_visits, [:visit_token], unique: true
|
42
|
+
|
43
|
+
create_table :ahoy_events do |t|
|
44
|
+
t.references :visit
|
45
|
+
t.references :user
|
46
|
+
|
47
|
+
t.string :name
|
48
|
+
t.<%= properties_type %> :properties
|
49
|
+
t.timestamp :time
|
50
|
+
end
|
51
|
+
|
52
|
+
add_index :ahoy_events, [:name, :time]<% if properties_type == "jsonb" && rails5? %>
|
53
|
+
add_index :ahoy_events, "properties jsonb_path_ops", using: "gin"<% end %>
|
56
54
|
end
|
57
55
|
end
|
@@ -2,11 +2,13 @@ class Ahoy::Event
|
|
2
2
|
include Mongoid::Document
|
3
3
|
|
4
4
|
# associations
|
5
|
-
belongs_to :visit
|
6
|
-
belongs_to :user
|
5
|
+
belongs_to :visit, index: true
|
6
|
+
belongs_to :user, index: true<%= rails5? ? ", optional: true" : nil %>
|
7
7
|
|
8
8
|
# fields
|
9
9
|
field :name, type: String
|
10
10
|
field :properties, type: Hash
|
11
11
|
field :time, type: Time
|
12
|
+
|
13
|
+
index({name: 1, time: 1})
|
12
14
|
end
|
@@ -1,11 +1,13 @@
|
|
1
|
-
class Visit
|
1
|
+
class Ahoy::Visit
|
2
2
|
include Mongoid::Document
|
3
3
|
|
4
4
|
# associations
|
5
|
-
|
5
|
+
has_many :events, class_name: "Ahoy::Event"
|
6
|
+
belongs_to :user, index: true<%= rails5? ? ", optional: true" : nil %>
|
6
7
|
|
7
8
|
# required
|
8
|
-
field :
|
9
|
+
field :visit_token, type: String
|
10
|
+
field :visitor_token, type: String
|
9
11
|
|
10
12
|
# the rest are recommended but optional
|
11
13
|
# simply remove the columns you don't want
|
@@ -40,4 +42,6 @@ class Visit
|
|
40
42
|
field :utm_campaign, type: String
|
41
43
|
|
42
44
|
field :started_at, type: Time
|
45
|
+
|
46
|
+
index({visit_token: 1}, {unique: true})
|
43
47
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative "../test_helper"
|
2
|
+
|
3
|
+
Mongoid.logger.level = Logger::WARN
|
4
|
+
Mongo::Logger.logger.level = Logger::WARN
|
5
|
+
|
6
|
+
Mongoid.configure do |config|
|
7
|
+
config.connect_to("ahoy_test")
|
8
|
+
end
|
9
|
+
|
10
|
+
class MongoidEvent
|
11
|
+
include Mongoid::Document
|
12
|
+
include Ahoy::QueryMethods
|
13
|
+
|
14
|
+
field :properties, type: Hash
|
15
|
+
end
|
16
|
+
|
17
|
+
class MongoidTest < Minitest::Test
|
18
|
+
include QueryMethodsTest
|
19
|
+
|
20
|
+
def model
|
21
|
+
MongoidEvent
|
22
|
+
end
|
23
|
+
end
|