Homepage

Ruby: Count Articles by Category [Elastic Search]

Have you ever wanted to count how many articles will be left in a certain category after search? To give you an idea of what I mean, as an example let’s use the application that will be the outcome of this tutorial. Our database contains music albums which are categorized by the genre and subgenre.

Have you ever wanted to count how many articles will be left in a certain category after search? To give you an idea of what I mean, as an example let’s use the application that will be the outcome of this tutorial. Our database contains music albums which are categorized by the genre and subgenre.

After searching for, let’s say ‘Miles’ the album count in a category will change according to how many articles (albums in this case) meet the search requirement.

My solution utilizes Ruby on Rails elastic aggregations and Drapper. The best thing about it is that it doesn’t make additional requests to the database. Elastic makes the searching process much easier and faster. A solution on the basis of a database is much more time-consuming to develop and heavier on the server than mine.

The only downside I can think of is that you need to create an additional service.

Steps

I'll skip steps taken to create the app and to add some layout. I'll use controller articles with action index for listing and search.

Install prerequisites:

  • Elastic search or use docker image

  • Add these gems to the Gemfile:

    • gem 'elasticsearch-model'

    • gem 'elasticsearch-rails'

    • gem 'draper'

  • Github: 'drapergem/draper'

  • Bundle install

  • Add elastic initializer to point elastic host

tsx

Create models:

tsx

Create a decorator for the Category:

tsx

Add attr_accessor :article_count to the decorator of the class.

Create collection decorator

Add an apply_counts method with a buckets_counts argument.

tsx

Later this will allow to set the article_count based on aggregations.

Add module Searchable to concerns

tsx

The Article model

Include Searchable:

Add associations:

tsx

Delegate author_name:

tsx

In the models folder create Articles::Index module to define the elastic index:

tsx

type: :text is for thetext search.

indexes :category_ids and type: :integer will allow to aggregate the results by category.

Include the Articles::Index in Article model.

Seed data

I've prepared the seed with some jazz albums with assigned jazz sub-genres.

tsx

The last two lines in the seed create the index in elastic, so:

tsx

Now we can take care of searching and aggregates.

Oh look! We're halfway through the post! Here's a picture of a cute kitten:

Add a simple class for the search form:

tsx

Add search query object:

tsx

Search with elastic could be as simple as Article.search(search_text).records.to_a but we can ask elastic to count something for us in one go.

To do this we will need a little bit more complex query which we'll prepare using elastic DSL and put as an argument to the search method.

All methods beneath are private.

Search definition object will do almost the same thing as the search above.

tsx

Attributes :size, :from are for paging, default elastic page size is 10.

How to add aggregations? Using elastic DSL allows us to define aggs criteria:

tsx

And add them to search definition object:

tsx

Result of our query object at the end should look like a.e.

tsx

When we assign a search object to a search variable as search = Article.search(query) then we check search.response on search object which should look like this:

tsx

In aggregations => by_categories we can find buckets and that's what we're interested in! Key buckets contain counts for category_ids.

Extract them:

tsx

Map categories_ids:

tsx

Prepare the bucket hash:

tsx

Find categories, decorate the collection and apply counts:

tsx

Update public method call:

tsx

We will return object with categories and articles.

Last step is to add some logic to ArticlesController

tsx

Working app

That's all, you can check working example downloading repo:

  • Clone or download repo

  • Install docker if needed

  • Run

tsx

Let’s Create a Great Website Together

We'll shape your web platform the way you win it!

More posts in this category