Class: Hecks::Bluebook::ReadModel

Inherits:
QuerySpecification::ReadModel::Specification show all
Includes:
Behaviour::ReadModel, Construct, IR
Defined in:
lib/hecks/bluebook/read_model.rb

Overview

A read model — an ask that gathers heads from more than one aggregate.

Like a query it crosses over as an INSTANCE, and for the same reason: its body is inherited from QuerySpecification::ReadModel::Specification, whose readers the runtime and the SQLite adapter both call on the object. It gains an identity and an owner ; it keeps the name it always had, because only a CLASS ever had a competing answer for name.

Instance Attribute Summary collapse

Attributes included from Construct

#hecks_name, #hecks_owner, #hecks_root

Attributes inherited from QuerySpecification::ReadModel::Specification

#joins

Attributes inherited from QuerySpecification::Common::Options

#authorization, #cursor, #inspection, #limit, #null_semantics, #offset, #order_by, #wheres

Instance Method Summary collapse

Methods included from Behaviour::ReadModel

#count?, #filtered_head_name, #group_by_fields, #query_name

Methods included from IR

extended, included

Methods included from Construct

#hecks_fqn, #hecks_root?, #hecks_separator

Methods inherited from QuerySpecification::Common::Options

#extra_options_to_h, #options_to_h

Constructor Details

#initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [], group_by: [], count: nil, median_field: nil, **options) ⇒ ReadModel

reference_name:/reference_target: are nil for a ROOTLESS read model (no reference_to declared) — &. throughout, rather than the .to_s/.to_sym this used to require unconditionally, so reference_target.nil? stays a real, checkable fact for the interpreter instead of silently becoming "".



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/hecks/bluebook/read_model.rb', line 37

def initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [],
               group_by: [], count: nil, median_field: nil, **options)
  super(joins: aggregate_heads, **options)
  @name             = name.to_s
  @hecks_name       = @name
  @description      = description
  @reference_name   = reference_name&.to_sym
  @reference_target = reference_target&.to_s
  @aggregate_heads  = aggregate_heads
  # Hash rows (`{field: :agg}`), same shape as `aggregate_heads` —
  # `group_by_fields` is the convenience reader everything but
  # `to_h`/the Judge's own generic walk actually wants.
  @group_by         = group_by
  # ABSENT IS NOT FALSE. `@count` stays nil rather than becoming
  # `false` when undeclared — the same "if you declare it, declare
  # something" reading `Lifecycle`'s own optional fields rely on
  # (MetaValidator::Judge#setters skips a setter whose every
  # source is `nil`, so an ALWAYS-false `@count` would dispatch
  # `ReadModel.Count` on every read model that never wrote the
  # word at all). `!!count` collapses a truthy DSL `true` (or a
  # reconstructed `true`) to the same `true`/`nil` pair either path
  # produces — never `false`.
  @count            = count ? true : nil
  @median_field     = median_field&.to_sym
end

Instance Attribute Details

#aggregate_headsObject (readonly)

Returns the value of attribute aggregate_heads.



29
30
31
# File 'lib/hecks/bluebook/read_model.rb', line 29

def aggregate_heads
  @aggregate_heads
end

#countObject (readonly)

Returns the value of attribute count.



29
30
31
# File 'lib/hecks/bluebook/read_model.rb', line 29

def count
  @count
end

#descriptionObject (readonly)

Returns the value of attribute description.



29
30
31
# File 'lib/hecks/bluebook/read_model.rb', line 29

def description
  @description
end

#group_byObject (readonly)

Returns the value of attribute group_by.



29
30
31
# File 'lib/hecks/bluebook/read_model.rb', line 29

def group_by
  @group_by
end

#median_fieldObject (readonly)

Returns the value of attribute median_field.



29
30
31
# File 'lib/hecks/bluebook/read_model.rb', line 29

def median_field
  @median_field
end

#nameObject (readonly)

Returns the value of attribute name.



29
30
31
# File 'lib/hecks/bluebook/read_model.rb', line 29

def name
  @name
end

#reference_nameObject (readonly)

Returns the value of attribute reference_name.



29
30
31
# File 'lib/hecks/bluebook/read_model.rb', line 29

def reference_name
  @reference_name
end

#reference_targetObject (readonly)

Returns the value of attribute reference_target.



29
30
31
# File 'lib/hecks/bluebook/read_model.rb', line 29

def reference_target
  @reference_target
end

Instance Method Details

#to_hObject

wheres/order_by/limit are spelled explicitly here, the SAME mechanism Query#to_h already uses (query.rb, read directly before this was written) — always present, wheres a (possibly empty) array and order_by/limit nil when undeclared, exactly like a Query's. extra_options_to_h still excludes these three by name (options.rb), so nothing here double-spells them; the two constructs now share one encoding for the same three words rather than a reader needing to learn a second one. See this file's own filtered_head_name and language/bluebook/syntax.bluebook's ReadModel where/order_by/limit member rows for the history of why this WAS narrower, and 2026-08-11's read-model where/ order_by/limit task for why it stopped being. Same dynamic tail as a Query's, plus two collections whose ROWS are plain hashes rather than constructs — they are normalised here rather than by many, which recurses through to_h and would have nothing to call. count/median_field are ABSENT (no key at all), not nil, when undeclared — unlike group_by (always a [], never missing) they are the FIRST fields this construct has ever added that no OLDER real corpus member could possibly carry, so merging them unconditionally would have put a null onto every existing read model's own wire shape (banking's ComplianceDashboard/CustomerPortfolio, the language's own WholeBluebook, ...) for a fact nothing about them changed — the exact extra_options_to_h reads for cursor/offset/etc, applied here for the same reason.



97
98
99
100
101
102
103
104
105
106
# File 'lib/hecks/bluebook/read_model.rb', line 97

def to_h
  reductions = {}
  reductions[:count] = true if @count
  reductions[:median_field] = @median_field.to_s if @median_field
  super
    .merge(aggregate_heads: @aggregate_heads.map { |head| head.merge(as: head[:as].to_s) })
    .merge(group_by: @group_by.map { |row| row.merge(field: row[:field].to_s) })
    .merge(reductions)
    .merge(extra_options_to_h)
end