module SearchFlip::Filterable

The SearchFlip::Filterable mixin provides chainable methods like where, exists, range, etc to add search filters to a criteria.

@example

CommentIndex.where(public: true)
CommentIndex.exists(:user_id)
CommentIndex.range(:created_at, gt: Date.today - 7)

Public Class Methods

included(base) click to toggle source
# File lib/search_flip/filterable.rb, line 11
def self.included(base)
  base.class_eval do
    attr_accessor :must_values, :must_not_values, :filter_values
  end
end

Public Instance Methods

exists(field) click to toggle source

Adds an exists filter to the criteria, which selects all documents for which the specified field has a non-null value.

@example

CommentIndex.exists(:notified_at)

@param field [Symbol, String] The field that should have a non-null value

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 264
def exists(field)
  filter(exists: { field: field })
end
exists_not(field) click to toggle source

Adds an exists not query to the criteria, which selects all documents for which the specified field's value is null.

@example

CommentIndex.exists_not(:notified_at)

@param field [Symbol, String] The field that should have a null value

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 278
def exists_not(field)
  must_not(exists: { field: field })
end
filter(clause) click to toggle source

Adds raw filter queries to the criteria.

@example

CommentIndex.filter(term: { state: "new" })
CommentIndex.filter(range: { created_at: { gte: Time.parse("2016-01-01") }})

@param args [Array, Hash] The raw filter query arguments

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 109
def filter(clause)
  fresh.tap do |criteria|
    criteria.filter_values = (filter_values || []) + Helper.wrap_array(clause)
  end
end
match_all(options = {}) click to toggle source

Adds a match all filter to the criteria, which simply matches all documents. This can be eg be used within filter aggregations or for filter chaining. Check out the Elasticsearch docs for further details.

@example Basic usage

CommentIndex.match_all

@example Filter chaining

query = CommentIndex.match_all
query = query.where(public: true) unless current_user.admin?

@example Filter aggregation

query = CommentIndex.aggregate(filtered_tags: {}) do |aggregation|
  aggregation = aggregation.match_all
  aggregation = aggregation.where(user_id: current_user.id) if current_user
  aggregation = aggregation.aggregate(:tags)
end

query.aggregations(:filtered_tags).tags.buckets.each { ... }

@param options [Hash] Options for the match_all filter, like eg boost

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 234
def match_all(options = {})
  filter(match_all: options)
end
match_none() click to toggle source

Adds a match none filter to the criteria, which simply matches none documents at all. Check out the Elasticsearch docs for further details.

@example Basic usage

CommentIndex.match_none

@example Filter chaining

query = CommentIndex.search("...")
query = query.match_none unless current_user.admin?

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 250
def match_none
  filter(match_none: {})
end
must(clause, bool_options = {}) click to toggle source

Adds raw must queries to the criteria.

@example

CommentIndex.must(term: { state: "new" })
CommentIndex.must(range: { created_at: { gt: Time.parse("2016-01-01") }})

@param args [Array, Hash] The raw must query arguments

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 125
def must(clause, bool_options = {})
  fresh.tap do |criteria|
    criteria.must_values = (must_values || []) + Helper.wrap_array(clause)
  end
end
must_not(clause) click to toggle source

Adds raw must_not queries to the criteria.

@example

CommentIndex.must_not(term: { state: "new" })
CommentIndex.must_not(range: { created_at: { gt: Time.parse"2016-01-01") }})

@param args [Array, Hash] The raw must_not query arguments

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 141
def must_not(clause)
  fresh.tap do |criteria|
    criteria.must_not_values = (must_not_values || []) + Helper.wrap_array(clause)
  end
end
range(field, options = {}) click to toggle source

Adds a range filter to the criteria without being forced to specify the left and right end of the range, such that you can eg simply specify lt, lte, gt and gte. For fully specified ranges, you can as well use where, etc. Check out the Elasticsearch docs for further details regarding the range filter.

@example

CommentIndex.range(:created_at, gte: Time.parse("2016-01-01"))
CommentIndex.range(:likes_count, gt: 10, lt: 100)

@param field [Symbol, String] The field name to specify the range for @param options [Hash] The range filter specification, like lt, lte, etc

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 206
def range(field, options = {})
  filter(range: { field => options })
end
should(clause) click to toggle source

Adds a raw should query to the criteria.

@example

CommentIndex.should([
  { term: { state: "new" } },
  { term: { state: "reviewed" } }
])

@param args [Array] The raw should query arguments

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 187
def should(clause)
  must(bool: { should: clause })
end
to_query() click to toggle source

Returns all added queries and filters, including post filters, as a raw query.

@example

CommentIndex.where(state: "new").search("text").to_query
# => {:bool=>{:filter=>[{:term=>{:state=>"new"}}], :must=>[{:query_string=>{:query=>"text", ...}}]}}

CommentIndex.must(term: { state: "new" }).to_query
# => {:term=>{:state=>"new"}}

@return [Hash] The raw query

# File lib/search_flip/filterable.rb, line 159
def to_query
  must_clauses = must_values.to_a
  must_not_clauses = must_not_values.to_a + post_must_not_values.to_a
  filter_clauses = post_must_values.to_a + filter_values.to_a + post_filter_values.to_a

  return must_clauses.first if must_clauses.size == 1 && must_not_clauses.empty? && filter_clauses.empty?

  {
    bool: {
      must: must_clauses,
      must_not: must_not_clauses,
      filter: filter_clauses
    }.reject { |_, value| value.empty? }
  }
end
where(hash) click to toggle source

Adds filters to your criteria for the supplied hash composed of field-to-filter mappings which specify terms, term or range filters, depending on the type of the respective hash value, namely array, range or scalar type like Fixnum, String, etc.

@example

CommentIndex.where(id: [1, 2, 3], state: ["approved", "declined"])
CommentIndex.where(id: 1 .. 100)
CommentIndex.where(created_at: Time.parse("2016-01-01") .. Time.parse("2017-01-01"))
CommentIndex.where(id: 1, message: "hello")
CommentIndex.where(state: nil)

@param hash [Hash] A field-to-filter mapping specifying filter values for

the respective fields

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 54
def where(hash)
  hash.inject(fresh) do |memo, (key, value)|
    if value.is_a?(Array)
      memo.filter(terms: { key => value })
    elsif value.is_a?(Range)
      memo.filter(range: { key => { gte: value.min, lte: value.max } })
    elsif value.nil?
      memo.must_not(exists: { field: key })
    else
      memo.filter(term: { key => value })
    end
  end
end
where_not(hash) click to toggle source

Adds filters to exclude documents in accordance to the supplied hash composed of field-to-filter mappings. Check out where for further details.

@see where See where for further details

@example

CommentIndex.where_not(state: "approved")
CommentIndex.where_not(created_at: Time.parse("2016-01-01") .. Time.parse("2017-01-01"))
CommentIndex.where_not(id: [1, 2, 3], state: "new")
CommentIndex.where_not(state: nil)

@param hash [Hash] A field-to-filter mapping specifying filter values for the

respective fields

@return [SearchFlip::Criteria] A newly created extended criteria

# File lib/search_flip/filterable.rb, line 85
def where_not(hash)
  hash.inject(fresh) do |memo, (key, value)|
    if value.is_a?(Array)
      memo.must_not(terms: { key => value })
    elsif value.is_a?(Range)
      memo.must_not(range: { key => { gte: value.min, lte: value.max } })
    elsif value.nil?
      memo.filter(exists: { field: key })
    else
      memo.must_not(term: { key => value })
    end
  end
end