I'm building a JSON API in Rails and I'd like to use Elasticsearch in order to speed up responses and allow for search.
I'm just done implementing the elasticsearch-rails Gem for my first model and I can query ES from the console successfully.
Now I'd like to make results available to API consumers, so for example a GET request to /articles/index.json?q="blah" would retrieve the matching articles from ES and render them according to JSON:API standards.
Is it possible to use the rails active_model_serializers gem to achieve this? I'm asking because there (in contrast with jbuilder) the JSON:API format is already taken care of.
EDIT: Here's where I stand at the moment:
In my model I have the following:
require 'elasticsearch/rails'
require 'elasticsearch/model'
class Thing < ApplicationRecord
validates :user_id, :active, :status, presence: true
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
index_name Rails.application.class.parent_name.underscore
document_type self.name.downcase
settings index: { number_of_shards: 1, number_of_replicas: 1 } do
mapping dynamic: 'strict' do
indexes :id, type: :string
indexes :user_id, type: :string
indexes :active, type: :boolean
indexes :status, type: :string
end
end
def as_indexed_json(options = nil)
self.as_json({
only: [:id, :user_id, :active, :status],
})
end
def self.search(query)
__elasticsearch__.search( {
query: {
multi_match: {
query: query,
fields: ['id^5', 'user_id']
}
}
} )
end
end
This correctly indexes the model in ES and makes it possible to search the ES index. In my controller I have:
class ThingsController < ApplicationController
def index
things = Thing.search(params[:query]).results.map{|m| m._source}
render json: things, each_serializer: ThingSerializer
end
end
In the serializer, at the moment, is the following:
class ThingSerializer < ActiveModel::Serializer
attributes :id, :user_id, :active, :status
end
This unfortunately brings up the following JSON in the view:
{"data":[{"id":"","type":"hashie-mashes","attributes":{"user-id":null,"active":null,"status":null}}]}
So the serializer is not correctly parsing the result, which is wrapped from the ES gem into this Hashie::Mash object.
I finally managed to make it work nicely and without the need to fetch records from the DB. Here's the complete solution for future googlers:
Serializer (probably better to create a dedicated one for search results):
class SearchableThingSerializer < ActiveModel::Serializer
type 'things' # This needs to be overridden, otherwise it will print "hashie-mashes"
attributes :id # For some reason the mapping below doesn't work with :id
[:user_id, :active, :status].map{|a| attribute(a) {object[:_source][a]}}
def id
object[:_source].id
end
end
Controller:
def index
things = Thing.search(params[:query])
render json: things, each_serializer: SearchableThingSerializer
end
With this you can build a JSON API as described in this guide, with the additional benefit of serving data straight from Elasticsearch: