Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
You can’t perform that action at this time.