omnitrack-rb 0.1.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 +7 -0
- data/.env.example +43 -0
- data/AI_GEM_SETUP.md +164 -0
- data/CHANGELOG.md +38 -0
- data/LICENSE.txt +21 -0
- data/README.md +364 -0
- data/USAGE.md +276 -0
- data/lib/generators/omnitrack/install/install_generator.rb +47 -0
- data/lib/generators/omnitrack/install/templates/README +19 -0
- data/lib/generators/omnitrack/install/templates/env.example +43 -0
- data/lib/generators/omnitrack/install/templates/initializer.rb +112 -0
- data/lib/omnitrack/adapters/base.rb +197 -0
- data/lib/omnitrack/adapters/google_ads.rb +172 -0
- data/lib/omnitrack/adapters/google_analytics.rb +97 -0
- data/lib/omnitrack/adapters/meta.rb +151 -0
- data/lib/omnitrack/adapters/snapchat.rb +100 -0
- data/lib/omnitrack/adapters/tiktok.rb +114 -0
- data/lib/omnitrack/concerns/controller.rb +66 -0
- data/lib/omnitrack/configuration.rb +109 -0
- data/lib/omnitrack/context.rb +119 -0
- data/lib/omnitrack/errors.rb +26 -0
- data/lib/omnitrack/helpers/view_helpers.rb +148 -0
- data/lib/omnitrack/jobs/tracking_job.rb +27 -0
- data/lib/omnitrack/logger.rb +122 -0
- data/lib/omnitrack/middleware/request_tracker.rb +40 -0
- data/lib/omnitrack/pipeline/dispatcher.rb +56 -0
- data/lib/omnitrack/railtie.rb +53 -0
- data/lib/omnitrack/registry.rb +59 -0
- data/lib/omnitrack/result.rb +113 -0
- data/lib/omnitrack/tasks/omnitrack.rake +39 -0
- data/lib/omnitrack/version.rb +5 -0
- data/lib/omnitrack.rb +166 -0
- metadata +207 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3159ca70c1926bd7576f69daa82b043b7712219c920ad13c956560294b52940c
|
|
4
|
+
data.tar.gz: 8f86771d6bba1b2c6531b1076c369eb464faa40406d111aacebe0610273f83df
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d49fed2d40c06182c33922feafdf8fb7969480c3468d03b95248594fb16517ce0e6f8cc3171c002bd3d8b83ce9fa097d0f61bb88bdf7156015cfa10ea50d16de
|
|
7
|
+
data.tar.gz: d95277587d7eab7b07e4cebbad26e593d96ee00dafff1ebcbcc45953d073db1255c6b27585011e61b006131078c93814d6e530e4986660a6d0c7cd692efef4db
|
data/.env.example
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# OmniTrack (omnitrack-rb) — copy to .env in your Rails app and fill real values.
|
|
2
|
+
# Never commit .env. Use "cp .env.example .env" and enable only what you need.
|
|
3
|
+
#
|
|
4
|
+
# Enable flags: set to the string "true" to turn an adapter on (see config/initializers/omnitrack.rb).
|
|
5
|
+
|
|
6
|
+
# ---------------------------------------------------------------------------
|
|
7
|
+
# Google Ads (server: Click Conversion Upload API)
|
|
8
|
+
# ---------------------------------------------------------------------------
|
|
9
|
+
GOOGLE_ADS_ENABLED=false
|
|
10
|
+
GOOGLE_ADS_CUSTOMER_ID=
|
|
11
|
+
GOOGLE_ADS_DEVELOPER_TOKEN=
|
|
12
|
+
GOOGLE_ADS_ACCESS_TOKEN=
|
|
13
|
+
GOOGLE_ADS_CONVERSION_ACTION_ID=
|
|
14
|
+
# GOOGLE_ADS_LOGIN_CUSTOMER_ID= # MCC / manager account only
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# Google Analytics 4 (server: Measurement Protocol; browser: gtag in layout)
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
GA4_ENABLED=false
|
|
20
|
+
GA4_MEASUREMENT_ID=
|
|
21
|
+
GA4_API_SECRET=
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Meta (Facebook / Instagram) Conversions API + pixel
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
META_ENABLED=false
|
|
27
|
+
META_PIXEL_ID=
|
|
28
|
+
META_ACCESS_TOKEN=
|
|
29
|
+
# META_TEST_EVENT_CODE= # optional: Events Manager test tool
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# TikTok Ads
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
TIKTOK_ENABLED=false
|
|
35
|
+
TIKTOK_PIXEL_ID=
|
|
36
|
+
TIKTOK_ACCESS_TOKEN=
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Snapchat Ads
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
SNAPCHAT_ENABLED=false
|
|
42
|
+
SNAPCHAT_PIXEL_ID=
|
|
43
|
+
SNAPCHAT_ACCESS_TOKEN=
|
data/AI_GEM_SETUP.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# OmniTrack-rb — integration guide for tools and maintainers
|
|
2
|
+
|
|
3
|
+
Use this document when **adding `omnitrack-rb` to an existing Rails 6+ app** so the project boots, initializers run, and ENV is wired with **no runtime errors** from the gem. Follow the steps in order.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Gemfile (required)
|
|
8
|
+
|
|
9
|
+
In the **host** application `Gemfile`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem "omnitrack-rb"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then run:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bundle install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Do not** add a `require` in `config/application.rb` for this gem; the Railtie loads with Rails when Bundler requires the default group.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. Install initializer (required)
|
|
26
|
+
|
|
27
|
+
From the app root:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
rails generate omnitrack:install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This creates:
|
|
34
|
+
|
|
35
|
+
- `config/initializers/omnitrack.rb`
|
|
36
|
+
|
|
37
|
+
**Boot check (must succeed):**
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
bin/rails runner "puts Omnitrack::VERSION"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If this fails, fix Bundler / Ruby version and ensure `bundle install` completed.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 3. Environment variables (no secrets in git)
|
|
48
|
+
|
|
49
|
+
The generated initializer uses `ENV[...]` for all credentials. **All adapters are off until you set each `*_ENABLED=true`.** That avoids accidental API calls with empty keys.
|
|
50
|
+
|
|
51
|
+
### Option A — `.env` in development (recommended for local work)
|
|
52
|
+
|
|
53
|
+
1. Add to **host** `Gemfile` (development and test; optional for other groups):
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
group :development, :test do
|
|
57
|
+
gem "dotenv-rails"
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2. `bundle install`
|
|
62
|
+
|
|
63
|
+
3. If the install generator did not create it, copy the example file in the app root:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cp .env.example .env
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
(Running `rails generate omnitrack:install` creates `.env.example` in the app root when it does not already exist. You can also copy from the gem: `bundle show omnitrack-rb` then open that directory’s `.env.example`.)
|
|
70
|
+
|
|
71
|
+
4. Add **host** `.gitignore` line if missing:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
.env
|
|
75
|
+
.env.*.local
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
5. In `.env`, set only the platforms you use. Start with all `*_ENABLED=false` and flip one to `true` when credentials are real.
|
|
79
|
+
|
|
80
|
+
6. **Load order:** `dotenv-rails` runs before `config/initializers/*`, so `ENV` is set when `omnitrack.rb` runs.
|
|
81
|
+
|
|
82
|
+
### Option B — Production / staging (no .env file)
|
|
83
|
+
|
|
84
|
+
Set the same variable names in your host (Kubernetes secrets, Heroku config, 1Password, Doppler, etc.). **Never** require `.env` in production; the app reads the real environment.
|
|
85
|
+
|
|
86
|
+
### Option C — `Rails credentials` (optional)
|
|
87
|
+
|
|
88
|
+
If you use encrypted credentials, read values in the initializer and assign them before `Omnitrack.configure`, or set `ENV` in `config/application.rb` from `Rails.application.credentials` — keep adapter keys out of version control.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 4. ActiveJob and async (avoid confusion)
|
|
93
|
+
|
|
94
|
+
`config/initializers/omnitrack.rb` (generated) may set `config.async = Rails.env.production?`.
|
|
95
|
+
|
|
96
|
+
- If `async` is **true** in an environment, **ActiveJob** must be usable and a queue adapter (e.g. Sidekiq) should be set for production.
|
|
97
|
+
- If you are not ready for jobs, set in the initializer:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
config.async = false
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Until `Omnitrack::Jobs::TrackingJob` and ActiveJob are loaded, the gem runs synchronously when `async` is false (safe default for first integration).
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 5. Full-stack: layout and controller (one-time)
|
|
108
|
+
|
|
109
|
+
- **Layout head** — `app/views/layouts/application.html.erb` (or your main layout) inside `<head>`:
|
|
110
|
+
|
|
111
|
+
```erb
|
|
112
|
+
<%= omnitrack_tags %>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- **Controller** — call tracking **after** a successful business outcome (e.g. order created). The Railtie already includes `Omnitrack::Controller` on `ActionController` — you can use `omnitrack_event` / `Omnitrack.track` in actions. See [USAGE.md](USAGE.md#8-concrete-examples-files-and-line-where-to-put-calls) for file-level examples.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 6. API-only apps
|
|
120
|
+
|
|
121
|
+
- No `omnitrack_tags` in layout (optional to omit).
|
|
122
|
+
- Pass `gclid`, `fbclid`, `ttclid` from the client in params or body; the middleware and `Omnitrack::Context` still apply when the request includes them.
|
|
123
|
+
- `config.mode` default `:auto` will treat `api_only` apps as **backend**-oriented (no browser pixels unless you set `:hybrid` / `:frontend` on purpose).
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 7. Common mistakes (avoid “mysterious” errors)
|
|
128
|
+
|
|
129
|
+
| Symptom | What to do |
|
|
130
|
+
|--------|------------|
|
|
131
|
+
| `uninitialized constant Omnitrack` | Run `bundle install`; ensure `config/application.rb` loads Bundler default group; boot with `bin/rails c`. |
|
|
132
|
+
| `ENV[...] is nil` in logs | Adapters with `enabled: true` but missing tokens may log or skip; set `*_ENABLED=false` until credentials exist. |
|
|
133
|
+
| Job errors when `async: true` | Set `config.async = false` until ActiveJob and queue are configured, or add queue adapter. |
|
|
134
|
+
| `.env` not applied | Add `dotenv-rails` in **development**; restart Spring/server; do not commit `.env`. |
|
|
135
|
+
| Clicks not attributed | Pass click IDs in request params/headers; ensure `config.auto_capture = true` (default in template). |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 8. Files the host app should have after a clean setup
|
|
140
|
+
|
|
141
|
+
| Path | Purpose |
|
|
142
|
+
|------|--------|
|
|
143
|
+
| `Gemfile` | `gem "omnitrack-rb"` (and `dotenv-rails` if using `.env`) |
|
|
144
|
+
| `config/initializers/omnitrack.rb` | `Omnitrack.configure` |
|
|
145
|
+
| `.env.example` | Document variable names (no secrets) — optional if committed |
|
|
146
|
+
| `.env` | Local secrets — **gitignored** |
|
|
147
|
+
| `app/views/layouts/...` | `<%= omnitrack_tags %>` for full-stack |
|
|
148
|
+
| `log/omnitrack.log` | Created at runtime when the logger writes (ensure `log/` is writable) |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 9. Smoke test after install
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
bin/rails omnitrack:status
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Enable one adapter in `.env` with valid test credentials, then (carefully) run:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bin/rails "omnitrack:test_event[purchase]"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
For day-to-day **method placement** (which file, which line pattern), use [USAGE.md](USAGE.md).
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `AI_GEM_SETUP.md` — step-by-step integration for host apps (Bundler, initializer, ENV, ActiveJob, pitfalls)
|
|
7
|
+
- `.env.example` — variable names matching the install template; install generator copies `env.example` → `.env.example` when missing
|
|
8
|
+
- `USAGE.md` — concrete ERB/Ruby examples for layout, HTML/API controllers, and a service object
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Conventional `lib/omnitrack` gem layout, loadable `require "omnitrack"`
|
|
12
|
+
- `rails generate omnitrack:install` generator at `lib/generators/omnitrack/install/`
|
|
13
|
+
- `Omnitrack::Controller` as an alias of `Omnitrack::Concerns::Controller`
|
|
14
|
+
- Default `adapter_name` for adapters uses `underscore` (snake_case) so config keys (e.g. `google_ads`) match the registry; `Omnitrack.reset!` re-registers first-party adapters in tests
|
|
15
|
+
- `activesupport` as a direct runtime dependency; explicit requires for `ActiveSupport` extensions used at load time
|
|
16
|
+
|
|
17
|
+
## [0.1.0] - 2024-01-15
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Initial release
|
|
21
|
+
- Adapter-based architecture with `Omnitrack::Adapters::Base`
|
|
22
|
+
- Google Ads adapter (Click Conversion Upload API)
|
|
23
|
+
- Google Analytics 4 adapter (Measurement Protocol)
|
|
24
|
+
- Meta adapter (Conversions API)
|
|
25
|
+
- TikTok adapter (Events API)
|
|
26
|
+
- Snapchat adapter (Conversions API)
|
|
27
|
+
- Rack middleware for automatic click ID / UTM capture
|
|
28
|
+
- Thread-local `Omnitrack::Context` for request data
|
|
29
|
+
- Structured JSON logging to `log/omnitrack.log`
|
|
30
|
+
- `Omnitrack::Result` and `Omnitrack::MultiResult` value objects
|
|
31
|
+
- ActiveJob integration for async dispatch
|
|
32
|
+
- Retry logic with exponential back-off
|
|
33
|
+
- Rails view helpers: `omnitrack_tags`, `omnitrack_event_tag`
|
|
34
|
+
- Controller concern: `Omnitrack::Controller` (alias of `Omnitrack::Concerns::Controller`)
|
|
35
|
+
- `rails generate omnitrack:install` generator
|
|
36
|
+
- Rake tasks: `omnitrack:status`, `omnitrack:test_event`, `omnitrack:rotate_log`
|
|
37
|
+
- Adapter-level error isolation (never raises into host app)
|
|
38
|
+
- SHA-256 PII hashing for all platforms
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) OmniTrack contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# OmniTrack-rb 🎯
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/omnitrack-rb)
|
|
4
|
+
[](https://www.ruby-lang.org)
|
|
5
|
+
[](https://rubyonrails.org)
|
|
6
|
+
[](LICENSE.txt)
|
|
7
|
+
|
|
8
|
+
**Production-grade, modular tracking and conversion system for Ruby on Rails.**
|
|
9
|
+
|
|
10
|
+
OmniTrack dispatches events to multiple ad/analytics platforms through a clean adapter pattern — works identically in full-stack and API-only Rails apps, never crashes your main application, and emits structured JSON logs to a dedicated file.
|
|
11
|
+
|
|
12
|
+
**Practical usage (file placement + method examples):** [USAGE.md](USAGE.md)
|
|
13
|
+
**Add the gem to any Rails app without boot errors (ENV, jobs, checklists):** [AI_GEM_SETUP.md](AI_GEM_SETUP.md)
|
|
14
|
+
**ENV variable names (copy into host `.env.example`):** [.env.example](.env.example)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Supported Platforms
|
|
19
|
+
|
|
20
|
+
| Platform | Adapter | Server-side API | JS Pixel |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| Google Ads | `:google_ads` | ✅ Click Conversion Upload | via `omnitrack_tags` |
|
|
23
|
+
| Google Analytics 4 | `:google_analytics` | ✅ Measurement Protocol | via `omnitrack_tags` |
|
|
24
|
+
| Meta (Facebook/Instagram) | `:meta` | ✅ Conversions API | via `omnitrack_tags` |
|
|
25
|
+
| TikTok Ads | `:tiktok` | ✅ Events API | via `omnitrack_tags` |
|
|
26
|
+
| Snapchat Ads | `:snapchat` | ✅ Conversions API | via `omnitrack_tags` |
|
|
27
|
+
| Custom | Your class | ✅ Extend `Base` | — |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Add to your `Gemfile`:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
gem "omnitrack-rb"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Run the installer:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bundle install
|
|
43
|
+
rails generate omnitrack:install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This creates `config/initializers/omnitrack.rb` with a fully commented template.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# config/initializers/omnitrack.rb
|
|
54
|
+
|
|
55
|
+
Omnitrack.configure do |config|
|
|
56
|
+
# :auto — detects api_only? at runtime (recommended)
|
|
57
|
+
# :frontend — inject JS tags + use cookies
|
|
58
|
+
# :backend — server-side only (Conversions APIs)
|
|
59
|
+
# :hybrid — both simultaneously
|
|
60
|
+
config.mode = :auto
|
|
61
|
+
|
|
62
|
+
config.adapters = {
|
|
63
|
+
google_ads: {
|
|
64
|
+
enabled: ENV["GOOGLE_ADS_ENABLED"] == "true",
|
|
65
|
+
customer_id: ENV["GOOGLE_ADS_CUSTOMER_ID"],
|
|
66
|
+
developer_token: ENV["GOOGLE_ADS_DEVELOPER_TOKEN"],
|
|
67
|
+
access_token: ENV["GOOGLE_ADS_ACCESS_TOKEN"],
|
|
68
|
+
conversion_action_id: ENV["GOOGLE_ADS_CONVERSION_ACTION_ID"]
|
|
69
|
+
},
|
|
70
|
+
google_analytics: {
|
|
71
|
+
enabled: ENV["GA4_ENABLED"] == "true",
|
|
72
|
+
measurement_id: ENV["GA4_MEASUREMENT_ID"],
|
|
73
|
+
api_secret: ENV["GA4_API_SECRET"]
|
|
74
|
+
},
|
|
75
|
+
meta: {
|
|
76
|
+
enabled: ENV["META_ENABLED"] == "true",
|
|
77
|
+
pixel_id: ENV["META_PIXEL_ID"],
|
|
78
|
+
access_token: ENV["META_ACCESS_TOKEN"]
|
|
79
|
+
},
|
|
80
|
+
tiktok: {
|
|
81
|
+
enabled: ENV["TIKTOK_ENABLED"] == "true",
|
|
82
|
+
pixel_id: ENV["TIKTOK_PIXEL_ID"],
|
|
83
|
+
access_token: ENV["TIKTOK_ACCESS_TOKEN"]
|
|
84
|
+
},
|
|
85
|
+
snapchat: {
|
|
86
|
+
enabled: ENV["SNAPCHAT_ENABLED"] == "true",
|
|
87
|
+
pixel_id: ENV["SNAPCHAT_PIXEL_ID"],
|
|
88
|
+
access_token: ENV["SNAPCHAT_ACCESS_TOKEN"]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
config.auto_capture = true # capture gclid, fbclid, ttclid, UTMs automatically
|
|
93
|
+
config.log_level = :info
|
|
94
|
+
config.async = Rails.env.production? # use ActiveJob in production
|
|
95
|
+
config.timeout = 5
|
|
96
|
+
config.max_retries = 3
|
|
97
|
+
|
|
98
|
+
# Optional: error hook for Sentry / Honeybadger
|
|
99
|
+
config.on_error = ->(error, adapter) {
|
|
100
|
+
Sentry.capture_exception(error, tags: { adapter: adapter.name })
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Usage
|
|
108
|
+
|
|
109
|
+
### Full-stack Rails (views + controllers)
|
|
110
|
+
|
|
111
|
+
**Layout** — inject all platform pixels into `<head>`:
|
|
112
|
+
|
|
113
|
+
```erb
|
|
114
|
+
<!DOCTYPE html>
|
|
115
|
+
<html>
|
|
116
|
+
<head>
|
|
117
|
+
<%= omnitrack_tags %>
|
|
118
|
+
</head>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Controller** — track events server-side (`Omnitrack::Controller` is auto-included via the Railtie; you can also `include Omnitrack::Controller` explicitly in `ApplicationController` if needed):
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
class OrdersController < ApplicationController
|
|
125
|
+
def create
|
|
126
|
+
@order = Order.create!(order_params)
|
|
127
|
+
|
|
128
|
+
# Fires through all enabled server-side adapters
|
|
129
|
+
omnitrack_event("purchase",
|
|
130
|
+
value: @order.total,
|
|
131
|
+
currency: "USD",
|
|
132
|
+
order_id: @order.id)
|
|
133
|
+
|
|
134
|
+
redirect_to @order
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**View** — emit a JS event tag on the confirmation page:
|
|
140
|
+
|
|
141
|
+
```erb
|
|
142
|
+
<%= omnitrack_event_tag("purchase", value: @order.total, currency: "USD") %>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### API-only Rails
|
|
146
|
+
|
|
147
|
+
No JS involved — everything is server-side:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
class Api::V1::OrdersController < ApplicationController
|
|
151
|
+
def create
|
|
152
|
+
@order = Order.create!(order_params)
|
|
153
|
+
|
|
154
|
+
Omnitrack.track("purchase",
|
|
155
|
+
value: @order.total,
|
|
156
|
+
currency: "USD",
|
|
157
|
+
order_id: @order.id,
|
|
158
|
+
# Pass click IDs from your mobile client via request body or headers:
|
|
159
|
+
fbclid: params[:fbclid],
|
|
160
|
+
gclid: params[:gclid])
|
|
161
|
+
|
|
162
|
+
render json: @order
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Service objects / background jobs
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
class PurchaseTracker
|
|
171
|
+
def call(order)
|
|
172
|
+
Omnitrack.track("purchase",
|
|
173
|
+
value: order.total,
|
|
174
|
+
currency: order.currency,
|
|
175
|
+
order_id: order.id,
|
|
176
|
+
email: order.user.email)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Identify a user
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
Omnitrack.identify(
|
|
185
|
+
email: current_user.email,
|
|
186
|
+
phone: current_user.phone,
|
|
187
|
+
first_name: current_user.first_name,
|
|
188
|
+
last_name: current_user.last_name,
|
|
189
|
+
external_id: current_user.id.to_s
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
> PII (email, phone, name) is SHA-256 hashed before being sent to any platform.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Working with Results
|
|
198
|
+
|
|
199
|
+
Every call returns an `Omnitrack::MultiResult`:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
result = Omnitrack.track("purchase", value: 99.0)
|
|
203
|
+
|
|
204
|
+
result.success? # => true if ALL adapters succeeded
|
|
205
|
+
result.any_failure? # => true if at least one adapter failed
|
|
206
|
+
result.failures # => [Omnitrack::Result, ...]
|
|
207
|
+
result.successes # => [Omnitrack::Result, ...]
|
|
208
|
+
|
|
209
|
+
result.each do |r|
|
|
210
|
+
puts "#{r.adapter}: #{r.status}" # => google_ads: success
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Building a Custom Adapter
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
# app/tracking/my_platform_adapter.rb
|
|
220
|
+
|
|
221
|
+
class MyPlatformAdapter < Omnitrack::Adapters::Base
|
|
222
|
+
self.adapter_name = :my_platform
|
|
223
|
+
|
|
224
|
+
def track_event(event_name, payload = {})
|
|
225
|
+
safe_execute(event_name) do
|
|
226
|
+
response = http_post(
|
|
227
|
+
"https://api.myplatform.com/events",
|
|
228
|
+
body: {
|
|
229
|
+
event: event_name,
|
|
230
|
+
data: payload,
|
|
231
|
+
api_key: config[:api_key]
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
Omnitrack::Result.success(adapter: name, data: JSON.parse(response.body))
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def track_conversion(data = {})
|
|
239
|
+
track_event("conversion", data)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def identify_user(user_data = {})
|
|
243
|
+
# store or send user data
|
|
244
|
+
Omnitrack::Result.success(adapter: name)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
private
|
|
248
|
+
|
|
249
|
+
def validate_config
|
|
250
|
+
require_config!(:api_key, hint: "MyPlatform API key")
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Register in config:
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
Omnitrack.configure do |config|
|
|
259
|
+
config.adapters[:my_platform] = {
|
|
260
|
+
enabled: true,
|
|
261
|
+
api_key: ENV["MY_PLATFORM_API_KEY"]
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Register the class
|
|
266
|
+
Omnitrack::Registry.register(MyPlatformAdapter)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Logging
|
|
272
|
+
|
|
273
|
+
OmniTrack writes structured JSON to `log/omnitrack.log` (separate from Rails' main log):
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{"timestamp":"2024-01-15T10:23:45.123Z","message":"adapter.request","adapter":"meta","event":"purchase","payload":{"value":99.0}}
|
|
277
|
+
{"timestamp":"2024-01-15T10:23:45.891Z","message":"adapter.response","adapter":"meta","event":"purchase","status":"success","duration_ms":768.4}
|
|
278
|
+
{"timestamp":"2024-01-15T10:23:46.002Z","message":"adapter.error","adapter":"google_ads","event":"purchase","error":"Omnitrack::AdapterError","message":"HTTP 401 from googleads.googleapis.com"}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Configure log level:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
config.log_level = :debug # :debug | :info | :warn | :error | :none
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Rotate logs:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
rails omnitrack:rotate_log
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Rake Tasks
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
rails omnitrack:status # Show configured adapters and status
|
|
299
|
+
rails omnitrack:test_event # Send a test event through all enabled adapters
|
|
300
|
+
rails omnitrack:test_event[purchase] # Send a named test event
|
|
301
|
+
rails omnitrack:rotate_log # Rotate the log file
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Async / Background Jobs
|
|
307
|
+
|
|
308
|
+
When `config.async = true`, tracking calls are dispatched via `ActiveJob`:
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
config.async = true
|
|
312
|
+
config.queue_name = :omnitrack
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Ensure your queue adapter is configured in production:
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
# config/environments/production.rb
|
|
319
|
+
config.active_job.queue_adapter = :sidekiq
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Middleware
|
|
325
|
+
|
|
326
|
+
`Omnitrack::Middleware::RequestTracker` is automatically inserted into your Rack stack. It:
|
|
327
|
+
|
|
328
|
+
1. Captures `gclid`, `fbclid`, `ttclid`, UTMs, IP, and User-Agent from every request
|
|
329
|
+
2. Makes them available at `Omnitrack::Context.current` throughout the request
|
|
330
|
+
3. Clears thread-local state after each response (no cross-request leakage)
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Thread Safety
|
|
335
|
+
|
|
336
|
+
- All shared state uses `Mutex`-guarded access
|
|
337
|
+
- Context is stored in `Thread.current` — safe under Puma/Sidekiq
|
|
338
|
+
- Adapters are instantiated per-dispatch (stateless between requests)
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Development
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
bundle install
|
|
346
|
+
bundle exec rspec
|
|
347
|
+
bundle exec rubocop
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Contributing
|
|
353
|
+
|
|
354
|
+
1. Fork the repository
|
|
355
|
+
2. Create your branch (`git checkout -b feature/my-adapter`)
|
|
356
|
+
3. Write tests
|
|
357
|
+
4. Run `bundle exec rspec` — all green
|
|
358
|
+
5. Open a Pull Request
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
MIT. See [LICENSE.txt](LICENSE.txt).
|