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:
- when the
content
is updated, this change would never be synced to the thearticle
field in ElasticSearch, since the key isartile
instead - 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:
es_json_changes(changed_attributes)
- for a given set of changed attributes, it would response with the update to be processed by this recordElasticsearch::Model::CascadeUpdate
- class, when included, providing:- class methods:
es_register_attributes { key => lambda }
: to register a hash ofkey, lambda
pairs, to be used for rendering the json. if lambda is nil, default to the public method of this resource namedkey
.key_name, relationship_name: nil, reverse_relationship_name: nil, &blk
: to register an association of key, lambda. relationship_name default to key if not given, and reverse_relationship_name default to the singular form of the class name if not given. After this registration, whenever the corresponding resource is updated, it would trigger this resource (which is related to the resource) to update.
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( = {})
as_json
end
end
More features
- Exclusion of keys: simply call
record.as_indexed_json: exclude_keys: %w[keys you do not like]
. - Selective Import:
es_to_json_when(scope_name, &condition_check_block)
. if the scope_name is not given, it would import all by default.condition_check_block
is for checking before making the import
ToDos
- [ ] Tests.. since I don't have time to write test case for this project yet.
- [ ] Due to the
lazy-loading
of model in rails, you need to specifically specify the model in rails. Like in theReview
class above, it's reference toArticle
(to make sure thatArticle
class is loaded beforeReview
) - [ ] Auto generation of mapping
Contributing to elasticsearch-rails-dynamic-json-support
- Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
- Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
- Fork the project.
- Start a feature/bugfix branch.
- Commit and push until you are happy with your contribution.
- Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
- Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
Copyright
Copyright (c) 2016 Song Yangyu. See LICENSE.txt for further details.