Rails elasticsearch

March 20, 2020

Install Elasticsearch

brew install elasticsearch

Run

elasticsearch

Test

Open http://localhost:9200/ to make sure healthy.

Add To ./Gemfile

gem 'elasticsearch-model'

Install gem dependencies

bundle install

Configure

# config/initializers/elasticsearch.rb
Elasticsearch::Model.client = Elasticsearch::Client.new({
  log: true
})

Apply to Model

require 'elasticsearch/model'

class User < ApplicationRecord
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
end

Play with it

Open bin/rails c

User.__elasticsearch__.create_index!(force: true) # do this before indexing in seed
User.create(email: 'ex@ample.com', password: 'password', name: 'kevin')
User.__elasticsearch__.search('kevin').results.total

Custom index

Lets say you wanted to add more the waht is being indexed in eleasticsearch.

require 'elasticsearch/model'

class Example < ApplicationRecord
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks

  belongs_to :user

  def as_indexed_json(options = {})
    self.as_json(
      only: [:id, :title],
      include: {
        user: {
          only: [:id, :email]
        }
      }
    )
  end
end

Since Example has user index in it, make sure you update all examples when user is updated by doing the following:

class User < ApplicationRecord
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks

  has_many :examples

  after_save :index_examples_in_elasticsearch


  def index_examples_in_elasticsearch
    examples.find_each { |example| example.__elasticsearch__.index_document }
  end
end
Example.__elasticsearch__.create_index!(force: true) # do this before indexing in seed
Example.create(title: 'Example', archive: false, user: User.last)

Scan all fields

User.__elasticsearch__.search('kevin').records

Scan selected fields

User.__elasticsearch__.search(
  query: {
    multi_match: {
      query: 'kevin',
      fields: %w[name]
    }
  }
).records

Starts with

User.__elasticsearch__.search('kev*').records.total

Pagination

When you add kaminari or will_paginate make sure it is above elasticsearch-model

# Gemfile
gem 'kaminari'
gem 'elasticsearch-model'
User.__elasticsearch__.search('kev*').page(1).records

Use in a controller

class BooksController < ApplicationController
  def index
    if query
      response = User.__elasticsearch__.search(query).page(page).results

      # recomend using jbuilder instead of building json in controller
      render json: {
        results: response.results,
        total: response.total
      }
      return
    end

    render json: {
      results: User.page(page),
      total: User.count
    }
  end

  private

  def query
    params.fetch(:query)
  end

  def page
    param.fetch(:page, 1)
  end
end

This works create in a dev enviroment, but eleaticsearch needs to be secure to work in production.

Use AWS Elasticsearch

First you need to install a new gem.

# Gemfile
gem 'faraday_middleware-aws-signers-v4'

Configure

# config/initializers/elasticsearch.rb
if Rails.env.production?
  require 'faraday_middleware/aws_signers_v4'

  Elasticsearch::Model.client = Elasticsearch::Client.new({
    log: true
  }) do |f|
    f.request(
      :aws_signers_v4,
      credentials: Aws::Credentials.new(ENV.fetch('AWS_ACCESS_KEY'), ENV.fetch('AWS_SECRET_ACCESS_KEY')),
      service_name: 'es',
      region: ENV.fetch('AWS_REGION')
    )
  end
else
  Elasticsearch::Model.client = Elasticsearch::Client.new({
    log: true
  })
end

Search