elasticquery 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +12 -0
  3. data/README.md +151 -70
  4. data/Rakefile +1 -9
  5. data/elasticquery.gemspec +2 -2
  6. data/lib/elasticquery.rb +1 -0
  7. data/lib/elasticquery/base.rb +6 -48
  8. data/lib/elasticquery/builder.rb +74 -17
  9. data/lib/elasticquery/es.rb +32 -0
  10. data/lib/elasticquery/filters/base.rb +4 -0
  11. data/lib/elasticquery/filters/exists.rb +24 -0
  12. data/lib/elasticquery/filters/not.rb +7 -11
  13. data/lib/elasticquery/filters/range.rb +3 -7
  14. data/lib/elasticquery/filters/term.rb +4 -28
  15. data/lib/elasticquery/filters/terms.rb +23 -0
  16. data/lib/elasticquery/queries/base.rb +16 -0
  17. data/lib/elasticquery/queries/multi_match.rb +34 -0
  18. data/lib/elasticquery/query.rb +25 -36
  19. data/lib/elasticquery/version.rb +1 -1
  20. data/test/base_test.rb +4 -4
  21. data/test/builder_test.rb +24 -8
  22. data/test/es_test.rb +23 -0
  23. data/test/filters/exists_test.rb +30 -0
  24. data/test/filters/not_test.rb +23 -10
  25. data/test/filters/range_test.rb +10 -11
  26. data/test/filters/term_test.rb +6 -7
  27. data/test/filters/terms_test.rb +50 -0
  28. data/test/integration/chainable_call_test.rb +6 -6
  29. data/test/integration/exists_case_test.rb +56 -0
  30. data/test/integration/not_case_test.rb +11 -5
  31. data/test/integration/queries_inheritence_test.rb +12 -4
  32. data/test/integration/range_case_test.rb +4 -2
  33. data/test/integration/search_case_test.rb +20 -11
  34. data/test/integration/term_case_test.rb +4 -2
  35. data/test/integration/terms_case_test.rb +49 -0
  36. data/test/queries/multi_match_test.rb +49 -0
  37. data/test/query_test.rb +40 -40
  38. metadata +26 -27
  39. data/lib/elasticquery/filters/search.rb +0 -54
  40. data/test/filters/search_test.rb +0 -62
@@ -1,7 +1,10 @@
1
1
  require "elasticquery/filters/term"
2
- require "elasticquery/filters/search"
3
2
  require "elasticquery/filters/not"
3
+ require "elasticquery/filters/terms"
4
4
  require "elasticquery/filters/range"
5
+ require "elasticquery/filters/exists"
6
+
7
+ require "elasticquery/queries/multi_match"
5
8
 
6
9
  require "active_support/concern"
7
10
  require "active_support/core_ext/string/inflections"
@@ -10,23 +13,77 @@ module Elasticquery
10
13
  module Builder
11
14
  extend ActiveSupport::Concern
12
15
 
13
- filters = [
14
- Filters::Term,
15
- Filters::Search,
16
- Filters::Not,
17
- Filters::Range
18
- ]
19
-
20
- included do
21
- filters.each do |filter_class|
22
- filter_name = filter_class.to_s.split("::").last.underscore
23
-
24
- define_method filter_name do |*args|
25
- filter = filter_class.new *args
26
- query << filter
27
- self
28
- end
16
+ def filters
17
+ @namespace = "Filters"
18
+ if block_given?
19
+ yield
20
+ @namespace = nil
21
+ end
22
+ self
23
+ end
24
+
25
+ def queries
26
+ @namespace = "Queries"
27
+ if block_given?
28
+ yield
29
+ @namespace = nil
30
+ end
31
+ self
32
+ end
33
+
34
+ def execute(method, *args)
35
+ filter_class = "Elasticquery::#{@namespace}::#{method.camelize}".constantize
36
+ instance = filter_class.new *args
37
+ case @namespace
38
+ when "Filters"
39
+ query.push_filter instance
40
+ when "Queries"
41
+ query.push_query instance
42
+ else
43
+ raise "Please specify block. Queries and Filters are supported"
29
44
  end
45
+ self
46
+ end
47
+ private :execute
48
+
49
+ def term(*args)
50
+ execute "term", *args
51
+ end
52
+
53
+ def terms(*args)
54
+ execute "terms", *args
55
+ end
56
+ def where(*args)
57
+ terms *args
58
+ end
59
+
60
+ def not(*args)
61
+ execute "not", *args
62
+ end
63
+
64
+ def range(*args)
65
+ execute "range", *args
66
+ end
67
+
68
+ def multi_match(*args)
69
+ execute "multi_match", *args
70
+ end
71
+ def search(*args)
72
+ multi_match *args
73
+ end
74
+
75
+ def exists(*args)
76
+ execute "exists", *args
77
+ end
78
+ def with(*args)
79
+ exists *args
80
+ end
81
+
82
+ def missing(*args)
83
+ exists.not *args
84
+ end
85
+ def without(*args)
86
+ missing *args
30
87
  end
31
88
  end
32
89
  end
@@ -0,0 +1,32 @@
1
+ module Elasticquery
2
+ EsNotSupported = Class.new(StandardError)
3
+
4
+ class BaseChain < Elasticquery::Base
5
+ def initialize(klass)
6
+ @klass = klass
7
+ super
8
+ end
9
+
10
+ delegate :each, :empty?, :size, :slice, :[], :to_ary, to: :results
11
+
12
+ def records
13
+ results.records
14
+ end
15
+
16
+ def results
17
+ @klass.__elasticsearch__.search(build)
18
+ end
19
+ end
20
+
21
+ module Es
22
+ def self.extended(base)
23
+ unless base.ancestors.include? Elasticsearch::Model
24
+ raise EsNotSupported.new("Can't extend. It's not Elasticsearch::Model")
25
+ end
26
+ end
27
+
28
+ def es
29
+ BaseChain.new(self)
30
+ end
31
+ end
32
+ end
@@ -12,6 +12,10 @@ module Elasticquery
12
12
  !valid?
13
13
  end
14
14
 
15
+ def to_not_hash
16
+ {not: {filter: to_hash}}
17
+ end
18
+
15
19
  def dup_with(*args)
16
20
  self.class.new *args
17
21
  end
@@ -0,0 +1,24 @@
1
+ require_relative "base"
2
+
3
+ module Elasticquery
4
+ module Filters
5
+ class Exists < Base
6
+
7
+ def initialize(field = nil)
8
+ @field = field
9
+ end
10
+
11
+ def valid?
12
+ @field.present?
13
+ end
14
+
15
+ def to_hash
16
+ {exists: {field: @field}}
17
+ end
18
+
19
+ def to_not_hash
20
+ {missing: {field: @field}}
21
+ end
22
+ end
23
+ end
24
+ end
@@ -5,25 +5,21 @@ module Elasticquery
5
5
  class Not < Base
6
6
 
7
7
  def initialize(*args)
8
- @args = args
8
+ @args = Marshal.load(Marshal.dump(args))
9
9
  end
10
10
 
11
11
  def dup_with(*args)
12
12
  raise StandardError, "Cannot use Filters::Not twice"
13
13
  end
14
14
 
15
+ def valid?
16
+ args = @args
17
+ -> { filters[-2].dup_with(*args).valid? }
18
+ end
19
+
15
20
  def to_hash
16
21
  args = @args
17
- -> do
18
- filter = filters.last.dup_with *args
19
- if filter.valid?
20
- subquery = filter.to_hash
21
- q = subquery[:query][:filtered][:filter][:and][0]
22
- {query: {filtered: {filter: {and: [{not: {filter: q}}]}}}}
23
- else
24
- {}
25
- end
26
- end
22
+ -> { filters[-2].dup_with(*args).to_not_hash }
27
23
  end
28
24
  end
29
25
  end
@@ -5,8 +5,8 @@ module Elasticquery
5
5
  class Range < Base
6
6
  OPTIONS = %i(_cache execution)
7
7
 
8
- def initialize(field=nil, gte: nil, lte: nil, **options)
9
- @options = options.extract! *OPTIONS
8
+ def initialize(field = nil, gte: nil, lte: nil, **options)
9
+ @options = options.slice *OPTIONS
10
10
  @field, @gte, @lte = field, gte, lte
11
11
  end
12
12
 
@@ -15,11 +15,7 @@ module Elasticquery
15
15
  end
16
16
 
17
17
  def to_hash
18
- if valid?
19
- {query: {filtered: {filter: {and: [{range: range_with_options}]}}}}
20
- else
21
- {}
22
- end
18
+ {range: range_with_options}
23
19
  end
24
20
 
25
21
  private
@@ -5,41 +5,17 @@ module Elasticquery
5
5
  class Term < Base
6
6
  OPTIONS = %i(_cache)
7
7
 
8
- # Create term filter for elasticsearch builder
9
- #
10
- # @param [Hash] condition passed to define
11
- #
12
- # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-filtered-query.html#_filtering_without_a_query
13
- def initialize(data={})
14
- @options = data.extract! *OPTIONS
15
- @condition = data
8
+ def initialize(data = {})
9
+ @options = data.slice *OPTIONS
10
+ @condition = data.except *OPTIONS
16
11
  end
17
12
 
18
- # Is passed condition valid. Passed condition must have
19
- # one key and present value
20
- #
21
- # @return [Boolean] is condition have at least one key
22
- #
23
- # @example
24
- # rule = Elasticquery::Rules::Term.new
25
- # rule.valid? #=> false
26
13
  def valid?
27
14
  @condition.keys.size == 1 && @condition.values[0].present?
28
15
  end
29
16
 
30
- # Hash presentation of query.
31
- #
32
- # @return [Hash] presentation of filter.
33
- #
34
- # @example
35
- # valid = Elasticquery::Filters::Term.new {name: "John"}
36
- # valid.to_hash #=> {query: {filtered: {filter: {and: [{term: {name: 'John'}}]}}}
37
17
  def to_hash
38
- if valid?
39
- {query: {filtered: {filter: {and: [{term: @condition.merge(@options)}]}}}}
40
- else
41
- {}
42
- end
18
+ {term: @condition.merge(@options)}
43
19
  end
44
20
  end
45
21
  end
@@ -0,0 +1,23 @@
1
+ require "active_support/core_ext/array/wrap"
2
+ require_relative "base"
3
+
4
+ module Elasticquery
5
+ module Filters
6
+ class Terms < Base
7
+ OPTIONS = %i(_cache execution)
8
+
9
+ def initialize(data = {})
10
+ @options = data.slice *OPTIONS
11
+ @conditions = data.except(*OPTIONS).delete_if{ |_, value| value.blank? }
12
+ end
13
+
14
+ def valid?
15
+ @conditions.keys.any?
16
+ end
17
+
18
+ def to_hash
19
+ {terms: @conditions.merge(@options)}
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module Elasticquery
4
+ module Queries
5
+ class Base
6
+
7
+ def valid?
8
+ true
9
+ end
10
+
11
+ def invalid?
12
+ !valid?
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ require_relative "base"
2
+
3
+ module Elasticquery
4
+ module Queries
5
+ class MultiMatch < Base
6
+ OPERATORS = %w(and or)
7
+ TYPES = %w(best_fields most_fields cross_fields phrase pharse_prefix)
8
+
9
+ def initialize(query, fields: "_all", operator: "and", type: "best_fields")
10
+ @fields = fields
11
+ @operator = operator
12
+ @type = type
13
+ @query = query
14
+ end
15
+
16
+ def valid?
17
+ OPERATORS.include?(@operator) &&
18
+ TYPES.include?(@type) &&
19
+ @query.present? &&
20
+ ( Array === @fields || @fields == "_all" )
21
+ end
22
+
23
+ def to_hash
24
+ {multi_match: subquery}
25
+ end
26
+
27
+ private
28
+
29
+ def subquery
30
+ {fields: @fields, operator: @operator, type: @type, query: @query}
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,64 +1,53 @@
1
- require "deep_merge/rails_compat"
2
-
3
1
  module Elasticquery
4
2
  class Query
5
3
 
6
- attr_reader :filters
4
+ attr_reader :filters, :queries
7
5
 
8
6
  DEFAULT = {query: {filtered: {query: {match_all:{}}}}}
9
7
 
10
- # Create new query from hash.
11
- #
12
- # @param [Hash] query passed as hash. When it not passed use default es 'match_all' query
13
- #
14
- # @example
15
- # Elasticsearch::Query.new.to_hash #=> {query: {filtered: {query: {match_all:{}}}}}
16
8
  def initialize(query = DEFAULT)
17
9
  @query = query
18
- @filters = []
10
+ @filters, @queries = [], []
19
11
  end
20
12
 
21
- # Convery query object ot hash
22
- #
23
- # @return [Hash] hash presentation of query
24
13
  def to_hash
25
- return DEFAULT if @query == {}
26
14
  @query
27
15
  end
28
16
 
29
- # Merge filter to query. If current query is `matche_all`ed
30
- # then clear it and use new value. Populate #filters array
31
- # with given classes
32
- #
33
- # @param [Elasticquery::filter::Base] filter passed
34
- #
35
- # @see #match_all?
36
- def <<(filter)
37
- @query = {} if match_all?
38
- merge filter
17
+ def push_filter(filter)
39
18
  @filters << filter
19
+ merge_filter filter
40
20
  end
41
21
 
42
- # Ckeck current object to default elasticsearch param
43
- #
44
- # @return [Boolean] is passed query undefined
45
- def match_all?
46
- @query == DEFAULT
22
+ def push_query(query)
23
+ @queries << query
24
+ merge_query query
47
25
  end
48
26
 
49
27
  private
50
28
 
51
- def merge(filter)
29
+ def merge_filter(filter)
30
+ return unless filter_valid?(filter)
31
+
52
32
  hash = filter.to_hash
53
33
  subquery = hash.respond_to?(:call) ? instance_exec(&hash) : hash
54
- @query.deeper_merge! subquery
34
+ @query = {query: {filtered: {}}} if @query == DEFAULT
35
+
36
+ @query[:query][:filtered][:filter] ||= {and: []}
37
+ @query[:query][:filtered][:filter][:and] << subquery
55
38
  end
56
39
 
57
- def _filters
58
- @query[:query] &&
59
- @query[:query][:filtered] &&
60
- @query[:query][:filtered][:filter] &&
61
- @query[:query][:filtered][:filter][:and]
40
+ def filter_valid?(filter)
41
+ validation = filter.valid?
42
+ validation.respond_to?(:call) ? instance_exec(&validation) : validation
43
+ end
44
+
45
+ def merge_query(query)
46
+ return unless query.valid?
47
+ @query = {query: {filtered: {}}} if @query == DEFAULT
48
+
49
+ @query[:query][:filtered][:query] ||= {bool: {must: []}}
50
+ @query[:query][:filtered][:query][:bool][:must] << query.to_hash
62
51
  end
63
52
  end
64
53
  end