Module: RailsOnboarding::Backfill

Defined in:
lib/rails_onboarding/backfill.rb

Overview

One-shot data fixes for applications that installed RailsOnboarding on top of an existing users table.

Users who signed up before the gem was installed have NULL onboarding columns, so the admin reports them as "Not Started" and - depending on the onboarding_required_for strategy - they can be pushed back through a flow they have no business seeing. Backfilling marks them as already onboarded.

Everything here writes with update_all in batches: no callbacks, no validations, and deliberately no analytics events. Backfilled users never went through onboarding, so recording completion events for them would put fiction into the funnel metrics.

Defined Under Namespace

Classes: Result

Constant Summary collapse

DEFAULT_BATCH_SIZE =
1_000
REQUIRED_COLUMNS =
%w[
  onboarding_completed
  onboarding_completed_at
  onboarding_current_step
].freeze

Class Method Summary collapse

Class Method Details

.mark_existing_users_onboarded(created_before: nil, completed_at: Time.current, batch_size: DEFAULT_BATCH_SIZE, dry_run: false) ⇒ Result

Marks pre-existing users as having finished onboarding.

Only users who have never engaged with onboarding are touched: not completed, not skipped, and sitting on no current step. Anyone mid-flow is left alone so a backfill can be re-run safely while the app is live.

Parameters:

  • created_before (Time, Date, String, nil) (defaults to: nil)

    restrict to users created before this moment. Users with a NULL created_at are always included when a cutoff is given - a row with no creation timestamp predates any cutoff worth naming, and those rows are exactly the legacy ones this task exists for.

  • completed_at (Time) (defaults to: Time.current)

    timestamp used for users whose created_at is NULL. Users with a created_at get their own signup time, which keeps completion-over-time charts from spiking on backfill day.

  • batch_size (Integer) (defaults to: DEFAULT_BATCH_SIZE)

    rows per UPDATE statement.

  • dry_run (Boolean) (defaults to: false)

    count the affected rows without writing.

Returns:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/rails_onboarding/backfill.rb', line 50

def mark_existing_users_onboarded(created_before: nil, completed_at: Time.current,
  batch_size: DEFAULT_BATCH_SIZE, dry_run: false)
  ensure_columns!

  scope = pending_scope(created_before: created_before)
  matched = scope.count
  return Result.new(matched: matched, updated: 0, dry_run: true) if dry_run

  updated = 0
  scope.in_batches(of: batch_size) do |batch|
    updated += batch.update_all(completion_assignment(completed_at))
  end

  Result.new(matched: matched, updated: updated, dry_run: false)
end

.pending_scope(created_before: nil) ⇒ Object

Users the backfill would touch. Exposed so callers can inspect or further narrow the set before running.



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/rails_onboarding/backfill.rb', line 68

def pending_scope(created_before: nil)
  scope = user_class
    .where(onboarding_current_step: nil)
    .where(onboarding_completed: [ false, nil ])

  scope = scope.where(onboarding_skipped: [ false, nil ]) if column?("onboarding_skipped")
  return scope unless created_before

  cutoff = normalize_time(created_before)
  scope.where("#{quoted_table}.created_at < ? OR #{quoted_table}.created_at IS NULL", cutoff)
end

.user_classObject



80
81
82
# File 'lib/rails_onboarding/backfill.rb', line 80

def user_class
  RailsOnboarding.configuration.user_class_name.constantize
end