Tip 4: Think of different states as different resources#4
Conversation
|
In this instance I would probably prefer to have - Route::post('/podcasts/{id}/publish', 'PodcastsController@publish');
+ Route::post('/podcasts/{id}/publish', 'PublishedPodcastsController@store');The url schema feels more connected to the model, especially if I was pushing this logic out as a public API, its much more logical to use. That said great tip. |
|
@DanielDarrenJones What about destroying then? |
|
yeah looking back I would probably model this differently, with a publish_at attribute on the podcast and the publish action would simply be an update request to update that, with the unpublish either being another update to that, or a delete request to /podcast/:id After working a lot more with API’s I like to keep really strict to the rest actions on a model, but would love to discuss it more with you, hit me up on twitter if you have any questions @DannyDJones |
|
Hi @adamwathan, I've only just watched your "Cruddy by design" talk yesterday. I really like this approach. I've got a question though: What are your thoughts about this: in routes: - Route::get('/podcasts', 'PodcastsController@index');
+ Route::get('/podcasts', 'PublishedPodcastsController@index');And of move this |
|
@loekwetzels I faced almost the same problem and came here to see who had already solved it and how. Only I have a slightly more complicated one. In terms of presentation, I need to model a podcast that can be published and unpublished. And then display them on separate pages, to have all unpublished podcasts on another page. I couldn't think of anything better than to pass information about the publication in the query parameter: It seems like the query parameters are just created to impose restrictions on the selection. |
The last custom actions we have are
PodcastsController@publishandPodcastsController@unpublish.Here are the endpoints:
At first glance you might think:
"All we're doing is updating the
published_atcolumn, let's usePodcastsController@updatefor this."But we already have an
updateaction, and as we've already discussed, trying to cram two actions into one is a recipe for complexity.So how can we model "publish" as it's own standard REST action?
If we ask ourselves the same question we did in the previous refactoring:
"After publishing a podcast, what do I have now that I didn't have before?"
...one answer that comes to mind is a "published podcast."
So what would it look like to model "publishing a podcast" as "creating a published podcast"?
✅ Create a dedicated
PublishedPodcastsControllerIn these situations, it can often be helpful to think of a resource in a certain state as it's own independent resource.
If we are creating a new "published podcast", the standard endpoint structure look like this:
As before, we lose the
{id}route parameter here, so we'll need to pass that through the request body.This feels pretty natural when you think of the request body as being the "raw materials" needed to create the resource. When you are creating a published podcast, the raw materials needed are an existing unpublished podcast, and the best way to represent that entity is with it's identifier.
Here's what our new controller action would look like:
One thing to note here is the return value. Previously we were returning an empty
204response, but now that we're "creating" a new resource, returning the model seems more appropriate.Modelling
unpublishJust like
subscribe, ifpublishbecomes creating a published podcast, thenunpublishcould become destroying a published podcast.This one probably requires the biggest mental leap of any of our refactorings so far, but I promise that in practice it doesn't end up feeling as weird as it first sounds.
Here's what the endpoint would become:
"Published podcasts" don't have any sort of special unique ID; instead their ID is the same as a regular podcast's ID, so that's what our
{id}parameter will represent here.Here's what the full controller would look like:
After this refactoring, here's where we stand:
PodcastsController, with 7 standard actions and no custom actionsEpisodesController, with 4 standard actions and no custom actionsPodcastEpisodesController, with 3 standard actions and no custom actionsPodcastCoverImageController, with 1 standard action and no custom actionsSubscriptionsController, with 2 standard actions and no custom actionsPublishedPodcastsController, with 2 standard actions and no custom actionsThat's 6 controllers with an average of ~3 methods per controller. Small, clean, and simple!