Project

indexers

0.0
No commit activity in last 3 years
No release in over 3 years
Dsl to delegate searches to elasticsearch in rails.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.2
~> 0.21

Runtime

~> 5.1
 Project Readme

Gem Version Code Climate Build Status Dependency Status

Indexers

Dsl to delegate searches to elasticsearch in rails.

Why

We did this gem to:

  • Gain control of the queries without losing simplicity.
  • Have out of the box integration with activerecord and pagers.
  • Deal with the just in time nature of elasticsearch.
  • Integrate activerecord includes on it.
  • Have a convention of how to use suggestions.

Install

Put this line in your Gemfile:

gem 'indexers'

Then bundle:

$ bundle

To install Redis you can use homebrew:

$ brew install elasticsearch

NOTE: This gem is tested agains version 5.6.

Configuration

Generate the configuration file:

$ bundle exec rails g indexers:install

Set the global settings:

Indexers.configure do |config|

  config.properties = {
    category: { type: 'string' },
    shop_id: { type: 'long' },
    price: { type: 'long' },
    position: { type: 'long' },
    currency: { type: 'string' },
    name: {
      type: 'string',
      fields: {
        raw: {
          type: 'string',
          index: 'not_analyzed'
        }
      }
    },
    product_suggestion: {
      type: 'completion',
      analyzer: 'simple',
      contexts: {
        name: 'shop_id',
        type: 'category'
      }
    }
  }

  config.settings = {
    analysis: {
      filter: {
        ngram: {
          type: 'nGram',
          min_gram: 2,
          max_gram: 20
        }
      }
    }
  }

  config.suggestion :product do |term, options={}|
    prefix = (term || '')
    contexts = {}
    if shop = options[:shop]
      contexts[:shop_id] = (shop.id.to_s || 'all')
    end
    { prefix: prefix, completion: { field: 'product_suggestion', contexts: contexts } }
  end

  config.computed_sort :price do |direction|
    inline = <<~CODE
      if (params['_source']['currency'] == 'UYU') {
        doc['price'].value * 30
      }
    CODE
    { type: 'number', script: { source: inline }, order: direction }
  end

end

NOTE: You may want to personalize the generated config/elasticsearch.yml.

Definitions

Generate an index:

$ bundle exec rails g indexers:indexer product

Define the mappings, serialization and search in the index:

class ProductIndexer < ApplicationIndexer

  def mappings
    properties = configuration.properties.slice(
      :name, :category, :shop_id, :price, :currency, :product_suggestion
    )
    { properties: properties, _parent: { type: 'shop' } }
  end

  def serialize(record)
    record.slice(
      :name, :category, :shop_id, :price, :position, :currency
    ).merge(
      product_suggestion: {
        input: record.name,
        contexts: {
          shop_id: [record.shop_id.to_s, 'all'].compact
        }
      }
    )
  end

  def query(term, options={})
    bool = {}
    query = { bool: bool }
    if term.present?
      bool[:must] = {
        multi_match: {
          query: term,
          type: 'phrase_prefix',
          fields: %w(name category)
        }
      }
    else
      bool[:must] = { match_all: {} }
    end
    if shop = options[:shop]
      bool[:filters] = {
        term: { _parent: shop.id }
      }
    end
    query
  end

end

Indexing

The index will be updated every time a record is created, updated or destroyed:

product = Product.create(name: 'Les Paul', category: 'Gibson')

You can force this actions manually with:

product.index
product.reindex
product.unindex

Rake tasks

At any time you can build/rebuild your indexers using:

$ bundle exec rake indexers:index
$ bundle exec rake indexers:reindex
$ bundle exec rake indexers:unindex

Search

Use the included search method in the model:

products = Product.search('Les Paul')

The result can be used as a collection in views:

<%= render products %>

Includes

Similar to using activerecod:

Product.search.includes(:shop)

Pagination

Works the same as pagers gem:

Products.search.page(1, padding: 4, length: 30)

And you can send the collection directly to the view helper:

<%= paginate products %>

Order

Same as using activerecord:

Product.search.order(name: :asc)

NOTE: You can use a computed sort declared it in the configuration.

Suggestions

You can get suggestions using the previous configured block:

Indexers.suggest :product, 'gibson'

The result is an array of hashes with a text property and the record:

[{ text: 'Les Paul', <ActiveRecord::Base ...> }, ...]

Contributing

Any issue, pull request, comment of any kind is more than welcome!

We will mainly ensure compatibility to Rails, AWS, PostgreSQL, Redis, Elasticsearch and FreeBSD. 

Credits

This gem is maintained and funded by museways.

License

It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.