nano-pure-pkg 0.0.1
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.
- checksums.yaml +7 -0
- data/ahoy_matey-5.5.0/CHANGELOG.md +403 -0
- data/ahoy_matey-5.5.0/CONTRIBUTING.md +42 -0
- data/ahoy_matey-5.5.0/LICENSE.txt +22 -0
- data/ahoy_matey-5.5.0/README.md +802 -0
- data/ahoy_matey-5.5.0/app/controllers/ahoy/base_controller.rb +44 -0
- data/ahoy_matey-5.5.0/app/controllers/ahoy/events_controller.rb +51 -0
- data/ahoy_matey-5.5.0/app/controllers/ahoy/visits_controller.rb +15 -0
- data/ahoy_matey-5.5.0/config/routes.rb +10 -0
- data/ahoy_matey-5.5.0/lib/ahoy/base_store.rb +104 -0
- data/ahoy_matey-5.5.0/lib/ahoy/controller.rb +56 -0
- data/ahoy_matey-5.5.0/lib/ahoy/database_store.rb +96 -0
- data/ahoy_matey-5.5.0/lib/ahoy/engine.rb +37 -0
- data/ahoy_matey-5.5.0/lib/ahoy/geocode_v2_job.rb +31 -0
- data/ahoy_matey-5.5.0/lib/ahoy/helper.rb +40 -0
- data/ahoy_matey-5.5.0/lib/ahoy/model.rb +15 -0
- data/ahoy_matey-5.5.0/lib/ahoy/query_methods.rb +88 -0
- data/ahoy_matey-5.5.0/lib/ahoy/tracker.rb +287 -0
- data/ahoy_matey-5.5.0/lib/ahoy/utils.rb +7 -0
- data/ahoy_matey-5.5.0/lib/ahoy/version.rb +3 -0
- data/ahoy_matey-5.5.0/lib/ahoy/visit_properties.rb +122 -0
- data/ahoy_matey-5.5.0/lib/ahoy/warden.rb +5 -0
- data/ahoy_matey-5.5.0/lib/ahoy.rb +167 -0
- data/ahoy_matey-5.5.0/lib/ahoy_matey.rb +1 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/activerecord_generator.rb +59 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/base_generator.rb +13 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/install_generator.rb +44 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/mongoid_generator.rb +16 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/active_record_event_model.rb.tt +10 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/active_record_migration.rb.tt +62 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +6 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/base_store_initializer.rb.tt +25 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/database_store_initializer.rb.tt +10 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/mongoid_event_model.rb.tt +14 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/mongoid_visit_model.rb.tt +50 -0
- data/ahoy_matey-5.5.0/vendor/assets/javascripts/ahoy.js +544 -0
- data/nano-pure-pkg.gemspec +12 -0
- metadata +77 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
class BaseController < ApplicationController
|
|
3
|
+
filters = _process_action_callbacks.map(&:filter) - Ahoy.preserve_callbacks
|
|
4
|
+
skip_before_action(*filters, raise: false)
|
|
5
|
+
skip_after_action(*filters, raise: false)
|
|
6
|
+
skip_around_action(*filters, raise: false)
|
|
7
|
+
|
|
8
|
+
if respond_to?(:protect_from_forgery)
|
|
9
|
+
protect_from_forgery with: :null_session, if: -> { Ahoy.protect_from_forgery }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
before_action :verify_request_size
|
|
13
|
+
before_action :check_params
|
|
14
|
+
before_action :renew_cookies
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
def ahoy
|
|
19
|
+
@ahoy ||= Ahoy::Tracker.new(controller: self, api: true)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def check_params
|
|
23
|
+
if ahoy.send(:missing_params?)
|
|
24
|
+
logger.info "[ahoy] Missing required parameters"
|
|
25
|
+
render plain: "Missing required parameters\n", status: :bad_request
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# set proper ttl if cookie generated from JavaScript
|
|
30
|
+
# approach is not perfect, as user must reload the page
|
|
31
|
+
# for new cookie settings to take effect
|
|
32
|
+
def renew_cookies
|
|
33
|
+
set_ahoy_cookies if params[:js] && !Ahoy.api_only
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def verify_request_size
|
|
37
|
+
if request.content_length > Ahoy.max_content_length
|
|
38
|
+
logger.info "[ahoy] Payload too large"
|
|
39
|
+
status = Rack::RELEASE.to_f >= 3.1 ? :content_too_large : :payload_too_large
|
|
40
|
+
render plain: "Payload too large\n", status: status
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
class EventsController < Ahoy::BaseController
|
|
3
|
+
def create
|
|
4
|
+
events =
|
|
5
|
+
if params[:name]
|
|
6
|
+
# legacy API and AMP
|
|
7
|
+
[request.params]
|
|
8
|
+
elsif params[:events]
|
|
9
|
+
request.params[:events]
|
|
10
|
+
else
|
|
11
|
+
data =
|
|
12
|
+
if params[:events_json]
|
|
13
|
+
request.params[:events_json]
|
|
14
|
+
else
|
|
15
|
+
request.body.read
|
|
16
|
+
end
|
|
17
|
+
begin
|
|
18
|
+
ActiveSupport::JSON.decode(data)
|
|
19
|
+
rescue ActiveSupport::JSON.parse_error
|
|
20
|
+
# TODO change to nil in Ahoy 6
|
|
21
|
+
[]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
max_events_per_request = Ahoy.max_events_per_request
|
|
26
|
+
|
|
27
|
+
# check before creating any events
|
|
28
|
+
unless events.is_a?(Array) && events.first(max_events_per_request).all? { |v| v.is_a?(Hash) }
|
|
29
|
+
logger.info "[ahoy] Invalid parameters"
|
|
30
|
+
# :unprocessable_entity is probably more correct
|
|
31
|
+
# but keep consistent with missing parameters for now
|
|
32
|
+
render plain: "Invalid parameters\n", status: :bad_request
|
|
33
|
+
return
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
events.first(max_events_per_request).each do |event|
|
|
37
|
+
time = Time.zone.parse(event["time"]) rescue nil
|
|
38
|
+
|
|
39
|
+
# timestamp is deprecated
|
|
40
|
+
time ||= Time.zone.at(event["time"].to_f) rescue nil
|
|
41
|
+
|
|
42
|
+
options = {
|
|
43
|
+
id: event["id"],
|
|
44
|
+
time: time
|
|
45
|
+
}
|
|
46
|
+
ahoy.track event["name"], event["properties"], options
|
|
47
|
+
end
|
|
48
|
+
render json: {}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
class VisitsController < BaseController
|
|
3
|
+
def create
|
|
4
|
+
ahoy.track_visit
|
|
5
|
+
|
|
6
|
+
render json: {
|
|
7
|
+
visit_token: ahoy.visit_token,
|
|
8
|
+
visitor_token: ahoy.visitor_token,
|
|
9
|
+
# legacy
|
|
10
|
+
visit_id: ahoy.visit_token,
|
|
11
|
+
visitor_id: ahoy.visitor_token
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
class BaseStore
|
|
3
|
+
attr_writer :user
|
|
4
|
+
|
|
5
|
+
def initialize(options)
|
|
6
|
+
@user = options[:user]
|
|
7
|
+
@options = options
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def track_visit(data)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def track_event(data)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def geocode(data)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def authenticate(data)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def visit
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def user
|
|
26
|
+
@user ||= begin
|
|
27
|
+
if Ahoy.user_method.respond_to?(:call)
|
|
28
|
+
if Ahoy.user_method.arity == 1
|
|
29
|
+
Ahoy.user_method.call(controller)
|
|
30
|
+
else
|
|
31
|
+
Ahoy.user_method.call(controller, request)
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
controller.send(Ahoy.user_method) if controller.respond_to?(Ahoy.user_method, true)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def exclude?
|
|
40
|
+
(!Ahoy.track_bots && bot?) ||
|
|
41
|
+
exclude_by_method? ||
|
|
42
|
+
(defined?(Rails::HealthController) && controller.is_a?(Rails::HealthController))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def generate_id
|
|
46
|
+
Ahoy.token_generator.call
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def visit_or_create
|
|
50
|
+
visit
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
|
|
55
|
+
def bot?
|
|
56
|
+
unless defined?(@bot)
|
|
57
|
+
@bot = begin
|
|
58
|
+
if request
|
|
59
|
+
if Ahoy.user_agent_parser == :device_detector
|
|
60
|
+
detector = DeviceDetector.new(request.user_agent)
|
|
61
|
+
if Ahoy.bot_detection_version == 2
|
|
62
|
+
detector.bot? || (detector.device_type.nil? && detector.os_name.nil?)
|
|
63
|
+
else
|
|
64
|
+
detector.bot?
|
|
65
|
+
end
|
|
66
|
+
else
|
|
67
|
+
# no need to throw friendly error if browser isn't defined
|
|
68
|
+
# since will error in visit_properties
|
|
69
|
+
Browser.new(request.user_agent).bot?
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
false
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@bot
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def exclude_by_method?
|
|
81
|
+
if Ahoy.exclude_method
|
|
82
|
+
if Ahoy.exclude_method.arity == 1
|
|
83
|
+
Ahoy.exclude_method.call(controller)
|
|
84
|
+
else
|
|
85
|
+
Ahoy.exclude_method.call(controller, request)
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def request
|
|
93
|
+
@request ||= @options[:request] || controller.try(:request)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def controller
|
|
97
|
+
@controller ||= @options[:controller]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def ahoy
|
|
101
|
+
@ahoy ||= @options[:ahoy]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
module Controller
|
|
3
|
+
def self.included(base)
|
|
4
|
+
if base.respond_to?(:helper_method)
|
|
5
|
+
base.helper_method :current_visit
|
|
6
|
+
base.helper_method :ahoy
|
|
7
|
+
end
|
|
8
|
+
base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
|
|
9
|
+
base.around_action :set_ahoy_request_store, unless: -> { Ahoy.api_only }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def ahoy
|
|
13
|
+
@ahoy ||= Ahoy::Tracker.new(controller: self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def current_visit
|
|
17
|
+
ahoy.visit
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def set_ahoy_cookies
|
|
21
|
+
if Ahoy.cookies?
|
|
22
|
+
ahoy.set_visitor_cookie
|
|
23
|
+
ahoy.set_visit_cookie
|
|
24
|
+
else
|
|
25
|
+
# delete cookies if exist
|
|
26
|
+
ahoy.reset
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def track_ahoy_visit
|
|
31
|
+
defer = Ahoy.server_side_visits != true
|
|
32
|
+
|
|
33
|
+
if defer && !Ahoy.cookies?
|
|
34
|
+
# avoid calling new_visit?, which triggers a database call
|
|
35
|
+
elsif !Ahoy.cookies? && ahoy.exclude?
|
|
36
|
+
# avoid calling new_visit?, which triggers a database call
|
|
37
|
+
# may or may not be a new visit
|
|
38
|
+
Ahoy.log("Request excluded")
|
|
39
|
+
elsif ahoy.new_visit?
|
|
40
|
+
ahoy.track_visit(defer: defer)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
set_ahoy_cookies
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def set_ahoy_request_store
|
|
47
|
+
previous_value = Ahoy.instance
|
|
48
|
+
begin
|
|
49
|
+
Ahoy.instance = ahoy
|
|
50
|
+
yield
|
|
51
|
+
ensure
|
|
52
|
+
Ahoy.instance = previous_value
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
class DatabaseStore < BaseStore
|
|
3
|
+
def track_visit(data)
|
|
4
|
+
@visit = visit_model.create!(slice_data(visit_model, data))
|
|
5
|
+
rescue => e
|
|
6
|
+
raise e unless unique_exception?(e)
|
|
7
|
+
|
|
8
|
+
# so next call to visit will try to fetch from DB
|
|
9
|
+
if defined?(@visit)
|
|
10
|
+
remove_instance_variable(:@visit)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def track_event(data)
|
|
15
|
+
visit = visit_or_create(started_at: data[:time])
|
|
16
|
+
if visit
|
|
17
|
+
event = event_model.new(slice_data(event_model, data))
|
|
18
|
+
event.visit = visit
|
|
19
|
+
event.time = visit.started_at if event.time < visit.started_at
|
|
20
|
+
begin
|
|
21
|
+
event.save!
|
|
22
|
+
rescue => e
|
|
23
|
+
raise e unless unique_exception?(e)
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
Ahoy.log "Event excluded since visit not created: #{data[:visit_token]}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def geocode(data)
|
|
31
|
+
visit_token = data.delete(:visit_token)
|
|
32
|
+
data = slice_data(visit_model, data)
|
|
33
|
+
if defined?(Mongoid::Document) && visit_model < Mongoid::Document
|
|
34
|
+
# upsert since visit might not be found due to eventual consistency
|
|
35
|
+
visit_model.where(visit_token: visit_token).find_one_and_update({"$set": data}, {upsert: true})
|
|
36
|
+
elsif visit
|
|
37
|
+
visit.update!(data)
|
|
38
|
+
else
|
|
39
|
+
Ahoy.log "Visit for geocode not found: #{visit_token}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def authenticate(_)
|
|
44
|
+
if visit && visit.respond_to?(:user) && !visit.user
|
|
45
|
+
begin
|
|
46
|
+
visit.user = user
|
|
47
|
+
visit.save!
|
|
48
|
+
rescue ActiveRecord::AssociationTypeMismatch
|
|
49
|
+
# do nothing
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def visit
|
|
55
|
+
unless defined?(@visit)
|
|
56
|
+
if ahoy.send(:existing_visit_token) || ahoy.instance_variable_get(:@visit_token)
|
|
57
|
+
# find_by raises error by default with Mongoid when not found
|
|
58
|
+
@visit = visit_model.where(visit_token: ahoy.visit_token).take if ahoy.visit_token
|
|
59
|
+
elsif !Ahoy.cookies? && ahoy.visitor_token
|
|
60
|
+
@visit = visit_model.where(visitor_token: ahoy.visitor_token).where(started_at: Ahoy.visit_duration.ago..).order(started_at: :desc).first
|
|
61
|
+
else
|
|
62
|
+
@visit = nil
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
@visit
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# if we don't have a visit, let's try to create one first
|
|
69
|
+
def visit_or_create(started_at: nil)
|
|
70
|
+
ahoy.track_visit(started_at: started_at) if !visit && Ahoy.server_side_visits
|
|
71
|
+
visit
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
protected
|
|
75
|
+
|
|
76
|
+
def visit_model
|
|
77
|
+
::Ahoy::Visit
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def event_model
|
|
81
|
+
::Ahoy::Event
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def slice_data(model, data)
|
|
85
|
+
column_names = model.try(:column_names) || model.attribute_names
|
|
86
|
+
data.slice(*column_names.map(&:to_sym)).select { |_, v| !v.nil? }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def unique_exception?(e)
|
|
90
|
+
return true if defined?(ActiveRecord::RecordNotUnique) && e.is_a?(ActiveRecord::RecordNotUnique)
|
|
91
|
+
return true if defined?(PG::UniqueViolation) && e.is_a?(PG::UniqueViolation)
|
|
92
|
+
return true if defined?(Mongo::Error::OperationFailure) && e.is_a?(Mongo::Error::OperationFailure) && e.message.include?("duplicate key error")
|
|
93
|
+
false
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
initializer "ahoy", after: "sprockets.environment" do
|
|
4
|
+
Ahoy.logger ||= Rails.logger
|
|
5
|
+
|
|
6
|
+
# allow Devise to be loaded after Ahoy
|
|
7
|
+
require "ahoy/warden" if defined?(Warden)
|
|
8
|
+
|
|
9
|
+
next unless Ahoy.quiet
|
|
10
|
+
|
|
11
|
+
# Parse PATH_INFO by assets prefix
|
|
12
|
+
AHOY_PREFIX = "/ahoy/".freeze
|
|
13
|
+
|
|
14
|
+
# Just create an alias for call in middleware
|
|
15
|
+
Rails::Rack::Logger.class_eval do
|
|
16
|
+
def call_with_quiet_ahoy(env)
|
|
17
|
+
if env["PATH_INFO"].start_with?(AHOY_PREFIX) && logger.respond_to?(:silence)
|
|
18
|
+
logger.silence do
|
|
19
|
+
call_without_quiet_ahoy(env)
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
call_without_quiet_ahoy(env)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
alias_method :call_without_quiet_ahoy, :call
|
|
26
|
+
alias_method :call, :call_with_quiet_ahoy
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# for importmap
|
|
31
|
+
initializer "ahoy.importmap" do |app|
|
|
32
|
+
if app.config.respond_to?(:assets) && defined?(Importmap) && defined?(Sprockets)
|
|
33
|
+
app.config.assets.precompile << "ahoy.js"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
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 NameError
|
|
10
|
+
raise "Add the geocoder gem to your Gemfile to use geocoding"
|
|
11
|
+
rescue => e
|
|
12
|
+
Ahoy.log "Geocode error: #{e.class.name}: #{e.message}"
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if location && location.country.present?
|
|
17
|
+
data = {
|
|
18
|
+
country: location.country,
|
|
19
|
+
country_code: location.try(:country_code).presence,
|
|
20
|
+
region: location.try(:state).presence,
|
|
21
|
+
city: location.try(:city).presence,
|
|
22
|
+
postal_code: location.try(:postal_code).presence,
|
|
23
|
+
latitude: location.try(:latitude).presence,
|
|
24
|
+
longitude: location.try(:longitude).presence
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Ahoy::Tracker.new(visit_token: visit_token).geocode(data)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
module Helper
|
|
3
|
+
def amp_event(name, properties = {})
|
|
4
|
+
url = Ahoy::Engine.routes.url_helpers.events_url(
|
|
5
|
+
url_options.slice(:host, :port, :protocol).merge(
|
|
6
|
+
name: name,
|
|
7
|
+
properties: properties,
|
|
8
|
+
screen_width: "SCREEN_WIDTH",
|
|
9
|
+
screen_height: "SCREEN_HEIGHT",
|
|
10
|
+
platform: "Web",
|
|
11
|
+
landing_page: "AMPDOC_URL",
|
|
12
|
+
referrer: "DOCUMENT_REFERRER",
|
|
13
|
+
random: "RANDOM"
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
url = "#{url}&visit_token=${clientId(ahoy_visit)}&visitor_token=${clientId(ahoy_visitor)}"
|
|
17
|
+
|
|
18
|
+
content_tag "amp-analytics" do
|
|
19
|
+
content_tag "script", type: "application/json" do
|
|
20
|
+
json_escape({
|
|
21
|
+
requests: {
|
|
22
|
+
pageview: url
|
|
23
|
+
},
|
|
24
|
+
triggers: {
|
|
25
|
+
trackPageview: {
|
|
26
|
+
on: "visible",
|
|
27
|
+
request: "pageview"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
transport: {
|
|
31
|
+
beacon: true,
|
|
32
|
+
xhrpost: true,
|
|
33
|
+
image: false
|
|
34
|
+
}
|
|
35
|
+
}.to_json).html_safe
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
module Model
|
|
3
|
+
def visitable(name = :visit, **options)
|
|
4
|
+
class_eval do
|
|
5
|
+
belongs_to(name, class_name: "Ahoy::Visit", optional: true, **options)
|
|
6
|
+
before_create :set_ahoy_visit
|
|
7
|
+
end
|
|
8
|
+
class_eval %{
|
|
9
|
+
def set_ahoy_visit
|
|
10
|
+
self.#{name} ||= Ahoy.instance.try(:visit_or_create)
|
|
11
|
+
end
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module Ahoy
|
|
2
|
+
module QueryMethods
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
module ClassMethods
|
|
6
|
+
def where_event(name, properties = {})
|
|
7
|
+
where(name: name).where_props(properties)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def where_props(properties)
|
|
11
|
+
return all if properties.empty?
|
|
12
|
+
|
|
13
|
+
adapter_name = respond_to?(:connection_db_config) ? connection_db_config.adapter.to_s : "mongoid"
|
|
14
|
+
case adapter_name
|
|
15
|
+
when "mongoid"
|
|
16
|
+
where(properties.to_h { |k, v| ["properties.#{k}", v] })
|
|
17
|
+
when /mysql|trilogy/i
|
|
18
|
+
where("JSON_CONTAINS(properties, ?, '$') = 1", properties.to_json)
|
|
19
|
+
when /postg/i
|
|
20
|
+
case columns_hash["properties"].type
|
|
21
|
+
when :hstore
|
|
22
|
+
properties.inject(all) do |relation, (k, v)|
|
|
23
|
+
if v.nil?
|
|
24
|
+
relation.where("properties -> ? IS NULL", k.to_s)
|
|
25
|
+
else
|
|
26
|
+
relation.where("properties -> ? = ?", k.to_s, v.to_s)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
when :jsonb
|
|
30
|
+
where("properties @> ?", properties.to_json)
|
|
31
|
+
else
|
|
32
|
+
where("properties::jsonb @> ?", properties.to_json)
|
|
33
|
+
end
|
|
34
|
+
when /sqlite/i
|
|
35
|
+
properties.inject(all) do |relation, (k, v)|
|
|
36
|
+
if v.nil?
|
|
37
|
+
relation.where("JSON_EXTRACT(properties, ?) IS NULL", "$.#{k}")
|
|
38
|
+
else
|
|
39
|
+
relation.where("JSON_EXTRACT(properties, ?) = ?", "$.#{k}", v.as_json)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
raise "Adapter not supported: #{adapter_name}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
alias_method :where_properties, :where_props
|
|
47
|
+
|
|
48
|
+
def group_prop(*props)
|
|
49
|
+
# like with group
|
|
50
|
+
props.flatten!
|
|
51
|
+
|
|
52
|
+
relation = all
|
|
53
|
+
adapter_name = respond_to?(:connection_db_config) ? connection_db_config.adapter.to_s : "mongoid"
|
|
54
|
+
case adapter_name
|
|
55
|
+
when "mongoid"
|
|
56
|
+
raise "Adapter not supported: #{adapter_name}"
|
|
57
|
+
when /mysql|trilogy/i
|
|
58
|
+
props.each do |prop|
|
|
59
|
+
quoted_prop = connection_pool.with_connection { |c| c.quote("$.#{prop}") }
|
|
60
|
+
relation = relation.group("JSON_UNQUOTE(JSON_EXTRACT(properties, #{quoted_prop}))")
|
|
61
|
+
end
|
|
62
|
+
when /postg/i
|
|
63
|
+
# convert to jsonb to fix
|
|
64
|
+
# could not identify an equality operator for type json
|
|
65
|
+
# and for text columns
|
|
66
|
+
column_type = columns_hash["properties"].type
|
|
67
|
+
cast = [:jsonb, :hstore].include?(column_type) ? "" : "::jsonb"
|
|
68
|
+
|
|
69
|
+
props.each do |prop|
|
|
70
|
+
quoted_prop = connection_pool.with_connection { |c| c.quote(prop) }
|
|
71
|
+
relation = relation.group("properties#{cast} -> #{quoted_prop}")
|
|
72
|
+
end
|
|
73
|
+
when /sqlite/i
|
|
74
|
+
props.each do |prop|
|
|
75
|
+
quoted_prop = connection_pool.with_connection { |c| c.quote("$.#{prop}") }
|
|
76
|
+
relation = relation.group("JSON_EXTRACT(properties, #{quoted_prop})")
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
raise "Adapter not supported: #{adapter_name}"
|
|
80
|
+
end
|
|
81
|
+
relation
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# backward compatibility
|
|
88
|
+
Ahoy::Properties = Ahoy::QueryMethods
|