Ruby / Integration: data, resources, runtime evidence, and trust / Lesson 18
Review a typed archive-import policy and its limits
Combine strict conversion, explicit result objects, pattern handling, complete rejection counts, and honest evidence in an offline capstone.
On this page
Lesson goals
- Preserve an understandable outcome for every supplied record before aggregation.
- Separate a local Ruby policy from real storage, authorization, concurrency, and recovery guarantees.
State the small offline promise precisely
The capstone receives fixed text quantities for fictional archive records. It accepts integers from one to fifty, distinguishes malformed text from out-of-range quantities, and summarizes accepted units. It does not modify an archive, reserve copies, create files, connect to a database, or send messages. Data result classes require Ruby 3.2 or later and are used under the selected Ruby 3.3 baseline. A real import would additionally need authenticated authority, source provenance, idempotent request identity, durable state, and recovery. Keep those missing obligations visible instead of giving a local script an unearned production-ready label.
Give accepted and rejected outcomes their own data
Accepted carries an integer value, while Rejected carries a stable reason code. The parser validates input type, conversion, and the invented range in distinct steps. case/in deconstructs each result for display. Ruby does not statically seal this set of result classes, so the producer and consumer contract must remain explicit; an unexpected result can still break handling. Public construction can also bypass the parser, so a larger design should decide where trusted result creation belongs. The sample keeps every supplied input visible rather than silently filtering away its rejection.
Accepted = Data.define(:value)
Rejected = Data.define(:reason)
def parse_count(text)
return Rejected.new(reason: 'not-text') unless text.is_a?(String)
value = Integer(text.strip, 10, exception: false)
return Rejected.new(reason: 'not-an-int') if value.nil?
return Rejected.new(reason: 'out-of-range') unless value.between?(1, 50)
Accepted.new(value: value)
end
['2', 'bad', '0'].each do |text|
case parse_count(text)
in Accepted[value]
puts "accepted:#{value}"
in Rejected[reason]
puts "rejected:#{reason}"
end
end
accepted:2
rejected:not-an-int
rejected:out-of-range
Expected output is accepted:2, rejected:not-an-int, rejected:out-of-range. The code uses Ruby 3.3-documented Data and patterns and was not executed.
Choose whether a summary retains detailed rejection evidence
A count of rejected items cannot reconstruct their individual reasons or source associations. If the product needs that detail, keep the result objects and aggregate over them. The second script intentionally returns only totals under a declared summary policy. compact removes nil parse/range rejections while preserving the established Integer values; it is not a generic filter_map that might drop meaningful false results. The synthetic small input avoids resource-scale claims. An actual import must bound record count, input size, and retained outcomes and decide whether partial acceptance is permitted before reporting success.
Keep arithmetic and output effects separate
summarize returns a Hash and leaves printing to its caller. It uses the same one-to-fifty policy but omits detailed rejection reasons deliberately. The function assumes supplied elements are Strings in this compact example; the earlier parser has the explicit type rejection needed for an unknown-input boundary. A complete application should not let these two contracts drift silently. Reuse a shared validated representation when several consumers must agree, and verify the range boundaries from an independently stated requirement rather than merely duplicating code in tests.
def summarize(texts)
values = texts.map do |text|
value = Integer(text.strip, 10, exception: false)
value if value && value.between?(1, 50)
end
accepted = values.compact
{accepted: accepted.length, rejected: values.length - accepted.length, total: accepted.sum}
end
summary = summarize(['2', 'bad', '0', '3'])
puts "accepted=#{summary.fetch(:accepted)}"
puts "rejected=#{summary.fetch(:rejected)}"
puts "total=#{summary.fetch(:total)}"
accepted=2
rejected=2
total=5
Expected output is accepted=2, rejected=2, total=5. No record is stored or external effect performed. The source was not parsed or run by Ruby during authoring.
End with evidence that matches the actual claim
Check non-text input, malformed text, zero, negatives, one, fifty, above fifty, whitespace policy, and ordinary values in a compatible isolated interpreter. Verify that every result is handled, no fallback hides a defect, and copies or frozen containers have the intended depth. For a real import, test authoritative concurrency, storage failures, duplicate request identity, and recovery separately. This course validates its JSON structure and bilingual technical fields but executes no Ruby source. The final report must keep reasoned outputs, proposed checks, actual runs, and untested operational requirements visibly distinct.
Common errors and fixes
Rejected records disappear from an apparently complete import.
- Why it happens
- Filtering replaced an explicit per-record outcome contract.
- How to fix it
- Retain detailed outcomes or report partial acceptance explicitly.
- Prevent it next time
- Define completeness before designing the aggregation.
Two consumers enforce different quantity ranges.
- Why it happens
- Validation logic drifted across separate representations.
- How to fix it
- Share the validated model or a deliberate common policy boundary.
- Prevent it next time
- Check the same independently defined boundary cases for all consumers.
A local count summary is called a verified production import.
- Why it happens
- Operational responsibilities and missing runtime evidence were ignored.
- How to fix it
- State the offline scope and verify each real effect at its own boundary.
- Prevent it next time
- Report only commands and outcomes actually observed.
Check your understanding
1. What changes if every rejected record must retain its original reason?
Show worked solution
Keep one Accepted or Rejected result with its source reference, then derive totals from that result list. A rejected count alone cannot recover the reasons. Decide whether raw text may be retained under the data-handling policy and keep machine codes separate from translated display wording. Do not silently switch to compacting invalid values when the product requires complete feedback.
2. Name three evidence boundaries before turning the capstone into a real service.
Show worked solution
First execute the local policy checks in the declared Ruby engine and library environment. Then verify actor/resource authorization and authoritative state transitions, including concurrency. Finally verify durable request identity, persistence, external outcomes, and recovery. Each boundary may need several tests. Printing a predicted total proves none of those absent service guarantees.
Key takeaways
- Explicit result objects preserve meaning before aggregation.
- An honest capstone separates local semantics from untested operational guarantees.