All lessons

Spot the Tax · Card 18 of 20

One model class serving four teams

Why a single Order model used by checkout, fulfillment, support, and analytics becomes the file no one wants to touch.

The code

What will this cost you in six months?

class Order < ApplicationRecord
  # Used by checkout (cart, totals)
  has_many :line_items
  scope :pending, -> { where(status: "pending") }

  # Used by fulfillment (warehouse picks)
  has_many :shipments
  scope :ready_to_ship, -> { where(status: "submitted").where.not(warehouse_id: nil) }

  # Used by support (refund flow)
  has_many :refund_requests
  scope :refundable, -> { where(status: "fulfilled").where("created_at > ?", 30.days.ago) }

  # Used by analytics (cohort reports)
  scope :by_cohort, ->(month) { where(created_at: month.all_month) }

  # 60 columns, 30 scopes, 200 lines of model code...
end

The problem

A single Order class is shared between checkout, fulfillment, support, and analytics. A change driven by one team can break another — adding a new status value for refunds breaks a checkout query that didn't account for it. The word "submitted" means slightly different things to different parts of the app. Every team works with 60 columns even when they only care about 5. The file becomes the thing nobody wants to touch because nobody fully understands what depends on it.

Take a moment. Before revealing, ask yourself whether sharing the same class across all those teams is actually what you want. What do they each need from "an order"?