### Install Dependencies with Bundler Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Installs all project dependencies using Bundler. Ensure you have a Gemfile in your project root. ```bash bundle install ``` -------------------------------- ### Install Dependencies and Run Tests Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/README.md Install project dependencies using Bundler and run the RSpec tests directly. ```bash bundle install ``` ```bash rspec ``` -------------------------------- ### Install Locally Built Gem Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Installs a gem that has been built locally. Useful for testing the gem before publishing. ```bash gem install snowplow-tracker-*.gem ``` -------------------------------- ### Iglu Schema Format Example Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Illustrates the standard Iglu schema format used for self-describing events. ```ruby # Iglu schema format: iglu:{vendor}/{name}/{format}/{version} 'iglu:com.example/save_game/jsonschema/1-0-0' ``` -------------------------------- ### View YARD Documentation Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Starts a local web server to view the generated YARD documentation. Allows for easy browsing of the API. ```bash yard server ``` -------------------------------- ### Standard RSpec Setup with WebMock Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Shows a typical RSpec configuration that includes `spec_helper` and sets up tests for the `SnowplowTracker::Tracker` class. ```ruby # Standard RSpec setup with WebMock require 'spec_helper' describe SnowplowTracker::Tracker do # Tests... end ``` -------------------------------- ### Generate Documentation with YARD Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/README.md Generate documentation using YARD. Ensure the YARD and redcarpet gems are installed locally before running this command. ```bash yard doc ``` -------------------------------- ### Run All RSpec Tests Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Execute all tests in the project using the `rspec` command. Ensure you have the RSpec gem installed. ```bash # Run all tests rspec ``` -------------------------------- ### Initialize Synchronous Emitter Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Creates a synchronous HTTP emitter that buffers events and sends them to a Snowplow collector. GET requests default to a buffer of 1; POST requests default to a buffer of 10. Configure options like protocol, port, method, buffer size, callbacks, and logger. ```ruby require 'snowplow-tracker' require 'logger' # Callbacks on_success = ->(count) { puts "#{count} event(s) sent OK" } on_failure = ->(ok, failed) { puts "#{ok} OK, #{failed.size} failed: #{failed.inspect}" } emitter = SnowplowTracker::Emitter.new( endpoint: 'collector.example.com', options: { protocol: 'https', port: 443, method: 'post', buffer_size: 5, on_success: on_success, on_failure: on_failure, logger: Logger.new(STDOUT) } ) # => logs: "Emitter initialized with endpoint https://collector.example.com:443/com.snowplowanalytics.snowplow/tp2" ``` -------------------------------- ### HTTP Method Configuration for Emitter Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Defines default HTTP configuration including protocol and method. Buffer size is dependent on the HTTP method used (1 for GET, 10 for POST). ```ruby DEFAULT_CONFIG = { protocol: 'http', method: 'get' } # GET: buffer_size = 1 (single event) # POST: buffer_size = 10 (batch events) ``` -------------------------------- ### SimpleCov Configuration for Test Coverage Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Configure SimpleCov to start coverage reporting and exclude test files from the report. ```ruby SimpleCov.start do add_filter 'spec/' # Exclude test files from coverage end ``` -------------------------------- ### HTTP GET Request Building in Ruby Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Constructs and sends an HTTP GET request with payload parameters encoded in the URL. Used for single event transmission. ```ruby def http_get(payload) uri = URI(@collector_uri + '?' + URI.encode_www_form(payload)) Net::HTTP.get_response(uri) end ``` -------------------------------- ### Add snowplow-tracker Gem to Gemfile Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Add the snowplow-tracker gem to your Gemfile to include it in your project. Bundle install to install the gem. ```ruby gem "snowplow-tracker", ">= 0.8.0" ``` ```bash bundle install ``` -------------------------------- ### Setter Method Pattern for Chaining Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md All setter methods in the Subject class return self, enabling method chaining for a more fluent API. Example: `tracker.set_user_id('user1').set_platform('web')`. ```ruby def set_user_id(user_id) @details['uid'] = user_id self end ``` -------------------------------- ### WebMock Stubbing HTTP Requests in Specs Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Provides an example of how to stub HTTP requests using WebMock within RSpec tests. ```ruby # Stub HTTP requests in specs stub_request(:any, /localhost/) .to_return(status: 200, body: 'stubbed response') ``` -------------------------------- ### Correct Buffer Management for POST Method Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Illustrates the correct configuration for an Emitter using the POST method with a buffer size. POST is recommended for batching events. ```ruby emitter = SnowplowTracker::Emitter.new(endpoint: 'localhost', options: { method: 'post', buffer_size: 10 }) ``` -------------------------------- ### Correct Tracker Initialization with Emitter Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Demonstrates the proper initialization of the Tracker class, requiring an Emitter instance to be provided. ```ruby emitter = SnowplowTracker::Emitter.new(endpoint: 'collector.example.com') tracker = SnowplowTracker::Tracker.new(emitters: emitter) ``` -------------------------------- ### Tracker.new Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Initializes the main tracking object. Requires at least one Emitter and accepts optional Subject, namespace, app_id, and encode_base64 flag. ```APIDOC ## Tracker.new ### Description Initializes the main tracking object. Requires at least one Emitter. Accepts an optional `Subject`, a tracker `namespace` (maps to `name_tracker` in processed events), an `app_id`, and an `encode_base64` flag that controls base64 encoding of all JSON payloads. ### Method `Tracker.new(emitters:, subject: nil, namespace: nil, app_id: nil, encode_base64: true)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby emitter = SnowplowTracker::Emitter.new(endpoint: 'collector.example.com') subject = SnowplowTracker::Subject.new.set_user_id('user_42').set_platform('web') tracker = SnowplowTracker::Tracker.new( emitters: emitter, subject: subject, namespace: 'cf', # → name_tracker in processed event app_id: 'shop', # → app_id in processed event encode_base64: false # send JSONs as plain strings ) ``` ### Response #### Success Response (200) Tracker initialized successfully. #### Response Example None provided. ``` -------------------------------- ### Single Entry Point for File Requirements Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Shows the correct way to require the tracker library, using a single entry point to avoid circular dependencies. ```ruby require 'snowplow-tracker' ``` -------------------------------- ### Build and Run Docker Image Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/README.md Build a Docker image for the Ruby tracker and run it. The -v flag creates a bind mount for the project directory, automatically applying file changes within the Docker image. Rebuild the image if Gemfile or snowplow-tracker.gemspec are modified. ```bash docker build . -t ruby-tracker ``` ```bash docker run -v "$(pwd)":"/code" ruby-tracker ``` -------------------------------- ### Define Schema Constants Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Sets up base paths and specific schema URIs for contexts and unstructured events. ```ruby BASE_SCHEMA_PATH = 'iglu:com.snowplowanalytics.snowplow' SCHEMA_TAG = 'jsonschema' CONTEXT_SCHEMA = "#{BASE_SCHEMA_PATH}/contexts/#{SCHEMA_TAG}/1-0-1" UNSTRUCT_EVENT_SCHEMA = "#{BASE_SCHEMA_PATH}/unstruct_event/#{SCHEMA_TAG}/1-0-0" ``` -------------------------------- ### Fluent Interface for Tracker Configuration Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Demonstrates the fluent interface pattern where setter methods return self, allowing for method chaining. This enhances readability and conciseness. ```ruby tracker.set_user_id('123').track_page_view(page_url: 'example.com') ``` -------------------------------- ### Logging Configuration Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Configure global or per-emitter logging using the standard Ruby `Logger`. ```APIDOC ## Logging Emitters write activity to `STDERR` via the Ruby standard library `Logger`. The module-level `LOGGER` constant can be reconfigured at any time, or a custom logger injected per-Emitter. ### Examples: 1. **Increase verbosity globally (DEBUG shows per-event payloads)** ```ruby require 'logger' SnowplowTracker::LOGGER.level = Logger::DEBUG ``` 2. **Silence logging entirely** ```ruby SnowplowTracker::LOGGER.level = Logger::FATAL ``` 3. **Per-emitter custom logger (e.g. log to a file)** ```ruby file_logger = Logger.new('/var/log/snowplow.log') file_logger.level = Logger::WARN emitter = SnowplowTracker::Emitter.new( endpoint: 'collector.example.com', options: { logger: file_logger } ) ``` ``` -------------------------------- ### Proper URL Encoding Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Demonstrates correct URL encoding for payload parameters to avoid issues. ```ruby # ❌ Wrong: URL encoding issues URI(@collector_uri + '?' + payload.to_query) # ✅ Correct: Proper encoding URI(@collector_uri + '?' + URI.encode_www_form(payload)) ``` -------------------------------- ### RSpec Configuration Testing Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Write RSpec tests to verify that objects initialize with default settings correctly. ```ruby describe 'configuration' do it 'should initialise with default settings' do obj = SnowplowTracker::Class.new expect(obj.setting).to eq(default_value) end end ``` -------------------------------- ### Initialize Tracker with Emitter and Subject Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Initializes the main tracking object. Requires at least one Emitter. Accepts an optional Subject, a tracker namespace, an app_id, and an encode_base64 flag that controls base64 encoding of all JSON payloads. ```ruby emitter = SnowplowTracker::Emitter.new(endpoint: 'collector.example.com') subject = SnowplowTracker::Subject.new.set_user_id('user_42').set_platform('web') tracker = SnowplowTracker::Tracker.new( emitters: emitter, subject: subject, namespace: 'cf', # → name_tracker in processed event app_id: 'shop', # → app_id in processed event encode_base64: false # send JSONs as plain strings ) ``` -------------------------------- ### Asynchronous Flush for Non-Blocking Execution Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Demonstrates the correct, non-blocking way to flush events using `async: true`. ```ruby # ✅ Correct: Async flush for non-blocking tracker.flush(async: true) ``` -------------------------------- ### Correct Module Namespace Usage Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Illustrates the correct way to define a class within a module namespace. This prevents naming conflicts and organizes code. ```ruby module SnowplowTracker class Tracker # ... end end ``` -------------------------------- ### Page Context Property Storage Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Initializes the page context with optional URL, title, and referrer. These details are stored in a hash for easy access. ```ruby def initialize(page_url: nil, page_title: nil, referrer: nil) @details = { 'url' => page_url, 'page' => page_title, 'refr' => referrer } end ``` -------------------------------- ### Emitter.new Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Creates a synchronous HTTP emitter that buffers events and sends them to a Snowplow collector. It accepts an endpoint and various options for configuration. ```APIDOC ## Emitter.new ### Description Creates a synchronous HTTP emitter that buffers events and sends them to a Snowplow collector. The only required argument is `endpoint`; all tuning options are passed through the `options` hash. GET requests default to a buffer of 1; POST requests default to a buffer of 10. ### Method `Emitter.new(endpoint:, options: {})` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby require 'snowplow-tracker' require 'logger' # Callbacks on_success = ->(count) { puts "#{count} event(s) sent OK" } on_failure = ->(ok, failed) { puts "#{ok} OK, #{failed.size} failed: #{failed.inspect}" } emitter = SnowplowTracker::Emitter.new( endpoint: 'collector.example.com', options: { protocol: 'https', port: 443, method: 'post', buffer_size: 5, on_success: on_success, on_failure: on_failure, logger: Logger.new(STDOUT) } ) ``` ### Response #### Success Response (200) Emitter initialized successfully. #### Response Example ``` # logs: "Emitter initialized with endpoint https://collector.example.com:443/com.snowplowanalytics.snowplow/tp2" ``` ``` -------------------------------- ### Buffer Pre-allocation Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Initializes the event buffer with a specified maximum size. ```ruby def initialize(buffer_size: 10) @buffer = [] @buffer_size = buffer_size end ``` -------------------------------- ### Run Tests with Coverage Report Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Executes all tests and generates a code coverage report. This helps identify areas of the code not adequately tested. ```bash bundle exec rspec ``` -------------------------------- ### Correct Event Context Pattern Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Shows how to correctly pass context to tracking methods using an array of SelfDescribingJson objects, rather than a plain hash. ```ruby context = [SnowplowTracker::SelfDescribingJson.new('iglu:schema', { user: 'john' })] tracker.track_page_view(page_url: 'example.com', context: context) ``` -------------------------------- ### Unit Test File Naming Convention Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Follow the 'spec/unit/_spec.rb' convention for naming unit test files. ```ruby spec/unit/_spec.rb ``` -------------------------------- ### Create SelfDescribingJson for Custom Event Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Wraps an Iglu schema URI and a data hash into a `{ schema:, data: }` structure. This is used for custom events and for attaching context entities to any event type. ```ruby # Custom event schema ad_click = SnowplowTracker::SelfDescribingJson.new( 'iglu:com.snowplowanalytics/ad_click/jsonschema/1-0-0', { bannerId: '4acd518feb82' } ) # Context entity describing the web page page_entity = SnowplowTracker::SelfDescribingJson.new( 'iglu:com.example/web_page/jsonschema/1-0-0', { 'id' => SecureRandom.uuid, 'category' => 'news' } ) tracker.track_self_describing_event( event_json: ad_click, context: [page_entity] ) # to_json output (before encoding): # { schema: "iglu:com.snowplowanalytics/ad_click/jsonschema/1-0-0", # data: { bannerId: "4acd518feb82" } } ``` -------------------------------- ### Add Test Dependencies to Gemfile Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Include RSpec, WebMock, and SimpleCov in your Gemfile or gemspec for testing. ```ruby # In Gemfile or gemspec gem 'rspec', '~> 3.10' gem 'webmock', '~> 3.14' gem 'simplecov' # Coverage reporting gem 'simplecov-lcov' # LCOV format output ``` -------------------------------- ### Default Platform Setting in Subject Class Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Sets the default platform to 'srv' (server-side application) and defines supported platforms. This is used to identify the application type sending events. ```ruby DEFAULT_PLATFORM = 'srv' # Server-side application SUPPORTED_PLATFORMS = %w[ web mob pc srv app tv cnsl iot ] ``` -------------------------------- ### Run All Tests with RSpec Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Executes all tests in the project using the RSpec framework. This is a standard way to verify code integrity. ```bash rspec ``` -------------------------------- ### Tracker#flush / Emitter#input Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Force buffered events to be sent immediately using `flush`. `AsyncEmitter#input` can be used for manual retries. ```APIDOC ## Tracker#flush / Emitter#input `Tracker#flush` forces all buffered events to be sent immediately, even if the buffer is not full. With `AsyncEmitter`, calling `flush(async: false)` blocks until every queued batch has been transmitted — useful at application shutdown or end of a background job. ### Examples: 1. **Sync flush (default for Tracker#flush) — blocks until done** ```ruby tracker.flush ``` 2. **Async flush — returns immediately, sends in background thread** ```ruby tracker.flush(async: true) ``` 3. **Manual retry via Emitter#input inside an on_failure callback** ```ruby retry_emitter = SnowplowTracker::AsyncEmitter.new( endpoint: 'collector.example.com', options: { method: 'post', on_failure: ->(ok_count, failed_events) { failed_events.each { |ev| retry_emitter.input(ev) } } } ) ``` ``` -------------------------------- ### Define Tracker Version Constants Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Defines the tracker version and a combined version string for event tracking. ```ruby module SnowplowTracker VERSION = '0.8.1-rc1' TRACKER_VERSION = "rb-" + VERSION # Sent with events end ``` -------------------------------- ### License Header Requirement in Ruby Files Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md All files must include the Apache 2.0 license header with proper attribution. This is a mandatory requirement for code contributions. ```ruby # Copyright (c) 2013-2021 Snowplow Analytics Ltd. All rights reserved. # [Full license text...] # Author:: Snowplow Analytics Ltd # Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd # License:: Apache License Version 2.0 ``` -------------------------------- ### Build Gem Package Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Builds the Snowplow Ruby Tracker gem from its specification file. This creates a distributable package. ```bash gem build snowplow-tracker.gemspec ``` -------------------------------- ### Subject Scope: Correct Event-Specific Subject Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Demonstrates the correct way to manage subject properties for individual events to avoid global scope confusion. ```ruby # ✅ Correct: Event-specific subject subject1 = SnowplowTracker::Subject.new.set_user_id('user1') subject2 = SnowplowTracker::Subject.new.set_user_id('user2') tracker.track_page_view(page_url: 'page1', subject: subject1) tracker.track_page_view(page_url: 'page2', subject: subject2) ``` -------------------------------- ### Run Specific Test File with RSpec Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Executes tests from a single specified file. Useful for focused testing during development. ```bash rspec spec/unit/tracker_spec.rb ``` -------------------------------- ### Integration Test: End-to-End Event Flow Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Verify the complete event sending process in integration tests, from tracking to emitter. ```ruby it 'sends complete event with all components' do emitter = create_emitter subject = create_subject tracker = SnowplowTracker::Tracker.new(emitters: emitter, subject: subject) tracker.track_page_view(page_url: 'test.com') verify_event_payload(emitter.get_last_querystring) end ``` -------------------------------- ### Expose Private Tracker State for Testing Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Extend the Tracker class to expose private attributes like 'settings' and 'encode_base64' for testing purposes. ```ruby module SnowplowTracker class Tracker attr_reader :settings, :encode_base64 # Expose for testing end end ``` -------------------------------- ### Track Screen View Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Tracks a mobile or desktop screen view. This method internally uses `track_self_describing_event` with the `screen_view` schema. At least one of `name` or `id` must be provided. ```ruby tracker.track_screen_view( name: 'HUD > Save Game', id: 'screen23' ) # Sends: e=ue wrapping schema iglu:.../screen_view/jsonschema/1-0-0 # data: { "name": "HUD > Save Game", "id": "screen23" } ``` -------------------------------- ### HTTP POST Request Building in Ruby Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Constructs and sends an HTTP POST request with a JSON body containing schema and data. Used for batch event transmission. ```ruby def http_post(payload_array) req = Net::HTTP::Post.new(uri) req.body = { schema: POST_SCHEMA, data: payload_array }.to_json req['Content-Type'] = 'application/json' end ``` -------------------------------- ### Page Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Attaches webpage metadata such as URL, title, and referrer to events. Page properties can override equivalent keyword arguments in tracking methods. ```APIDOC ## Page ### Description Attaches webpage metadata (`page_url`, `page_title`, `referrer`) to any event as atomic columns. When passed to `track_page_view`, Page properties override the equivalent keyword arguments. ### Method `SnowplowTracker::Page.new(page_url:, page_title:, referrer:) ### Parameters #### Path Parameters - `page_url` (String) - Required - The URL of the current page. - `page_title` (String) - Optional - The title of the current page. - `referrer` (String) - Optional - The referrer URL. ### Usage ```ruby page = SnowplowTracker::Page.new( page_url: 'https://www.example.com/second-page', page_title: 'Part 2: Advanced Topics', referrer: 'https://www.example.com/first-page' ) tracker.track_struct_event( category: 'video', action: 'play', page: page ) ``` ### Response When a `Page` object is passed to a tracking method, its properties (`url`, `page`, `refr`) are added to the event payload. ``` -------------------------------- ### Synchronous Flush Blocking Execution Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Illustrates the incorrect, blocking behavior of a synchronous `flush` call. ```ruby # ❌ Wrong: Synchronous flush blocks execution tracker.flush # Blocks until all events sent ``` -------------------------------- ### Run RuboCop Linter Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Analyzes Ruby code for style and potential issues using the RuboCop linter. Helps maintain code quality and consistency. ```bash bundle exec rubocop ``` -------------------------------- ### Initialize Asynchronous Emitter and Tracker Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt A subclass of Emitter that offloads HTTP sending to a background thread pool. Accepts the same options as Emitter plus thread_count. Useful for production Rails apps where tracking must not block the request thread. Initializes the main tracking object with the async emitter, namespace, and app_id. ```ruby async_emitter = SnowplowTracker::AsyncEmitter.new( endpoint: 'collector.example.com', options: { protocol: 'https', port: 443, method: 'post', buffer_size: 10, thread_count: 3, on_success: ->(n) { Rails.logger.info "Snowplow: #{n} events sent" }, on_failure: ->(ok, bad) { Rails.logger.error "Snowplow: #{bad.size} events failed" } } ) tracker = SnowplowTracker::Tracker.new( emitters: async_emitter, namespace: 'rails_app', app_id: 'my_app' ) ``` -------------------------------- ### Track Self-Describing Event with Context Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Tracks a custom, schema-validated event using `SelfDescribingJson`. This is the recommended method for new event types. Context entities can be attached to enrich the event. ```ruby save_event = SnowplowTracker::SelfDescribingJson.new( 'iglu:com.example_company/save_game/jsonschema/1-0-2', { 'saveId' => '4321', 'level' => 23, 'difficultyLevel' => 'HARD', 'dlContent' => true } ) user_entity = SnowplowTracker::SelfDescribingJson.new( 'iglu:com.example_company/user/jsonschema/1-0-0', { 'userId' => 'user_42', 'isPremium' => true } ) tracker.track_self_describing_event( event_json: save_event, context: [user_entity] ) # Sends: e=ue, ue_px=, cx= ``` -------------------------------- ### Buffer Management for Event Emitter Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Manages an event buffer, adding payloads and flushing the buffer when it reaches the configured size. This is key for batching events. ```ruby def input(payload) @buffer.push(payload) flush if @buffer.size >= @buffer_size end ``` -------------------------------- ### Correct E-commerce Transaction Tracking Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Shows the correct way to track an e-commerce transaction, including all required fields for both the transaction and its items. ```ruby # ✅ Correct: All required fields tracker.track_ecommerce_transaction( transaction: { 'order_id' => '123', 'total_value' => 99.99 }, items: [{ 'sku' => 'ABC', 'price' => 99.99, 'quantity' => 1 }] ) ``` -------------------------------- ### Network Error Handling and Recovery Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Includes a begin-rescue block to handle potential network errors during HTTP requests. Logs warnings and calls a failure handler if an error occurs. ```ruby begin response = http_get(payload) handle_success(response) rescue StandardError => e logger.warn("Request failed: #{e.message}") handle_failure(payload) end ``` -------------------------------- ### Forcing Event Transmission with Tracker#flush Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Use `Tracker#flush` to immediately send all buffered events. With `AsyncEmitter`, `flush(async: false)` blocks until all batches are transmitted, which is useful for application shutdown. ```ruby # Sync flush (default for Tracker#flush) — blocks until done tracker.flush ``` ```ruby # Async flush — returns immediately, sends in background thread tracker.flush(async: true) ``` ```ruby # Manual retry via Emitter#input inside an on_failure callback retry_emitter = SnowplowTracker::AsyncEmitter.new( endpoint: 'collector.example.com', options: { method: 'post', on_failure: ->(ok_count, failed_events) { failed_events.each { |ev| retry_emitter.input(ev) } } } ) ``` -------------------------------- ### Test Context Attachment Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Verify that context is correctly attached to events by checking for the 'cx' parameter in the payload. ```ruby # Verify context attachment context = [SnowplowTracker::SelfDescribingJson.new(schema, data)] tracker.track_page_view(page_url: 'test.com', context: context) parsed = CGI.parse(emitter.get_last_querystring) expect(parsed).to include('cx') # Context present ``` -------------------------------- ### Avoid Ruby 2.5+ Hash Features Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Provides a compatible way to transform hash keys for older Ruby versions. ```ruby # ❌ Wrong: Using Ruby 2.5+ features hash.transform_keys(&:to_s) # ✅ Correct: Compatible with Ruby 2.1+ hash.keys.each { |key| hash[key.to_s] = hash.delete key } ``` -------------------------------- ### DeviceTimestamp Class Implementation Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Represents a device-created timestamp ('dtm') for an event. Inherits from the base Timestamp class. ```ruby class DeviceTimestamp < Timestamp def initialize(value) super 'dtm', value # Device-created timestamp end end ``` -------------------------------- ### Attach Page Metadata to Event Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Attaches webpage metadata such as URL, title, and referrer to an event as atomic columns. When used with `track_page_view`, these properties override equivalent keyword arguments. ```ruby page = SnowplowTracker::Page.new( page_url: 'https://www.example.com/second-page', page_title: 'Part 2: Advanced Topics', referrer: 'https://www.example.com/first-page' ) tracker.track_struct_event( category: 'video', action: 'play', page: page ) # → url, page, refr fields added to the event payload ``` -------------------------------- ### Run Specific RSpec File Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Execute tests within a specific file by providing the file path to the `rspec` command. This is useful for focusing on a particular set of tests. ```bash # Run specific file rspec spec/unit/tracker_spec.rb ``` -------------------------------- ### Managing Tracker Subject and Emitters Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Replace the tracker-level Subject using `set_subject` to update user context mid-session. Append additional Emitters with `add_emitter` to send events to multiple collectors concurrently. ```ruby # Swap the active subject mid-session (e.g. after login) new_subject = SnowplowTracker::Subject.new .set_user_id('logged_in_user') .set_platform('web') tracker.set_subject(new_subject) ``` ```ruby # Mirror events to a second collector (e.g. staging + production) staging_emitter = SnowplowTracker::Emitter.new( endpoint: 'collector-staging.example.com', options: { protocol: 'https', method: 'post' } ) tracker.add_emitter(staging_emitter) tracker.track_struct_event(category: 'test', action: 'dual_send') # → event dispatched to both the original and staging emitters ``` -------------------------------- ### Subject Parameter Mapping to Protocol Fields Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Maps Subject class methods to their corresponding Snowplow Tracker Protocol fields. This ensures data is correctly formatted for the collector. ```ruby # Ruby method -> Protocol field set_user_id -> 'uid' set_platform -> 'p' set_lang -> 'lang' set_ip_address -> 'ip' ``` -------------------------------- ### SelfDescribingJson Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt A utility class for wrapping Iglu schema URIs and data hashes into a structured JSON format, used for custom events and context entities. ```APIDOC ## SelfDescribingJson ### Description Wraps an Iglu schema URI and a data hash into a `{ schema:, data: }` envelope. Used both for custom events (`track_self_describing_event`) and for attaching context entities to any event type. ### Method `SnowplowTracker::SelfDescribingJson.new(schema_uri, data_hash)` ### Parameters #### Path Parameters - `schema_uri` (String) - Required - The Iglu schema URI. - `data_hash` (Hash) - Required - The data payload conforming to the schema. ### Usage #### Custom event schema ```ruby ad_click = SnowplowTracker::SelfDescribingJson.new( 'iglu:com.snowplowanalytics/ad_click/jsonschema/1-0-0', { bannerId: '4acd518feb82' } ) ``` #### Context entity describing the web page ```ruby page_entity = SnowplowTracker::SelfDescribingJson.new( 'iglu:com.example/web_page/jsonschema/1-0-0', { 'id' => SecureRandom.uuid, 'category' => 'news' } ) tracker.track_self_describing_event( event_json: ad_click, context: [page_entity] ) ``` ### Output Example (to_json) ```json { "schema": "iglu:com.snowplowanalytics/ad_click/jsonschema/1-0-0", "data": { "bannerId": "4acd518feb82" } } ``` ``` -------------------------------- ### Configuring Logging for Emitters Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Modify the global `SnowplowTracker::LOGGER` or provide a custom logger instance to an Emitter for flexible logging. Default logging is to STDERR. ```ruby require 'logger' # Increase verbosity globally (DEBUG shows per-event payloads) SnowplowTracker::LOGGER.level = Logger::DEBUG # Silence logging entirely SnowplowTracker::LOGGER.level = Logger::FATAL # Per-emitter custom logger (e.g. log to a file) file_logger = Logger.new('/var/log/snowplow.log') file_logger.level = Logger::WARN emitter = SnowplowTracker::Emitter.new( endpoint: 'collector.example.com', options: { logger: file_logger } ) ``` -------------------------------- ### Iglu Schema Identifier Format Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Defines the standard format for Iglu schema identifiers, which are used to version and describe self-describing JSONs. ```ruby # Iglu schema identifier format 'iglu:{vendor}/{name}/{format}/{version}' # Example: 'iglu:com.snowplowanalytics/ad_click/jsonschema/1-0-0' ``` -------------------------------- ### Examine Event Payloads with CGI.parse Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Parse and inspect the querystring of an event payload using CGI.parse for debugging. Pretty print the resulting parameters for easier analysis. ```ruby # Parse and inspect querystring params = CGI.parse(emitter.get_last_querystring) pp params # Pretty print for debugging ``` -------------------------------- ### Timestamp Handling Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Control event timestamps using raw numeric values, explicit DeviceTimestamps, or TrueTimestamps for server-verified times. ```APIDOC ## Timestamp Handling By default, every `track_*` call attaches the current time as a `DeviceTimestamp` (`dtm`). You can override this by passing a numeric millisecond value, an explicit `DeviceTimestamp`, or a `TrueTimestamp` (`ttm`). `TrueTimestamp` signals a server-verified time and is used for the `derived_tstamp` calculation. ### Examples: 1. **Raw numeric → auto-wrapped as DeviceTimestamp** ```ruby tracker.track_page_view(page_url: 'https://example.com', tstamp: 1_633_596_554_978) ``` 2. **Explicit DeviceTimestamp** ```ruby dt = SnowplowTracker::DeviceTimestamp.new(1_633_596_554_978) tracker.track_page_view(page_url: 'https://example.com', tstamp: dt) ``` 3. **TrueTimestamp — use when the device clock may be unreliable** ```ruby tt = SnowplowTracker::TrueTimestamp.new(1_633_596_554_978) tracker.track_self_describing_event( event_json: SnowplowTracker::SelfDescribingJson.new( 'iglu:com.example/server_call/jsonschema/1-0-0', { endpoint: '/checkout' } ), tstamp: tt ) # Raw event: ttm=1633596554978 (instead of dtm) ``` ``` -------------------------------- ### Integration Test: Multi-Event Scenarios Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Test scenarios involving multiple events, such as an e-commerce transaction with associated items. ```ruby it 'handles transaction with items' do tracker.track_ecommerce_transaction( transaction: transaction_hash, items: items_array ) # Verify transaction event + item events expect(emitter.buffer).to have(items_array.length + 1).events end ``` -------------------------------- ### Subject Management Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Manages user and device properties that are attached to every event. Subject properties can be set at the tracker level or overridden for individual events. ```APIDOC ## Subject ### Description Stores user and device properties that are attached to every event as first-class atomic columns. A `Subject` is auto-created for each `Tracker`; its methods are accessible directly on the `Tracker` via metaprogramming. Event-specific Subjects can be passed to any `track_*` call to override the tracker-level Subject. ### Methods These methods can be called directly on the `Tracker` instance or on a `Subject` instance. - `set_user_id(user_id)` - `set_platform(platform)` - `set_ip_address(ip_address)` - `set_useragent(useragent)` - `set_timezone(timezone)` - `set_lang(lang)` - `set_screen_resolution(width:, height:) - `set_viewport(width:, height:) - `set_domain_user_id(domain_user_id)` ### Usage #### Tracker-level subject (applies to all events) ```ruby tracker.set_user_id('user_42') .set_platform('web') .set_ip_address('203.0.113.5') .set_useragent('Mozilla/5.0 (X11; Linux x86_64)') .set_timezone('Europe/London') .set_lang('en-GB') .set_screen_resolution(width: 2560, height: 1440) .set_viewport(width: 1280, height: 800) ``` #### Per-event subject (overrides tracker-level for that event only) ```ruby event_subject = SnowplowTracker::Subject.new .set_user_id('user_99') .set_platform('mob') tracker.track_page_view( page_url: 'https://m.example.com', subject: event_subject ) ``` #### Stitch server-side events with client-side JS tracker via first-party cookie ```ruby sp_cookie = cookies.find { |k, _| k =~ /^_sp_id/ } domain_uid = sp_cookie&.last&.split('.')&.first tracker.set_domain_user_id(domain_uid) if domain_uid ``` ``` -------------------------------- ### RSpec Helper Monkey Patching for Emitter Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/CLAUDE.md Demonstrates extending the `Emitter` class in `spec_helper.rb` to add custom testing methods like `get_last_querystring`. ```ruby # spec_helper.rb extends Emitter for testing class Emitter def get_last_querystring(n = 1) @@querystrings[-n] end end ``` -------------------------------- ### Stub HTTP Requests for Emitter Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Use WebMock to stub HTTP requests made by the Emitter. Avoid making real HTTP requests during tests. ```ruby # ❌ Wrong: Real HTTP requests emitter = SnowplowTracker::Emitter.new(endpoint: 'real-collector.com') # ✅ Correct: Stubbed requests stub_request(:any, /localhost/).to_return(status: 200) emitter = SnowplowTracker::Emitter.new(endpoint: 'localhost') ``` -------------------------------- ### Run RSpec Tests with Coverage Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Enable code coverage reporting when running RSpec tests by setting the `COVERAGE` environment variable to `true`. This helps identify untested parts of your codebase. ```bash # Run with coverage COVERAGE=true rspec ``` -------------------------------- ### Enable WebMock Debugging Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Enable debugging for WebMock to inspect outgoing HTTP requests and their corresponding responses during tests. This is useful for verifying that your tracker is making the expected network calls. ```ruby # Enable WebMock debugging WebMock.after_request do |request, response| puts "Request: #{request.method} #{request.uri}" puts "Response: #{response.status}" end ``` -------------------------------- ### Dynamic Method Delegation in Ruby Tracker Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md The Tracker class delegates Subject methods using metaprogramming, dynamically defining methods based on Ruby version. This enables chaining of subject-related operations. ```ruby # Dynamic method definition based on Ruby version Subject.instance_methods(false).each do |name| if RUBY_VERSION >= '3.0.0' define_method name, ->(*args, **kwargs) do @subject.method(name.to_sym).call(*args, **kwargs) self # Enable chaining end end end ``` -------------------------------- ### Timestamp Creation in Milliseconds Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Calculates the current timestamp in milliseconds since the epoch. This is used for event timestamps. ```ruby def self.create (Time.now.to_f * 1000).to_i # Milliseconds since epoch end ``` -------------------------------- ### Run RSpec Tests with a Pattern Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Filter and run RSpec tests that match a specific pattern using the `-e` flag. This allows you to target tests based on their descriptions. ```bash # Run with pattern rspec -e "tracks page views" ``` -------------------------------- ### Set Tracker-Level Subject Properties Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Configures user and device properties that will be attached to all events tracked by this tracker instance. These settings can be overridden on a per-event basis. ```ruby # Tracker-level subject (applies to all events) tracker.set_user_id('user_42') .set_platform('web') .set_ip_address('203.0.113.5') .set_useragent('Mozilla/5.0 (X11; Linux x86_64)') .set_timezone('Europe/London') .set_lang('en-GB') .set_screen_resolution(width: 2560, height: 1440) .set_viewport(width: 1280, height: 800) # Per-event subject (overrides tracker-level for that event only) event_subject = SnowplowTracker::Subject.new .set_user_id('user_99') .set_platform('mob') tracker.track_page_view( page_url: 'https://m.example.com', subject: event_subject ) # Stitch server-side events with client-side JS tracker via first-party cookie sp_cookie = cookies.find { |k, _| k =~ /^_sp_id/ } domain_uid = sp_cookie&.last&.split('.')&.first tracker.set_domain_user_id(domain_uid) if domain_uid ``` -------------------------------- ### RSpec Test Structure Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Organize RSpec tests using 'describe' blocks for classes and methods, and 'it' blocks for individual test cases. ```ruby describe SnowplowTracker::ClassName do describe 'configuration' do # Initialization tests end describe '#method_name' do # Method-specific tests end end ``` -------------------------------- ### Tracker#track_struct_event Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Tracks a structured event using the category/action taxonomy. Optional parameters include label, property, and value. ```APIDOC ## Tracker#track_struct_event ### Description Tracks a Google-Analytics-style structured event (`e=se`) using the `category`/`action` taxonomy. Provided for compatibility with GA-oriented pipelines; prefer `track_self_describing_event` for new schemas. ### Method `tracker.track_struct_event(category:, action:, label: nil, property: nil, value: nil)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby tracker.track_struct_event( category: 'shop', action: 'add-to-basket', label: 'pbz0026', # optional: item label property: 'pcs', # optional: property name value: 2 # optional: numeric value ) ``` ### Response #### Success Response (200) Structured event tracked successfully. #### Response Example ``` # Sends: e=se, se_ca=shop, se_ac=add-to-basket, se_la=pbz0026, se_pr=pcs, se_va=2 ``` ``` -------------------------------- ### Track Structured Event Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Tracks a Google-Analytics-style structured event using the category/action taxonomy. Provided for compatibility with GA-oriented pipelines; prefer track_self_describing_event for new schemas. Supports optional label, property, and value. ```ruby tracker.track_struct_event( category: 'shop', action: 'add-to-basket', label: 'pbz0026', # optional: item label property: 'pcs', # optional: property name value: 2 # optional: numeric value ) # Sends: e=se, se_ca=shop, se_ac=add-to-basket, se_la=pbz0026, se_pr=pcs, se_va=2 ``` -------------------------------- ### Track Page View Event Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Tracks a page view event. page_url is required. An optional subject or page argument lets you attach per-event user or page context without mutating the Tracker-level Subject. Supports custom page context and true timestamps. ```ruby page_ctx = SnowplowTracker::SelfDescribingJson.new( 'iglu:com.example/page_meta/jsonschema/1-0-0', { section: 'blog', author: 'alice' } ) tracker.track_page_view( page_url: 'https://www.example.com/blog/post-1', page_title: 'My First Post', referrer: 'https://www.google.com', context: [page_ctx], tstamp: SnowplowTracker::TrueTimestamp.new(1_633_596_554_978) ) # Sends: e=pv, url=..., page=..., refr=..., ttm=1633596554978 ``` -------------------------------- ### Silent Failure for Nil Optional Fields Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Demonstrates that nil values for optional fields are silently ignored by the payload.add method, preventing errors. ```ruby # Don't raise errors for nil optional fields payload.add('optional_field', nil) # Silently ignored ``` -------------------------------- ### Emitter Test Helpers Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/spec/CLAUDE.md Utilize test helpers added to the Emitter class in spec_helper to access the last request's querystring or body. ```ruby # Access last GET request querystring emitter.get_last_querystring # Access last POST request body emitter.get_last_body ``` -------------------------------- ### Module Namespace Convention in Ruby Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Ensures classes are defined within the SnowplowTracker module to prevent global namespace pollution. Use this pattern for all library components. ```ruby # ❌ Wrong: Global namespace pollution class Tracker end # ✅ Correct: Module encapsulation module SnowplowTracker class Tracker end end ``` -------------------------------- ### Add Snowplow Tracker Gem Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/README.md Add this gem to your Gemfile to include the Snowplow tracker. Compatible with Ruby versions 2.1 to 3.0+. ```ruby gem "snowplow-tracker", "~> 0.8.0" ``` -------------------------------- ### Tracker#set_subject / Tracker#add_emitter Source: https://context7.com/snowplow/snowplow-ruby-tracker/llms.txt Modify the tracker's subject with `set_subject` or send events to multiple collectors using `add_emitter`. ```APIDOC ## Tracker#set_subject / Tracker#add_emitter `set_subject` replaces the tracker-level Subject for all subsequent events. `add_emitter` appends an additional Emitter so events are sent to multiple collectors in parallel. ### Examples: 1. **Swap the active subject mid-session (e.g. after login)** ```ruby new_subject = SnowplowTracker::Subject.new .set_user_id('logged_in_user') .set_platform('web') tracker.set_subject(new_subject) ``` 2. **Mirror events to a second collector (e.g. staging + production)** ```ruby staging_emitter = SnowplowTracker::Emitter.new( endpoint: 'collector-staging.example.com', options: { protocol: 'https', method: 'post' } ) tracker.add_emitter(staging_emitter) tracker.track_struct_event(category: 'test', action: 'dual_send') # → event dispatched to both the original and staging emitters ``` ``` -------------------------------- ### Thread-Safe Buffer Input Source: https://github.com/snowplow/snowplow-ruby-tracker/blob/master/lib/snowplow-tracker/CLAUDE.md Ensures thread-safe access to the event buffer using a mutex. ```ruby # Use mutex for thread-safe buffer access @lock = Mutex.new def input(payload) @lock.synchronize do @buffer.push(payload) end end ```