Creating a hasMany Relation in Laravel 5

  • February 25, 2015

Welcome to the incredibly popular Easy Laravel 5 companion blog. To celebrate the new edition's release (updated for Laravel 5.5!) use the discount code easteregg to receive 20% off the book or book/video package! » Buy the book

The One-to-Many (also known as the Has Many) relationship is useful when you want to relate one table record to one or many other table records. This relationship type is used throughout TODOParrot, so in this section we'll look at some actual code used to power the application. To recap from the chapter introduction, the One-to-Many relation is used when you want to relate a single table record to multiple table records. For instance a list can have multiple tasks, therefore one list is related to many tasks, meaning we'll need to relate the Todolist and Task models using a One-to-Many relation. Read on to learn how this is implemented.

Creating the Task Model

In the last chapter we created the Todolist model, meaning we'll need to create the Task model in order to begin associating tasks with lists. Use Artisan to generate the Task model:

$ php artisan make:model Task

You'll find the newly generated model inside app/Task.php:

<?php namespace todoparrot;

use Illuminate\Database\Eloquent\Model;

class Task extends Model {

    //

}

Next, create the Task model's underlying tasks table. We'll use Artisan's make:migration command

$ php artisan make:migration --create=tasks create_tasks_table
Created Migration: 2015_01_20_210010_create_tasks_table

Open the newly created migration file (found in database/migrations) and modify it to look like this:

public function up()
{
  Schema::create('tasks', function(Blueprint $table)
  {
    $table->increments('id');
    $table->integer('todolist_id')->unsigned();
    $table->foreign('todolist_id')
      ->references('id')->on('todolists')
      ->onDelete('cascade');
    $table->string('name');
    $table->text('description'); 
    $table->boolean('done')->default(false);
    $table->timestamps();
  });
}

public function down()
{
  Schema::drop('tasks');
}

Notice we're including an integer-based column named todolist_id in the tasks table, followed by a specification that this column be defined as a foreign key. In doing so, Laravel will ensure that the column is indexed, and additionally you'll optionally be able to determine what happens to these records should the parent be updated or deleted (more about this latter matter in a moment). After saving the changes, run the migration:

$ php artisan migrate
Migrated: 2014_10_30_164456_create_tasks_table

Defining the One-to-Many Relation

With the Task model and underlying table created, it's time to create the relation. Open the Todolist model and create a public method named tasks, inside it referencing the hasMany method:

class Todolist extends Model {

  ...

  public function tasks()
  {
    return $this->hasMany('todoparrot\Task');
  }

You'll likely also want to define the opposite side of the relation within the Task model using the belongsTo method:

class Task extends Model {

  ...

  public function todolist()
  {
    return $this->belongsTo('todoparrot\Todolist');
  }

}

If you don't understand why we'd want to use the belongsTo relation here, please refer back to the earlier section, "Introducing the Belongs To Relation".

With the relation defined, let's next review how to associate tasks with a list.

Associating Tasks with a TODO List

To assign a task to a list, you'll first create a new Task object and then save it through the Todolist object, as demonstrated here:

$list = Todolist::find(245);

$task = new Task;
$task->name = 'Walk the dog';
$task->description = 'Walk Barky the Mutt';

$list->tasks()->save($task);

$task = new Task;
$task->name = 'Make tacos for dinner';
$task->description = 'Mexican sounds really yummy!';

$list->tasks()->save($task);

With two tasks saved, you can now iterate over the list's tasks within a view like you would any other collection. Let's modify the Lists controller's show action/view (created in the last chapter) to additionally display list tasks. The Lists controller's show action doesn't actually change at all, but I'll include it here anyway for easy reference:

public function show($id)  
{
  $list = Todolist::find($id);    
  return view('lists.show')->with('list', $list);
}

We'll only need to update the view (resources/views/lists/show.blade.php) to iterate over the tasks. I'll present the modified view here:

{% raw %}
@extends('layouts.master') 

@section('content')

<h1>{{ $list->name }}</h1> 

<p>
Created on: {{ $list->created_at }} 
Last modified: {{ $list->updated_at }}<br />
</p>

<p>
{{ $list->description }}
</p>

<h2>Tasks</h2>

@if ($list->tasks->count() > 0)
  <ul>
  @foreach ($list->tasks as $task)

    <li>{{ $task->name }}</li>

  @endforeach
  </ul>
@else
 <p>
  You haven't created any tasks. 
  <a href="{{ URL::route('lists.tasks.create', [$list->id]) }}" 
     class='btn btn-primary'>Create a task</a>
</p>  
@endif

@endsection
{% endraw %}

Filtering Related Records

You'll often wish to retrieve a filtered collection of related records. For instance the user might desire to only see a list of incomplete list tasks. You can do so by filtering on the tasks table's done column:

$completedTasks = Todolist::find(1)->tasks()->where('done', true)->get();

Chapter 7 offers a comprehensive introduction to all Laravel relationship types.

Buy the book