Skip to main content

Tracking IDs

An Opinionated Guide to Identifying Users

If you are only concerned with logged-in users, your user.id will work just fine as the sticky property for roll-outs.

If you are working with users as they transition from anonymous to logged-in users however, we need a value that will persist across this transition. If we don't keep this value consistent, we will run into situations where a new user lands on our site and gets the "Control" variant of an experiment, then logs in and is thrown into a different variant.

tip

Prefab's recommendation is that you create a separate tracking ID the moment you see a request, save it in a cookie and then persist it to the user record upon creation.

Adding a tracking ID to a Rails application

rails g migration AddTrackingId

Migration to add a column and initialize it.

class AddTrackingId < ActiveRecord::Migration[7.0]
def change
add_column :users, :tracking_id, :string
execute "update users set tracking_id = id" # initialize pre-existing users to have a tracking_id == their user_id
change_column :users, :tracking_id, :string, null: false
end
end

Useful to always have @tracking_id available in our controllers.

class ApplicationController < ActionController::Base
before_action :set_tracking_id
def set_tracking_id
@tracking_id = TrackingId.build(user: current_user, cookies: cookies)
end
end

TrackingId looks at the (possibly nil) user and cookies and gets us the correct tracking_id while setting it as a long-lived cookie.

class TrackingId
COOKIE_KEY = "tid".freeze

def self.build(user:, cookies:)
builder = new(user, cookies)
builder.persist_to_cookies
builder.tracking_id
end

def initialize(user = nil, cookies = {})
@user = user
@cookies = cookies
end
def tracking_id
@tracking_id ||= user_tracking_id || cookie_tracking_id || self.class.new_tracking_id
end

def persist_to_cookies
@cookies[COOKIE_KEY] = {
value: tracking_id,
expires: 1.year.from_now
}
end

private

def user_tracking_id
@user.try(:tracking_id)
end

def cookie_tracking_id
return if @cookies[COOKIE_KEY].blank?
@cookies[COOKIE_KEY]
end

def self.new_tracking_id
SecureRandom.uuid
end
end

When a user signs up and created an account, we need to remember to permanently set the tracking ID on the user account.

class RegistrationsController < Devise::RegistrationsController
def sign_up_params
devise_parameter_sanitizer.sanitize(:sign_up).merge(tracking_id: TrackingId.build(user: current_user, cookies: cookies))
end
end