Jonathan Bennett

Design routes around intent, not tables

When I first started with Rails, my routes, models, and tables always matched up. I had resources :posts, a Post model, and a posts table. It felt tidy and predictable.

But here’s the thing: you don’t have to keep it that way—and you probably shouldn’t.

An Example: Smart Deletion

Let’s say you have an Event resource. Events can have nested child events. When a user deletes a parent event, they should be able to choose:

  • Delete just the parent
  • Delete the parent and all its children

Now, you could overload EventsController#destroy with extra parameters to handle this logic. But why not give this behavior its own home?

resources :events do
  resource :deletion, only: [:new, :create], module: :events
end

This sets up a controller like Events::DeletionsController, which handles just the deletion UI and logic. You can present a simple form with options, event embedding it in a modal with a Turbo Stream, and link to it cleanly:

<%= link_to "Delete", new_event_deletion_path(@event), class: "btn-danger" %>

Why This Is Better

  • Keeps your EventsController focused on the core resource lifecycle.
  • Makes complicated deletions a first-class concept in your domain.
  • Keeps the route semantics expressive: POST /events/:id/deletion is clear and purposeful.

So… What Else Are You Hiding?

Look at your app. Where are you jamming complex logic into overly generic controllers? What behaviours deserve a name, a route, a view—even without a dedicated model or table?

You’re allowed to make up resources. Rails won’t stop you.