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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +7 -0
  3. data/CHANGELOG.md +22 -0
  4. data/CONTRIBUTING.md +40 -0
  5. data/LICENSE.txt +1 -1
  6. data/README.md +210 -489
  7. data/Rakefile +1 -0
  8. data/ahoy_matey.gemspec +6 -8
  9. data/app/controllers/ahoy/base_controller.rb +2 -6
  10. data/app/controllers/ahoy/events_controller.rb +7 -1
  11. data/app/controllers/ahoy/visits_controller.rb +7 -1
  12. data/app/jobs/ahoy/geocode_job.rb +10 -0
  13. data/app/jobs/ahoy/geocode_v2_job.rb +29 -0
  14. data/config/routes.rb +1 -1
  15. data/docs/Ahoy-2-Upgrade.md +147 -0
  16. data/docs/Data-Store-Examples.md +240 -0
  17. data/lib/ahoy.rb +30 -88
  18. data/lib/ahoy/base_store.rb +72 -0
  19. data/lib/ahoy/controller.rb +4 -10
  20. data/lib/ahoy/database_store.rb +72 -0
  21. data/lib/ahoy/engine.rb +5 -7
  22. data/lib/ahoy/model.rb +4 -26
  23. data/lib/ahoy/{properties.rb → query_methods.rb} +18 -4
  24. data/lib/ahoy/tracker.rb +60 -38
  25. data/lib/ahoy/version.rb +1 -1
  26. data/lib/ahoy/visit_properties.rb +65 -39
  27. data/lib/generators/ahoy/activerecord_generator.rb +58 -0
  28. data/lib/generators/ahoy/base_generator.rb +13 -0
  29. data/lib/generators/ahoy/install_generator.rb +44 -0
  30. data/lib/generators/ahoy/mongoid_generator.rb +20 -0
  31. data/lib/generators/ahoy/templates/active_record_event_model.rb +10 -0
  32. data/lib/generators/ahoy/{stores/templates/active_record_visits_migration.rb → templates/active_record_migration.rb} +19 -21
  33. data/lib/generators/ahoy/templates/active_record_visit_model.rb +6 -0
  34. data/lib/generators/ahoy/templates/base_store_initializer.rb +17 -0
  35. data/lib/generators/ahoy/templates/database_store_initializer.rb +5 -0
  36. data/lib/generators/ahoy/{stores/templates → templates}/mongoid_event_model.rb +4 -2
  37. data/lib/generators/ahoy/{stores/templates → templates}/mongoid_visit_model.rb +7 -3
  38. data/test/query_methods/mongoid_test.rb +23 -0
  39. data/test/{properties → query_methods}/mysql_json_test.rb +1 -1
  40. data/test/{properties → query_methods}/mysql_text_test.rb +1 -1
  41. data/test/{properties → query_methods}/postgresql_hstore_test.rb +1 -1
  42. data/test/{properties → query_methods}/postgresql_json_test.rb +1 -1
  43. data/test/{properties → query_methods}/postgresql_jsonb_test.rb +1 -1
  44. data/test/{properties → query_methods}/postgresql_text_test.rb +1 -1
  45. data/test/test_helper.rb +4 -3
  46. data/vendor/assets/javascripts/ahoy.js +551 -325
  47. metadata +67 -112
  48. data/lib/ahoy/deckhands/location_deckhand.rb +0 -49
  49. data/lib/ahoy/deckhands/request_deckhand.rb +0 -52
  50. data/lib/ahoy/deckhands/technology_deckhand.rb +0 -47
  51. data/lib/ahoy/deckhands/traffic_source_deckhand.rb +0 -22
  52. data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +0 -23
  53. data/lib/ahoy/geocode_job.rb +0 -13
  54. data/lib/ahoy/logger_silencer.rb +0 -75
  55. data/lib/ahoy/stores/active_record_store.rb +0 -61
  56. data/lib/ahoy/stores/active_record_token_store.rb +0 -114
  57. data/lib/ahoy/stores/base_store.rb +0 -88
  58. data/lib/ahoy/stores/bunny_store.rb +0 -33
  59. data/lib/ahoy/stores/fluentd_store.rb +0 -17
  60. data/lib/ahoy/stores/kafka_store.rb +0 -42
  61. data/lib/ahoy/stores/kinesis_firehose_store.rb +0 -42
  62. data/lib/ahoy/stores/log_store.rb +0 -53
  63. data/lib/ahoy/stores/mongoid_store.rb +0 -63
  64. data/lib/ahoy/stores/nats_store.rb +0 -34
  65. data/lib/ahoy/stores/nsq_store.rb +0 -36
  66. data/lib/ahoy/subscribers/active_record.rb +0 -19
  67. data/lib/ahoy/throttle.rb +0 -17
  68. data/lib/generators/ahoy/stores/active_record_events_generator.rb +0 -59
  69. data/lib/generators/ahoy/stores/active_record_generator.rb +0 -16
  70. data/lib/generators/ahoy/stores/active_record_visits_generator.rb +0 -49
  71. data/lib/generators/ahoy/stores/bunny_generator.rb +0 -15
  72. data/lib/generators/ahoy/stores/custom_generator.rb +0 -15
  73. data/lib/generators/ahoy/stores/fluentd_generator.rb +0 -15
  74. data/lib/generators/ahoy/stores/kafka_generator.rb +0 -15
  75. data/lib/generators/ahoy/stores/kinesis_firehose_generator.rb +0 -15
  76. data/lib/generators/ahoy/stores/log_generator.rb +0 -15
  77. data/lib/generators/ahoy/stores/mongoid_events_generator.rb +0 -19
  78. data/lib/generators/ahoy/stores/mongoid_generator.rb +0 -14
  79. data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +0 -27
  80. data/lib/generators/ahoy/stores/nats_generator.rb +0 -15
  81. data/lib/generators/ahoy/stores/nsq_generator.rb +0 -15
  82. data/lib/generators/ahoy/stores/templates/active_record_event_model.rb +0 -12
  83. data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +0 -20
  84. data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +0 -3
  85. data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +0 -4
  86. data/lib/generators/ahoy/stores/templates/bunny_initializer.rb +0 -9
  87. data/lib/generators/ahoy/stores/templates/custom_initializer.rb +0 -10
  88. data/lib/generators/ahoy/stores/templates/fluentd_initializer.rb +0 -3
  89. data/lib/generators/ahoy/stores/templates/kafka_initializer.rb +0 -9
  90. data/lib/generators/ahoy/stores/templates/kinesis_firehose_initializer.rb +0 -17
  91. data/lib/generators/ahoy/stores/templates/log_initializer.rb +0 -3
  92. data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +0 -3
  93. data/lib/generators/ahoy/stores/templates/nats_initializer.rb +0 -9
  94. data/lib/generators/ahoy/stores/templates/nsq_initializer.rb +0 -9
  95. data/test/visit_properties_test.rb +0 -44
data/lib/ahoy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ahoy
2
- VERSION = "1.6.1"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -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
- REQUEST_KEYS = [:ip, :user_agent, :referrer, :landing_page, :platform, :app_version, :os_version, :screen_height, :screen_width]
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, options = {})
9
+ def initialize(request, api:)
18
10
  @request = request
19
- @options = options
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 [](key)
23
- send(key)
16
+ def generate
17
+ @generate ||= request_properties.merge(tech_properties).merge(traffic_properties).merge(utm_properties)
24
18
  end
25
19
 
26
- def keys
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 to_hash
35
- keys.inject({}) { |memo, key| memo[key] = send(key); memo }
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
- protected
33
+ def traffic_properties
34
+ # cache for performance
35
+ @@referrer_parser ||= RefererParser::Parser.new
39
36
 
40
- def request_deckhand
41
- @request_deckhand ||= Deckhands::RequestDeckhand.new(@request, @options)
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 traffic_source_deckhand
45
- @traffic_source_deckhand ||= Deckhands::TrafficSourceDeckhand.new(request_deckhand.referrer)
46
- end
43
+ def tech_properties
44
+ # cache for performance
45
+ @@user_agent_parser ||= UserAgentParser::Parser.new
47
46
 
48
- def utm_parameter_deckhand
49
- @utm_parameter_deckhand ||= Deckhands::UtmParameterDeckhand.new(request_deckhand.landing_page, request_deckhand.params)
50
- end
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
- def technology_deckhand
53
- @technology_deckhand ||= Deckhands::TechnologyDeckhand.new(request_deckhand.user_agent)
65
+ {
66
+ browser: agent.name,
67
+ os: agent.os.name,
68
+ device_type: device_type,
69
+ }
54
70
  end
55
71
 
56
- def location_deckhand
57
- @location_deckhand ||= Deckhands::LocationDeckhand.new(request_deckhand.ip)
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
- create_table :visits do |t|
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 the columns you don't want
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 :visits, [:visit_token], unique: true
55
- add_index :visits, [:user_id]
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
@@ -0,0 +1,6 @@
1
+ class Ahoy::Visit < <%= rails5? ? "ApplicationRecord" : "ActiveRecord::Base" %>
2
+ self.table_name = "ahoy_visits"
3
+
4
+ has_many :events, class_name: "Ahoy::Event"
5
+ belongs_to :user<%= rails5? ? ", optional: true" : nil %>
6
+ end
@@ -0,0 +1,17 @@
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
@@ -0,0 +1,5 @@
1
+ class Ahoy::Store < Ahoy::DatabaseStore
2
+ end
3
+
4
+ # set to true for JavaScript tracking
5
+ 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<%= 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
- belongs_to :user
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 :visitor_id, type: <%= @visitor_id_type %>
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