In config/routes.rb, how do only: and except: trim resources :photos, and what happens to a request for an action you removed?
answer
- allow-list versus deny-list
- names must be among the seven
- ArgumentError names the route in 8.1
- no route: RoutingError, 404
- /photos/new can fall through to show
basics
~20 sonly: keeps the listed actions and except: drops them; the removed routes and their helpers are simply not drawn. A request for a removed route raises ActionController::RoutingError (404), unless another route, such as GET /photos/:id, matches it first.
solid answer
~40 s`resources :photos, only: [:index, :show]` draws just those two routes; `except: [:destroy]` draws the other six. Both accept a symbol or an array, and every name must be one of the seven actions: in Rails 8.1 an unknown name raises an `ArgumentError` naming the route (`Route \`resources :photos\` - :only and :except must include only ...`) while the routes load. A removed route also removes its helper, so a template calling `new_photo_path` raises `NameError`. A request for a removed route usually matches nothing: `ActionController::RoutingError`, a 404 in production. The trap is `GET /photos/new` with `new` removed and `show` kept: it matches `/photos/:id`, so `show` runs with `params[:id] == "new"` and the lookup fails.
code
ruby · 8 lines# config/routes.rb
Rails.application.routes.draw do
resources :photos, only: [:index, :show]
namespace :admin do
resources :photos, except: :destroy
end
endgo deeper
Know that only: keeps the listed actions and except: removes them, and that the helpers of removed routes disappear with them.
Explain what happens to requests for removed routes: a RoutingError 404 when nothing matches, and the /photos/new fall-through to show when new is gone but show is not.
Default to allow-lists so new actions never appear by accident, and treat the route table as public surface to review, authorize and test.
Set a team convention for trimming resources and for when a missing action calls for a new controller rather than a custom route.
## Why trim a resource at all `resources :photos` in `config/routes.rb` draws seven routes for `PhotosController`: `index`, `new`, `create`, `show`, `edit`, `update` and `destroy`. A photography portfolio rarely needs all of them on every resource. The public gallery may only list and show photos, while uploads happen in an admin area. Drawing routes the controller never implements leaves URLs that only fail at request time, helpers nobody should call, and noise in `bin/rails routes`. Rails therefore lets the `resources` line say which actions exist. ## only: and except: - **`only:`** is an **allow-list**: `resources :photos, only: [:index, :show]` draws exactly those two routes. - **`except:`** is a **deny-list**: `resources :photos, except: [:destroy]` draws the other six. - Both take a single symbol or an array: `only: :show` works. - Pick whichever list is shorter and clearer. An allow-list is safer as a default, because a new action never appears by accident. The same options work on the singular `resource :profile`, whose action set has no `index`. Two less common details: - **A surrounding scope can carry them.** `scope only: [:index, :show] do ... end` applies the list to every resource inside, intersected with each one's own actions. A resource that passes its own `only:` or `except:` ignores the scope's list. - **Both together** are legal: `only:` picks the starting set and `except:` then removes from it, though writing both on one line rarely reads well. ## Names are validated Every name you pass must be one of the seven standard actions. A typo or a custom action such as `:publish` raises an `ArgumentError` when the routes are drawn, so the application fails as soon as the routes file loads instead of serving a silently incomplete table. Since Rails 8.1.0 the message names the line that caused it: ``` Route `resources :photos` - :only and :except must include only [:index, :create, :new, :show, :update, :destroy, :edit], but also included [:publish] ``` Custom actions are not trimmed in; they are added with `member` or `collection` blocks, a separate part of the routing DSL. ## What disappears with a route Removing an action removes more than one line of the table: 1. **The route itself.** No verb-and-path pair for that action is drawn. 2. **The helper, if nothing else uses the name.** With `only: [:index, :show]`, `new_photo_path` and `edit_photo_path` no longer exist, and a template that still calls them raises `NameError` when it renders. `photo_path` survives, because `show` still uses the name `photo`. 3. **Nothing in the controller.** The router never looks at controller methods; an unused `destroy` method simply becomes unreachable. ## Requests for a removed action What a client sees depends on whether **any other route** matches the request: | Setup | Request | Result | |---|---|---| | `except: [:destroy]` | `DELETE /photos/7` | no route matches: `ActionController::RoutingError`, 404 | | `only: [:index, :show]` | `POST /photos` | no route matches: 404 | | `only: [:index, :show]` | `GET /photos/new` | matches `GET /photos/:id`: `show` runs with `params[:id] == "new"` | The last row is the classic surprise. `resources` normally draws `new` before the member routes, which is what keeps `/photos/new` away from `show`. Remove `new` and keep `show`, and `/photos/new` is just another id. `Photo.find("new")` then raises `ActiveRecord::RecordNotFound`, which also renders a 404 in production, so the bug hides until someone reads the logs. A missing route never produces a 405 here: Rails reports an unmatched verb as a routing error. ## Where interviewers take it - **Trimming is part of the public surface.** Fewer routes means fewer endpoints to authorize, test and keep working; a route that exists only because `resources` drew it is a liability once someone finds it. - **Match the trim to the controller.** If `PhotosController` defines no `destroy`, leaving the route in place turns a missing feature into an `AbstractController::ActionNotFound` at request time instead of an absent URL. - **Confirm with the router, not by reading.** `bin/rails routes -c photos` lists what the line actually draws, which is quicker than reasoning about combinations of options and scopes. - **Custom actions are a different tool.** When the action you need is not one of the seven, the answer is a custom route or, often better, another resourceful controller, not stretching `only:` to a name it does not accept. - **Generated code assumes the full set.** A scaffolded controller and its views link to `new`, `edit` and `destroy`; trimming the routes means removing those links and methods too, or the first render fails.
- A template still calls new_photo_path after you changed the line to only: [:index, :show]. What happens?The `new` route is no longer drawn, so the `new_photo` name and its `new_photo_path` and `new_photo_url` helpers are not defined. Rendering the template raises `NameError`. Searching templates for the helper, or a test that renders the page, catches it before production.
- How would you stop GET /photos/new from reaching show once new is removed?Either keep the trimmed set consistent with the links the app renders, so nothing points at `/photos/new`, or constrain the id segment to digits so `new` cannot match `/photos/:id`. Without one of those, `show` runs with `params[:id] == "new"` and the lookup raises `ActiveRecord::RecordNotFound`.
saying these in an interview costs you the question
- only: accepts any action name, including custom ones like :publish
- A request for a removed action reaches the controller and raises ActionNotFound
- Trimming routes also deletes the matching controller methods
- A DELETE on a route that only allows GET answers 405 Method Not Allowed
- new_photo_path keeps working after new is removed from only: