Class: OnlineMigrations::DataMigrations::BackfillQuoteBuilderToIq

Inherits:
OnlineMigrations::DataMigration
  • Object
show all
Defined in:
lib/online_migrations/data_migrations/backfill_quote_builder_to_iq.rb

Overview

Folds the short-lived 'QuoteBuilder' reception-type literal back
into the canonical 'IQ' ("Instant Quote") value on the two
columns that carry reception type:

  • opportunities.opportunity_reception_type (~195 rows since
    2025-12-24)
  • room_configurations.reception_type (~8,552 rows)

Background: the December 2025 quote-builder rewrite started writing
'QuoteBuilder' as a new label for the same flow that had been
tagged 'IQ' since 2013 (~59,137 opportunity rows + ~13,711 room
rows). Carrying both literals confused tooling — most visibly the
AI assistant, which silently filtered to one or the other and gave
the team inconsistent quote-builder conversion stats for the same
question (chats /assistant/2475 and /assistant/2477).

The team's call: standardize on 'IQ' going forward — it has the
longer historical run, it's already what RoomConfiguration#room_prefix
branches on (legacy IQR rooms keep their IPR reference numbers
generated from reception_type == 'IQ'), it's already what
Quote::INSTANT_QUOTE = 'IQ' and the Delivery#instant_quote?
predicate hardcode, and 'Quote Builder' survives as the
user-facing feature name (URL /quote-builder, page heading,
QuoteBuilder::ProjectCreator class name) without leaking into
the database literal.

See Also:

Constant Summary collapse

BATCH_SIZE =

Batch size — small enough to keep each transaction quick on
the two reception-type tables, large enough that the ~8.7k
rows finish in reasonable time at the iteration_pause cadence.

1_000
TARGETS =

Tables and the column on each that holds the reception-type
literal. The two updates are independent.

[
  { model: ::Opportunity,       column: :opportunity_reception_type },
  { model: ::RoomConfiguration, column: :reception_type             }
].freeze

Instance Method Summary collapse

Instance Method Details

#collectionArray<Array(Class, Symbol, Array<Integer>)>

Returns one [model_class, column_name, ids] tuple per batch
across the target tables. process dispatches the right
update_all per tuple.

Returned eagerly as an Array (not a streaming Enumerator)
because online_migrations' MigrationJob#build_enumerator
only handles ActiveRecord::Relation, BatchEnumerator, and
Array from #collection — a plain Enumerator raises
ArgumentError before on_start runs and leaves the row
wedged at status='enqueued'. The eager pass is cheap here:
the two source tables hold ~8.7k matching rows total, which
produces only ~9 tuples at BATCH_SIZE=1_000.

Returns:

  • (Array<Array(Class, Symbol, Array<Integer>)>)


65
66
67
68
69
70
71
72
73
74
75
# File 'lib/online_migrations/data_migrations/backfill_quote_builder_to_iq.rb', line 65

def collection
  batches = []
  TARGETS.each do |target|
    target[:model]
          .where(target[:column] => "QuoteBuilder")
          .in_batches(of: BATCH_SIZE) do |batch|
      batches << [target[:model], target[:column], batch.ids]
    end
  end
  batches
end

#countInteger

Total un-migrated rows across the target tables. Drives the
online_migrations progress bar.

Returns:

  • (Integer)


95
96
97
# File 'lib/online_migrations/data_migrations/backfill_quote_builder_to_iq.rb', line 95

def count
  TARGETS.sum { |t| t[:model].where(t[:column] => "QuoteBuilder").count }
end

#process(batch) ⇒ void

This method returns an undefined value.

Switches one batch of rows from 'QuoteBuilder' to 'IQ' via
update_all. The conditional where(column => 'QuoteBuilder')
makes the write idempotent — already-migrated rows are left
alone on a re-run (no updated_at churn).

Parameters:

  • batch (Array(Class, Symbol, Array<Integer>))

    tuple of
    [model_class, column_name, ids] produced by #collection.



85
86
87
88
89
# File 'lib/online_migrations/data_migrations/backfill_quote_builder_to_iq.rb', line 85

def process(batch)
  model, column, ids = batch
  model.where(id: ids, column => "QuoteBuilder")
       .update_all(column => "IQ", updated_at: Time.current)
end