ahoy_matey 1.5.3 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +88 -0
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +1 -1
- data/README.md +369 -380
- data/app/controllers/ahoy/base_controller.rb +15 -15
- data/app/controllers/ahoy/events_controller.rb +8 -2
- data/app/controllers/ahoy/visits_controller.rb +8 -1
- data/app/jobs/ahoy/geocode_job.rb +10 -0
- data/app/jobs/ahoy/geocode_v2_job.rb +28 -0
- data/config/routes.rb +1 -1
- data/lib/ahoy.rb +68 -86
- data/lib/ahoy/base_store.rb +97 -0
- data/lib/ahoy/controller.rb +26 -17
- data/lib/ahoy/database_store.rb +89 -0
- data/lib/ahoy/engine.rb +7 -7
- data/lib/ahoy/helper.rb +40 -0
- data/lib/ahoy/model.rb +5 -27
- data/lib/ahoy/{properties.rb → query_methods.rb} +25 -9
- data/lib/ahoy/tracker.rb +101 -42
- data/lib/ahoy/utils.rb +7 -0
- data/lib/ahoy/version.rb +1 -1
- data/lib/ahoy/visit_properties.rb +99 -37
- data/lib/generators/ahoy/activerecord_generator.rb +41 -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 +16 -0
- data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +10 -0
- data/lib/generators/ahoy/templates/active_record_migration.rb.tt +62 -0
- data/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +6 -0
- data/lib/generators/ahoy/templates/base_store_initializer.rb.tt +20 -0
- data/lib/generators/ahoy/templates/database_store_initializer.rb.tt +5 -0
- data/lib/generators/ahoy/{stores/templates/mongoid_event_model.rb → templates/mongoid_event_model.rb.tt} +4 -2
- data/lib/generators/ahoy/{stores/templates/mongoid_visit_model.rb → templates/mongoid_visit_model.rb.tt} +15 -9
- data/vendor/assets/javascripts/ahoy.js +336 -113
- metadata +54 -144
- data/.gitignore +0 -17
- data/Gemfile +0 -6
- data/Rakefile +0 -8
- data/ahoy_matey.gemspec +0 -38
- 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 -60
- data/lib/ahoy/stores/active_record_token_store.rb +0 -113
- 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 -40
- 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/subscribers/active_record.rb +0 -19
- data/lib/ahoy/throttle.rb +0 -17
- data/lib/generators/ahoy/stores/active_record_events_generator.rb +0 -53
- data/lib/generators/ahoy/stores/active_record_generator.rb +0 -16
- data/lib/generators/ahoy/stores/active_record_visits_generator.rb +0 -43
- 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/templates/active_record_event_model.rb +0 -12
- data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +0 -19
- 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/active_record_visits_migration.rb +0 -57
- 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/test/properties/mysql_json_test.rb +0 -18
- data/test/properties/mysql_text_test.rb +0 -19
- data/test/properties/postgresql_hstore_test.rb +0 -18
- data/test/properties/postgresql_json_test.rb +0 -18
- data/test/properties/postgresql_jsonb_test.rb +0 -18
- data/test/properties/postgresql_text_test.rb +0 -19
- data/test/test_helper.rb +0 -99
- data/test/visit_properties_test.rb +0 -44
data/lib/ahoy/utils.rb
ADDED
data/lib/ahoy/version.rb
CHANGED
@@ -1,60 +1,122 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "device_detector"
|
3
|
+
require "uri"
|
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
|
7
|
+
attr_reader :request, :params, :referrer, :landing_page
|
10
8
|
|
11
|
-
|
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)
|
16
|
-
|
17
|
-
def initialize(request, options = {})
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
20
|
+
private
|
21
|
+
|
22
|
+
def utm_properties
|
23
|
+
landing_params = {}
|
24
|
+
begin
|
25
|
+
landing_uri = URI.parse(landing_page)
|
26
|
+
# could also use Rack::Utils.parse_nested_query
|
27
|
+
landing_params = CGI.parse(landing_uri.query) if landing_uri
|
28
|
+
rescue
|
29
|
+
# do nothing
|
30
|
+
end
|
31
|
+
|
32
|
+
props = {}
|
33
|
+
%w(utm_source utm_medium utm_term utm_content utm_campaign).each do |name|
|
34
|
+
props[name.to_sym] = params[name] || landing_params[name].try(:first)
|
31
35
|
end
|
36
|
+
props
|
32
37
|
end
|
33
38
|
|
34
|
-
def
|
35
|
-
|
39
|
+
def traffic_properties
|
40
|
+
uri = URI.parse(referrer) rescue nil
|
41
|
+
{
|
42
|
+
referring_domain: uri.try(:host).try(:first, 255)
|
43
|
+
}
|
36
44
|
end
|
37
45
|
|
38
|
-
|
46
|
+
def tech_properties
|
47
|
+
if Ahoy.user_agent_parser == :device_detector
|
48
|
+
client = DeviceDetector.new(request.user_agent)
|
49
|
+
device_type =
|
50
|
+
case client.device_type
|
51
|
+
when "smartphone"
|
52
|
+
"Mobile"
|
53
|
+
when "tv"
|
54
|
+
"TV"
|
55
|
+
else
|
56
|
+
client.device_type.try(:titleize)
|
57
|
+
end
|
39
58
|
|
40
|
-
|
41
|
-
|
42
|
-
|
59
|
+
{
|
60
|
+
browser: client.name,
|
61
|
+
os: client.os_name,
|
62
|
+
device_type: device_type
|
63
|
+
}
|
64
|
+
else
|
65
|
+
raise "Add browser to your Gemfile to use legacy user agent parsing" unless defined?(Browser)
|
66
|
+
raise "Add user_agent_parser to your Gemfile to use legacy user agent parsing" unless defined?(UserAgentParser)
|
43
67
|
|
44
|
-
|
45
|
-
|
46
|
-
|
68
|
+
# cache for performance
|
69
|
+
@@user_agent_parser ||= UserAgentParser::Parser.new
|
70
|
+
|
71
|
+
user_agent = request.user_agent
|
72
|
+
agent = @@user_agent_parser.parse(user_agent)
|
73
|
+
browser = Browser.new(user_agent)
|
74
|
+
device_type =
|
75
|
+
if browser.bot?
|
76
|
+
"Bot"
|
77
|
+
elsif browser.device.tv?
|
78
|
+
"TV"
|
79
|
+
elsif browser.device.console?
|
80
|
+
"Console"
|
81
|
+
elsif browser.device.tablet?
|
82
|
+
"Tablet"
|
83
|
+
elsif browser.device.mobile?
|
84
|
+
"Mobile"
|
85
|
+
else
|
86
|
+
"Desktop"
|
87
|
+
end
|
47
88
|
|
48
|
-
|
49
|
-
|
89
|
+
{
|
90
|
+
browser: agent.name,
|
91
|
+
os: agent.os.name,
|
92
|
+
device_type: device_type
|
93
|
+
}
|
94
|
+
end
|
50
95
|
end
|
51
96
|
|
52
|
-
|
53
|
-
|
97
|
+
# masking based on Google Analytics anonymization
|
98
|
+
# https://support.google.com/analytics/answer/2763052
|
99
|
+
def ip
|
100
|
+
ip = request.remote_ip
|
101
|
+
if ip && Ahoy.mask_ips
|
102
|
+
Ahoy.mask_ip(ip)
|
103
|
+
else
|
104
|
+
ip
|
105
|
+
end
|
54
106
|
end
|
55
107
|
|
56
|
-
def
|
57
|
-
|
108
|
+
def request_properties
|
109
|
+
{
|
110
|
+
ip: ip,
|
111
|
+
user_agent: Ahoy::Utils.ensure_utf8(request.user_agent),
|
112
|
+
referrer: referrer,
|
113
|
+
landing_page: landing_page,
|
114
|
+
platform: params["platform"],
|
115
|
+
app_version: params["app_version"],
|
116
|
+
os_version: params["os_version"],
|
117
|
+
screen_height: params["screen_height"],
|
118
|
+
screen_width: params["screen_width"]
|
119
|
+
}
|
58
120
|
end
|
59
121
|
end
|
60
122
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "rails/generators/active_record"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Generators
|
5
|
+
class ActiverecordGenerator < Rails::Generators::Base
|
6
|
+
include ActiveRecord::Generators::Migration
|
7
|
+
source_root File.join(__dir__, "templates")
|
8
|
+
|
9
|
+
class_option :database, type: :string, aliases: "-d"
|
10
|
+
|
11
|
+
def copy_templates
|
12
|
+
template "database_store_initializer.rb", "config/initializers/ahoy.rb"
|
13
|
+
template "active_record_visit_model.rb", "app/models/ahoy/visit.rb"
|
14
|
+
template "active_record_event_model.rb", "app/models/ahoy/event.rb"
|
15
|
+
migration_template "active_record_migration.rb", "db/migrate/create_ahoy_visits_and_events.rb", migration_version: migration_version
|
16
|
+
puts "\nAlmost set! Last, run:\n\n rails db:migrate"
|
17
|
+
end
|
18
|
+
|
19
|
+
def properties_type
|
20
|
+
# use connection_config instead of connection.adapter
|
21
|
+
# so database connection isn't needed
|
22
|
+
case ActiveRecord::Base.connection_config[:adapter].to_s
|
23
|
+
when /postg/i # postgres, postgis
|
24
|
+
"jsonb"
|
25
|
+
when /mysql/i
|
26
|
+
"json"
|
27
|
+
else
|
28
|
+
"text"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def rails52?
|
33
|
+
ActiveRecord::VERSION::STRING >= "5.2"
|
34
|
+
end
|
35
|
+
|
36
|
+
def migration_version
|
37
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
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.join(__dir__, "templates")
|
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.join(__dir__, "templates")
|
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,16 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
module Generators
|
5
|
+
class MongoidGenerator < Rails::Generators::Base
|
6
|
+
source_root File.join(__dir__, "templates")
|
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
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
create_table :ahoy_visits do |t|
|
4
|
+
t.string :visit_token
|
5
|
+
t.string :visitor_token
|
6
|
+
|
7
|
+
# the rest are recommended but optional
|
8
|
+
# simply remove any you don't want
|
9
|
+
|
10
|
+
# user
|
11
|
+
t.references :user
|
12
|
+
|
13
|
+
# standard
|
14
|
+
t.string :ip
|
15
|
+
t.text :user_agent
|
16
|
+
t.text :referrer
|
17
|
+
t.string :referring_domain
|
18
|
+
t.text :landing_page
|
19
|
+
|
20
|
+
# technology
|
21
|
+
t.string :browser
|
22
|
+
t.string :os
|
23
|
+
t.string :device_type
|
24
|
+
|
25
|
+
# location
|
26
|
+
t.string :country
|
27
|
+
t.string :region
|
28
|
+
t.string :city
|
29
|
+
t.float :latitude
|
30
|
+
t.float :longitude
|
31
|
+
|
32
|
+
# utm parameters
|
33
|
+
t.string :utm_source
|
34
|
+
t.string :utm_medium
|
35
|
+
t.string :utm_term
|
36
|
+
t.string :utm_content
|
37
|
+
t.string :utm_campaign
|
38
|
+
|
39
|
+
# native apps
|
40
|
+
t.string :app_version
|
41
|
+
t.string :os_version
|
42
|
+
t.string :platform
|
43
|
+
|
44
|
+
t.timestamp :started_at
|
45
|
+
end
|
46
|
+
|
47
|
+
add_index :ahoy_visits, [:visit_token], unique: true
|
48
|
+
|
49
|
+
create_table :ahoy_events do |t|
|
50
|
+
t.references :visit
|
51
|
+
t.references :user
|
52
|
+
|
53
|
+
t.string :name
|
54
|
+
t.<%= properties_type %> :properties
|
55
|
+
t.timestamp :time
|
56
|
+
end
|
57
|
+
|
58
|
+
add_index :ahoy_events, [:name, :time]<% if properties_type == "jsonb" %><% if rails52? %>
|
59
|
+
add_index :ahoy_events, :properties, using: :gin, opclass: :jsonb_path_ops<% else %>
|
60
|
+
add_index :ahoy_events, "properties jsonb_path_ops", using: "gin"<% end %><% end %>
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Ahoy::Store < Ahoy::BaseStore
|
2
|
+
def track_visit(data)
|
3
|
+
# do
|
4
|
+
end
|
5
|
+
|
6
|
+
def track_event(data)
|
7
|
+
# something
|
8
|
+
end
|
9
|
+
|
10
|
+
def geocode(data)
|
11
|
+
# amazing
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate(data)
|
15
|
+
# !!!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# set to true for JavaScript tracking
|
20
|
+
Ahoy.api = false
|
@@ -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, optional: true
|
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, optional: true
|
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
|
@@ -14,23 +16,20 @@ class Visit
|
|
14
16
|
field :ip, type: String
|
15
17
|
field :user_agent, type: String
|
16
18
|
field :referrer, type: String
|
17
|
-
field :landing_page, type: String
|
18
|
-
|
19
|
-
# traffic source
|
20
19
|
field :referring_domain, type: String
|
21
|
-
field :
|
20
|
+
field :landing_page, type: String
|
22
21
|
|
23
22
|
# technology
|
24
23
|
field :browser, type: String
|
25
24
|
field :os, type: String
|
26
25
|
field :device_type, type: String
|
27
|
-
field :screen_height, type: Integer
|
28
|
-
field :screen_width, type: Integer
|
29
26
|
|
30
27
|
# location
|
31
28
|
field :country, type: String
|
32
29
|
field :region, type: String
|
33
30
|
field :city, type: String
|
31
|
+
field :latitude, type: Float
|
32
|
+
field :longitude, type: Float
|
34
33
|
|
35
34
|
# utm parameters
|
36
35
|
field :utm_source, type: String
|
@@ -39,5 +38,12 @@ class Visit
|
|
39
38
|
field :utm_content, type: String
|
40
39
|
field :utm_campaign, type: String
|
41
40
|
|
41
|
+
# native apps
|
42
|
+
field :app_version, type: String
|
43
|
+
field :os_version, type: String
|
44
|
+
field :platform, type: String
|
45
|
+
|
42
46
|
field :started_at, type: Time
|
47
|
+
|
48
|
+
index({visit_token: 1}, {unique: true})
|
43
49
|
end
|