|

Sorting Threads By Number Of Replies

Sorting Threads By Number Of Replies

This tutorial will find us tackling a few different topics, however the main goal is to be able to set up sorting threads by popularity according to the number of replies each thread has. To start off, we’ll need to make some changes to the main view which displays all threads. Once we get that sorted to our liking, we can begin fleshing out the test that will be required for our new feature. We’ll then write the test, implement the code, and make sure everything works in the browser as well.


Using Basic CSS To Position A UI Element

We saw in the last tutorial how to display the number of replies to any given thread on the show.blade.php view. That also gave us a nice introduction to global query scopes. Now, it would be nice to also list the number of thread replies on the home page too. In other words, when the index.blade.php view is being shown to display all threads, we should show the reply count for each thread. So first off, let’s just look at the code that displays each individual thread on the main page. It is highlighted below inside the <article> html tags.

Our goal is to put the reply count for each thread on the right hand side of each article box. How can we accomplish this goal? Here is just a few additions and modifications to the index view to get started. We add a div with a css class of ‘level’ so we can target that for styling in a bit. The <h4> element also receives a new class named ‘flex’ we will set up rules for shortly. Directly after the <h4> element is where we add the code to display the number of thread replies. Finally, we did add a horizontal <hr> element after the <article> html tag to better separate each individual thread.

While our threads are quite silly, the layout is looking pretty decent. Even at this early stage!
basic ui positioning on index page

Now we can focus on positioning that reply count that is being displayed. All we will do is add some really basic css rules right in the app.blade.php file for now. Below you will see we added the basic styling in the <style> tag that we need.

Coming along nicely!
css style pushing element to right no float


Linking The Replies Count

Here we wrap the <strong> tag which contains the thread replies count with an <a> tag where the href is set to the value of {{ $thread->path() }}. This will create a link to the show.blade.php view which will display that particular thread with all of it’s replies.

wrapping strong tag with anchor tag


Sorting Threads By Number Of Replies

Now that the display of the thread counts on the main page is working nicely, it’s time to add a way to filter or sort the threads based on the number of replies each thread has. We want to always include a test for the new features we add. In this scenario, we are dealing with how the user is to read threads. It makes sense to open up our ReadThreadsTest class and start concocting a test for this feature. Here are all of our tests so far in this class, and the stubbed out method for our new test we are creating now which is called test_a_user_can_filter_threads_by_number_of_replies().


Brainstorming The Pseudocode

Before we can write the test, we have to think about how to create it. In other words, what is the Given – When – Then sequence of events we are looking at here.

  • Given there are 3 threads
  • Given there are 2, 3, and 0 replies on said threads
  • When the user filters all threads by count
  • Then the threads should be returned in order of most replies to least

This scenario translates to this code in the test_a_user_can_filter_threads_by_number_of_replies() method. Note that in the assertion method there is a call to array_column. Check it out in the PHP manual if you need a refresher like I did.

Ok time to give this test a run and see how we are doing.

vagrant@homestead:~/Code/forumio$ phpunit --filter test_a_user_can_filter_threads_by_number_of_replies
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 1.51 seconds, Memory: 10.00MB

There was 1 failure:

1) Tests\Feature\ReadThreadsTest::test_a_user_can_filter_threads_by_number_of_replies
Invalid JSON was returned from the route.

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:513
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:527
/home/vagrant/Code/forumio/tests/Feature/ReadThreadsTest.php:73

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

We are seeing an error of Invalid JSON was returned from the route. Why is this happening? Well we never did set up any kind of ability for any of the controller methods to return JSON as a response. They are returning full pages of HTML at this point. There is a way to conditionally return JSON however by way of the wantsJson() method. To allow for this, you can update the index() method in ThreadsController.php to use this ‘if’ conditional.

The index() method now has the ability to return json if it is requested. Our test is making a request for json, so it should now work. We can run the test_a_user_can_filter_threads_by_number_of_replies test one more time now.
vagrant@homestead:~/Code/forumio$ phpunit –filter test_a_user_can_filter_threads_by_number_of_replies

PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 1.2 seconds, Memory: 8.00MB

There was 1 failure:

1) Tests\Feature\ReadThreadsTest::test_a_user_can_filter_threads_by_number_of_replies
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
-    0 => 3
-    1 => 2
-    2 => 0
+    0 => '0'
+    1 => '2'
+    2 => '3'

/home/vagrant/Code/forumio/tests/Feature/ReadThreadsTest.php:74

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Well this is kind of neat. Check out the array that was plucked out of the json, pretty cool. Note the response explicitly shows us what is expected, and what actually happened. We expect order of 3,2,0 however we got order of 0,2,3. We need to be able to filter the query by popularity, which we do not have the ability of doing yet.


Building On The ThreadFilters Class

Recently, we took a long time learning how to refactor sql queries into a dedicated class. This is how we came up with the ThreadFilters class which as you may recall, currently is able to filter by user name. Well guess what. Since you did all that legwork already, you can add many more types of filtering abilities to that class which will now be pretty easy since the hard work is already done. The additions we make to the class hare highlighted here, then we will discuss.

The first thing we did was to add a ‘popular’ key to the $filters array. After that, we added a corresponding popular() method to the class. This is an interesting method actually. At first glance, you would think we could just get away with the $this->builder->orderBy(‘replies_count’, ‘desc’); all by itself. Why is there this $this->builder->getQuery()->orders = []; code present? Well, in the index() method of the ThreadsController, a call is being made to getThreads(). This is a method we created during refactoring. Let’s look at that method real quick.

What we would like to point out here is that this method is already building up the query for us. The latest() method is in fact setting the default ‘order by’ to order by the created_at field in the database. What we want in our popular() filter is to ‘order by’ the replies_count. In order for this to work, we have to clear out any existing ‘order by’ clauses in the existing query. To do this, you use $this->builder->getQuery()->orders = []; like we did in the popular() method we created above.

We can now run our test.

vagrant@homestead:~/Code/forumio$ phpunit --filter test_a_user_can_filter_threads_by_number_of_replies
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 1.07 seconds, Memory: 8.00MB

OK (1 test, 1 assertion)

It is working!

For good measure, we can run the full suite of tests again, and find that everything is passing. Wonderful.

vagrant@homestead:~/Code/forumio$ phpunit
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

......................                                            22 / 22 (100%)

Time: 3.1 seconds, Memory: 12.00MB

OK (22 tests, 38 assertions)

Adding A Link For The Popular Filter

The last thing we will need to do, is to ensure that a user can actually trigger this filter from the browser. The easiest way to do that, is to simply provide a link which includes the popular filter in the query string. This is very easy of course, we simply open up app.blade.php and add a link to one of the dropdown menus.

We are now able to filter by all threads, popular threads, and my threads right from the navigation bar. Fantastic.
click link to apply filter to query string


Sorting Threads By Number Of Replies Summary

It looks like we got this sorting business sorted out! Thanks to the earlier work we had done on setting up that dedicated thread filtering class, adding a new type of filter for the threads was pretty easy to do.

|