Course outline

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.

About 5 min readContent reviewed
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.

ruby Explicit Data results and pattern-based presentation
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
Expected output
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.

ruby A local summary with visible rejection count
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)}"
Expected output
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.

Official references