Follow evolution

Idea: add extra fields to our Todos application, add dummy records by the way of a migration plan and update views to reflect these changes.

Table des matières

1. Add new fields

Edit the migration /database/migrations/2018_08_01_000000_create_todos_table.php and add extra columns in the up() function.

We’ll add a description field (that can stay empty) and the ID of the user who has filled in the Todo. We’ll also add a foreign key to the users table and, we can too, add a cascade delete trigger.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTodosTable extends Migration
{
  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    Schema::create('todos', function (Blueprint $table) {
      $table->increments('id');
      $table->timestamps();
      $table->string('title', 100);
      $table->boolean('completed');
      $table->text('description', 1000)->nullable();
      $table->integer('user_id')->unsigned();
      $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    });
  }

  /**
   * Reverse the migrations.
   *
   * @return void
   */
  public function down()
  {
    Schema::dropIfExists('todos');
  }
}

Perhaps is it a bad idea to remove all todo’s when the user removes his account. If so, just use $table->foreign('user_id')->references('id')->on('users'); i.e. without the trigger.

New structure of the Todos table

2. Refresh the table’s structure

Run the following instruction for refreshing the structure of all tables. Be careful: tables will be cleared first.

php artisan migrate:refresh

By looking at the table’s structure we’ll see the changes:

3. Populate the table

To add a few items in our table, we can use a script:

php artisan make:migration PopulateTestingDatas

Just like a table, Laravel will create a php file in the folder /database/migrations/; here, for this test, the file will be called 2018_08_01_000000_populate_testing_datas.php.

<?php

use Illuminate\Database\Migrations\Migration;

class PopulateTestingDatas extends Migration
{
  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    // Create user Christophe
    DB::table('users')->insert([[
      'name' => 'Christophe',
      'email' => 'christophe@todos.com',
      'password' => bcrypt('admin')
    ]
    ]);

    // Getting the ID of the user Christophe
    $user_id = DB::table('users')->where('name', 'Christophe')->take(1)->value('id');

    // Insert a few items for him
    for ($i = 0; $i < 20; $i++) {
      DB::table('todos')->insert([
        [
          'title' => 'Todo #' . ($i + 1),
          'description' => 'Some important content for the Todo #' . ($i + 1),
          'user_id' => $user_id,
          'completed' => rand(0, 1),
          'created_at' => now(),
          'updated_at' => now()
        ]
      ]);
    }
  }

  /**
   * Reverse the migrations.
   *
   * @return void
   */
  public function down()
  {
    // Getting the ID of the user Christophe
    $user_id = DB::table('users')->where('name', 'Christophe')->take(1)->value('id');

    // Delete todos added by him
    DB::table('todos')->where('user_id', $user_id)->delete();

    // And remove the user
    DB::table('users')->where('name', '=', 'Christophe')->delete();
  }
}

3.1. Using Faker

Just for the fun, use Faker (see https://github.com/fzaninotto/Faker). So, we’ll have random dummy text.

First install Faker

composer require fzaninotto/faker

Then edit the up() function:

public function up()
    {
        // Create user Christophe
        DB::table('users')->insert([[
            'name' => 'Christophe',
            'email' => 'christophe@todos.com',
            'password' => bcrypt('admin')
        ]]);

        // Getting the ID of the user Christophe
        $user_id = DB::table('users')->where('name', 'Christophe')->take(1)->value('id');

        // Use faker to get french dummy text
        // If needed, just run "composer require fzaninotto/faker" in
        // a DOS prompt
        $faker = Faker\Factory::create('fr_FR');

        // Insert a few items for him
        for ($i = 0; $i < 20; $i++) {
            DB::table('todos')->insert([
                [
                    'title' => $faker->sentence($nbWords = 6, $variableNbWords = true) .
                        ' (todo #' . ($i + 1) . ')',
                    'description' => $faker->realText($maxNbChars = 1000),
                    'user_id' => $user_id,
                    'completed' => $faker->boolean(),
                    'created_at' => now(),
                    'updated_at' => now()
                ]
            ]);
        }
  }

4. Edit the todos view

Now that we’ve a description field and we know the author, we can use these infos in our todos view.

Edit /resources/views/todos.blade.php:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Some stupid Todos application</title>
  <link media="screen" rel="stylesheet" type="text/css" href="/css/app.css" />
</head>
<body>
  <main role="main">
      <div class="jumbotron">
        <div class="container">
          <h1 class="display-3">Some stupid Todos application</h1>
          <p>A simple Laravel application</p>
        </div>
      </div>
  </main>
  <div class="container">
    @isset($data)
      @foreach($data as $post)
         <h3><a href="todo/{{ $post->id }}">{{ $post->title }}</a></h3>
         <p>{{ $post->description }}</p>
         <small>Author: {{ $post->user_id }}</small>
         <hr/>
      @endforeach
    @endisset
    <hr/>
    <a href="/todo" >Add new item</a>
  </div>

</body>
</html>

This will result into this blog view. The author is here displayed with only his ID; we’ll change this.

Show index

As you can see, we’ve added an hyperlink for the todo’s title:

<h3><a href="todo/{{ $post->id }}">{{ $post->title }}</a></h3>

So, we need to create a route that will show the selected Todo.

5. Retrieve user’s information

Edit /app\Todo.php and since we’ve a foreign key between the user_id of the todos table and the field id of the table users; it’s really use to retrieve informations.

Just add the user() function like below:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    protected $table = 'todos';

    // Only if $table->timestamps() was mentioned in the
    // CreateTodosTable class; set to False if not mentioned
    public $timestamps = true;

    public function user()
    {
        return $this->belongsTo('App\User', 'user_id', 'id');
    }
}

This works since, in our up() function for the CreateTodosTable we’ve:

$table->foreign('user_id')->references('id')->on('users');

So, now, we can use every columns of the users table by just referencing the ´user()` function.

6. Edit the todos view once more

Edit /resources/views/todos.blade.php and replace

<small>Author: {{ $post->user_id }}</small>

by

<small>Author: {{ $post->user->name }}</small>

And now, we’ve the name...

Show author

7. Add a detail todo route

Edit /app/Http/Controllers/TodoController.php and add this function:

public function show($id)
{
    $data = Todo::where('id', $id)->firstOrFail();

    return view('show', compact('data'));
}

We’ll use the Todo model (as defined in /app/Todo.php) and the where method will retrieved the specified item.

8. Add a detail todo view

Create this file /resources/views/show.blade.php:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Some stupid Todos application</title>
  <link media="screen" rel="stylesheet" type="text/css" href="/css/app.css" />
</head>
<body>
  <main role="main">
      <div class="jumbotron">
        <div class="container">
          <h1 class="display-3">Some stupid Todos application</h1>
          <p>A simple Laravel application</p>
        </div>
      </div>
  </main>
  <div class="container">
    @isset($data)
      <h3>{{ $data->title }}</h3>
      <p>{{ $data->description }}</p>
      <small>
        Created at: {{ $data->created_at }}
        <br/>
        Last updated: {{ $data->updated_at }}
        <br/>
        Author: {{ $data->user->name }}
      </small>
      <hr/>
    @endisset
    <a href="/todo" >Add new item</a> - <a href="/todos">Show all</a>
  </div>
</body>
</html>

9. Edit the form

We need to add a description field.

Edit /resources/views/form.blade.php and add the two lines below:

{!! Form::label('description', 'Description (optional)'); !!}
{!! Form::textarea('description', null, array('class' => 'form-control')) !!}

Then form will thus become:

{!! Form::open(['route' => 'storeTodo']) !!}
<div class="form-group {!! $errors->has('todo') ? 'has-error' : '' !!}">
  {!! Form::text('title', null, array('size' => '100', 'class' => 'form-control', 'placeholder' => 'Enter Todo\'s title')) !!}
  {!! $errors->first('title', '<div class="alert alert-danger">:message</div>') !!}
</div>  
<div class="form-group ">
  {!! Form::checkbox('completed', 1, 0)  !!}
  {!! Form::label('completed', 'Completed'); !!}
</div>
<div class="form-group ">
  {!! Form::label('description', 'Description (optional)'); !!}
  {!! Form::textarea('description', null, array('class' => 'form-control')) !!}
</div>
{!! Form::submit('Submit !') !!}
{!! Form::close() !!}

10. Store the description

Edit /app/Http/Controllers/TodoController.php and add in the postForm() function:

$todo->description = $request->input('description');

The function will become:

public function postForm(TodoRequest $request)
{
  $todo = new Todo();

  $todo->title = $request->input('title');
  $todo->description = $request->input('description');
  $todo->completed = $request->input('completed');

  $todo->save(); // Save the submitted data

  return view('todo_ok'); // And show a "successful" page
}