content_signals 0.1.0 → 0.1.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.
data/README.md CHANGED
@@ -1,28 +1,420 @@
1
1
  # ContentSignals
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ **Listen to signals from your content.**
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/content_signals`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ A Rails engine for tracking page views and content engagement with rich analytics. Track views on any model (Pages, Posts, Events, Profiles) with demographics, device detection, and multi-tenant support.
6
+
7
+ ## Features
8
+
9
+ - 📊 **Page view tracking** with visitor identification
10
+ - 🌍 **Geolocation** (country, city, region) via MaxMind GeoLite2
11
+ - 📱 **Device detection** (mobile, tablet, desktop, hybrid apps)
12
+ - 🏢 **Multi-tenant support** (optional)
13
+ - 🤖 **Bot filtering** (automatically excludes crawlers)
14
+ - 🔄 **Background processing** (non-blocking with ActiveJob)
15
+ - 📈 **Rich analytics** with time-based scopes and aggregations
16
+ - 🎯 **Polymorphic tracking** (works with any model)
17
+ - 📲 **Hybrid app support** (Capacitor, Cordova, React Native, Flutter)
6
18
 
7
19
  ## Installation
8
20
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'content_signals'
25
+ ```
10
26
 
11
- Install the gem and add to the application's Gemfile by executing:
27
+ Then execute:
12
28
 
13
29
  ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
30
+ bundle install
15
31
  ```
16
32
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
33
+ Run the install generator:
18
34
 
19
35
  ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
36
+ rails generate content_signals:install
37
+ rails db:migrate
21
38
  ```
22
39
 
23
40
  ## Usage
24
41
 
25
- TODO: Write usage instructions here
42
+ ### Basic Setup
43
+
44
+ #### 1. Add counter cache column to your trackable models
45
+
46
+ Add a `page_views_count` column to any model you want to track views for:
47
+
48
+ ```ruby
49
+ # Migration example for Pages
50
+ class AddPageViewsCountToPages < ActiveRecord::Migration[7.0]
51
+ def change
52
+ add_column :pages, :page_views_count, :integer, default: 0, null: false
53
+ add_index :pages, :page_views_count
54
+ end
55
+ end
56
+ ```
57
+
58
+ **Important:** The counter column must be named `page_views_count` (following Rails counter_cache conventions). The ContentSignals PageView model uses `counter_cache: :page_views_count` to automatically increment this column when view records are created.
59
+
60
+ For multiple trackable models:
61
+
62
+ ```ruby
63
+ # Add to any models you want to track
64
+ add_column :posts, :page_views_count, :integer, default: 0, null: false
65
+ add_column :events, :page_views_count, :integer, default: 0, null: false
66
+ add_column :profiles, :page_views_count, :integer, default: 0, null: false
67
+ ```
68
+
69
+ #### 2. Include tracking in your controller
70
+
71
+ ```ruby
72
+ class PagesController < ApplicationController
73
+ include ContentSignals::TrackablePageViews
74
+
75
+ def show
76
+ @page = Page.find(params[:id])
77
+ # Page views are automatically tracked!
78
+ end
79
+ end
80
+ ```
81
+
82
+ **How it works:**
83
+ 1. When a user visits a tracked page, the concern triggers tracking
84
+ 2. A background job is enqueued to create a detailed PageView record
85
+ 3. The `page_views_count` counter is automatically incremented via Rails counter_cache if the 'hit' is unique for the day
86
+ 4. No blocking - the user gets instant response while tracking happens asynchronously
87
+
88
+ That's it! Page views will now be tracked automatically with:
89
+ - Total view counter via counter_cache (incremented when PageView record is created)
90
+ - Detailed PageView records with demographics (via background job)
91
+ - Visitor identification (authenticated users, device IDs, or anonymous)
92
+ - Device and location data
93
+ - Bot filtering
94
+
95
+ ### Configuration
96
+
97
+ Create an initializer `config/initializers/content_signals.rb`:
98
+
99
+ ```ruby
100
+ ContentSignals.configure do |config|
101
+ # Multi-tenancy (optional)
102
+ config.multitenancy = false
103
+ config.current_tenant_method = :current_tenant_id
104
+ config.tenant_model = 'Account'
105
+
106
+ # Redis for unique visitor tracking (optional)
107
+ # When enabled, prevents duplicate tracking of the same visitor on the same day
108
+ # Keys expire automatically after 24 hours
109
+ # Without Redis: all PageView records are still stored in the database,
110
+ # but the same visitor may create multiple records per day
111
+ config.redis_enabled = true
112
+ config.redis_namespace = 'content_signals'
113
+
114
+ # MaxMind GeoLite2 database path
115
+ config.maxmind_db_path = Rails.root.join('db', 'GeoLite2-City.mmdb')
116
+
117
+ # Tracking preferences
118
+ config.track_bots = false
119
+ config.track_admins = false
120
+ end
121
+ ```
122
+
123
+ ### Analytics Queries
124
+
125
+ Access rich analytics data through PageView scopes:
126
+
127
+ ```ruby
128
+ # Get page views for a specific page
129
+ page = Page.find(1)
130
+ page_views = ContentSignals::PageView.where(trackable: page)
131
+
132
+ # Time-based queries
133
+ page_views.today # returns all toyda's records
134
+ page_views.today.count # counter for today's views
135
+ page_views.yesterday
136
+ page_views.this_week
137
+ page_views.last_30_days
138
+ page_views.this_month
139
+ page_views.this_year
140
+
141
+ # Device filtering
142
+ page_views.mobile
143
+ page_views.desktop
144
+ page_views.tablet
145
+
146
+ # Location filtering
147
+ page_views.from_country('US')
148
+ page_views.from_city('New York')
149
+
150
+ # Platform filtering
151
+ page_views.from_website # Regular web browsers
152
+ page_views.from_hybrid_app # Mobile app WebViews
153
+ page_views.from_native_app # Native mobile apps
154
+
155
+ # User filtering
156
+ page_views.authenticated # Logged-in users
157
+ page_views.anonymous # Anonymous visitors
158
+
159
+ # Analytics aggregations
160
+ page_views.unique_count # Unique visitors
161
+ page_views.top_countries(10) # Top 10 countries
162
+ page_views.top_cities(10) # Top 10 cities
163
+ page_views.device_breakdown # Device type distribution
164
+ page_views.browser_breakdown # Browser distribution
165
+ page_views.location_heatmap # Lat/lng data for maps
166
+
167
+ # Growth rate
168
+ page_views.growth_rate(:this_week, :last_week) # Weekly growth %
169
+ ```
170
+
171
+ ### Example: Analytics Dashboard
172
+
173
+ ```ruby
174
+ class AnalyticsController < ApplicationController
175
+ def show
176
+ @page = Page.find(params[:id])
177
+ @views = ContentSignals::PageView.where(trackable: @page).last_30_days
178
+
179
+ @stats = {
180
+ total_views: @views.count,
181
+ unique_visitors: @views.unique_count,
182
+ top_countries: @views.top_countries(5),
183
+ top_cities: @views.top_cities(5),
184
+ device_breakdown: @views.device_breakdown,
185
+ growth_rate: @views.growth_rate(:this_week, :last_week)
186
+ }
187
+ end
188
+ end
189
+ ```
190
+
191
+ ### Custom Trackable Finder
192
+
193
+ By default, the concern looks for `@page`, `@post`, `@article`, `@profile`, `@event`, or `@trackable`. To customize:
194
+
195
+ ```ruby
196
+ class ArticlesController < ApplicationController
197
+ include ContentSignals::TrackablePageViews
198
+
199
+ def show
200
+ @my_article = Article.find(params[:id])
201
+ end
202
+
203
+ private
204
+
205
+ def find_trackable_for_tracking
206
+ @my_article
207
+ end
208
+ end
209
+ ```
210
+
211
+ ### Skip Tracking Conditions
212
+
213
+ Override to add custom skip conditions:
214
+
215
+ ```ruby
216
+ class PagesController < ApplicationController
217
+ include ContentSignals::TrackablePageViews
218
+
219
+ private
220
+
221
+ def should_skip_tracking?
222
+ super || draft_mode? || current_user&.internal?
223
+ end
224
+
225
+ def draft_mode?
226
+ params[:draft].present?
227
+ end
228
+ end
229
+ ```
230
+
231
+ ### Manual Tracking
232
+
233
+ Track views programmatically without the controller concern:
234
+
235
+ ```ruby
236
+ ContentSignals::PageViewTracker.track(
237
+ trackable: @page,
238
+ user: current_user,
239
+ request: request
240
+ )
241
+ ```
242
+
243
+ ### Multi-Tenant Setup
244
+
245
+ For multi-tenant applications:
246
+
247
+ ```ruby
248
+ # config/initializers/content_signals.rb
249
+ ContentSignals.configure do |config|
250
+ config.multitenancy = true
251
+ config.current_tenant_method = :current_account_id
252
+ end
253
+
254
+ # In your ApplicationController
255
+ class ApplicationController < ActionController::Base
256
+ def current_account_id
257
+ Current.account_id # or however you track tenant context
258
+ end
259
+ end
260
+ ```
261
+
262
+ All PageView queries will automatically scope to the current tenant.
263
+
264
+ ### Hybrid Mobile App Support
265
+
266
+ Track views from mobile apps (Capacitor, Cordova, React Native, Flutter):
267
+
268
+ #### Send custom headers from your app:
269
+
270
+ ```javascript
271
+ // In your mobile app
272
+ fetch('https://yoursite.com/pages/1', {
273
+ headers: {
274
+ 'X-App-Platform': 'capacitor',
275
+ 'X-App-Version': '1.2.3',
276
+ 'X-Device-ID': 'unique-device-uuid'
277
+ }
278
+ });
279
+ ```
280
+
281
+ #### Or use URL parameters:
282
+
283
+ ```
284
+ https://yoursite.com/pages/1?app_platform=capacitor&device_id=abc123&app_version=1.2.3
285
+ ```
286
+
287
+ Views from mobile apps will be automatically detected and tracked with `app_platform` and `device_id`.
288
+
289
+ ### Geolocation Setup
290
+
291
+ 1. **Sign up for MaxMind GeoLite2** (free): https://www.maxmind.com/en/geolite2/signup
292
+ 2. **Download GeoLite2-City.mmdb** database
293
+ 3. **Place in:** `db/GeoLite2-City.mmdb`
294
+ 4. **Configure path** in initializer (shown above)
295
+
296
+ The gem will automatically enrich page views with:
297
+ - Country (code and name)
298
+ - City
299
+ - Region/State
300
+ - Latitude/Longitude
301
+
302
+ ### PageView Model
303
+
304
+ Access individual page view records:
305
+
306
+ ```ruby
307
+ view = ContentSignals::PageView.last
308
+
309
+ view.trackable # => #<Page id: 1>
310
+ view.user # => #<User id: 5> (if authenticated)
311
+ view.visitor_id # => "user_5" or "device_abc123" or "anon_1a2b3c"
312
+ view.country_name # => "United States"
313
+ view.city # => "New York"
314
+ view.device_type # => "mobile"
315
+ view.browser # => "Chrome"
316
+ view.os # => "iOS"
317
+ view.app_platform # => "capacitor" (if from mobile app)
318
+ view.viewed_at # => 2026-01-06 10:30:00 UTC
319
+
320
+ # Helper methods
321
+ view.authenticated? # => true if user present
322
+ view.anonymous? # => true if no user
323
+ view.mobile_device? # => true if mobile
324
+ view.desktop_device? # => true if desktop
325
+ view.hybrid_app? # => true if from mobile app
326
+ view.web_browser? # => true if from web browser
327
+ ```
328
+
329
+ ## Advanced Usage
330
+
331
+ ### Custom User Method
332
+
333
+ If you use a different method name for current user:
334
+
335
+ ```ruby
336
+ class PagesController < ApplicationController
337
+ include ContentSignals::TrackablePageViews
338
+
339
+ private
340
+
341
+ def current_user_for_tracking
342
+ current_person # or whatever your method is called
343
+ end
344
+ end
345
+ ```
346
+
347
+ ### Background Job Queue
348
+
349
+ By default, tracking jobs use the `default` queue. To customize:
350
+
351
+ ```ruby
352
+ # config/initializers/content_signals.rb
353
+ ContentSignals::TrackPageViewJob.queue_as :analytics
354
+ ```
355
+
356
+ ### Export Analytics Data
357
+
358
+ ```ruby
359
+ # Export to CSV
360
+ require 'csv'
361
+
362
+ CSV.generate(headers: true) do |csv|
363
+ csv << ['Date', 'Country', 'City', 'Device', 'Browser']
364
+
365
+ ContentSignals::PageView.where(trackable: @page).find_each do |view|
366
+ csv << [
367
+ view.viewed_at.to_date,
368
+ view.country_name,
369
+ view.city,
370
+ view.device_type,
371
+ view.browser
372
+ ]
373
+ end
374
+ end
375
+ ```
376
+
377
+ ## Requirements
378
+
379
+ - Rails >= 7.0
380
+ - Ruby >= 3.0
381
+ - ActiveJob (for background processing)
382
+ - Redis (optional, for unique visitor tracking)
383
+
384
+ ### Optional Dependencies
385
+
386
+ ```ruby
387
+ # Gemfile
388
+ gem 'maxminddb' # For IP geolocation
389
+ gem 'browser' # For device/browser detection
390
+ gem 'redis' # For unique visitor tracking
391
+ ```
392
+
393
+ ## Performance
394
+
395
+ - **Fast**: Counter increments are synchronous (< 1ms)
396
+ - **Non-blocking**: Detailed tracking happens in background jobs
397
+ - **Scalable**: Uses counter caching and database indexes
398
+ - **Efficient**: Redis-based unique visitor deduplication (optional)
399
+
400
+ ## Testing
401
+
402
+ In your test environment, you may want to disable tracking:
403
+
404
+ ```ruby
405
+ # config/environments/test.rb
406
+ config.after_initialize do
407
+ ContentSignals.configure do |config|
408
+ config.track_bots = true # Allow test requests
409
+ end
410
+ end
411
+ ```
412
+
413
+ Or stub the tracker in specs:
414
+
415
+ ```ruby
416
+ allow(ContentSignals::PageViewTracker).to receive(:track)
417
+ ```
26
418
 
27
419
  ## Development
28
420