Thoughts on adding non-standard REST actions to a resource?
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!