Using Meilisearch full-text search engine with Rails

Meilisearch is “An open source, blazingly fast and hyper relevant search-engine”. In this article, I’ll show you how to integrate it with Rails app.


Download and install process described here:

There’s also a nice article about installing MeiliSearch in production and running it as a service:

Milisearch has a gem for Ruby apps, so first, you need to add to your Gemfile:

gem 'meilisearch', require: false

And run bundle install. Next, let’s create a file where we gonna keep our connection configuration. You don’t need a master key unless Meilisearch running in a production environment, so you can leave a key empty for your development env. Don’t forget to add this file to .gitignore


key: masterkey

Now we are ready to connect to our Meilisearch client, for example like this:

config = YAML.load_file('config/meilisearch.yml')
client =['url'], config['api_key'])

Creating modules

We want to do two basic things with our database objects: to index them and to search in indexes. So, we’ll need two modules: Searchable to mix class method that will allow us to search in the index, and Indexable to mix instance methods to model. Both modules will need methods to connect to a client so it’ll be handy to create the third module, MeiliSearchable, and include it in those two. Let’s start with it:

#/lib/modules/meili_searchable.rbrequire 'meilisearch'

module MeiliSearchable


def client
config = YAML.load_file('config/meilisearch.yml')

@client ||=['host'], config['key'])

def index
@index ||= client.index(index_name)
def index_name
if self.class.to_s == 'Class'

In this module, the client method is used to connect to MeiliSearch client, and the index method allows us to receive specific index for model on that we call our methods.The index_name method will return our object class name or our class name either module is included to class or class is extended with the module. For example, the Account class index name will be accounts. And for Account class instance it will be the same.

The Indexable module needs only one public method: add_to_index. We will add this method to after_commit callback in our model because add_documents method of MeiliSearch client updates the document if it already exists (it even has alias add_or_replace_documents). Note that there’s also a method update_documents with alias add_or_update_documents. The difference is that the second method won’t remove existing fields in the document if they were sent empty from this method.

module Indexable
include MeiliSearchable

def add_to_index

Note the to_indexed method. This method must be implemented in each indexable class and define how exactly our object will be stored in our index. We’ll return to this later.

Finally, we need a Searchable module to search through index

module Searchable
include MeiliSearchable

def search(text), limit: 1000)

Let’s continue with our Account example. First of all, we shall use our modules:

class Account < ActiveRecord::Base
include Indexable
extend Searchable

We also need to create a callback to add our accounts to index after create or update

after_commit :add_to_index

And, finally, to implement to_indexed method:

def to_indexed
{ id: "account#{id}", account_id: id, email: email }

Note that id must be unique through indexes, so we add the ‘account’ prefix to it.

Creating an index

Update: as for meilisearch 0.16, you don’t need to do this, an index will be created automatically after you push first document!

For now, we can automatically add our accounts to index, and to search through it. We only need to create an index to start it all working. Let’s do it with migration:

rails g migration create_meilisearch_accounts_index

And edit it like this:

require 'meilisearch'

class CreateMeilisearchAccountsIndex < ActiveRecord::Migration
include MeiliSearchable

def up

def down

That’s it! Now run the migration and have some fun

rails db:migrate

rails c

=> {“hits”=>[{“id”=>”account53120", “account_id”=>”53120", “email”=>””}], “offset”=>0, “limit”=>1000, “nbHits”=>1, “exhaustiveNbHits”=>false, “processingTimeMs”=>2, “query”=>”some”}

If you want to fetch objects from your databes, you can do like this:

ids ='some')['hits']&.map{|r| r['account_id']}

Account.where(id: ids)

Further improvements. Sidekiq

When you have a lot of documents, adding new documents to index might be slow. It’s a good idea to do in asynchronously, for example, with Sidekiq. I won’t show here how to install and configure it, it’s pretty easy to google. After you deal with it, we can create a worker

# app/workers/meili_search_worker.rbclass MeiliSearchWorker
include Sidekiq::Worker

def perform(class_name, id)

Add this method to our Indexable module

def async_add_to_index

and finally change our after_commit Account callback

after_commit :async_add_to_index

Final. Adding existing documents

For an existing app, we can use a simple rake task to add existing documents

namespace :meilisearch do
require 'meilisearch'

task :fill_indexes => [ :environment ] do
Account.find_in_batches(batch_size: 500) do |group|
add_batch(index, group)

def index
config = YAML.load_file('config/meilisearch.yml')

@index ||=['host'], config['key']).index('comments')

def add_batch(index, group)
documents =
puts "Got timeout, sleeping for 2"
sleep 2

Note that we use add_documents method here, that adds multiple documents to the index.

Thank you for reading this article, hope it was helpful! Please leave feedback if you have any questions or suggestions.




Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

7 DevOps adoption statistics for 2017 [Infographic]

State of Web Development 2022

Flutter Quick Tip — How To Set Text Background Color With Curve

Why you must know about web accessibility

Smart Home Security using IOT

CS373 Fall 2021: Week 11

10 Mac Apps For June

The essentials of regular expressions

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alexey Ivanov

Alexey Ivanov

More from Medium

How to access associated models in Sinatra with Active Record controller

Synchronous Messaging using RabbitMQ and Rails

All About Service Objects: Part 2 — Method Object Pattern

Accelerate your oAuth 2.0 client testing in your local environment.