Module: Hecks::Freezer
- Defined in:
- lib/hecks/freezer.rb
Overview
THE ONE PLACE THAT KNOWS HOW TO FREEZE A DOMAIN VALUE.
Freezing here has been fixed four times in four places, each time by
topping the container and leaving the contents: list attributes, the
event log, query rows, and value objects. .freeze on a Hash stops a
key being added or removed and NOTHING ELSE — the String, Array or
Hash a field holds stays mutable, so a caller reaches straight
through and edits in place. Each fix looked complete and none was.
WHAT SHOULD BE FROZEN, and why it is not everything:
A VALUE OBJECT has no identity to change over. `with` already
answers a new one rather than mutating, so freezing it through is
what it always claimed to be.
AN EMITTED EVENT is a record of something that happened. A mutable
audit trail is not one.
A QUERY ROW is an answer, not a handle — mutating one edits nobody's
state and silently disagrees with the store.
AN INSTANCE'S STATE is NOT frozen, deliberately: the interpreter
builds it up across a dispatch, and a command's whole job is to
change it. Its VALUES are frozen; the holder is not.
A COMMAND'S ARGUMENTS are not frozen either. They arrive from
outside, are normalised and coerced on the way in, and the coerced
result is what becomes a frozen value.
Class Method Summary collapse
-
.deep(held) ⇒ Object
Numbers, symbols, nil and booleans are already immediate or frozen; a Value froze itself when it was built.
-
.deeply_frozen?(held) ⇒ Boolean
The question a gate asks, rather than the act.
-
.immune?(held) ⇒ Boolean
Immediates are frozen in every Ruby that matters, but asking
frozen?of them and trusting the answer has bitten enough people that it is worth being explicit. -
.unfrozen_within(held, path = []) ⇒ Object
The path to the first mutable thing reachable from
held, or nil.
Class Method Details
.deep(held) ⇒ Object
Numbers, symbols, nil and booleans are already immediate or frozen; a Value froze itself when it was built. What is left is the mutable trio, and each has to be WALKED rather than topped.
36 37 38 39 40 41 42 43 |
# File 'lib/hecks/freezer.rb', line 36 def deep(held) case held when Hash then held.each_value { |inner| deep(inner) }.freeze when Array then held.each { |inner| deep(inner) }.freeze when String then held.freeze else held end end |
.deeply_frozen?(held) ⇒ Boolean
The question a gate asks, rather than the act. Answers false for the FIRST thing that is reachable and mutable, which is what makes a failure message worth reading.
48 |
# File 'lib/hecks/freezer.rb', line 48 def deeply_frozen?(held) = unfrozen_within(held).nil? |
.immune?(held) ⇒ Boolean
Immediates are frozen in every Ruby that matters, but asking
frozen? of them and trusting the answer has bitten enough people
that it is worth being explicit.
65 |
# File 'lib/hecks/freezer.rb', line 65 def immune?(held) = held.nil? || held == true || held == false || held.is_a?(Numeric) || held.is_a?(Symbol) |
.unfrozen_within(held, path = []) ⇒ Object
The path to the first mutable thing reachable from held, or nil.
A path rather than a boolean because "something in this event is
mutable" is not an actionable sentence.
53 54 55 56 57 58 59 60 |
# File 'lib/hecks/freezer.rb', line 53 def unfrozen_within(held, path = []) return (path.empty? ? "(the value itself)" : path.join(".")) unless immune?(held) || held.frozen? case held when Hash then held.lazy.filter_map { |key, inner| unfrozen_within(inner, path + [key.to_s]) }.first when Array then held.each_with_index.lazy.filter_map { |inner, i| unfrozen_within(inner, path + [i.to_s]) }.first end end |