ruby 40 lines · 8 steps

Modeling subscription lifecycle in Rails

A Subscription model uses an enum, composable scopes, and guarded bang methods to manage billing state transitions.

Explained by highlit
1class Subscription < ApplicationRecord
2 belongs_to :account
3 
4 enum :status, {
5 trialing: 0,
6 active: 1,
7 past_due: 2,
8 canceled: 3,
9 expired: 4
10 }, default: :trialing, validate: true
11 
12 scope :billable, -> { where(status: %i[active past_due]) }
13 scope :renewable, -> { active.where(cancel_at_period_end: false) }
14 scope :ending_soon, ->(window = 3.days) { active.where(current_period_end: ..window.from_now) }
15 
16 def can_reactivate?
17 canceled? || expired?
18 end
19 
20 def mark_past_due!
21 return if past_due?
22 
23 update!(status: :past_due, delinquent_since: Time.current)
24 BillingMailer.with(subscription: self).payment_failed.deliver_later
25 end
26 
27 def cancel!(immediately: false)
28 if immediately
29 update!(status: :canceled, canceled_at: Time.current, current_period_end: Time.current)
30 else
31 update!(cancel_at_period_end: true)
32 end
33 end
34 
35 def reactivate!
36 raise ActiveRecord::RecordInvalid, self unless can_reactivate?
37 
38 update!(status: :active, canceled_at: nil, cancel_at_period_end: false)
39 end
40end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A validated enum turns a status column into named predicates and value scopes for free.
  2. 2Scopes chain onto enum-generated scopes, letting you express business rules declaratively.
  3. 3Guarded bang methods keep invalid state transitions from ever hitting the database.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Modeling subscription lifecycle in Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code