Created
November 19, 2014 15:45
-
-
Save dimroc/2eec84498b6a35550f48 to your computer and use it in GitHub Desktop.
Elasticsearch query/filter builder in ruby following a jquery/monad design pattern. Built on top of Arelastic.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Monads! | |
class SearchBuilder | |
class << self | |
def filter(filters) | |
new.filter(filters) | |
end | |
def match(fields, query, options = {}) | |
new.match(fields, query, options) | |
end | |
def instant_search(fields, query) | |
match(fields, query, type: 'phrase_prefix') | |
end | |
def sort(fields) | |
new.sort(fields) | |
end | |
def all | |
new | |
end | |
end | |
attr_accessor :filter_value, :query_value, :sort_value | |
def filter(filters) | |
monad = clone | |
tuples = filters.to_a | |
return monad unless tuples.present? | |
if monad.filter_value.blank? | |
top = tuples.pop | |
monad.filter_value = generate_filter_node(top) | |
end | |
tuples.each do |filter| | |
monad.filter_value = monad.filter_value.and(generate_filter_node(filter)) | |
end | |
monad | |
end | |
def match(fields, query, options = {}) | |
wrapped = Array.wrap fields | |
monad = clone | |
monad.query_value = Arelastic::Queries::MultiMatch.new wrapped, query, options | |
monad | |
end | |
def sort(fields) | |
wrapped = Array.wrap fields | |
monad = clone | |
monad.sort_value = wrapped.map do |f| | |
attribute, order = decouple_attribute_and_order(f) | |
order = 'asc' unless order.present? | |
Arelastic::Builders::Sort[attribute].send(order.downcase) | |
end | |
monad | |
end | |
def instant_search(fields, query) | |
match(fields, query, type: 'phrase_prefix') | |
end | |
def as_json(options={}) | |
as_elastic.as_json(options) | |
end | |
def to_s | |
as_json.to_s | |
end | |
def to_hash | |
as_json | |
end | |
def as_elastic | |
nonsort = if query_value && filter_value | |
Arelastic::Searches::Query.new( | |
Arelastic::Queries::Filtered.new(query_value, filter_value)) | |
elsif query_value | |
Arelastic::Searches::Query.new(query_value) | |
elsif filter_value | |
Arelastic::Searches::Filter.new(filter_value) | |
else | |
Arelastic::Searches::Query.new(Arelastic::Queries::MatchAll.new) | |
end | |
sort = if sort_value.present? | |
Arelastic::Searches::Sort.new(sort_value) | |
else | |
nil | |
end | |
Arelastic::Nodes::HashGroup.new([nonsort, sort].compact).as_elastic | |
end | |
private | |
def initialize | |
end | |
def decouple_attribute_and_order(term) | |
tokens = term.split | |
raise ArgumentError, "Sort format incorrect. Should be 'field asc'" if tokens.count > 2 | |
raise ArgumentError, "Sort format incorrect. Should be 'field asc'" if tokens.count == 0 | |
tokens | |
end | |
def generate_filter_node(value) | |
case value.second | |
when Hash | |
generate_range_filter_node(value) | |
else | |
Arelastic::Filters::Term.new(value.first, value.second) | |
end | |
end | |
# Example input: value = [:created_at, { gteq: 1.day.ago }] | |
def generate_range_filter_node(filter) | |
node = nil | |
attribute = filter.first | |
range_hash = filter.second.with_indifferent_access.slice(:gt, :gte, :lt, :lte) | |
if range_hash.present? | |
node = Arelastic::Filters::Range.new attribute, range_hash | |
end | |
in_hash = filter.second.with_indifferent_access.slice(:in) | |
if in_hash.present? | |
builder = Arelastic::Builders::Filter[attribute] | |
in_node = builder.in(in_hash[:in]) | |
if node.present? | |
node.and in_node | |
else | |
node = in_node | |
end | |
end | |
node | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment