Class: Hecks::Bluebook::DSL::ReadModelBuilder

Inherits:
Object
  • Object
show all
Includes:
WordGate, QuerySpecification::Common::DSL
Defined in:
lib/hecks/bluebook/dsl/read_model_builder.rb

Constant Summary collapse

GRAMMAR_CONTEXT =
"ReadModel"

Constants included from WordGate

WordGate::NOT_ADMITTED

Class Method Summary collapse

Instance Method Summary collapse

Methods included from QuerySpecification::Common::DSL

#authorize_impl, #cursor, #inspect_query, #limit_impl, #nulls, #offset_impl, #order_by_impl, #where_impl

Constructor Details

#initialize(name) ⇒ ReadModelBuilder

Returns a new instance of ReadModelBuilder.



11
12
13
# File 'lib/hecks/bluebook/dsl/read_model_builder.rb', line 11

def initialize(name)
  @name = name
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Hecks::Bluebook::DSL::WordGate

Class Method Details

.build(name, &block) ⇒ Object



129
130
131
132
133
# File 'lib/hecks/bluebook/dsl/read_model_builder.rb', line 129

def self.build(name, &block)
  builder = new(name)
  builder.instance_eval(&block) if block
  builder.build
end

Instance Method Details

#buildObject

reference_to is now OPTIONAL — a read model with no root is a BULK one: every included head reads its own aggregate whole (no FK match against a root that doesn't exist), and dispatch takes no id argument at all. This used to be REQUIRED, on the assumption a read model was always "one root record's own cross-aggregate view" — true of every real corpus report so far, but not a truth about read models themselves: group_by's own real use (nesting an aggregate's OWN whole table by its own field values) has no root to speak of. Still needs to describe SOMETHING — zero includes AND no reference is refused.

Raises:



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/hecks/bluebook/dsl/read_model_builder.rb', line 109

def build
  raise Malformed,
        "#{@name} needs an aggregate-head reference or at least one include" if !@reference_target && Array(@includes).empty?

  Array(@includes).each do |target, as|
    add_aggregate_head(target, as, many: target != @reference_target)
  end
  seal_query_options
  seal_group_by
  seal_aggregation
  seal_cursor
  ReadModel.new(name: @name, description: @description, reference_name: @reference_name,
                reference_target: @reference_target, aggregate_heads: @aggregate_heads || [],
                wheres: @wheres || [], order_by: @order_by, limit: @limit, offset: @offset,
                cursor: @cursor,
                authorization: @authorization, null_semantics: @null_semantics,
                inspection: @inspection, group_by: @group_by || [],
                count: @count, median_field: @median_field)
end

#description(value) ⇒ Object



15
16
17
18
# File 'lib/hecks/bluebook/dsl/read_model_builder.rb', line 15

def description(value)
  # moved to the language: ProjectionText / purpose, on Projection.Declare
  @description = value
end

#group_by_impl(*fields) ⇒ Object

NAMES which of the eligible head's own fields to nest its rows under — one level per field, the leaf being that row with the named fields removed (they're already spent, as the keys that reached it). The same "exactly one many-side head" rule seal_query_options already enforces for where/order_by/etc applies here too (seal_group_by) — grouping is a question about ONE collection's own rows, same as those are.



58
59
60
61
62
63
64
65
66
# File 'lib/hecks/bluebook/dsl/read_model_builder.rb', line 58

def group_by_impl(*fields)
  # Hash rows, `{field:}`, not bare symbols — same shape
  # `aggregate_heads` already uses for exactly the reason it
  # does: the language's own self-hosted grammar (`projection
  # .bluebook`'s `GroupByField`) has to have SOMETHING to read a
  # `field:` off of when `Judge` walks this list generically: a
  # bare `Symbol` has no attribute of its own to read.
  @group_by = fields.map { |field| { field: field.to_sym } }
end

#include_impl(type, as: nil) ⇒ Object

Order-independent. many: is decided by comparing the included type against the reference target, so this used to REFUSE an include declared before the reference — a rule guarding an implementation limitation rather than a truth about read models. The includes are collected raw and resolved at build, when the reference is known, so there is no rule left to enforce. RENAMED FROM include/group_by — item #13's full metaprogrammed dispatch (slice 4c). include IS bootstrap- reachable (every core chapter's own read_model names which aggregates it includes with it — a first grep dismissed this as Module#include noise and was wrong; the cold-boot test after this rename caught it directly), so it's in BOOTSTRAP_CALLS_FALLBACK; group_by is not (no core read_model groups). The class-level include WordGate this file's own class body uses is Module#include, a different receiver, unaffected by renaming this INSTANCE method either way.



46
47
48
49
# File 'lib/hecks/bluebook/dsl/read_model_builder.rb', line 46

def include_impl(type, as: nil)
  @includes ||= []
  @includes << [Naming.demodulise(type), as]
end

#reference_to_impl(type, as: nil) ⇒ Object

RENAMED FROM reference_to — item #13's full metaprogrammed dispatch (slice 4b). Bootstrap-reachable, in GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.

Raises:



23
24
25
26
27
28
# File 'lib/hecks/bluebook/dsl/read_model_builder.rb', line 23

def reference_to_impl(type, as: nil)
  raise Malformed, "#{@name} already has a projection reference" if @reference_target

  @reference_target = Naming.demodulise(type)
  @reference_name   = (as || Naming.snake(@reference_target)).to_sym
end