Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Aug 2, 2017

Conversation

adamwathan
Copy link
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

uxweb commented Aug 3, 2017

@adamwathan The beauty keeps going! Awesome!!

@uxweb
Copy link

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

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

mreduar commented Jun 12, 2019

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

@nomikz
Copy link

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.

None yet

4 participants