require 'custom_attributes/search_query_field' module CustomAttributes # Builds a query in elastic search DSL class SearchQuery attr_accessor :customizable, :query, :field_list, :defaults, :sort_by, :page, :per_page, :match_any, :filter_by def build { query: subquery, from: from, size: per_page, sort: sort_hash_to_array } end def from (page - 1) * per_page end # fulltext search query, by default applied to all fields def query @query ||= '*' end def page @page ||= 1 end def per_page @per_page ||= 20 end def filter_by @filter_by ||= {} end def sort_by @sort_by ||= {} end def field_list @field_list ||= {} end def defaults { query: query, fuzziness: 0 } end def match_any? @match_any end def default_fields default_fields = { custom_fields: Hash[customizable.available_custom_fields .select { |cf| cf.searchable == true } .map { |cf| [cf.slug.to_sym, {}] }] } if customizable.present? default_fields[:fields] = Hash[ customizable.class.default_fields.map { |f| [f.to_sym, {}] } ] end default_fields end private def subquery filter = filter_hash_to_term_array + [{ term: { visible_in_search: true } }] # match all documents if no particular query isset if query == '*' && field_list.empty? return { bool: { must: [ { match_all: {} } ], filter: filter } } end { bool: { filter: filter }.merge(match_any_decorator(collect_queries)) } end def collect_queries # build field specific queries queries = model_field_queries + custom_value_queries # if there are not field specific queries, match all fields if queries.count.zero? if query == '*' { match_all: {} } else { match: { _all: query } } end else queries end end def custom_value_queries return [] if filter_only_custom_fields(field_list).count.zero? filter_only_custom_fields(field_list).map do |field_slug, field| custom_query_scaffold(field.to_query_hash, field_slug) unless field.to_query_hash.empty? end.compact end def model_field_queries return [] if filter_out_custom_fields(field_list).count.zero? queries = filter_out_custom_fields(field_list).map do |field_slug, field| { match: { field_slug => field.to_query_hash } } unless field.to_query_hash.empty? end.compact return [] if queries.empty? [{ bool: { should: queries, minimum_should_match: 1 } }] end def sort_hash_to_array hash_to_array(sort_by) do |type, field_slug, field_data| if type == :custom_fields next unless find_custom_field_by_slug(field_slug).sortable { 'custom_values.value.raw' => { order: field_data || 'asc', nested_path: 'custom_values', nested_filter: { term: { 'custom_values.custom_field_id' => resolve_custom_field_id(field_slug) } } } } else { field_slug.to_s => { order: field_data } } end end end def filter_hash_to_term_array hash_to_array(filter_by) do |type, field_slug, field_data| if type == :custom_fields custom_query_scaffold(field_data, field_slug, true) else key = 'term' key = key.pluralize if field_data.is_a? Array { key.to_sym => { field_slug.to_s => field_data } } end end end def filter_out_custom_fields(fields) return {} if fields[:fields].nil? Hash[fields[:fields].map { |k, v| [k, CustomAttributes::SearchQueryField.new(v, defaults)] }] end def filter_only_custom_fields(fields) return {} if fields[:custom_fields].nil? Hash[fields[:custom_fields].map { |k, v| [k, CustomAttributes::SearchQueryField.new(v, defaults)] }] end def resolve_custom_field_id(field_slug) find_custom_field_by_slug(field_slug).try(:id) || raise('Field id not found') end def find_custom_field_by_slug(field_slug) customizable.available_custom_fields.find { |cf| cf.slug == field_slug.to_s } || raise('Field not found') end def hash_to_array(field_list) field_list.map do |type, fields| fields.map do |field_slug, field_data| yield(type, field_slug, field_data) end end.flatten.compact end def match_any_decorator(query_array) if !match_any? { must: query_array } else { should: query_array, minimum_should_match: 1 } end end def custom_query_scaffold(query, field_slug, filter = false) condition = { match: { 'custom_values.value' => query } } condition = { term: { 'custom_values.value.raw' => query } } if filter { nested: { path: 'custom_values', query: { bool: { must: [ condition, { term: { 'custom_values.custom_field_id' => resolve_custom_field_id(field_slug) } } ] } } } } end end end