Skip to content

Tip 2: Treat properties edited independently as separate resources#2

Merged
adamwathan merged 1 commit intomasterfrom
edited-independently
Aug 2, 2017
Merged

Tip 2: Treat properties edited independently as separate resources#2
adamwathan merged 1 commit intomasterfrom
edited-independently

Conversation

@adamwathan
Copy link
Copy Markdown
Owner

@adamwathan adamwathan commented Aug 2, 2017

The next custom action I'd like to look at is PodcastsController@updateCoverImage.

Here's the endpoint:

Route::post('/podcasts/{id}/update-cover-image', 'PodcastsController@updateCoverImage');

If we want to remodel this custom action as a standard REST action, what should our endpoint be and what controller should we use?

Evaluating against our list of REST actions:

  • Index
  • Show
  • Create
  • Store
  • Edit
  • Update
  • Destroy

...it seems like "update" is probably our best choice, but what is the resource we are updating?

If you look at the current implementation, you can see that all we are really doing is updating a field on the podcasts table:

public function updateCoverImage($id)
{
    $podcast = Auth::user()->podcasts()->findOrFail($id);

    request()->validate([
        'cover_image' => ['required', 'image', Rule::dimensions()->minHeight(500), Rule::dimensions()->minWidth(500)],
    ]);

    $podcast->update([
        'cover_path' => request()->file('cover_image')->store('images', 'public'),
    ]);

    return redirect("/podcasts/{$podcast->id}");
}

You might think that what we are doing here is an "update" action against our podcasts resource, but similar to our nested resource example, we already have an "update" action for podcasts:

public function update($id)
{
    $podcast = Auth::user()->podcasts()->findOrFail($id);

    request()->validate([
        'title' => ['required', 'max:150'],
        'description' => ['max:500'],
        'website' => ['url'],
    ]);

    $podcast->update(request([
        'title',
        'description',
        'website',
    ]));

    return redirect("/podcasts/{$podcast->id}");
}

This action is used to update the basic fields for a podcast, and is exposed through a completely separate form in the UI than updateCoverImage.

We could try to piggy back off of this action, but remember, we don't want to re-use a single controller action for multiple user actions otherwise we risk introducing more complexity into our controller.

So what should we do?

✅ Create a dedicated PodcastCoverImageController

Resources exposed through your controllers and endpoints don't have to map one-to-one with your models or database tables.

If a user edits one part of model using a different form than another part of a model, you should probably expose it as it's own resource.

So instead of making a POST request to update-cover-image, lets make a PUT request that treats the cover image like it's own resource:

- Route::post('/podcasts/{id}/update-cover-image', 'PodcastsController@updateCoverImage');
+ Route::put('/podcasts/{id}/cover-image', 'PodcastCoverImageController@update');

Once we create that new controller and move the old PodcastsController@updateCoverImage action over to PodcastCoverImageController@update, we're left with the following controllers:

  • PodcastsController, with 7 standard actions and 4 custom actions
  • EpisodesController, with 4 standard actions and no custom actions
  • PodcastEpisodesController, with 3 standard actions and no custom actions
  • PodcastCoverImageController, with 1 standard action and no custom actions

Next up: Treat pivot records as their own resource

@uxweb
Copy link
Copy Markdown

uxweb commented Aug 3, 2017

@adamwathan The beauty keeps going! Awesome!!

@uxweb
Copy link
Copy Markdown

uxweb commented Aug 3, 2017

Many times I got caught by questioning myself about where to put these actions. Now I see the light by following your mental process to achieve it. Thank you!

@uxweb
Copy link
Copy Markdown

uxweb commented Aug 3, 2017

Before this, I had been using the same update method to update just a property of the model by adding a check for the cover image file, for instance:

if (request()->hasFile('cover_image')) {
    // Store image and update model property
}

This is trivial if is the only property that needs to be updated in isolation and without a required validation rule. But it gets more complex when you have several fields that can be updated independently and each field has its own validation rules.

@mreduar
Copy link
Copy Markdown

mreduar commented Jun 12, 2019

This means that we will create a new Controller for each custom action we want?

@nomikz
Copy link
Copy Markdown

nomikz commented Jun 17, 2020

When you do send put with formRequest from front, you can't retrieve the image.
Then it is post method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants