39.筛选零回复的话题

未匹配的标注

本节说明

  • 对应视频教程第 39 小节:A User Can Filter By Unanswered Threads

本节内容

上一节的最后我们运行了测试,但是有失败的测试我们没有修复。我们现在进行修复:
file
首先修复an_authenticated_user_may_participate_in_forum_threads
forum\tests\Feature\ParticipateInForumTest.php

.
.
/** @test */
    function an_authenticated_user_may_participate_in_forum_threads()
    {
        // Given we have a authenticated user
        $this->signIn();
        // And an existing thread
        $thread = create('App\Thread');

        // When the user adds a reply to the thread
        $reply = make('App\Reply');
        $this->post($thread->path() .'/replies',$reply->toArray());

        // Then their reply should be visible on the page
//        $this->get($thread->path())
//            ->assertSee($reply->body);

        $this->assertDatabaseHas('replies',['body' => $reply->body]);
    }
    .
    .

我们用Ajax的方式提交回复,所以我们的测试需要进行如上的修改。再次运行测试:
file
接着修复a_user_can_read_replies_that_are_associated_with_a_thread。由于我们建立了a_user_can_request_all_replies_for_a_given_thread测试,所以实际上a_user_can_read_replies_that_are_associated_with_a_thread测试我们已经不需要了,删除即可。再次运行测试:
file
wo我们在上一节定义的每页回复数是 1 ,而现在每页回复数是 20。所以我们需要进行修改:
forum\tests\Feature\ReadThreadsTest.php

    .
    .
    /** @test */
    public function a_user_can_request_all_replies_for_a_given_thread()
    {
        $thread = create('App\Thread');
        create('App\Reply',['thread_id' => $thread->id],40);

        $response = $this->getJson($thread->path() . '/replies')->json();

        $this->assertCount(20,$response['data']);
        $this->assertEquals(40,$response['total']);
    }
}

我们生成了 40 个回复,并且测试返回的首页数据是 20 个,且总数是40个。现在我们再次运行测试:
file
由此我们想到一个问题,现在我们每页的回复数是 20 个,我们想让用户点击翻页后聚焦到回复区域的最上方,给用户良好的使用体验:
forum\resources\assets\js\components\Replies.vue

.
.
refresh({data}) {
    this.dataSet = data;
    this.items = data.data;

    window.scrollTo(0,0);
}
.
.

现在正式开始本节的内容:刷选零回复的话题。首先添加入口:
forum\resources\views\layouts\nav.blade.php

.
.
<ul class="dropdown-menu">
    <li><a href="/threads">ALL Threads</a> </li>

    @if(auth()->check())
        <li><a href="/threads?by={{ auth()->user()->name }}">My Threads</a> </li>
    @endif

    <li><a href="/threads?popularity=1">Popular Threads</a> </li>
    <li><a href="/threads?unanswered=1">Unanswered Threads</a> </li>
</ul>
.
.

接下来按照惯例新建测试:
forum\tests\Feature\ReadThreadsTest.php

    .
    .
    /** @test */
    public function a_user_can_filter_threads_by_those_that_are_unanswered()
    {
        $thread = create('App\Thread');
        create('App\Reply',['thread_id' => $thread->id]);

        $response = $this->getJson('threads?unanswered=1')->json();

        $this->assertCount(1,$response);
    }

    /** @test */
    public function a_user_can_request_all_replies_for_a_given_thread()
    .
    .

注:在我们的模型工厂文件中,当我们create一个reply时,也会新建一个thread,所以我们实际上会有两条话题,一个有回复,一个没有回复。所以我们期望筛选得到的没有回复的话题数量是 1.。

得益于我们之前的工作,我们现在想要新增一种筛选条件,只需要很少的代码就能实现:
forum\app\Filters\ThreadsFilters.php

.
.
class ThreadsFilters extends Filters
{
    protected $filters = ['by','popularity','unanswered'];
    .
    .
    public function unanswered()
    {
        return $this->builder->where('replies_count',0);
    }
}

运行测试:
file
但是现在我们有一个新的想法:将replies_count加到threads表结构当中。因为我们在很多地方都用到replies_count,并且我们在Thread.php模型中使用了全局作用域预先进行加载replies_count属性。首先我们去掉全局作用域:
forum\app\Thread.php

    .
    .
    protected static function boot()
    {
        parent::boot();

        static::deleting(function ($thread) {
            $thread->replies->each->delete();
        });
    }
    .
    .

接下来我们修改迁移文件:
forum\database\migrations\2018_04_27_023037_create_threads_table.php

<?php

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

class CreateThreadsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('threads', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id');
            $table->unsignedInteger('channel_id');
            $table->unsignedInteger('replies_count')->default(0);
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

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

运行迁移:

$ php artisan migrate:refresh

我们还需要重新填充数据。进入Tinker

$ php artisan tinker

填充数据:

>>> factory('App\Thread',30)->create();

注意,我们修改了数据库表结构,相应的测试也需要进行修改:
forum\tests\Feature\ParticipateInForumTest.php

    .
    .
    /** @test */
    function an_authenticated_user_may_participate_in_forum_threads()
    {
        // Given we have a authenticated user
        $this->signIn();
        // And an existing thread
        $thread = create('App\Thread');

        // When the user adds a reply to the thread
        $reply = make('App\Reply');
        $this->post($thread->path() .'/replies',$reply->toArray());

        // Then their reply should be visible on the page
//        $this->get($thread->path())
//            ->assertSee($reply->body);

        $this->assertDatabaseHas('replies',['body' => $reply->body]);
        $this->assertEquals(1,$thread->fresh()->replies_count);
    }
    .
    .

运行测试会失败,因为我们还没有添加回复时replies_count的处理逻辑:
file
模型事件可以轻松地帮助我们完成处理逻辑:
forum\app\Reply.php

.
.
class Reply extends Model
{
    use Favoritable,RecordsActivity;

    protected $guarded = [];
    protected $with = ['owner','favorites'];
    protected $appends = ['favoritesCount','isFavorited'];

    protected static function boot()
    {
        parent::boot(); //

        static::created(function ($reply){
           $reply->thread->increment('replies_count');
        });

        static::deleted(function ($reply){
            $reply->thread->decrement('replies_count');
        });
    }
    .
    .

再次运行测试:
file
还有一个测试需要修改:
forum\tests\Feature\ParticipateInForumTest.php

    .
    .
    /** @test */
    public function authorized_users_can_delete_replies()
    {
        $this->signIn();

        $reply = create('App\Reply',['user_id' => auth()->id()]);

        $this->delete("/replies/{$reply->id}")->assertStatus(302);

        $this->assertDatabaseMissing('replies',['id' => $reply->id]);
        $this->assertEquals(0,$reply->thread->fresh()->replies_count);
    }
    .
    .

运行测试:
file
现在我们回到测试a_user_can_filter_threads_by_those_that_are_unanswered当中来:
file
现在我们填充回复数据,然后在应用中进行测试。我们先进入Tinker

$ php artisan tinker

填充数据:

>>> factory('App\Reply',30)->create(['thread_id' => App\Thread::latest()->first()->id]);

进行测试:
file
最后运行全部测试:
file

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~