Skip to content

Commit

Permalink
Performance optimisations (laravelio#761)
Browse files Browse the repository at this point in the history
* Migrate to timestamp

* Update last active on save

* Update tests

* Load only required models

* Apply fixes from StyleCI

* Rename column

* Apply fixes from StyleCI
  • Loading branch information
joedixon authored Nov 24, 2021
1 parent 9a8fbd1 commit bbe75ca
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 23 deletions.
1 change: 1 addition & 0 deletions app/Jobs/CreateThread.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public function handle(): Thread
'subject' => $this->subject,
'body' => $this->body,
'slug' => $this->subject,
'last_activity_at' => now(),
]);
$thread->authoredBy($this->author);
$thread->syncTags($this->tags);
Expand Down
17 changes: 17 additions & 0 deletions app/Listeners/MarkLastActivity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace App\Listeners;

use App\Events\ReplyWasCreated;

final class MarkLastActivity
{
public function handle(ReplyWasCreated $event): void
{
$replyAble = $event->reply->replyAble();
$replyAble->last_activity_at = now();
$replyAble->save();
}
}
27 changes: 10 additions & 17 deletions app/Models/Thread.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ final class Thread extends Model implements ReplyAble, SubscriptionAble, Feedabl
'updatedByRelation',
];

/**
* {@inheritdoc}
*/
protected $dates = [
'last_activity_at',
];

public function id(): int
{
return $this->id;
Expand Down Expand Up @@ -227,26 +234,12 @@ public static function feedByTagQuery(Tag $tag): Builder
*/
public static function feedQuery(): Builder
{
return static::with([
'solutionReplyRelation',
'likesRelation',
'repliesRelation',
'repliesRelation.authorRelation',
return static::withOnly([
'tagsRelation',
'authorRelation',
])
->leftJoin('replies', function ($join) {
$join->on('threads.id', 'replies.replyable_id')
->where('replies.replyable_type', static::TABLE);
})
->orderBy('latest_creation', 'DESC')
->groupBy('threads.id')
->select('threads.*', DB::raw('
CASE WHEN COALESCE(MAX(replies.created_at), 0) > threads.created_at
THEN COALESCE(MAX(replies.created_at), 0)
ELSE threads.created_at
END AS latest_creation
'));
->withCount(['repliesRelation as reply_count', 'likesRelation as like_count'])
->latest('last_activity_at');
}

/**
Expand Down
2 changes: 2 additions & 0 deletions app/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Events\ArticleWasApproved;
use App\Events\ReplyWasCreated;
use App\Listeners\MarkLastActivity;
use App\Listeners\SendArticleApprovedNotification;
use App\Listeners\SendNewReplyNotification;
use App\Listeners\StoreTweetIdentifier;
Expand All @@ -20,6 +21,7 @@ class EventServiceProvider extends ServiceProvider
protected $listen = [
ReplyWasCreated::class => [
SendNewReplyNotification::class,
MarkLastActivity::class,
],
ArticleWasApproved::class => [
SendArticleApprovedNotification::class,
Expand Down
3 changes: 2 additions & 1 deletion database/factories/ReplyFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ public function definition(): array
return [
'body' => $this->faker->text(),
'author_id' => User::factory(),
'replyable_id' => Thread::factory(),
'replyable_id' => Thread::factory(),
'replyable_type' => Thread::TABLE,
'created_at' => $this->faker->dateTimeBetween('-6 months'),
];
}
}
1 change: 1 addition & 0 deletions database/factories/ThreadFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public function definition(): array
'body' => $this->faker->text,
'slug' => $this->faker->unique()->slug,
'author_id' => $attributes['author_id'] ?? User::factory(),
'created_at' => $this->faker->dateTimeBetween('-6 months'),
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use App\Models\Thread;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class MigrateThreadFeedToTimestamp extends Migration
{
public function up()
{
Schema::table('threads', function (Blueprint $table) {
$table->timestamp('last_activity_at')
->after('slug')
->nullable()
->index();
});

// Iterate over all the threads and update the `last_active_at` timestamp to
// the latest reply `created_at` or thread `created_at` if no reply exists.
if (! app()->runningUnitTests()) {
Thread::with(['repliesRelation' => function ($query) {
$query->latest();
}])
->lazy()
->each(function ($thread) {
$thread->update([
'last_activity_at' => ($reply = $thread->replies()->first()) ? $reply->created_at : $thread->created_at,
]);
});
}
}
}
4 changes: 2 additions & 2 deletions resources/views/components/threads/overview-summary.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@
<div class="flex gap-x-5">
<span class="flex items-center gap-x-2">
<x-heroicon-o-thumb-up class="w-6 h-6" />
<span>{{ count($thread->likes()) }}</span>
<span>{{ $thread->like_count }}</span>
<span class="sr-only">Likes</span>
</span>

<span class="flex items-center gap-x-2">
<x-heroicon-o-chat-alt-2 class="w-6 h-6" />
<span>{{ count($thread->replies()) }}</span>
<span>{{ $thread->reply_count }}</span>
<span class="sr-only">Replies</span>
</span>
</div>
Expand Down
14 changes: 14 additions & 0 deletions tests/Feature/ReplyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,17 @@
'You\'ll need to verify your account before participating in this thread.',
);
});

test('replyable activity is updated when reply is created', function () {
$thread = Thread::factory()->create(['subject' => 'The first thread', 'slug' => 'the-first-thread']);

$this->login();

$this->post('/replies', [
'body' => 'The first reply',
'replyable_id' => $thread->id,
'replyable_type' => Thread::TABLE,
]);

$this->assertNotNull($thread->fresh()->last_activity_at);
});
20 changes: 17 additions & 3 deletions tests/Integration/Models/ThreadTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use App\Jobs\CreateReply;
use App\Models\Reply;
use App\Models\Thread;
use App\Models\User;
Expand Down Expand Up @@ -81,6 +82,19 @@
$this->assertTrue($threadFromTwoDaysAgo->is($threads->last()), 'Last thread is incorrect');
});

it('bumps threads when a reply is added', function () {
$threadUpdatedYesterday = createThreadFromYesterday();
$threadFromToday = createThreadFromToday();
$threadFromTwoDaysAgo = createThreadFromTwoDaysAgo();
dispatch_sync(new CreateReply('Hello world', User::factory()->create(), $threadFromTwoDaysAgo));

$threads = Thread::feed();

$this->assertTrue($threadFromTwoDaysAgo->is($threads->first()), 'First thread is incorrect');
$this->assertTrue($threadFromToday->is($threads->slice(1)->first()), 'Second thread is incorrect');
$this->assertTrue($threadUpdatedYesterday->is($threads->last()), 'Last thread is incorrect');
});

it('can retrieve only resolved threads', function () {
createThreadFromToday();
$resolvedThread = createResolvedThread();
Expand Down Expand Up @@ -126,21 +140,21 @@ function createThreadFromToday(): Thread
{
$today = Carbon::now();

return Thread::factory()->create(['created_at' => $today]);
return Thread::factory()->create(['created_at' => $today, 'last_activity_at' => $today]);
}

function createThreadFromYesterday(): Thread
{
$yesterday = Carbon::yesterday();

return Thread::factory()->create(['created_at' => $yesterday]);
return Thread::factory()->create(['created_at' => $yesterday, 'last_activity_at' => $yesterday]);
}

function createThreadFromTwoDaysAgo(): Thread
{
$twoDaysAgo = Carbon::now()->subDay(2);

return Thread::factory()->create(['created_at' => $twoDaysAgo]);
return Thread::factory()->create(['created_at' => $twoDaysAgo, 'last_activity_at' => $twoDaysAgo]);
}

function createResolvedThread()
Expand Down

0 comments on commit bbe75ca

Please sign in to comment.