track_relay 1.2.0 → 1.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aaa3f984be840d9f657d23e543aae04eccac6c45da4877fcdaad30e8f3dc7f7a
4
- data.tar.gz: ed659de0a4cd9e7a63d50d8d6ddeb32c962c667471e4c9b56f143fcf16f0b26b
3
+ metadata.gz: 4d71fbb5a7101c3f407ba9fc7c05bf032a5718447a7cacd341647b9b794c47b2
4
+ data.tar.gz: b36598739beda0519f71b98cf906159b3857234dd9510d7aff35d1f33ecd9808
5
5
  SHA512:
6
- metadata.gz: 19dd615e0a99f54df3bedaf1eb01b61ace7a338fa04d4d77373cbb43b9e786e221ade99ea3fd52e13b82a64be705a5ec2674bdc3fb69223d84e2b25157da272b
7
- data.tar.gz: eabf9d5db4cb4da4643815b628cc7c065b6b93b4f4ad8345b2d4525a93c6ea982f7877db25fae2df294bef512a222cdfb3b25966988bd5f13f9e54ad58b70646
6
+ metadata.gz: f71946bad98b203c57850991846de8e3fb95828bc825f5d9ccb2d6b5a205d6531253087a65e77c0e4eb4981f5e95129439ecf081fc719939618daeb93b611ca0
7
+ data.tar.gz: 1b05f9d685e43a6c97e37f306aa6f0a970f1b7fe6ccb774a037a4a26043295d7464a22ec2d821425aad6eb9ac6e1e07461420f7fc8ce6875f3bec1919e3d95e1
data/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.0] - 2026-07-31
11
+
12
+ ### Added
13
+ - `TrackRelay::PageViewTracking` — opt-in automatic server-side
14
+ `page_view` emission for hosts delivering GA4 purely via the
15
+ Measurement Protocol (no client gtag). `include
16
+ TrackRelay::PageViewTracking` + `track_page_views` emits exactly one
17
+ `page_view` per full HTML page render — never for JSON/non-HTML,
18
+ turbo-frame fetches, turbo-stream responses, redirects, non-GETs, or
19
+ unsuccessful responses. A host suppression hook (`track_page_views
20
+ if: :method_name` or a proc, instance-exec'd per request) is where
21
+ tracking policy (opt-out, geo, bot checks) plugs in. Events ride the
22
+ normal `TrackRelay.track` path, so `track_gate`, subscribers, and
23
+ GA4 page-context enrichment apply unchanged.
24
+ - The builtin `page_view` event is registered directly with the
25
+ catalog (and therefore the manifest), deliberately bypassing the
26
+ DSL's GA4 reserved-name validation: that guard protects *custom*
27
+ events from shadowing gtag auto-collection, but the MP-standard
28
+ `page_view` is exactly what a server-side host must send.
29
+
10
30
  ## [1.2.0] - 2026-07-31
11
31
 
12
32
  ### Added
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module TrackRelay
6
+ # Opt-in automatic server-side `page_view` emission.
7
+ #
8
+ # Hosts that deliver GA4 purely via the Measurement Protocol (no
9
+ # client-side gtag) still need standard `page_view` events for GA4's
10
+ # page and engagement reports. Include this concern and call the
11
+ # macro:
12
+ #
13
+ # class ApplicationController < ActionController::Base
14
+ # include TrackRelay::PageViewTracking
15
+ # track_page_views
16
+ # end
17
+ #
18
+ # Events go through the normal {TrackRelay.track} path, so the host's
19
+ # `track_gate`, subscribers, and GA4 page-context enrichment apply
20
+ # unchanged.
21
+ #
22
+ # ## The builtin `page_view` catalog entry
23
+ #
24
+ # `page_view` sits in {TrackRelay::GA4_RESERVED_NAMES}, which guards
25
+ # *custom* catalog events from shadowing gtag's auto-collected ones.
26
+ # A server-side host has no auto-collection — the MP-standard
27
+ # `page_view` is exactly what it must send — so the concern registers
28
+ # the definition directly with the catalog, bypassing the DSL's
29
+ # reserved-name validation on purpose.
30
+ module PageViewTracking
31
+ extend ActiveSupport::Concern
32
+
33
+ include ControllerTracking
34
+
35
+ # Register the builtin `page_view` {EventDefinition} (no params —
36
+ # page context is attached by GA4 enrichment at delivery time).
37
+ # Idempotent so it can be re-run after test-suite `Catalog.clear!`.
38
+ #
39
+ # @return [void]
40
+ def self.register_builtin_event!
41
+ return if Catalog.defined?(:page_view)
42
+
43
+ Catalog.register(EventDefinition.new(name: :page_view))
44
+ end
45
+
46
+ included do
47
+ class_attribute :_track_relay_page_view_condition,
48
+ instance_accessor: false, instance_predicate: false, default: nil
49
+ end
50
+
51
+ class_methods do
52
+ # Enable automatic page_view emission for this controller (and
53
+ # subclasses).
54
+ #
55
+ # @param if [Symbol, Proc, nil] host-side suppression hook —
56
+ # a method name or proc evaluated in the controller instance
57
+ # per request; a falsy result suppresses the page_view.
58
+ # Tracking *policy* (opt-out, geo, bot checks) belongs to the
59
+ # host; this is where it plugs in.
60
+ # @return [void]
61
+ def track_page_views(if: nil)
62
+ self._track_relay_page_view_condition = binding.local_variable_get(:if)
63
+ PageViewTracking.register_builtin_event!
64
+ after_action :_track_relay_emit_page_view
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def _track_relay_emit_page_view
71
+ return unless _track_relay_page_view_response?
72
+ return unless _track_relay_page_view_allowed?
73
+
74
+ PageViewTracking.register_builtin_event!
75
+ track(:page_view)
76
+ end
77
+
78
+ def _track_relay_page_view_allowed?
79
+ condition = self.class._track_relay_page_view_condition
80
+ case condition
81
+ when nil then true
82
+ when Symbol then !!send(condition)
83
+ else !!instance_exec(&condition)
84
+ end
85
+ end
86
+
87
+ # Exactly one page_view per full HTML page render:
88
+ #
89
+ # - GET only (a POST that re-renders a form is not a page view)
90
+ # - successful (2xx) only — redirects land on a page that fires its
91
+ # own page_view; error pages are noise
92
+ # - `text/html` only (excludes JSON and turbo-stream's
93
+ # `text/vnd.turbo-stream.html`)
94
+ # - not a turbo-frame fetch (partial navigation within a page)
95
+ def _track_relay_page_view_response?
96
+ request.get? &&
97
+ response.successful? &&
98
+ response.media_type == "text/html" &&
99
+ request.headers["Turbo-Frame"].nil?
100
+ end
101
+ end
102
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TrackRelay
4
- VERSION = "1.2.0"
4
+ VERSION = "1.3.0"
5
5
  end
data/lib/track_relay.rb CHANGED
@@ -24,6 +24,7 @@ require "track_relay/subscribers/ahoy"
24
24
  require "track_relay/delivery_job"
25
25
  require "track_relay/dispatcher"
26
26
  require "track_relay/controller_tracking"
27
+ require "track_relay/page_view_tracking"
27
28
  require "track_relay/job_tracking"
28
29
  require "track_relay/linter"
29
30
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: track_relay
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dchuk
@@ -209,6 +209,7 @@ files:
209
209
  - lib/track_relay/job_tracking.rb
210
210
  - lib/track_relay/linter.rb
211
211
  - lib/track_relay/manifest.rb
212
+ - lib/track_relay/page_view_tracking.rb
212
213
  - lib/track_relay/railtie.rb
213
214
  - lib/track_relay/subscribers/ahoy.rb
214
215
  - lib/track_relay/subscribers/base.rb