Persistenceedit

The elasticsearch-persistence Rubygem provides persistence layer for Ruby domain objects.

It supports two design patterns for integrating with your objects: repository and active record.

Repositoryedit

The Elasticsearch::Persistence::Repository module provides an implementation of the repository pattern and allows to save, delete, find and search objects stored in Elasticsearch, as well as configure mappings and settings for the index.

Features At a Glanceedit

  • Access to the Elasticsearch client
  • Setting the index name, document type, and object class for deserialization
  • Composing mappings and settings for the index
  • Creating, deleting or refreshing the index
  • Finding or searching for documents
  • Providing access both to domain objects and hits for search results
  • Providing access to the Elasticsearch response for search results (aggregations, total, …)
  • Defining the methods for serialization and deserialization

Usageedit

Let’s have a simple plain old Ruby object (PORO):

class Note
  attr_reader :attributes

  def initialize(attributes={})
    @attributes = attributes
  end

  def to_hash
    @attributes
  end
end

Let’s create a default, "dumb" repository, as a first step:

require 'elasticsearch/persistence'
repository = Elasticsearch::Persistence::Repository.new

We can save a Note instance into the repository, find it, search it, delete it:

note = Note.new id: 1, text: 'Test'

repository.save(note)
# PUT http://localhost:9200/repository/note/1
# > {"id":1,"text":"Test"}
# < {"_index":"repository","_type":"note","_id":"1","_version":1,"created":true}

n = repository.find(1)
# GET http://localhost:9200/repository/_all/1
# < {"_index":"repository","_type":"note","_id":"1","_version":2,"found":true, "_source" : {"id":1,"text":"Test"}}
=> <Note:0x007fcbfc0c4980 @attributes={"id"=>1, "text"=>"Test"}>

repository.search(query: { match: { text: 'test' } }).first
# GET http://localhost:9200/repository/_search
# > {"query":{"match":{"text":"test"}}}
# < {"took":2, ... "hits":{"total":1, ... "hits":[{ ... "_source" : {"id":1,"text":"Test"}}]}}
=> <Note:0x007fcbfc1c7b70 @attributes={"id"=>1, "text"=>"Test"}>

repository.delete(note)
# DELETE http://localhost:9200/repository/note/1
# < {"found":true,"_index":"repository","_type":"note","_id":"1","_version":3}
=> {"found"=>true, "_index"=>"repository", "_type"=>"note", "_id"=>"1", "_version"=>2}

The repository module provides a number of features and facilities to configure and customize the behaviour, as well as support for extending your own, custom repository class.

Please refer to the documentation for more information.

Also, check out the example application which demonstrates the usage patterns of the repository approach to persistence.

Active Recordedit

The ‘Elasticsearch::Persistence::Model` module provides an implementation of the active record pattern, with a familiar interface for using Elasticsearch as a persistence layer in Ruby on Rails applications. The model is fully compatible with Rails’ conventions and helpers, such as url_for.

All the methods are documented with comprehensive examples in the source code, available also online.

Features At a Glanceedit

  • Familiar interface for persistence methods from ActiveRecord
  • Common model features such as validations and callbacks
  • Methods for defining model attributes, including Elasticsearch mappings
  • Support for fetching data in bulk (find_in_batches, find_each)
  • Decorated search results for easy access to model instances and meta data (such as highlights or aggregations)
  • Easy access to the underlying gateway and client

Usageedit

To use the library in a Rails application, add it to your Gemfile with a require statement:

gem "elasticsearch-persistence", require: 'elasticsearch/persistence/model'

Include the module in a plain Ruby class, and set up the properties, mappings, etc:

class Article
  include Elasticsearch::Persistence::Model

  # Define a plain `title` attribute
  #
  attribute :title,  String

  # Define an `author` attribute, with multiple analyzers for this field
  #
  attribute :author, String, mapping: { fields: {
                               author: { type: 'string'},
                               raw:    { type: 'string', analyzer: 'keyword' }
                             } }


  # Define a `views` attribute, with default value
  #
  attribute :views,  Integer, default: 0, mapping: { type: 'integer' }

  # Validate the presence of the `title` attribute
  #
  validates :title, presence: true

  # Execute code after saving the model.
  #
  after_save { puts "Successfully saved: #{self}" }
end

The model attribute definition support is implemented with the Virtus Rubygem, and the naming, validation, etc. features with the ActiveModel Rubygem.

Attribute validations work like for any other ActiveModel-compatible implementation:

article = Article.new                                                                                             # => #<Article { ... }>

article.valid?
# => false

article.errors.to_a
# => ["Title can't be blank"]

We can create a new article in the database and find it:

Article.create id: 1, title: 'Test', author: 'John'
# PUT http://localhost:9200/articles/article/1 [status:201, request:0.015s, query:n/a]

article = Article.find(1)
# => #<Article { ... }>

article._index
# => "articles"

article.id
# => "1"

article.title
# => "Test"

To update the model, either update the attribute and save the model or use the update_attributes method:

article.title = 'Updated'

article.save
# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>2, "created"=>false}

article.update_attributes title: 'Test', author: 'Mary'
# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>3}

The implementation supports the familiar interface for updating model timestamps and numeric attributes:

article.touch
# => => { ... "_version"=>4}

article.views
# => 0

article.increment :views
article.views
# => 1

Any callbacks defined in the model will be triggered during the persistence operations:

article.save
# Successfully saved: #<Article {...}>

Please see the extensive documentation in the library README for detailed information.

Also, check out the example application which demonstrates the usage patterns of the active record approach to persistence.