How To Implement Elasticsearch in Spree: Step-by-Step Guide

JetRuby Agency
JetRuby Agency
Published in
6 min readFeb 10, 2017

--

Powerful, flexible and more importantly reliable search is a must for an online store to have, especially when large amounts of data are involved. Why? The easier and faster customers are able to find products they are looking for, the more they are likely to buy!

Compared to the default Spree Commerce “ransack” gem, Elasticsearch has a lot more to offer right out of the box.

  • Searching for items on the website is noticeably faster. What is more important, this doesn’t impact server workload which means lags won’t be the case.
  • Elasticsearch can easily manage large amounts of data. This comes in very handy when expanding business.
  • Smart ranking of search results.
  • Powerful data aggregation.
  • Elasticsearch allows for gathering statistics based on search results. Such statistics can be used to analyze customer needs and improve buying experience.
  • Flexible filtering by categories/price range. Applying a category or any other filter will output the list of products you want.
  • Autocomplete. You don’t have to type a whole phrase or word since Elasticsearch can guess your query just by its first letters.

In addition to that, Elasticsearch has a lot of other features in store to make your website even better.
In this article, you will learn how to implement Elasticsearch in Spree Commerce and even more. Fasten your seatbelts and here we go!

Installation

1.1. Add the gemfile into the project

Run bundle and setup generator:

1.2. elasticsearch.yml

Edit Elasticsearch configuration in config/elasticsearch.yml. Example:

1.3. Searcher class

Here you specify that the class responsible for searching has been changed:

1.4. Add callbacks to the model

Since both searching and filtering is by products, you need to create “Spree::Product decorator”. Next you need to add Elasticsearch callbacks into it.

After that, you will see something like this:

Usage

2.1. How to make a query to Elasticsearch

First, you need to create an instance of a searcher using the form parameters:

To get the results use the following command:

2.2. Index creation/reindexing

The command that initiates re-indexing:

The gem will provide you with rake-task to create/recreate index:

2.3. Facets

Along with products, “spree_elasticsearch” returns facets as well. Facets are summarized and generic data about search results. With the help of the gem, you can access the following information right out of the box:

  • min/max/avg product price + the sum of all products prices;
  • taxons ids to which search results belong and the amount of products per each taxon;
  • product_properties of the results found and the amount of products for each value.

This information may come in handy when using filters on the category page:

elasticsearch and spree
The amount of products displayed has changed after applying a filter

NB! Starting from Elasticsearch 1.0, facets have been replaced with Aggregations. You can read how to migrate from Facets to Aggregations in this article’s section “From Facets to Aggregations”.

Configuring “spree_elasticsearch” for your site

3.1. Mappings

Mappings allow you to specify how exactly both the document and its fields will be stored and indexed. Full-text string fields, numeric fields, prices or dates, etc. You can configure mappings in “Spree::Product decorator”.
Here’s an example of customized mappings in a Spree application:

Now let’s make a quick stop and analyze this piece of code. Elasticsearch has returned the results from the index, so it was necessary to specify the products to be analyzed while indexing (“index_analyzer”) and searching (“search_analyzer”) in the first line.

In most cases, you should use the same analyzer for both indexing and searching. Though, if you use the NGram analyzer for indexing, you need to choose some other analyzer for searching. E.g. “whitespace” that divides the search query in whitespaces. You can read more about it here and here.

In the last piece of code, the product name is being indexed using the “multi_field” type. You can learn more here and here.

3.2. Customizing the search query

To change the search query you need to rewrite the “to_hash” method in “Spree::Product decorator”. Basically, it sends a query to Elasticsearch based on the received attributes (query/price_min/price_max/properties/taxons/sorting). After that, the “retrieve_products” method sends a query to Elasticsearch. Wherein, “retrieve_products” is located in the searcher class: “Spree::Search::Elasticsearch”.

As “spree_elasticsearch” uses standard Elasticsearch Query DSL, you can rewrite the search query however you want. Here is some more detailed information about Query DSL and making queries.

This is how the “to_hash” method looks like out of the box:

3.3. Customizing index creation

As we’ve mentioned above, you can can use the following rake task “rake spree_elasticsearch:load_products” for index creation or recreation. What it does is basically:

  • deletes the existing index;
  • creates a new index using the specified analyzers and filters as well as mappings from the Product model;
  • imports products to the index.

If you want to change analyzers or other parameters of a rake task, we recommend moving its logic to a separate service and changing it from there. Furthermore, it’ll be easier to call index recreation throughout the project. Here is an example of a customized task in a service:

Common issues

4.1. From Facets to Aggregations

Facets that were used in “spree_elasticsearch” were removed in Elasticsearch 1.0. So if you use Elasticsearch version 1.0 or higher, keep in mind that “spree_elasticsearch” just won’t work.

Being active open source contributors we suggested moving to Elasticsearch version 2.x and using aggregations instead of facets. The idea was highly marked by other developers.

The notion above also applies to Spree 3.x. To move from Facets to Aggregations you need to:

1) Replace the following code in “Spree::Product decorator”:

with this

2) Switch to aggregations in the views, helpers or other methods that use facets. Keep in mind that aggregations structure has no “terms”. Instead, it has “buckets”.

Aggregation example:

4.2. How to add custom field to the index

Adding the “in_stock” field enables you to show products that are in stock only. To do that, follow the steps below:

Add a checkbox into the form:

Update the searcher class:

Edit “Spree::Product decorator.
Add “in_stock” to the mappings:

Edit the “as_indexed_json” method:

Add the “in_stock?” instance method:

Configure the Elasticsearch query:

4.3. Autocomplete in Elasticsearch

But default, “spree_elasticsearch” mappings aren’t the best choice for autocomplete since they output a full match, e.g. if you search for “Phillips” you will get the following results: if “Phi”, “Phil” or “Phillip”. To change this, we’ve modified the mappings and the index creation.
Mappings:

Here, the default analyzers have been changed and the partial search for “:name” has been added. You can read more about mappings in the “Mappings” section.
Index creation:

At this point, you need to use the same analyzers as in the mappings. Next, change the “edgeNGram_filter” options depending on how the search should interact with tokens.
“edgeNGram” is a token filter of the “edgeNGram” type. It defines that the search will start from the first letters of the word. E.g. if you search for “Pin“ you will get “Pins”, not “Alpine” or like that. “max_gram” and “min_gram” options define the sizes of the single n-gram. You can find more info about it here.

Summing up

Aren’t there are any pitfalls? Well, the answer is more “yes” than “no”, and here is why.

We’ve been using the Spree Commerce and Elasticsearch duo to develop commercial projects for a couple of years now, and after all this time we still can’t understand why the guys from Spree Commerce haven’t switched to the fast and powerful Elasticsearch as a default search solution yet. Doing this would spare a lot of time that many developers including us have to waste on removing slow and useless ransack.

Further, Elasticsearch integration may sometimes be a little bit tricky in the sense that if the ecommerce website you’re going to integrate it with is using some of the old Spree versions, there is no going without some “magic”. However, this isn’t too much of a problem if you have enough experience to handle it.

Hope this long piece of reading on how to implement Elasticsearch in Spree Commerce-based websites was useful. Should you have any questions, just leave a comment below or ask our developers here. Whether you’re an enthusiast or a business owner we’re always glad to help.

--

--

JetRuby is a Digital Agency that doesn’t stop moving. We expound on subjects as varied as developing a mobile app and through to disruptive technologies.