Skip to main content

Thoughts on adding non-standard REST actions to a resource?

Rails • Asked by Dan LeGrand

I have a milestone resouce that can be activated/deactivated as well as completed/reopened.

Rails pushes the standard 7 actions in your controllers: index, new, create, show, edit, update, destroy.

These 7 actions work great for most things, but my use case didn't strictly fit into those 7 REST actions. I read an article a while back that some respected Rails developers follow REST conventions by creating resources such as:

  • activate => POST /milestones/:id/activations
  • deactivate => DELETE /milestones/:id/activations
  • complete => POST /milestones/:id/completions
  • reopen => DELETE /milestones/:id/completions

I used this approach for a while but I've found it to be difficult to work with.

It adds additional files, which sometimes leads to more complexity since there is now more to manage.

The biggest problem I encountered was it didn't make logical sense that the reopening of a milestone record was at the endpoint DELETE /milestones/:id/activations. It made more logical sense to me that it would be PUT /milestones/:id/reopen, since it is something we are doing to the milestone record.

I've been contemplating moving these non-standard actions to the milestones_controller.rb file itself and updating my routes accordingly.

I wanted to get some thoughts on these 2 different approaches and see how others had solved this problem of custom actions on resources?


I think it's fine for you to make some non-standard actions here.

Are these actions changing the Milestone record or are they change an Activation / Completion records associated to a Milestone?


Chris,

These actions mostly touch the milestone model, but they update children associations (ie, when a milestone is activated, it's children task records are activated as well).

There is no separate Activation or Completion record, those were just names I gave to the actions I was taking on a milestone.

I also found that Github and Stripe both seem to use custom actions, so it made me feel a little better about moving to that approach.

Specifically with activate/deactivate, I have 10+ separate resources it can apply to, and it was much easier for me to remove those 10 separate controller files and just put the methods on the already-existing controllers.

It's always good to get input from folks in the community I look to for helping establish "best practices"!


So what I would probably do here is actually:

resources :milestone do
  member do
      post :activate, to: "milestones/activations#create"
        delete :deactivate, to: "milestones/activations#destroy"
    end
end

Then you can have a app/controllers/milestones/activations_controller.rb that handles them. That's great for separating out the logic from the main CRUD, and your routes aren't too complicated.

I'll often make a singular resource for things like this too.

resources :milestone do
  scope module: :milestones do
      resource :activate
    end
end

Works a bit better if you're doing like resource :advanced_search or something.

In your case, DELETE /milestones/1/activate doesn't make sense, and you'd want the destroy route to be DELETE /milestones/1/deactivate so it's more intuitive. And that's why I'd probably write the custom routes instead of resources in this case.


I didn't even though about using the custom route names but then pointing them to separate controllers to keep the code clean. I got too stuck in the "all or none" mindset of only doing it one way.

Thanks for the input!


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 27,623+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.