Integrating Elasticsearch with Ruby on Rails
Diving into the world of Elasticsearch in your Rails app? It’s exciting but can be a little intimidating at first. Here are 10 things you should do to integrate Elasticsearch smoothly with your Ruby on Rails app (don’t worry—we’ll keep it light and fun!).
Schema from Blog Post Project used as example:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password_digest VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE posts (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE tags (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE post_tags (
post_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (post_id, tag_id),
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
);
CREATE TABLE comments (
id INT AUTO_INCREMENT PRIMARY KEY,
post_id INT NOT NULL,
user_id INT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
1. Add Elasticsearch Gems
First things first, you need the right gems. Add elasticsearch
and elasticsearch-model
gems to your Gemfile
. These two will be your best friends throughout the integration.
# Gemfile
gem 'elasticsearch'
gem 'elasticsearch-model'
Run bundle install
to get things rolling.
2. Install Elasticsearch (Obviously)
You’ll need Elasticsearch running somewhere. Use Docker to simplify this. If Docker’s not your jam, just download it locally. With Docker:
docker run -d -p 9200:9200 elasticsearch:7.17.0
Optional: Adjust the version to whatever’s hip right now.
3. Model Integration
Now, you’ll need to integrate Elasticsearch with your models. Let’s use Post
as an example:
class Post < ApplicationRecord
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
# Elasticsearch indexing config, if you need custom mapping
settings do
mappings dynamic: false do
indexes :title, analyzer: 'english'
indexes :content, analyzer: 'english'
indexes :tags, type: 'nested' do
indexes :name, analyzer: 'english'
end
end
end
belongs_to :user
has_many :comments, dependent: :destroy
has_many :post_tags, dependent: :destroy
has_many :tags, through: :post_tags
def as_indexed_json(options = {})
self.as_json(
include: { tags: { only: :name } }
)
end
end
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
end
class Tag < ApplicationRecord
has_many :post_tags, dependent: :destroy
has_many :posts, through: :post_tags
end
class PostTag < ApplicationRecord
belongs_to :post
belongs_to :tag
end
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
# Add bcrypt for password encryption
has_secure_password
end
This hooks up your Post
model to Elasticsearch—the Callbacks
module makes sure the data stays in sync whenever a post is created, updated, or deleted.
4. Create the Index
After setting up your model, you need to create the corresponding index in Elasticsearch. It’s like setting up a database table but for searching purposes:
Post.__elasticsearch__.create_index! force: true
Post.import # This will index all your existing posts
Tip: Do this in a Rails console, but don’t do it every day—you’ll overwhelm Elasticsearch.
5. Don’t Forget to Configure Elasticsearch Host
Add your Elasticsearch server host to the config. You can keep it simple in config/initializers/elasticsearch.rb
:
Elasticsearch::Model.client = Elasticsearch::Client.new host: 'http://localhost:9200'
Optional: Make this more dynamic by fetching the host from environment variables if deploying elsewhere.
6. Customize Search Method
Create a custom search method in your model to make things cleaner:
class Post < ApplicationRecord
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
fields: ['title^5', 'content', 'tags.name']
}
}
}
)
end
end
That’s it—searching should be painless now.
7. Handle Search Results in Controllers
Remember: In your controller, process the search results and render them appropriately:
class PostsController < ApplicationController
def index
if params[:query].present?
@posts = Post.search(params[:query]).records
else
@posts = Post.all
end
end
end
8. Data Sync is a Must
Remember: Elasticsearch won’t magically stay in sync with your MySQL database unless you tell it to. elasticsearch-model
helps, but if you’re bulk importing or manually changing data, always run Post.import
to re-index.
9. Consider Pagination with Kaminari
Using Kaminari? It works smoothly with Elasticsearch results, which is pretty cool. Example:
# In your Gemfile
gem 'kaminari'
# Run bundle install
bundle install
# In your controller
class PostsController < ApplicationController
def index
if params[:query].present?
@posts = Post.search(params[:query]).page(params[:page]).records
else
@posts = Post.page(params[:page])
end
end
end
Optional: You can also use will_paginate
if that’s your preference.
10. Handle Failures Gracefully
Elasticsearch sometimes throws fits—network issues, server down, you name it. Wrap your queries in begin...rescue
blocks:
begin
@results = Post.search('Elasticsearch')
rescue Faraday::ConnectionFailed
@results = []
flash[:alert] = "Search is temporarily unavailable. Please try again later."
end
Optional: Set up monitoring to make sure Elasticsearch is up.
Wrapping Up
And there you have it! These tips should make Elasticsearch feel less like a dark art and more like a powerful ally in your Rails journey. Take it step by step, and remember—it’s all about making your app’s search smooth and efficient.
Happy coding, and may your searches be fast and relevant!