Skip to content

Instantly share code, notes, and snippets.

@dimroc
Created November 19, 2014 15:45
Show Gist options
  • Save dimroc/2eec84498b6a35550f48 to your computer and use it in GitHub Desktop.
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.
# 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