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/Rakefile CHANGED
@@ -5,4 +5,5 @@ task default: :test
5
5
  Rake::TestTask.new do |t|
6
6
  t.libs << "test"
7
7
  t.pattern = "test/**/*_test.rb"
8
+ t.warning = false
8
9
  end
data/ahoy_matey.gemspec CHANGED
@@ -9,7 +9,6 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Andrew Kane"]
10
10
  spec.email = ["andrew@chartkick.com"]
11
11
  spec.summary = "Simple, powerful visit tracking for Rails"
12
- spec.description = "Simple, powerful visit tracking for Rails"
13
12
  spec.homepage = "https://github.com/ankane/ahoy"
14
13
  spec.license = "MIT"
15
14
 
@@ -18,21 +17,20 @@ Gem::Specification.new do |spec|
18
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
18
  spec.require_paths = ["lib"]
20
19
 
21
- spec.add_dependency "railties"
20
+ spec.add_dependency "railties", ">= 4.2"
22
21
  spec.add_dependency "addressable"
23
- spec.add_dependency "browser", "~> 2.0"
24
22
  spec.add_dependency "geocoder"
25
- spec.add_dependency "referer-parser", ">= 0.3.0"
23
+ spec.add_dependency "browser", "~> 2.0"
24
+ spec.add_dependency "referer-parser", ">= 0.3"
26
25
  spec.add_dependency "user_agent_parser"
27
26
  spec.add_dependency "request_store"
28
- spec.add_dependency "uuidtools"
29
- spec.add_dependency "safely_block", ">= 0.1.1"
30
- spec.add_dependency "rack-attack", "< 6"
27
+ spec.add_dependency "safely_block", ">= 0.2.1"
31
28
 
32
- spec.add_development_dependency "bundler", "~> 1.5"
29
+ spec.add_development_dependency "bundler"
33
30
  spec.add_development_dependency "rake"
34
31
  spec.add_development_dependency "minitest"
35
32
  spec.add_development_dependency "activerecord"
36
33
  spec.add_development_dependency "pg"
37
34
  spec.add_development_dependency "mysql2"
35
+ spec.add_development_dependency "mongoid"
38
36
  end
@@ -1,18 +1,14 @@
1
1
  module Ahoy
2
2
  class BaseController < ApplicationController
3
- # skip all filters except for authlogic
4
- filters = _process_action_callbacks.map(&:filter) - [:load_authlogic]
3
+ filters = _process_action_callbacks.map(&:filter) - Ahoy.preserve_callbacks
5
4
  if Rails::VERSION::MAJOR >= 5
6
5
  skip_before_action(*filters, raise: false)
7
6
  skip_after_action(*filters, raise: false)
8
7
  skip_around_action(*filters, raise: false)
9
8
  before_action :verify_request_size
10
- elsif respond_to?(:skip_action_callback)
9
+ else
11
10
  skip_action_callback *filters
12
11
  before_action :verify_request_size
13
- else
14
- skip_filter *filters
15
- before_filter :verify_request_size
16
12
  end
17
13
 
18
14
  if respond_to?(:protect_from_forgery)
@@ -8,8 +8,14 @@ module Ahoy
8
8
  elsif params[:events]
9
9
  request.params[:events]
10
10
  else
11
+ data =
12
+ if params[:events_json]
13
+ request.params[:events_json]
14
+ else
15
+ request.body.read
16
+ end
11
17
  begin
12
- ActiveSupport::JSON.decode(request.body.read)
18
+ ActiveSupport::JSON.decode(data)
13
19
  rescue ActiveSupport::JSON.parse_error
14
20
  # do nothing
15
21
  []
@@ -2,7 +2,13 @@ module Ahoy
2
2
  class VisitsController < BaseController
3
3
  def create
4
4
  ahoy.track_visit
5
- render json: {visit_id: ahoy.visit_id, visitor_id: ahoy.visitor_id}
5
+ render json: {
6
+ visit_token: ahoy.visit_token,
7
+ visitor_token: ahoy.visitor_token,
8
+ # legacy
9
+ visit_id: ahoy.visit_token,
10
+ visitor_id: ahoy.visitor_token
11
+ }
6
12
  end
7
13
  end
8
14
  end
@@ -0,0 +1,10 @@
1
+ # for smooth update from Ahoy 1 -> 2
2
+ module Ahoy
3
+ class GeocodeJob < ActiveJob::Base
4
+ queue_as { Ahoy.job_queue }
5
+
6
+ def perform(visit)
7
+ Ahoy::GeocodeV2Job.perform_now(visit.visit_token, visit.ip)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,29 @@
1
+ module Ahoy
2
+ class GeocodeV2Job < ActiveJob::Base
3
+ queue_as { Ahoy.job_queue }
4
+
5
+ def perform(visit_token, ip)
6
+ location =
7
+ begin
8
+ Geocoder.search(ip).first
9
+ rescue => e
10
+ $stderr.puts e.message
11
+ nil
12
+ end
13
+
14
+ if location
15
+ data = {
16
+ visit_token: visit_token,
17
+ country: location.try(:country).presence,
18
+ region: location.try(:state).presence,
19
+ city: location.try(:city).presence,
20
+ postal_code: location.try(:postal_code).presence,
21
+ latitude: location.try(:latitude).presence,
22
+ longitude: location.try(:longitude).presence
23
+ }
24
+
25
+ Ahoy::Tracker.new(visit_token: visit_token).geocode(data)
26
+ end
27
+ end
28
+ end
29
+ end
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  Rails.application.routes.draw do
2
- mount Ahoy::Engine => "/ahoy" if Ahoy.mount
2
+ mount Ahoy::Engine => "/ahoy" if Ahoy.api
3
3
  end
4
4
 
5
5
  Ahoy::Engine.routes.draw do
@@ -0,0 +1,147 @@
1
+ # Ahoy 2 Upgrade
2
+
3
+ Ahoy 2.0 brings a number of exciting changes:
4
+
5
+ - jQuery is no longer required
6
+ - Uses `navigator.sendBeacon` by default in supported browsers
7
+ - Simpler interface for data stores
8
+
9
+ ## How to Upgrade
10
+
11
+ Update your Gemfile:
12
+
13
+ ```ruby
14
+ gem 'ahoy_matey', '~> 2'
15
+ ```
16
+
17
+ And run:
18
+
19
+ ```sh
20
+ bundle install
21
+ ```
22
+
23
+ Add to `config/initializers/ahoy.rb`:
24
+
25
+ ```ruby
26
+ Ahoy.api = true
27
+ Ahoy.server_side_visits = false
28
+ ```
29
+
30
+ If you use `visitable`, add `class_name` to each instance:
31
+
32
+ ```ruby
33
+ visitable class_name: "Visit"
34
+ ```
35
+
36
+ Then follow the instructions for your data store.
37
+
38
+ - [ActiveRecordTokenStore](#activerecordtokenstore)
39
+ - [ActiveRecordStore](#activerecordstore)
40
+ - [MongoidStore](#mongoidstore)
41
+ - [Others](#others)
42
+
43
+ ## Data Stores
44
+
45
+ ### ActiveRecordTokenStore
46
+
47
+ In `config/initializers/ahoy.rb`, replace `Ahoy::Store` with:
48
+
49
+ ```ruby
50
+ class Ahoy::Store < Ahoy::DatabaseStore
51
+ def visit_model
52
+ Visit
53
+ end
54
+ end
55
+ ```
56
+
57
+ ### ActiveRecordStore
58
+
59
+ Add [uuidtools](https://github.com/sporkmonger/uuidtools) to your Gemfile.
60
+
61
+ In `config/initializers/ahoy.rb`, replace `Ahoy::Store` with:
62
+
63
+ ```ruby
64
+ class Ahoy::Store < Ahoy::DatabaseStore
65
+ def track_visit(data)
66
+ data[:id] = ensure_uuid(data.delete(:visit_token))
67
+ data[:visitor_id] = ensure_uuid(data.delete(:visitor_token))
68
+ super(data)
69
+ end
70
+
71
+ def track_event(data)
72
+ data[:id] = ensure_uuid(data.delete(:event_id))
73
+ super(data)
74
+ end
75
+
76
+ def visit
77
+ @visit ||= visit_model.find_by(id: ensure_uuid(ahoy.visit_token)) if ahoy.visit_token
78
+ end
79
+
80
+ def visit_model
81
+ Visit
82
+ end
83
+
84
+ UUID_NAMESPACE = UUIDTools::UUID.parse("a82ae811-5011-45ab-a728-569df7499c5f")
85
+
86
+ def ensure_uuid(id)
87
+ UUIDTools::UUID.parse(id).to_s
88
+ rescue
89
+ UUIDTools::UUID.sha1_create(UUID_NAMESPACE, id).to_s
90
+ end
91
+ end
92
+ ```
93
+
94
+ ### MongoidStore
95
+
96
+ In `config/initializers/ahoy.rb`, replace `Ahoy::Store` with:
97
+
98
+ ```ruby
99
+ class Ahoy::Store < Ahoy::DatabaseStore
100
+ def track_visit(data)
101
+ data[:_id] = binary_uuid(data.delete(:visit_token))
102
+ data[:visitor_id] = binary_uuid(data.delete(:visitor_token))
103
+ super(data)
104
+ end
105
+
106
+ def track_event(data)
107
+ data[:_id] = binary_uuid(data.delete(:event_id))
108
+ super(data)
109
+ end
110
+
111
+ def geocode(data)
112
+ visit_model.where(id: binary_uuid(ahoy.visit_token)).find_one_and_update({"$set": data}, {upsert: true})
113
+ end
114
+
115
+ def visit
116
+ @visit ||= visit_model.where(id: binary_uuid(ahoy.visit_token)).first if ahoy.visit_token
117
+ end
118
+
119
+ def visit_model
120
+ Visit
121
+ end
122
+
123
+ def binary_uuid(token)
124
+ token = token.delete("-")
125
+ if defined?(::BSON)
126
+ ::BSON::Binary.new(token, :uuid)
127
+ elsif defined?(::Moped::BSON)
128
+ ::Moped::BSON::Binary.new(:uuid, token)
129
+ else
130
+ token
131
+ end
132
+ end
133
+ end
134
+ ```
135
+
136
+ ### Others
137
+
138
+ Check out the [data store examples](Data-Store-Examples.md).
139
+
140
+ ## Throttling
141
+
142
+ Throttling was removed due to limited practical usefulness. See [instructions for adding it back](../README.md#throttling) if you need it.
143
+
144
+ ## Options
145
+
146
+ - The `mount` option was renamed to `api`
147
+ - The `track_visits_immediately` option was renamed to `server_side_visits`
@@ -0,0 +1,240 @@
1
+ # Data Store Examples
2
+
3
+ - [Kafka](#kafka)
4
+ - [RabbitMQ](#rabbitmq)
5
+ - [Fluentd](#fluentd)
6
+ - [NATS](#nats)
7
+ - [NSQ](#nsq)
8
+ - [Amazon Kinesis Firehose](#amazon-kinesis-firehose)
9
+
10
+ ### Kafka
11
+
12
+ Add [ruby-kafka](https://github.com/zendesk/ruby-kafka) to your Gemfile.
13
+
14
+ ```ruby
15
+ class Ahoy::Store < Ahoy::BaseStore
16
+ def track_visit(data)
17
+ post("ahoy_visits", data)
18
+ end
19
+
20
+ def track_event(data)
21
+ post("ahoy_events", data)
22
+ end
23
+
24
+ def geocode(data)
25
+ post("ahoy_geocode", data)
26
+ end
27
+
28
+ def authenticate(data)
29
+ post("ahoy_auth", data)
30
+ end
31
+
32
+ private
33
+
34
+ def post(topic, data)
35
+ producer.produce(data.to_json, topic: topic)
36
+ end
37
+
38
+ def producer
39
+ @producer ||= begin
40
+ client =
41
+ Kafka.new(
42
+ seed_brokers: ENV["KAFKA_URL"] || "localhost:9092",
43
+ logger: Rails.logger
44
+ )
45
+ producer = client.async_producer(delivery_interval: 3)
46
+ at_exit { producer.shutdown }
47
+ producer
48
+ end
49
+ end
50
+ end
51
+ ```
52
+
53
+ ### RabbitMQ
54
+
55
+ Add [bunny](https://github.com/ruby-amqp/bunny) to your Gemfile.
56
+
57
+ ```ruby
58
+ class Ahoy::Store < Ahoy::BaseStore
59
+ def track_visit(data)
60
+ post("ahoy_visits", data)
61
+ end
62
+
63
+ def track_event(data)
64
+ post("ahoy_events", data)
65
+ end
66
+
67
+ def geocode(data)
68
+ post("ahoy_geocode", data)
69
+ end
70
+
71
+ def authenticate(data)
72
+ post("ahoy_auth", data)
73
+ end
74
+
75
+ private
76
+
77
+ def post(topic, message)
78
+ channel.queue(topic, durable: true).publish(message.to_json)
79
+ end
80
+
81
+ def channel
82
+ @channel ||= begin
83
+ conn = Bunny.new
84
+ conn.start
85
+ conn.create_channel
86
+ end
87
+ end
88
+ end
89
+ ```
90
+
91
+ ### Fluentd
92
+
93
+ Add [fluent-logger](https://github.com/fluent/fluent-logger-ruby) to your Gemfile.
94
+
95
+ ```ruby
96
+ class Ahoy::Store < Ahoy::BaseStore
97
+ def track_visit(data)
98
+ post("ahoy_visits", data)
99
+ end
100
+
101
+ def track_event(data)
102
+ post("ahoy_events", data)
103
+ end
104
+
105
+ def geocode(data)
106
+ post("ahoy_geocode", data)
107
+ end
108
+
109
+ def authenticate(data)
110
+ post("ahoy_auth", data)
111
+ end
112
+
113
+ private
114
+
115
+ def post(topic, message)
116
+ logger.post(topic, message)
117
+ end
118
+
119
+ def logger
120
+ @logger ||= Fluent::Logger::FluentLogger.new("ahoy", host: "localhost", port: 24224)
121
+ end
122
+ end
123
+ ```
124
+
125
+ ### NATS
126
+
127
+ Add [nats-pure](https://github.com/nats-io/pure-ruby-nats) to your Gemfile.
128
+
129
+ ```ruby
130
+ class Ahoy::Store < Ahoy::BaseStore
131
+ def track_visit(data)
132
+ post("ahoy_visits", data)
133
+ end
134
+
135
+ def track_event(data)
136
+ post("ahoy_events", data)
137
+ end
138
+
139
+ def geocode(data)
140
+ post("ahoy_geocode", data)
141
+ end
142
+
143
+ def authenticate(data)
144
+ post("ahoy_auth", data)
145
+ end
146
+
147
+ private
148
+
149
+ def post(topic, data)
150
+ client.publish(topic, data.to_json)
151
+ end
152
+
153
+ def client
154
+ @client ||= begin
155
+ require "nats/io/client"
156
+ client = NATS::IO::Client.new
157
+ client.connect(servers: (ENV["NATS_URL"] || "nats://127.0.0.1:4222").split(","))
158
+ client
159
+ end
160
+ end
161
+ end
162
+ ```
163
+
164
+ ### NSQ
165
+
166
+ Add [nsq-ruby](https://github.com/wistia/nsq-ruby) to your Gemfile.
167
+
168
+ ```ruby
169
+ class Ahoy::Store < Ahoy::BaseStore
170
+ def track_visit(data)
171
+ post("ahoy_visits", data)
172
+ end
173
+
174
+ def track_event(data)
175
+ post("ahoy_events", data)
176
+ end
177
+
178
+ def geocode(data)
179
+ post("ahoy_geocode", data)
180
+ end
181
+
182
+ def authenticate(data)
183
+ post("ahoy_auth", data)
184
+ end
185
+
186
+ private
187
+
188
+ def post(topic, data)
189
+ client.write_to_topic(topic, data.to_json)
190
+ end
191
+
192
+ def client
193
+ @client ||= begin
194
+ require "nsq"
195
+ client = Nsq::Producer.new(
196
+ nsqd: ENV["NSQ_URL"] || "127.0.0.1:4150"
197
+ )
198
+ at_exit { client.terminate }
199
+ client
200
+ end
201
+ end
202
+ end
203
+ ```
204
+
205
+ ### Amazon Kinesis Firehose
206
+
207
+ Add [aws-sdk-firehose](https://github.com/aws/aws-sdk-ruby) to your Gemfile.
208
+
209
+ ```ruby
210
+ class Ahoy::Store < Ahoy::BaseStore
211
+ def track_visit(data)
212
+ post("ahoy_visits", data)
213
+ end
214
+
215
+ def track_event(data)
216
+ post("ahoy_events", data)
217
+ end
218
+
219
+ def geocode(data)
220
+ post("ahoy_geocode", data)
221
+ end
222
+
223
+ def authenticate(data)
224
+ post("ahoy_auth", data)
225
+ end
226
+
227
+ private
228
+
229
+ def post(topic, data)
230
+ client.put_record(
231
+ delivery_stream_name: topic,
232
+ record: {data: "#{data.to_json}\n"}
233
+ )
234
+ end
235
+
236
+ def client
237
+ @client ||= Aws::Firehose::Client.new
238
+ end
239
+ end
240
+ ```