← All campaigns

Time-Based Status with Range Types

LinkedIn carousel for the tsrange, testable scopes, and CurrentAttributes post.

Slide 1 — Hook — 1080 × 1080

The design choice that changes testing

Ruby on Rails

A small design choice that changed how I test time-dependent code.

Accept a now parameter.

dcyoung.devRuby on Rails tips

Slide 2 — The Usual Way — 1080 × 1080

Time.current hardcoded, travel_to everywhere

The usual way

Time.current inside the scope.
travel_to in every test.

scope :active, -> {
  where("tsrange(...) @> ?",
    Time.current)
}

It works, but the time manipulation is infrastructure, not intent. The tests read like plumbing.

dcyoung.devRuby on Rails tips

Slide 3 — The Fix — 1080 × 1080

Accept a now parameter, tests read like specs

The fix

Accept a now parameter.
Tests read like specs.

scope :active, lambda { |now = Time.current|
  where("tsrange(...) @> ?::timestamp",
    now)
}

# No travel_to, no frozen clocks
now = Time.zone.parse("2026-07-23 12:00")
expect(Banner.active(now))
  .to contain_exactly(permanent, temporary)

dcyoung.devRuby on Rails tips

Slide 4 — Safety Net — 1080 × 1080

Verify SQL and Ruby always agree

The safety net

Verify SQL and Ruby
always agree.

active_banners = Banner.active(now)

expect(active_banners
  .map { |b| b.active?(now) })
  .to be_all(true)

If someone changes the boundary semantics in one but forgets the other, this test catches it.

dcyoung.devRuby on Rails tips

Slide 5 — Takeaway — 1080 × 1080

Full post CTA

tsrange scope.
Reusable concern.
CurrentAttributes.
Every model, for free.

The full post covers the tsrange pattern, an Activatable concern, and a CurrentAttributes approach where you set the time once per request and every scope uses it automatically.

Read the full post at dcyoung.dev

dcyoung.devShip it.