Introduction

ElasticsearchRails DynamicJsonSupport - Small little change to help your model update easier in elasticsearch.

Problem it solved

In ElasicSearch, if you define your model the following way:

# app/models/article.rb
class Article < ActiveRecord::Base
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks

  has_many :reviews

  def as_indexed_json options={}
    {
        id: id,
        title: title,
        article: content,
        created_at: created_at,
        updated_at: updated_at,
        reviews: reviews.map(&:as_json),
    }
  end

# app/models/review.rb
class Review < ActiveRecord::Base
  belongs_to :article
end

There're 2 issues with the implementation above:

  1. when the content is updated, this change would never be synced to the the article field in ElasticSearch, since the key is artile instead

  2. when the corresponding review is updated, the article is never updated, since it's not awared of the change.

This library aims to solve these 2 issues, with the following convention:

Example usage (which solves the 2 issues above) is as given below:

# app/models/article.rb
class Article < ActiveRecord::Base
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
  include Elasticsearch::Model::CascadeUpdate

  has_many :reviews
  es_register_attributes :id, :title, article: lambda {|rec| rec.content }

  # silent attribute would not be output by default
  es_register_silent_attributes :created_at, :updated_at 

  # register association, with:
  #   - key_name = :reviews
  #   - get_assoc: default to be `#reviews` (the key_name)
  #   - &blk: the render_assoc, default to be: `object#as_indexed_json`.
  #         it would use a `map` by default for the has_many relationships
  #   - reverse_class: default to guess from the relationship
  #   - reverse_trigger: lambda {|obj, changes| do_things }
  #         the reverse_trigger, when the model change, to render the json
  #         document and update the objects. the returns is an array of the
  #         original object to be updated
  es_register_assoc(:reviews) { |review| review.as_indexed_json }

  # for the given changed_attributes, it would return the json to be fed
  # into the `#update_document` method. What's given here is the default
  # behaviour -- which is to find the keys, and do a matching
  def elasticsearch_json_changes(changed_attributes)
    keys_to_update = changed_attributes.keys.map {|k| key_map k}
    self.as_indexed_json.select { |k,_| keys_to_update.include? k.to_s }
  end

  private
  def key_map(key)
    key = key.to_s
    case key
    when 'content'
      'article'
    else
      key
    end
  end
end

# app/models/review.rb
class Review < ActiveRecord::Base
  # Make sure to register Article
  Article

  belongs_to :article

  def as_indexed_json(options = {})
    as_json
  end
end

More features

ToDos

Contributing to elasticsearch-rails-dynamic-json-support

Copyright

Copyright © 2016 Song Yangyu. See LICENSE.txt for further details.