1

I'm building a forum with Laravel. After clicking the submit button to create a new thread nothing happens, the user is not redirected and the thread doesn't appear in the database.

Here's my controller:

public function store(ThreadValidation $rules, $request)
{
    $thread = Thread::create([
        'user_id' => auth()->id(),
        'channel_id' => request('channel_id'),
        'title' => request('title'),
        'body' => request('body'),
    ]);

    return redirect($thread->path());
}

Model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Thread extends Model
{
    protected $guarded = [];
    protected $fillable = ['title', 'body', 'user_id', 'channel_id'];

    public function creator()
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function replies()
    {
        return $this->hasMany(Reply::class);
    }

    public function addReply($reply)
    {
        $this->replies()->create($reply);
    }

    public function path()
    {
        return "/threads/{$this->channel->slug}/{$this->id}";
    }

    public function channel()
    {
       return $this->belongsTo(Channel::class, 'channel_id');
    }
}

Routes:

Route::get('/', function () {
        return view('welcome');
});

Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
Route::get('threads', 'ThreadsController@index');
Route::get('threads/create', 'ThreadsController@create');
Route::get('threads/{channel}/{thread}', 'ThreadsController@show');
Route::post('/threads', 'ThreadsController@store');
Route::get('threads/{channel}', 'ThreadsController@index');
Route::post('threads/{channel}/{thread}/replies', 'RepliesController@store');

Blade:

<div class="container">
    <div class="row">
        <div class="col-md-8 offset-md-2">
            <div class="card card-default">
                <div class="card-header">Create a New Thread</div>
                <div class="card-body">
                  <form method="POST" action="/threads">
                    @csrf
                    <div class="form-group">
                      <label for="title">Add a title</label>
                      <input type="text" class="form-control" name="title" id="title">
                    </div>
                    <div class="form-group">
                      <label for="body"></label>
                      <textarea name="body" id="body" class="form-control" rows="8"></textarea> 
                    </div>
                    <button type="submit" class="btn btn-primary">Publish</button>
                  </form>
                </div>
            </div>
        </div>
    </div>
</div>

I would really appreciate if you could tell me what is wrong, because my test actually passes.

/**
 * @test
 */
public function an_authenticated_user_can_create_forum_threads()
{
    $this->withExceptionHandling()
        ->signIn();

    $thread = create('App\Models\Thread');
    $this->post('/threads', $thread->toArray());

    $this->get($thread->path())
        ->assertSee($thread->title)
        ->assertSee($thread->body);
}

Update: Form Request:

public function rules()
{
    return [
        'title' => 'required',
        'body' => 'required',
        'channel_id' => 'required||exists:channels, id',
    ];
}
Priyanka khullar
  • 509
  • 1
  • 5
  • 25
Lidia Mokevnina
  • 115
  • 1
  • 8

1 Answers1

2

Your post request does not contain a channel_id, thus this validation will fail: 'channel_id' => request('channel_id') and that is why your request will not be handled correctly.

You will have to add an input field which contains this variable, or remove it from the validation. In the order requests this will work because the url sometimes contains the id, but since this url is /threads it obviously does not contain this variable.

If you can not or do not want to force this variable you can remove the validation rule and provide a default value if the request variable is not present (however I would advice against this since it is better to require this variable if you need it anywhere else since this spares you the hassle of more checks if it is set). In order to do this you can use the Null coalescing operator, like this:

$thread = Thread::create(
    [
    'user_id' => auth()->id(),
    'channel_id' => request('channel_id') ?? "Default value",
    'title' => request('title'),
    'body' => request('body'),
    ]
);

I hope this helps you

Sven Hakvoort
  • 3,543
  • 2
  • 17
  • 34
  • Thank you for your answer. Channel_id is only required if it exists. The below code is from my form request 'channel_id' => 'required||exists:channels, id', – Lidia Mokevnina Dec 07 '18 at 07:31
  • @LiMi, it will require channel_id if it exists in the database, not if it exists on the request. So i suspect it is failing on that – Sven Hakvoort Dec 07 '18 at 07:48
  • You should remove the rule from the `Form Request` class. It's your server-side validation that's failing. – linuxartisan Dec 07 '18 at 08:06
  • I get this: SQLSTATE[HY000]: General error: 1364 Field 'channel_id' doesn't have a default value (SQL: insert into `threads` (`user_id`, `title`, `body`, `updated_at`, `created_at`) , which is logical, because I don't post it to a certain channel yet. I've been following the Jeffrey Way's tutorial on how to build a forum and I know this will be fixed when a thread is posted to a certain channel, but I'm just wondering why exactly the same codes works when Jeffrey does it. – Lidia Mokevnina Dec 07 '18 at 08:34
  • @LiMi, he might have set a default value for the channel_id either in his database or in php. Anyway this will be the cause of your issue. You either need to provide this ID in order for the validation to succeed, or remove the validation step and provide a default value if the variable is not present (i'll update my answer for this in a minute) – Sven Hakvoort Dec 07 '18 at 08:37
  • @SvenHakvoort it worked, but I got another issue, because my url consists of channel->slug, so it must be an existing channel. For the time being I will just ask the user to provide the channel id. – Lidia Mokevnina Dec 07 '18 at 09:10
  • @SvenHakvoort Oh, my bad, I just had to set the default to one of the existing channels. It works now. – Lidia Mokevnina Dec 07 '18 at 09:12