---
title: "RR005 – Paginate All Collection Endpoints"
impact: medium
impactDescription: "Loading unbounded collections into memory causes OOM crashes and unacceptably slow responses at scale."
tags: [ruby, rails, performance, pagination]
---

# RR005 – Paginate All Collection Endpoints

## Rule

Every controller action that returns a list must apply pagination. Never return an unbounded collection with `Model.all` or `.where(...)` without a `.page` or `.limit` call.

## Why

A table that currently has 1,000 rows will have millions. An unguarded `Order.all` will load all records into memory on each request. Pagination (Kaminari or Pagy) is the standard Rails solution.

## Wrong

```ruby
def index
  @orders = Order.all                         # ❌ unbounded
  @users  = User.where(active: true)          # ❌ unbounded
  render json: @orders
end
```

## Correct

```ruby
# Gemfile: gem 'pagy'  —OR—  gem 'kaminari'

# With Pagy (preferred — fastest)
include Pagy::Backend

def index
  @pagy, @orders = pagy(Order.order(created_at: :desc), items: 25)
  render json: { orders: @orders, meta: pagy_metadata(@pagy) }
end

# With Kaminari
def index
  @orders = Order.order(created_at: :desc).page(params[:page]).per(25)
  render json: @orders
end
```

## Notes

- Default page size should be configurable but capped (e.g., max 100 per page).
- Pass `params[:per_page]` through an explicit allowlist — never directly into `.per()`.
- Pagy is significantly faster than Kaminari for large tables; prefer it in new projects.
- For cursor-based APIs (infinite scroll / mobile), use `pagy_cursor` or write manual `WHERE id > last_id LIMIT n` queries.
