Copy This To Markdown So My Agent Can Read This Because I Don't Have The Time to Read It Myself ATM

5-minute guide · Rails

Give +1 the full Rails treatment.

Ruby can increment a number with += 1, but that lacks institutional confidence. We will give it an actor, SQL-backed state, a worker supervisor, and a durable ten-minute reminder so the eventual + 1 can survive Rails restarting and arrive with paperwork.

01

Install the enterprise integer department

Ruby already has +=. Add the gem for the ordered hold, durable alarm, and restart recovery surrounding our ambitious little operator.

Terminal
bundle add solid_objects
bin/rails generate solid_objects:install
bin/rails db:migrate
bin/rails solid_objects:doctor
02

Authorize the addition ceremony

The initializer denies every operation by default. For this controlled local example, permit calls and reads. Even sarcastic counters need real authorization boundaries.

config/initializers/solid_objects.rb
SolidObjects.configure do |configuration|
  configuration.authorize_message = ->(**) { true }
  configuration.authorize_query = ->(**) { true }
end

For this local example only: keep destroy, subscription, and administration denied. Bind every production policy to the authenticated user and tenant.

03

Escalate arithmetic into a domain model

One actor owns one event's availability. The hold and its keyed reminder commit with the counter change, which is a great deal of ceremony for + 1 and exactly enough for an expiring reservation.

Senior architecture note

We could increment a column. We could also forget which customer owns the hold, release it twice, and rediscover scheduled jobs during an incident. Choices abound.

app/actors/ticket_counter.rb
class TicketCounter < SolidObjects::Actor
  attribute :available, default: 1
  attribute :holds, default: {}

  def hold(buyer:)
    return { held: false, available: } if available.zero? || holds.key?(buyer)

    self.available -= 1
    self.holds = holds.merge(buyer => Time.current.to_i)
    schedule(at: 10.minutes.from_now, key: buyer).expire(buyer:)
    { held: true, available: }
  end

  def expire(buyer:)
    return available unless holds.key?(buyer)

    self.holds = holds.except(buyer)
    self.available += 1
  end
end
04

Restart Rails before the punchline

The direct hold is caller-assisted. The ten-minute reminder needs the Solid Objects runtime. Start it, place the hold in a console, then stop the runtime before expiry for maximum theatrical tension.

Terminal 1 · runtime
bundle exec solid_objects start
Terminal 2 · Rails console
counter = TicketCounter.ref("show-42")
counter.hold(buyer: "ada")
counter.available
hold → { held: true, available: 0 } available → 0

Stop Terminal 1. Leave it down past the deadline, restart the same command, then ask counter.available again. The due reminder runs and availability returns to 1. The expiry checks that the hold still exists, so retries are harmless instead of arithmetically creative.

The integer has returned

The counter was never the hard part.

The useful part is one event identity owning the guard, the hold, and the delayed release. If the work fits inside with_lock, use it. Ten minutes is a poor transaction duration and an excellent reminder deadline.

Seriously, for a moment: This same shape appears in inventory holds, carts, account workflows, rate limits, expiring sessions, job leases, games, and connected devices. Keeping state, delayed work, retries, and per-identity ordering correct through deploys and worker crashes is genuinely non-trivial. That is the problem Solid Objects is meant to solve.

Read the complete Rails guide →