3

I´m building my API (makes and models) and I want to have nested resources (not sure if this is correct Restfully speaking)

/makes/ferrari/models /makes/ferrari/models/f40

I defined the following route

Route::resource('makes.models', 'ModelsController');

and the ModelsController.php

/**
 * Display a listing of the resource.
 *
 * @return \Illuminate\Http\Response
 */
public function index()
{
    $data = Models::all();
    return response()->json($data);
}

/**
 * Display the specified resource.
 *
 * @param  int  $id
 * @return \Illuminate\Http\Response
 */
public function show($id)
{
    $data = Models::find($id);
    return response()->json($data);
}

and the models model (yeah I need to change the name)

class Models extends Model
{

    public function make()
    {
        return $this->belongsTo('App\Make');
    }
}

my problem is that even if the route works it returns all models in the db (not only ferraris) where should I define that relationship? is not automatic? I have 2 tables makes (id, name), models (id, name, make_id)

thank you!

2 Answers 2

3

The resource route will define the following routes:

Method  Path                               Action 
GET     /makes/{make}/models               index   
GET     /makes/{make}/models/create        create  
POST    /makes/{make}/models               store 
GET     /makes/{make}/models/{id}          show   
GET     /makes/{make}/models/{id}/edit     edit    
PUT     /makes/{make}/models/{id}          update  
DELETE  /makes/{make}/models/{id}          destroy 

Your request /makes/ferrari/models will not match any of those routes (as your show parameter only takes one parameter). You may request for /makes/models/1 to call show, but you are practically missing the route for this, as the nested route does not provide it.

If you say that you always get all items, you are very likely hitting the index action instead of show.

If you want to query your models with /makes/ferrari/models/f40, you would need a route like this:

Route::get('/makes/{make}/model/{model}', 'ModelsController@show');

Which is already part of the resource route created for you. Now, in your show controller, use the make and model parameters to find the correct dataset:

public function show($make, $model)
{
    $data = Model::with('makes')
               ->whereName($model)
               ->whereHas('makes', function ($query) use ($make) {
                                      $query->where('name', '=', $make); 
                                   })->get();

    return response()->json($data);
}

Laravel doesn't automatically do that for you.

Update: Route model binding

You might want to check out https://laravel.com/docs/5.3/routing#route-model-binding for a more sophisticated way of doing this. You can set your route key name in both of your models overwriting the getRouteKeyName() method and returning 'name' in this case, telling Laravel to use the name column instead of the id.

You can also bind parameters in your routes specifically to a custom resolution logic by doing something like

$router->bind('model', function ($value) {
    return Model::where('name', $value)->first();
});

and then every time you use {model} in your routes, it will use the name instead of the id.

Use slugs

However, be advised that you have to make absolutely sure that the names stored in the database for model and make are sluggified so that they are suited for use in URLs. If necessary, you may possibly do that in your bind as shown above, returning

return str_slug(Model::where('name', $value)->first());

This is untested, however, so it might or might not work.

Hope that helps :-)

Sign up to request clarification or add additional context in comments.

Comments

0

When using nested resources, all your controller actions will receive an additional first parameter (the parent resource identifier). So you need to update your controller actions accordingly:

public index($make) {
    $make = Make::with('models')->where('name', $make)->firstOrFail();

    return view('models.index', compact('make'));
}

public show($make, $model) {
    $make = Make::with('models')
        ->where('name', $make)
        ->firstOrFail();
    $model = $make->models()
        ->where('name', $make)
        ->firstOrFail();

    return view('models.show', compact('make', 'model'));
}

It should be the same with your other controller actions.

Note that I made assumptions regarding the structure of your database.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.