Module: ActiveRecord::Materialized::ViewQueryAccessClassMethods::ClassMethods

Defined in:
lib/activerecord/materialized/view_query_access_class_methods.rb

Overview

The read and refresh methods available on a ActiveRecord::Materialized::View subclass.

Instance Method Summary collapse

Instance Method Details

#all(*args) ⇒ Object



121
122
123
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 121

def all(*args)
  read_router.scope.all(*args)
end

#count(*args) ⇒ Object



137
138
139
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 137

def count(*args)
  read_router.scope.count(*args)
end

#dirty?Boolean

Whether dependency writes have marked the view dirty since its last refresh.

Returns:

  • (Boolean)


31
32
33
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 31

def dirty?
  view_class..dirty?
end

#find(*args) ⇒ Object



129
130
131
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 129

def find(*args)
  read_router.scope.find(*args)
end

#find_by(*args) ⇒ Object



133
134
135
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 133

def find_by(*args)
  read_router.partition_scope(args).find_by(*args)
end

#find_each(*args, **opts, &block) ⇒ Object



170
171
172
173
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 170

def find_each(*args, **opts, &block)
  require_materialized!(:find_each)
  super
end

#find_in_batches(*args, **opts, &block) ⇒ Object



175
176
177
178
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 175

def find_in_batches(*args, **opts, &block)
  require_materialized!(:find_in_batches)
  super
end

#ids(*args) ⇒ Object

ids and batch iteration (+find_each+/+find_in_batches+/+in_batches+) are defined in terms of the primary key: ids plucks it, and batching cursors on it and requires the cursor to be a unique column. A cold view's read-through derived table has no id (and no index to make a group-key cursor unique), so none of these can be served correctly — a warm, materialized cache table is required. Refuse with a clear, actionable error rather than emitting the cryptic "no such column: id" the underlying query would raise.



165
166
167
168
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 165

def ids(*args)
  require_materialized!(:ids)
  super
end

#implicit_order_columnArray, Object

Order-dependent finders (+first+, last, second, …) fall back to an implicit ORDER BY <primary key> when the relation carries no order. A cold view reads through to the source as a derived table with no id column, so that implicit order references a column that does not exist and the query blows up. Order by the GROUP BY key(s) instead — they exist on both the cold derived table and the warm cache — and the trailing nil tells ActiveRecord's _order_columns to use only these columns and NOT append the primary key. A non-grouped view has no such key, so it keeps the default primary-key behavior.

The keys are unqualified (a dotted GROUP BY like "items.category" maps to the projected category on the cache/derived table), matching how ActiveRecord::Materialized::ViewDefinition and CacheTableSchema resolve them — a qualified name would reference a column neither table has, on warm and cold.

Returns:

  • (Array, Object)

    the group-by key columns (plus a trailing nil), or the default



154
155
156
157
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 154

def implicit_order_column
  keys = maintenance_key_columns.map { |key| key.rpartition(".").last }
  keys.any? ? [*keys, nil] : super
end

#in_batches(*args, **opts, &block) ⇒ Object



180
181
182
183
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 180

def in_batches(*args, **opts, &block)
  require_materialized!(:in_batches)
  super
end

#last_refreshed_atTime?

When the view was last refreshed, or nil if it never has been.

Returns:

  • (Time, nil)


53
54
55
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 53

def last_refreshed_at
  view_class..last_refreshed_at
end

#mark_dependencies_changed!Object



74
75
76
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 74

def mark_dependencies_changed!
  view_class..mark_dirty!
end

#materialized?Boolean

Reads are served from the cache only once warmed and the table exists; otherwise they fall through to the cold-read path.

Returns:

  • (Boolean)


46
47
48
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 46

def materialized?
  view_class.table_exists? && view_class..warm?
end

#rebuild!(confirm: false) ⇒ RefreshResult

The only path that scans all base data; confirm: guards against firing a full materialization by accident.

Parameters:

  • confirm (Boolean) (defaults to: false)

    must be true to run the full materialization

Returns:

Raises:

  • (ArgumentError)

    unless confirm is true



112
113
114
115
116
117
118
119
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 112

def rebuild!(confirm: false)
  unless confirm
    Kernel.raise ArgumentError,
                 "#{view_class.name}.rebuild! performs a full materialization; call rebuild!(confirm: true)"
  end

  Refresher.new(view_class).rebuild!
end

#reconcile!(mode: :checksum, sample: nil) ⇒ ReconcileResult

Verify this view's contents against its source and repair any drift with scoped maintenance (never a full rebuild). See Reconciler.

Parameters:

  • mode (Symbol) (defaults to: :checksum)

    drift-check depth: :row_count, :checksum, or :full

  • sample (Numeric, nil) (defaults to: nil)

    verify a random subset (Integer count / Float fraction)

Returns:



102
103
104
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 102

def reconcile!(mode: :checksum, sample: nil)
  Reconciler.new(view_class, mode: mode, sample: sample).reconcile!
end

#refresh!RefreshResult

Incremental maintenance only — never scans all base data.

Returns:



85
86
87
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 85

def refresh!
  Refresher.new(view_class).refresh!
end

#refresh_if_stale!RefreshResult?

Refreshes the view only when it is materialized and stale; otherwise a no-op.

Returns:

  • (RefreshResult, nil)

    the refresh result, or nil when no refresh was needed



92
93
94
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 92

def refresh_if_stale!
  refresh! if materialized? && stale?
end

#refreshing?Boolean

Whether a refresh is currently in progress for the view.

Returns:

  • (Boolean)


70
71
72
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 70

def refreshing?
  view_class..refreshing?
end

#source_watermarkInteger?

The oldest applied CDC source watermark across the view's partitions — its most-behind partition — or nil if no watermarked change has been ingested (see SourceWatermark). Subtract from your source clock (same unit as the source_ts you pass ActiveRecord::Materialized.ingest_change) to get the view's freshness/lag.

Returns:

  • (Integer, nil)


63
64
65
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 63

def source_watermark
  SourceWatermark.new(view_class).oldest
end

#stale?Boolean

Whether the view needs refreshing — dirty, never refreshed, or past its max_staleness.

Returns:

  • (Boolean)


24
25
26
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 24

def stale?
  view_class..stale?
end

#table_exists?Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 78

def table_exists?
  view_class.connection.data_source_exists?(view_class.table_name)
end

#view_classObject



17
18
19
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 17

def view_class
  self
end

#warm?Boolean

Whether the view has been fully materialized; cold views are served read-through.

Returns:

  • (Boolean)


38
39
40
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 38

def warm?
  view_class..warm?
end

#where(*args) ⇒ Object



125
126
127
# File 'lib/activerecord/materialized/view_query_access_class_methods.rb', line 125

def where(*args)
  read_router.partition_scope(args).where(*args)
end