behavior_analytics 2.1.0 → 2.2.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.
data/README.md CHANGED
@@ -1,21 +1,45 @@
1
1
  # Behavior Analytics
2
2
 
3
- A Ruby gem for tracking user behavior events with multi-tenant support, computing analytics (engagement scores, time-based trends, feature usage), and supporting API calls, feature usage, and custom events.
3
+ A comprehensive Ruby gem for tracking user behavior events with multi-tenant support, visit/session management, device detection, geographic analytics, and advanced querying capabilities. Inspired by Ahoy analytics with enterprise-grade features.
4
4
 
5
5
  ## Features
6
6
 
7
+ ### Core Features
7
8
  - **Flexible Context Tracking**: Track events with multi-tenant support, user types, and custom filters
8
9
  - **Event Buffering**: Efficient batch processing with configurable buffer size and flush intervals
10
+ - **Visit/Session Management**: Automatic visit tracking with session management, visitor identification, and visit analytics
11
+ - **Device & Browser Detection**: Automatic device, browser, and OS detection from user agents
12
+ - **Geographic Analytics**: Country, city, and region tracking from IP addresses
13
+ - **Referrer Analytics**: UTM parameter tracking, source analysis, and search keyword extraction
14
+
15
+ ### Analytics & Insights
9
16
  - **Comprehensive Analytics**:
10
17
  - Event counts and aggregations
11
18
  - Engagement scoring with customizable weights
12
19
  - Time-based analytics (hourly, daily, weekly, monthly)
13
20
  - Feature usage statistics
14
- - **Storage Adapters**:
21
+ - Funnel analysis
22
+ - Cohort analysis
23
+ - Retention tracking
24
+ - **Geographic Analytics**: Country/city breakdowns, device/browser statistics
25
+ - **Referrer Analytics**: Traffic source analysis, UTM campaign tracking, search keyword insights
26
+
27
+ ### Storage & Performance
28
+ - **Multiple Storage Adapters**:
15
29
  - ActiveRecord adapter for production use
16
30
  - In-memory adapter for testing
17
- - **Rails Integration**: Automatic API call tracking via middleware
18
- - **Query Interface**: Fluent query builder for filtering events
31
+ - Redis adapter for high-throughput scenarios
32
+ - Elasticsearch adapter for advanced search
33
+ - Kafka adapter for event streaming
34
+ - **Async Processing**: Background job support (Sidekiq, DelayedJob, ActiveJob)
35
+ - **Event Streaming**: Real-time event pub/sub system
36
+
37
+ ### Developer Experience
38
+ - **Simplified API**: Ahoy-inspired simple tracking API with automatic context resolution
39
+ - **Rails Integration**: Automatic API call tracking via middleware with selective tracking
40
+ - **Query Interface**: Fluent query builder for filtering events with advanced aggregations
41
+ - **JavaScript Client**: Frontend tracking with automatic page views and click tracking
42
+ - **Data Retention**: Automatic cleanup policies for visits and events
19
43
 
20
44
  ## Installation
21
45
 
@@ -46,16 +70,21 @@ rails generate behavior_analytics:install
46
70
  ```
47
71
 
48
72
  This will:
49
- - Create a migration for the `behavior_events` table
73
+ - Create migrations for the `behavior_events` and `behavior_visits` tables
50
74
  - Create an initializer at `config/initializers/behavior_analytics.rb`
51
- - Create a model at `app/models/behavior_analytics_event.rb`
75
+ - Create models at `app/models/behavior_analytics_event.rb` and `app/models/behavior_analytics_visit.rb`
52
76
 
53
- ### 2. Run the migration
77
+ ### 2. Run the migrations
54
78
 
55
79
  ```bash
56
80
  rails db:migrate
57
81
  ```
58
82
 
83
+ This will create:
84
+ - `behavior_events` table for event tracking
85
+ - `behavior_visits` table for visit/session tracking
86
+ - Indexes for optimal query performance
87
+
59
88
  ### 3. Configure the initializer
60
89
 
61
90
  Edit `config/initializers/behavior_analytics.rb`:
@@ -87,6 +116,24 @@ BehaviorAnalytics.configure do |config|
87
116
  feature_diversity: 0.2,
88
117
  time_in_trial: 0.1
89
118
  }
119
+
120
+ # Visit/Session Management (Ahoy-inspired features)
121
+ config.track_visits = true # Enable visit tracking
122
+ config.visit_duration = 30.minutes # Visit expires after 30 min of inactivity
123
+ config.track_device_info = true # Auto-detect device, browser, OS
124
+ config.track_geolocation = true # Auto-detect country/city from IP
125
+ config.device_detector = :simple # :simple, :browser, or :user_agent_parser
126
+
127
+ # Data Retention
128
+ config.visit_retention_days = 90 # Keep visits for 90 days
129
+ config.event_retention_days = 365 # Keep events for 1 year
130
+
131
+ # Single-tenant support
132
+ config.default_tenant_id = "global" # For non-multi-tenant systems
133
+
134
+ # Optional: Advanced features
135
+ config.use_async = false # Enable async processing
136
+ config.debug_mode = Rails.env.development? # Enable debug logging
90
137
  end
91
138
  ```
92
139
 
@@ -98,8 +145,175 @@ class ApplicationController < ActionController::Base
98
145
  end
99
146
  ```
100
147
 
148
+ ### 5. (Optional) Add JavaScript Client
149
+
150
+ Include the JavaScript client in your layout:
151
+
152
+ ```erb
153
+ <!-- app/views/layouts/application.html.erb -->
154
+ <%= javascript_include_tag 'behavior_analytics' %>
155
+ ```
156
+
157
+ Or use the inline script generator:
158
+
159
+ ```erb
160
+ <script>
161
+ <%= BehaviorAnalytics::Javascript::Client.generate_script(
162
+ tracker_url: behavior_analytics_track_path,
163
+ auto_track: true
164
+ ).html_safe %>
165
+ </script>
166
+ ```
167
+
101
168
  ## Usage
102
169
 
170
+ ### Simplified API (Ahoy-Inspired)
171
+
172
+ The gem provides a simplified API similar to Ahoy for easy tracking:
173
+
174
+ ```ruby
175
+ # Simple event tracking with automatic context resolution
176
+ BehaviorAnalytics.track("button_click", properties: { button: "signup" })
177
+
178
+ # Page view tracking
179
+ BehaviorAnalytics.track_page_view(path: "/dashboard")
180
+
181
+ # Click tracking
182
+ BehaviorAnalytics.track_click(element: "signup_button", properties: { location: "header" })
183
+
184
+ # Conversion tracking
185
+ BehaviorAnalytics.track_conversion(conversion_name: "signup", value: 99.99)
186
+
187
+ # Or use the helper methods in controllers
188
+ class ApplicationController < ActionController::Base
189
+ include BehaviorAnalytics::Helpers::TrackingHelper
190
+
191
+ def create
192
+ # ... create logic ...
193
+ track_event("project_created", properties: { project_id: @project.id })
194
+ end
195
+ end
196
+ ```
197
+
198
+ ### Visit/Session Management
199
+
200
+ Visits are automatically created and tracked when visit tracking is enabled:
201
+
202
+ ```ruby
203
+ # Visits are automatically created on first request
204
+ # You can access the current visit in your controllers:
205
+
206
+ class ApplicationController < ActionController::Base
207
+ include BehaviorAnalytics::Integrations::Rails
208
+
209
+ def current_visit
210
+ @current_visit ||= visit_manager&.find_or_create_visit(
211
+ visitor_token: visit_auto_creator.get_or_create_visitor_token(request),
212
+ tenant_id: current_tenant&.id,
213
+ user_id: current_user&.id,
214
+ ip: request.ip,
215
+ user_agent: request.user_agent,
216
+ referrer: request.referer,
217
+ landing_page: request.path
218
+ )
219
+ end
220
+ end
221
+
222
+ # Query visits
223
+ tracker = BehaviorAnalytics.create_tracker
224
+ visit_manager = BehaviorAnalytics::Visits::Manager.new(
225
+ storage_adapter: tracker.storage_adapter
226
+ )
227
+
228
+ # Get user's visits
229
+ user_visits = visit_manager.find_visits_by_user(user_id, limit: 100)
230
+
231
+ # Get visitor's visits (anonymous)
232
+ visitor_visits = visit_manager.find_visits_by_visitor(visitor_token, limit: 100)
233
+
234
+ # Link anonymous visits to user on login
235
+ user_resolver = BehaviorAnalytics::Identification::UserResolver.new(
236
+ visit_manager: visit_manager
237
+ )
238
+ user_resolver.identify_user(user_id, visitor_token: visitor_token)
239
+ ```
240
+
241
+ ### Device & Browser Detection
242
+
243
+ Device information is automatically detected and stored in visits:
244
+
245
+ ```ruby
246
+ # Device detection happens automatically when track_device_info is enabled
247
+ # Visit will include:
248
+ # - browser: "Chrome", "Safari", "Firefox", etc.
249
+ # - os: "iOS", "Android", "Windows", "Mac OS", etc.
250
+ # - device_type: "mobile", "tablet", "desktop"
251
+
252
+ # You can also manually detect device info:
253
+ detector = BehaviorAnalytics::Detection::DeviceDetector.new(strategy: :simple)
254
+ device_info = detector.detect(request.user_agent)
255
+ # => { browser: "Chrome", os: "Mac OS", device_type: "desktop" }
256
+ ```
257
+
258
+ ### Geographic Analytics
259
+
260
+ Geographic information is automatically detected from IP addresses:
261
+
262
+ ```ruby
263
+ # Geolocation happens automatically when track_geolocation is enabled
264
+ # Visit will include:
265
+ # - country: "United States"
266
+ # - city: "San Francisco"
267
+ # - country_code: "US"
268
+
269
+ # Query geographic analytics
270
+ analytics = tracker.analytics
271
+
272
+ # Country breakdown
273
+ countries = analytics.geographic.country_breakdown(context)
274
+ # => [{ country: "United States", count: 150 }, { country: "Canada", count: 50 }]
275
+
276
+ # City breakdown
277
+ cities = analytics.geographic.city_breakdown(context)
278
+ # => [{ city: "San Francisco", count: 75 }, { city: "New York", count: 50 }]
279
+
280
+ # Device breakdown
281
+ devices = analytics.geographic.device_breakdown(context)
282
+ # => [{ device: "desktop", count: 100 }, { device: "mobile", count: 50 }]
283
+
284
+ # Browser breakdown
285
+ browsers = analytics.geographic.browser_breakdown(context)
286
+ # => [{ browser: "Chrome", count: 120 }, { browser: "Safari", count: 30 }]
287
+ ```
288
+
289
+ ### Referrer Analytics
290
+
291
+ Track traffic sources, UTM parameters, and search keywords:
292
+
293
+ ```ruby
294
+ analytics = tracker.analytics
295
+
296
+ # Traffic source breakdown
297
+ sources = analytics.referrer.source_breakdown(context)
298
+ # => [{ source: "google", count: 100 }, { source: "direct", count: 50 }]
299
+
300
+ # UTM source breakdown
301
+ utm_sources = analytics.referrer.utm_source_breakdown(context)
302
+ # => [{ utm_source: "newsletter", count: 75 }, { utm_source: "social", count: 25 }]
303
+
304
+ # UTM campaign breakdown
305
+ campaigns = analytics.referrer.utm_campaign_breakdown(context)
306
+ # => [{ utm_campaign: "summer_sale", count: 50 }, { utm_campaign: "winter_promo", count: 30 }]
307
+
308
+ # Search keyword breakdown
309
+ keywords = analytics.referrer.search_keyword_breakdown(context)
310
+ # => [{ keyword: "ruby gem", count: 20 }, { keyword: "analytics", count: 15 }]
311
+
312
+ # Referring domain breakdown
313
+ domains = analytics.referrer.referring_domain_breakdown(context)
314
+ # => [{ domain: "google.com", count: 100 }, { domain: "twitter.com", count: 25 }]
315
+ ```
316
+
103
317
  ### Supported Business Cases
104
318
 
105
319
  The gem is flexible and supports different business scenarios:
@@ -243,6 +457,66 @@ feature_stats = analytics.feature_usage_stats(context)
243
457
  # => { "projects" => 25, "search" => 10, ... }
244
458
 
245
459
  top_features = analytics.top_features(context, limit: 10)
460
+
461
+ # Funnel analysis
462
+ funnel = analytics.funnels.create(
463
+ name: "signup_funnel",
464
+ steps: ["page_view", "signup_form", "signup_submit", "email_verified"]
465
+ )
466
+ conversion_rate = funnel.conversion_rate(context)
467
+
468
+ # Cohort analysis
469
+ cohort = analytics.cohorts.create(
470
+ name: "signup_cohort",
471
+ event_name: "signup",
472
+ period: :monthly
473
+ )
474
+ cohort_data = cohort.analyze(context)
475
+
476
+ # Retention analysis
477
+ retention = analytics.retention.calculate(
478
+ context: context,
479
+ event_name: "login",
480
+ period: :weekly
481
+ )
482
+ ```
483
+
484
+ ### Data Retention & Cleanup
485
+
486
+ Automatically clean up old visits and events:
487
+
488
+ ```ruby
489
+ # Configure retention policy
490
+ retention_policy = BehaviorAnalytics::Cleanup::RetentionPolicy.new(
491
+ visit_retention_days: 90,
492
+ event_retention_days: 365
493
+ )
494
+
495
+ # Create scheduler
496
+ scheduler = BehaviorAnalytics::Cleanup::Scheduler.new(
497
+ storage_adapter: tracker.storage_adapter,
498
+ retention_policy: retention_policy
499
+ )
500
+
501
+ # Cleanup old data
502
+ results = scheduler.cleanup_all
503
+ # => { visits: 1500, events: 50000 }
504
+
505
+ # Or cleanup separately
506
+ deleted_visits = scheduler.cleanup_visits
507
+ deleted_events = scheduler.cleanup_events
508
+
509
+ # Schedule cleanup job (e.g., in a cron job or scheduled task)
510
+ # config/schedule.rb (whenever gem)
511
+ every 1.day, at: '2:00 am' do
512
+ runner "BehaviorAnalytics::Cleanup::Scheduler.new(
513
+ storage_adapter: BehaviorAnalytics.configuration.storage_adapter,
514
+ retention_policy: BehaviorAnalytics::Cleanup::RetentionPolicy.new(
515
+ visit_retention_days: BehaviorAnalytics.configuration.visit_retention_days,
516
+ event_retention_days: BehaviorAnalytics.configuration.event_retention_days
517
+ )
518
+ ).cleanup_all"
519
+ end
246
520
  ```
247
521
 
248
522
  ### Query Interface
@@ -264,6 +538,70 @@ count = query
264
538
  .for_tenant("org_123")
265
539
  .with_event_name("project_created")
266
540
  .count
541
+
542
+ # Advanced filtering
543
+ events = query
544
+ .with_metadata(key: "feature", value: "advanced_search")
545
+ .with_path("/api/search")
546
+ .with_method("POST")
547
+ .with_status_code(200)
548
+ .group_by(:user_id)
549
+ .aggregate(field: :duration_ms, function: :avg)
550
+ .execute
551
+
552
+ # Visit-based queries
553
+ visit_events = query
554
+ .for_visit(visit_token)
555
+ .with_event_type(:custom)
556
+ .execute
557
+ ```
558
+
559
+ ### JavaScript Client
560
+
561
+ The JavaScript client provides automatic frontend tracking:
562
+
563
+ ```javascript
564
+ // Automatic tracking (enabled by default)
565
+ // - Page views on load
566
+ // - Clicks on elements with data-track attribute
567
+ // - Form submissions with data-track attribute
568
+
569
+ // Manual tracking
570
+ BehaviorAnalytics.track("button_click", { button: "signup" });
571
+ BehaviorAnalytics.trackPageView();
572
+ BehaviorAnalytics.trackClick(element, { location: "header" });
573
+
574
+ // HTML attributes for automatic tracking
575
+ <button data-track="signup_click" data-track-properties='{"plan": "premium"}'>
576
+ Sign Up
577
+ </button>
578
+
579
+ <form data-track="newsletter_signup">
580
+ <!-- form fields -->
581
+ </form>
582
+ ```
583
+
584
+ ### User Identification & Visitor Tracking
585
+
586
+ Track anonymous visitors and merge with user accounts:
587
+
588
+ ```ruby
589
+ # Identify user on login (merges anonymous visits)
590
+ user_resolver = BehaviorAnalytics::Identification::UserResolver.new(
591
+ visit_manager: visit_manager
592
+ )
593
+
594
+ # On user login
595
+ user_resolver.identify_user(
596
+ user_id: current_user.id,
597
+ visitor_token: cookies[:behavior_visitor_token]
598
+ )
599
+
600
+ # Get all visits for a user (including anonymous visits before login)
601
+ user_visits = user_resolver.get_user_visits(user_id)
602
+
603
+ # Get visits for anonymous visitor
604
+ visitor_visits = user_resolver.get_visitor_visits(visitor_token)
267
605
  ```
268
606
 
269
607
  ### Custom Storage Adapter
@@ -288,6 +626,8 @@ tracker = BehaviorAnalytics.create_tracker(
288
626
 
289
627
  ## Configuration Options
290
628
 
629
+ ### Core Configuration
630
+
291
631
  - `storage_adapter`: Storage adapter instance (required)
292
632
  - `batch_size`: Number of events to buffer before flushing (default: 100)
293
633
  - `flush_interval`: Seconds between automatic flushes (default: 300)
@@ -295,12 +635,71 @@ tracker = BehaviorAnalytics.create_tracker(
295
635
  - `scoring_weights`: Hash of weights for engagement scoring
296
636
  - `default_tenant_id`: Default tenant ID for single-tenant systems (default: "default")
297
637
 
638
+ ### Visit/Session Management
639
+
640
+ - `track_visits`: Enable visit tracking (default: false)
641
+ - `visit_duration`: Visit expiration time after inactivity (default: 30.minutes)
642
+ - `track_device_info`: Enable automatic device/browser detection (default: false)
643
+ - `track_geolocation`: Enable automatic geographic detection from IP (default: false)
644
+ - `device_detector`: Device detection strategy - `:simple`, `:browser`, or `:user_agent_parser` (default: `:simple`)
645
+
646
+ ### Data Retention
647
+
648
+ - `visit_retention_days`: Days to keep visits before cleanup (default: 90)
649
+ - `event_retention_days`: Days to keep events before cleanup (default: 365)
650
+
651
+ ### Advanced Features
652
+
653
+ - `use_async`: Enable async event processing (default: false)
654
+ - `async_processor`: Async processor instance (Sidekiq, DelayedJob, ActiveJob)
655
+ - `event_stream`: Event streaming instance for pub/sub
656
+ - `hooks_manager`: Event hooks manager for lifecycle callbacks
657
+ - `sampling_strategy`: Event sampling strategy
658
+ - `rate_limiter`: Rate limiting configuration
659
+ - `schema_validator`: Event schema validator
660
+ - `metrics`: Metrics collection instance
661
+ - `tracer`: Distributed tracing instance
662
+ - `debug_mode`: Enable debug logging (default: development mode)
663
+ - `logger`: Custom logger instance
664
+
665
+ ### Rails Integration
666
+
667
+ - `tracking_whitelist`: Array of path patterns to whitelist (nil = track all)
668
+ - `tracking_blacklist`: Array of path patterns to blacklist (default: [])
669
+ - `skip_bots`: Skip tracking for bot user agents (default: true)
670
+ - `controller_action_filters`: Hash of controllers/actions to filter
671
+ - `slow_query_threshold`: Log slow queries above threshold (ms)
672
+ - `track_middleware_requests`: Track requests via middleware (default: false)
673
+
298
674
  ## Event Types
299
675
 
300
- - `:api_call` - HTTP API requests
676
+ - `:api_call` - HTTP API requests (automatically tracked via Rails integration)
301
677
  - `:feature_usage` - Feature usage events
302
678
  - `:custom` - Custom business events
303
679
 
680
+ ## Visit Model
681
+
682
+ Visits track user sessions and include:
683
+
684
+ - `visit_token`: Unique identifier for the visit
685
+ - `visitor_token`: Anonymous visitor identifier (persists across visits)
686
+ - `tenant_id`: Multi-tenant identifier
687
+ - `user_id`: User identifier (set when user logs in)
688
+ - `ip`: IP address
689
+ - `user_agent`: Browser user agent string
690
+ - `referrer`: HTTP referrer
691
+ - `landing_page`: First page visited in session
692
+ - `browser`: Detected browser (Chrome, Safari, Firefox, etc.)
693
+ - `os`: Detected operating system (iOS, Android, Windows, etc.)
694
+ - `device_type`: Device type (mobile, tablet, desktop)
695
+ - `country`: Country from IP geolocation
696
+ - `city`: City from IP geolocation
697
+ - `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`: UTM parameters
698
+ - `referring_domain`: Extracted referring domain
699
+ - `search_keyword`: Extracted search keyword from search engines
700
+ - `started_at`: Visit start time
701
+ - `ended_at`: Visit end time (null for active visits)
702
+
304
703
  ## Context
305
704
 
306
705
  The `Context` class encapsulates tracking context and is flexible to support different business cases:
@@ -372,16 +771,470 @@ context = BehaviorAnalytics::Context.new(
372
771
  tracker.track(context: context, event_name: "page_view")
373
772
  ```
374
773
 
774
+ ## Advanced Usage
775
+
776
+ ### Async Processing
777
+
778
+ Process events asynchronously using background jobs:
779
+
780
+ ```ruby
781
+ # Configure async processor
782
+ BehaviorAnalytics.configure do |config|
783
+ config.use_async = true
784
+ config.async_processor = BehaviorAnalytics::Processors::BackgroundJobProcessor.new(
785
+ job_class: BehaviorAnalytics::Jobs::ActiveEventJob
786
+ )
787
+ end
788
+
789
+ # Events will be processed in background
790
+ tracker.track(context: context, event_name: "event")
791
+ ```
792
+
793
+ ### Event Hooks
794
+
795
+ Execute callbacks on event lifecycle:
796
+
797
+ ```ruby
798
+ BehaviorAnalytics.configuration.hooks_manager.register_before_track do |event, context|
799
+ # Modify event before tracking
800
+ event[:metadata][:custom_field] = "value"
801
+ end
802
+
803
+ BehaviorAnalytics.configuration.hooks_manager.register_after_track do |event, context|
804
+ # Execute after tracking
805
+ NotificationService.notify(event)
806
+ end
807
+ ```
808
+
809
+ ### Event Sampling
810
+
811
+ Sample events to reduce storage:
812
+
813
+ ```ruby
814
+ sampling_strategy = BehaviorAnalytics::Sampling::Strategy.new(
815
+ sample_rate: 0.1 # Sample 10% of events
816
+ )
817
+ BehaviorAnalytics.configuration.sampling_strategy = sampling_strategy
818
+ ```
819
+
820
+ ### Rate Limiting
821
+
822
+ Limit events per context:
823
+
824
+ ```ruby
825
+ rate_limiter = BehaviorAnalytics::Throttling::Limiter.new(
826
+ max_events_per_minute: 100
827
+ )
828
+ BehaviorAnalytics.configuration.rate_limiter = rate_limiter
829
+ ```
830
+
831
+ ### Event Schema Validation
832
+
833
+ Validate events against JSON schemas:
834
+
835
+ ```ruby
836
+ schema = BehaviorAnalytics::Schema::Definition.new(
837
+ event_name: "signup",
838
+ schema: {
839
+ type: "object",
840
+ properties: {
841
+ email: { type: "string" },
842
+ plan: { type: "string", enum: ["basic", "premium"] }
843
+ },
844
+ required: ["email"]
845
+ }
846
+ )
847
+
848
+ validator = BehaviorAnalytics::Schema::Validator.new
849
+ validator.register_schema(schema)
850
+ BehaviorAnalytics.configuration.schema_validator = validator
851
+ ```
852
+
853
+ ## Migration Guide
854
+
855
+ ### From v1 to v2
856
+
857
+ v2 is backward compatible with v1. Existing code will continue to work:
858
+
859
+ ```ruby
860
+ # v1 code still works
861
+ tracker.track(context: context, event_name: "event")
862
+
863
+ # New v2 features are opt-in
864
+ BehaviorAnalytics.configure do |config|
865
+ config.track_visits = true # Enable new features
866
+ end
867
+ ```
868
+
869
+ ### Enabling Visit Tracking
870
+
871
+ 1. Run migrations:
872
+ ```bash
873
+ rails db:migrate
874
+ ```
875
+
876
+ 2. Enable in configuration:
877
+ ```ruby
878
+ BehaviorAnalytics.configure do |config|
879
+ config.track_visits = true
880
+ config.track_device_info = true
881
+ config.track_geolocation = true
882
+ end
883
+ ```
884
+
885
+ 3. Visits will be automatically created on requests
886
+
887
+ ### Migrating Existing Events
888
+
889
+ If you have existing events and want to link them to visits:
890
+
891
+ ```ruby
892
+ # Create visits for existing events (one-time migration)
893
+ BehaviorAnalyticsEvent.find_each do |event|
894
+ visit = visit_manager.find_or_create_visit(
895
+ visitor_token: generate_visitor_token_for_event(event),
896
+ tenant_id: event.tenant_id,
897
+ user_id: event.user_id,
898
+ ip: event.ip,
899
+ user_agent: event.user_agent
900
+ )
901
+ event.update(visit_id: visit.visit_token, visitor_id: visit.visitor_token)
902
+ end
903
+ ```
904
+
905
+ ## Performance Considerations
906
+
907
+ ### Database Indexes
908
+
909
+ The migrations create indexes for optimal query performance. For high-volume applications, consider:
910
+
911
+ - Additional composite indexes based on your query patterns
912
+ - Partitioning large tables by date
913
+ - Archiving old data to separate tables
914
+
915
+ ### Batch Processing
916
+
917
+ Configure appropriate batch sizes based on your volume:
918
+
919
+ ```ruby
920
+ # High volume
921
+ config.batch_size = 500
922
+ config.flush_interval = 60 # 1 minute
923
+
924
+ # Low volume
925
+ config.batch_size = 50
926
+ config.flush_interval = 600 # 10 minutes
927
+ ```
928
+
929
+ ### Async Processing
930
+
931
+ For high-throughput scenarios, use async processing:
932
+
933
+ ```ruby
934
+ config.use_async = true
935
+ config.async_processor = BehaviorAnalytics::Processors::BackgroundJobProcessor.new(
936
+ job_class: BehaviorAnalytics::Jobs::SidekiqEventJob
937
+ )
938
+ ```
939
+
940
+ ## Troubleshooting
941
+
942
+ ### Visits Not Being Created
943
+
944
+ 1. Check that `track_visits` is enabled:
945
+ ```ruby
946
+ BehaviorAnalytics.configuration.track_visits # => true
947
+ ```
948
+
949
+ 2. Verify migrations are run:
950
+ ```bash
951
+ rails db:migrate:status
952
+ ```
953
+
954
+ 3. Check Rails integration is included:
955
+ ```ruby
956
+ class ApplicationController < ActionController::Base
957
+ include BehaviorAnalytics::Integrations::Rails
958
+ end
959
+ ```
960
+
961
+ ### Device Detection Not Working
962
+
963
+ 1. Ensure `track_device_info` is enabled
964
+ 2. Check user agent is being passed:
965
+ ```ruby
966
+ request.user_agent # Should not be nil
967
+ ```
968
+
969
+ 3. For better detection, use a gem:
970
+ ```ruby
971
+ # Gemfile
972
+ gem 'browser' # or 'user_agent_parser'
973
+
974
+ # config
975
+ config.device_detector = :browser
976
+ ```
977
+
978
+ ### Geolocation Not Working
979
+
980
+ 1. Ensure `track_geolocation` is enabled
981
+ 2. Install geocoding gem:
982
+ ```ruby
983
+ # Gemfile
984
+ gem 'geocoder'
985
+
986
+ # Will automatically use Geocoder for IP lookup
987
+ ```
988
+
989
+ 3. For production, consider MaxMind GeoIP2 database
990
+
375
991
  ## Development
376
992
 
377
993
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
378
994
 
379
995
  To install this gem onto your local machine, run `bundle exec rake install`.
380
996
 
997
+ ### Running Tests
998
+
999
+ ```bash
1000
+ bundle exec rspec
1001
+ ```
1002
+
1003
+ ### Building the Gem
1004
+
1005
+ ```bash
1006
+ gem build behavior_analytics.gemspec
1007
+ ```
1008
+
1009
+ ### Publishing
1010
+
1011
+ ```bash
1012
+ gem push behavior_analytics-x.x.x.gem
1013
+ ```
1014
+
1015
+ ## Complete Example
1016
+
1017
+ Here's a complete example showing all features:
1018
+
1019
+ ```ruby
1020
+ # config/initializers/behavior_analytics.rb
1021
+ BehaviorAnalytics.configure do |config|
1022
+ config.storage_adapter = BehaviorAnalytics::Storage::ActiveRecordAdapter.new(
1023
+ model_class: BehaviorAnalyticsEvent
1024
+ )
1025
+
1026
+ # Visit tracking
1027
+ config.track_visits = true
1028
+ config.visit_duration = 30.minutes
1029
+ config.track_device_info = true
1030
+ config.track_geolocation = true
1031
+
1032
+ # Data retention
1033
+ config.visit_retention_days = 90
1034
+ config.event_retention_days = 365
1035
+
1036
+ # Single-tenant
1037
+ config.default_tenant_id = "my_app"
1038
+ end
1039
+
1040
+ # app/controllers/application_controller.rb
1041
+ class ApplicationController < ActionController::Base
1042
+ include BehaviorAnalytics::Integrations::Rails
1043
+ include BehaviorAnalytics::Helpers::TrackingHelper
1044
+
1045
+ private
1046
+
1047
+ def identify_user_after_login
1048
+ if user_signed_in?
1049
+ user_resolver = BehaviorAnalytics::Identification::UserResolver.new(
1050
+ visit_manager: visit_manager
1051
+ )
1052
+ user_resolver.identify_user(
1053
+ current_user.id,
1054
+ visitor_token: cookies[:behavior_visitor_token]
1055
+ )
1056
+ end
1057
+ end
1058
+ end
1059
+
1060
+ # app/controllers/projects_controller.rb
1061
+ class ProjectsController < ApplicationController
1062
+ def create
1063
+ @project = Project.create(project_params)
1064
+
1065
+ # Track with simplified API
1066
+ track_event("project_created", properties: {
1067
+ project_id: @project.id,
1068
+ project_name: @project.name
1069
+ })
1070
+
1071
+ redirect_to @project
1072
+ end
1073
+
1074
+ def show
1075
+ @project = Project.find(params[:id])
1076
+
1077
+ # Track page view
1078
+ track_page_view(path: request.path)
1079
+ end
1080
+ end
1081
+
1082
+ # app/jobs/cleanup_job.rb
1083
+ class CleanupJob < ApplicationJob
1084
+ def perform
1085
+ retention_policy = BehaviorAnalytics::Cleanup::RetentionPolicy.new(
1086
+ visit_retention_days: BehaviorAnalytics.configuration.visit_retention_days,
1087
+ event_retention_days: BehaviorAnalytics.configuration.event_retention_days
1088
+ )
1089
+
1090
+ scheduler = BehaviorAnalytics::Cleanup::Scheduler.new(
1091
+ storage_adapter: BehaviorAnalytics.configuration.storage_adapter,
1092
+ retention_policy: retention_policy
1093
+ )
1094
+
1095
+ scheduler.cleanup_all
1096
+ end
1097
+ end
1098
+
1099
+ # Analytics dashboard
1100
+ class AnalyticsController < ApplicationController
1101
+ def index
1102
+ tracker = BehaviorAnalytics.create_tracker
1103
+ context = BehaviorAnalytics::Context.new(
1104
+ tenant_id: current_tenant.id
1105
+ )
1106
+
1107
+ @analytics = {
1108
+ event_count: tracker.analytics.event_count(context, since: 7.days.ago),
1109
+ unique_users: tracker.analytics.unique_users(context),
1110
+ engagement_score: tracker.analytics.engagement_score(context),
1111
+ daily_activity: tracker.analytics.daily_activity(context),
1112
+ top_features: tracker.analytics.top_features(context, limit: 10),
1113
+ countries: tracker.analytics.geographic.country_breakdown(context),
1114
+ sources: tracker.analytics.referrer.source_breakdown(context)
1115
+ }
1116
+ end
1117
+ end
1118
+ ```
1119
+
1120
+ ## API Reference
1121
+
1122
+ ### BehaviorAnalytics Module
1123
+
1124
+ #### Class Methods
1125
+
1126
+ - `BehaviorAnalytics.configure { |config| ... }` - Configure the gem
1127
+ - `BehaviorAnalytics.create_tracker(options = {})` - Create a tracker instance
1128
+ - `BehaviorAnalytics.track(event_name, properties: {}, **options)` - Simplified tracking
1129
+ - `BehaviorAnalytics.track_page_view(path:, properties: {}, **options)` - Track page view
1130
+ - `BehaviorAnalytics.track_click(element:, properties: {}, **options)` - Track click
1131
+ - `BehaviorAnalytics.track_conversion(conversion_name:, value: nil, properties: {}, **options)` - Track conversion
1132
+ - `BehaviorAnalytics.tracker` - Get default tracker instance
1133
+
1134
+ ### Tracker Class
1135
+
1136
+ #### Methods
1137
+
1138
+ - `track(context:, event_name:, event_type: :custom, metadata: {}, **options)` - Track event
1139
+ - `track_api_call(context:, method:, path:, status_code:, duration_ms: nil, **options)` - Track API call
1140
+ - `track_feature_usage(context:, feature:, metadata: {}, **options)` - Track feature usage
1141
+ - `flush` - Flush buffered events
1142
+ - `analytics` - Get analytics engine
1143
+ - `query` - Get query builder
1144
+ - `subscribe_to_stream(filter: nil, &block)` - Subscribe to event stream
1145
+
1146
+ ### Analytics Engine
1147
+
1148
+ #### Methods
1149
+
1150
+ - `event_count(context, options = {})` - Count events
1151
+ - `unique_users(context, options = {})` - Count unique users
1152
+ - `active_days(context, options = {})` - Count active days
1153
+ - `engagement_score(context, options = {})` - Calculate engagement score
1154
+ - `activity_timeline(context, period: :daily, options = {})` - Get activity timeline
1155
+ - `daily_activity(context, options = {})` - Get daily activity
1156
+ - `feature_usage_stats(context, options = {})` - Get feature usage statistics
1157
+ - `top_features(context, limit: 10, options = {})` - Get top features
1158
+ - `funnels` - Access funnel analysis
1159
+ - `cohorts` - Access cohort analysis
1160
+ - `retention` - Access retention analysis
1161
+ - `geographic` - Access geographic analytics
1162
+ - `referrer` - Access referrer analytics
1163
+
1164
+ ### Visit Manager
1165
+
1166
+ #### Methods
1167
+
1168
+ - `find_or_create_visit(visitor_token:, tenant_id: nil, user_id: nil, **options)` - Find or create visit
1169
+ - `find_active_visit(visitor_token, user_id = nil)` - Find active visit
1170
+ - `save_visit(visit)` - Save visit
1171
+ - `end_visit(visit_token)` - End visit
1172
+ - `link_user_to_visits(visitor_token, user_id)` - Link visits to user
1173
+ - `find_visits_by_user(user_id, limit: 100)` - Get user's visits
1174
+ - `find_visits_by_visitor(visitor_token, limit: 100)` - Get visitor's visits
1175
+
1176
+ ### User Resolver
1177
+
1178
+ #### Methods
1179
+
1180
+ - `identify_user(user_id, visitor_token: nil, request: nil)` - Identify user and merge visits
1181
+ - `merge_visits(visitor_token, user_id)` - Merge anonymous visits with user
1182
+ - `get_user_visits(user_id, limit: 100)` - Get user's visits
1183
+ - `get_visitor_visits(visitor_token, limit: 100)` - Get visitor's visits
1184
+
1185
+ ## Best Practices
1186
+
1187
+ ### 1. Visit Tracking
1188
+
1189
+ - Enable visit tracking for web applications to get session analytics
1190
+ - Disable for API-only applications to reduce overhead
1191
+ - Use `visit_duration` to match your session timeout
1192
+
1193
+ ### 2. Device Detection
1194
+
1195
+ - Use `:simple` for basic detection (no dependencies)
1196
+ - Use `:browser` or `:user_agent_parser` for more accurate detection (requires gems)
1197
+ - Consider caching device info to reduce processing
1198
+
1199
+ ### 3. Geolocation
1200
+
1201
+ - Use geocoding service for production (Geocoder gem recommended)
1202
+ - Consider privacy implications of IP tracking
1203
+ - Cache geolocation results to reduce API calls
1204
+
1205
+ ### 4. Data Retention
1206
+
1207
+ - Set appropriate retention periods based on your needs
1208
+ - Schedule cleanup jobs to run regularly
1209
+ - Archive old data before deletion if needed for compliance
1210
+
1211
+ ### 5. Performance
1212
+
1213
+ - Use async processing for high-volume applications
1214
+ - Configure appropriate batch sizes
1215
+ - Monitor query performance and add indexes as needed
1216
+ - Consider using Redis or Elasticsearch adapters for scale
1217
+
1218
+ ### 6. Privacy & Compliance
1219
+
1220
+ - Be transparent about data collection
1221
+ - Implement data deletion on user request
1222
+ - Consider anonymizing IP addresses
1223
+ - Comply with GDPR, CCPA, and other regulations
1224
+
381
1225
  ## Contributing
382
1226
 
383
1227
  Bug reports and pull requests are welcome on GitHub at https://github.com/nerdawey/behavior_analytics.
384
1228
 
1229
+ ### Development Setup
1230
+
1231
+ 1. Fork the repository
1232
+ 2. Create a feature branch
1233
+ 3. Make your changes
1234
+ 4. Add tests
1235
+ 5. Run the test suite: `bundle exec rspec`
1236
+ 6. Submit a pull request
1237
+
385
1238
  ## License
386
1239
 
387
1240
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).