Class: Hecks::Runtime::Instance
- Inherits:
-
Object
- Object
- Hecks::Runtime::Instance
show all
- Defined in:
- lib/hecks/runtime/instance.rb
Instance Attribute Summary collapse
-
#aggregate ⇒ Object
readonly
Returns the value of attribute aggregate.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#state ⇒ Object
Returns the value of attribute state.
-
#version ⇒ Object
OUT-OF-BAND ADAPTER BOOKKEEPING, NOT DOMAIN STATE — the optimistic- concurrency version a CAS-capable adapter (Postgres today) stamps on a record it reads/writes, so a later save can assert "commit only if nobody has written since".
Class Method Summary
collapse
Instance Method Summary
collapse
-
#[](name) ⇒ Object
-
#[]=(name, value) ⇒ Object
-
#dup ⇒ Object
A COPY A MUTATION MAY TOUCH.
-
#initialize(aggregate:, id:, state: nil, args: nil) ⇒ Instance
constructor
args: — THE ORIGINAL COMMAND PAYLOAD, offered only by a fresh creation (CommandInterpreter#hydrate_legacy_creation/ #hydrate_complete_state/#hydrate_prior_or_initial, each already holding it when they mint a brand-new record).
-
#inspect ⇒ Object
-
#key?(name) ⇒ Boolean
-
#method_missing(name, *args) ⇒ Object
-
#respond_to_missing?(name, include_private = false) ⇒ Boolean
-
#to_h ⇒ Object
id: @id LAST, not first — see Facade::Handle#to_h's own comment for the full story (the same fix, landed there first): an aggregate free to declare its own attribute literally named id (BurningManPrep's Item, attribute :id, ItemId) has that attribute's own wrapped value object sitting in @state[:id] — merging @state on top of { id: @id } let it silently clobber the correct bare identity.
Constructor Details
#initialize(aggregate:, id:, state: nil, args: nil) ⇒ Instance
args: — THE ORIGINAL COMMAND PAYLOAD, offered only by a fresh
creation (CommandInterpreter#hydrate_legacy_creation/
#hydrate_complete_state/#hydrate_prior_or_initial, each already
holding it when they mint a brand-new record). See
materialize_identity! for why a composite identity needs it.
26
27
28
29
30
31
32
|
# File 'lib/hecks/runtime/instance.rb', line 26
def initialize(aggregate:, id:, state: nil, args: nil)
@aggregate = aggregate
@id = id
@state = state ? self.class.hydrate_with_defaults(aggregate, state) : self.class.defaults(aggregate)
@version = nil
materialize_identity!(args)
end
|
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args) ⇒ Object
80
81
82
83
84
|
# File 'lib/hecks/runtime/instance.rb', line 80
def method_missing(name, *args)
return @state[name] if @state.key?(name)
super
end
|
Instance Attribute Details
#aggregate ⇒ Object
Returns the value of attribute aggregate.
7
8
9
|
# File 'lib/hecks/runtime/instance.rb', line 7
def aggregate
@aggregate
end
|
#id ⇒ Object
Returns the value of attribute id.
7
8
9
|
# File 'lib/hecks/runtime/instance.rb', line 7
def id
@id
end
|
#state ⇒ Object
Returns the value of attribute state.
8
9
10
|
# File 'lib/hecks/runtime/instance.rb', line 8
def state
@state
end
|
#version ⇒ Object
OUT-OF-BAND ADAPTER BOOKKEEPING, NOT DOMAIN STATE — the optimistic-
concurrency version a CAS-capable adapter (Postgres today) stamps
on a record it reads/writes, so a later save can assert "commit
only if nobody has written since". Deliberately absent from
to_h/[]/[]=/method_missing : a domain author never declares
this, a given/ensures/invariant can never read it, and no
adapter that doesn't understand it (Memory, Heki) ever sets it —
nil there just means "no CAS attempted", which is exactly what a
plain save already does. See docs/decisions/ (concurrency-control
ADR) for the full mechanism.
19
20
21
|
# File 'lib/hecks/runtime/instance.rb', line 19
def version
@version
end
|
Class Method Details
.default_for(aggregate, attribute) ⇒ Object
60
61
62
63
64
65
66
67
68
69
70
|
# File 'lib/hecks/runtime/instance.rb', line 60
def self.default_for(aggregate, attribute)
return Value.for_attribute(aggregate, attribute, attribute.default) unless attribute.default.nil?
return nil unless aggregate.respond_to?(:value_object)
value_object = aggregate.value_object(attribute.type)
return nil unless value_object && value_object.attributes.all? { |field| !field.default.nil? }
Value.build(value_object, {}, aggregate)
end
|
.defaults(aggregate) ⇒ Object
48
49
50
51
52
53
54
55
56
57
58
|
# File 'lib/hecks/runtime/instance.rb', line 48
def self.defaults(aggregate)
state = aggregate.attributes.each_with_object({}) do |attr, acc|
acc[attr.name] = attr.list? ? Freezer.deep([]) : default_for(aggregate, attr)
end
state[aggregate.lifecycle.field.to_sym] = aggregate.lifecycle.default if aggregate.lifecycle
state
end
|
.hydrate_with_defaults(aggregate, state) ⇒ Object
Loading existing state runs the same default-fill a fresh instance
gets: an attribute the record predates — a newly-required field
with a declared default:, a list added since the record was
written — arrives filled instead of nil. Only declared defaults
fill in; an attribute with no default stays absent, exactly as
stored.
40
41
42
43
44
45
46
|
# File 'lib/hecks/runtime/instance.rb', line 40
def self.hydrate_with_defaults(aggregate, state)
hydrated = Value.hydrate(aggregate, state)
defaults(aggregate).each do |name, value|
hydrated[name] = value unless value.nil? || hydrated.key?(name)
end
hydrated
end
|
Instance Method Details
#[](name) ⇒ Object
72
|
# File 'lib/hecks/runtime/instance.rb', line 72
def [](name) = @state[name.to_sym]
|
#[]=(name, value) ⇒ Object
76
77
78
|
# File 'lib/hecks/runtime/instance.rb', line 76
def []=(name, value)
@state[name.to_sym] = value
end
|
#dup ⇒ Object
A COPY A MUTATION MAY TOUCH. Every adapter but Memory hands find
a freshly-decoded Instance already; Memory's holds the record it
eventually saves — the SAME state Hash, aliased. Before ensures
existed, nothing could refuse between apply_mutations and save, so
that aliasing was invisible: a dispatch either ran to completion or
raised before touching state at all. ensures is the first refusal
to sit AFTER mutation, and it found the bug the moment it did — an
in-memory record left half-mutated by a dispatch that then refused.
command_interpreter/entity_interpreter hydrate an EXISTING
record through this, never through the adapter's own return value
directly, so a refused ensures leaves the stored record untouched
regardless of which adapter is holding it.
111
112
113
114
115
|
# File 'lib/hecks/runtime/instance.rb', line 111
def dup
copy = super
copy.state = @state.dup
copy
end
|
#inspect ⇒ Object
117
118
119
120
|
# File 'lib/hecks/runtime/instance.rb', line 117
def inspect
fields = @state.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")
"#<#{@aggregate.hecks_name} #{@id} #{fields}>"
end
|
#key?(name) ⇒ Boolean
74
|
# File 'lib/hecks/runtime/instance.rb', line 74
def key?(name) = @state.key?(name.to_sym)
|
#respond_to_missing?(name, include_private = false) ⇒ Boolean
86
87
88
|
# File 'lib/hecks/runtime/instance.rb', line 86
def respond_to_missing?(name, include_private = false)
@state.key?(name) || super
end
|
#to_h ⇒ Object
id: @id LAST, not first — see Facade::Handle#to_h's own comment
for the full story (the same fix, landed there first): an
aggregate free to declare its own attribute literally named id
(BurningManPrep's Item, attribute :id, ItemId) has that
attribute's own wrapped value object sitting in @state[:id] —
merging @state on top of { id: @id } let it silently clobber
the correct bare identity. @id merged last always wins.
97
|
# File 'lib/hecks/runtime/instance.rb', line 97
def to_h = @state.merge(id: @id)
|