elasticquery 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,53 @@
1
+ require_relative 'base'
2
+
3
+ module Elasticquery
4
+ module Filters
5
+ class Search < Base
6
+ OPERATORS = %w(and or)
7
+ TYPES = %w(best_fields most_fields cross_fields phrase pharse_prefix)
8
+
9
+ # Create new search subquery
10
+ #
11
+ # @params [String] query keyword
12
+ # @params [Array<String>] fields to search with. Default to "_all"
13
+ # @params [String] operator search option
14
+ # @params [String] type search option
15
+ def initialize(query, fields: "_all", operator: "and", type: "best_fields")
16
+ @fields = fields
17
+ @operator = operator
18
+ @type = type
19
+ @query = query
20
+ end
21
+
22
+ # Is current query valid to exec
23
+ #
24
+ # @return [Boolean]
25
+ #
26
+ # @example
27
+ # filter = Elasticquery::Filters::Search.new 'hello'
28
+ # filter.valid? #=> true
29
+ def valid?
30
+ OPERATORS.include?(@operator) &&
31
+ TYPES.include?(@type) &&
32
+ ( Array === @fields || @fields == "_all" )
33
+ end
34
+
35
+ # Hash presentation of query.
36
+ #
37
+ # @return [Hash] presentation of filter.
38
+ #
39
+ # @example
40
+ # r = Elasticquery::Filters::Search.new { fields: ['name', 'country'], query: 'belarus' }
41
+ # r.to_hash #=> {query: {filtered: {query: {multi_match: {fields: ['name', 'country'], query: 'belarus'}}}}}
42
+ def to_hash
43
+ valid? ? {query: {filtered: {query: {multi_match: subquery}}}} : {}
44
+ end
45
+
46
+ private
47
+
48
+ def subquery
49
+ {fields: @fields, operator: @operator, type: @type, query: @query}
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'base'
2
+
3
+ module Elasticquery
4
+ module Filters
5
+ class Term < Base
6
+
7
+ # Create filtered -> filter filter for elasticsearch
8
+ # 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(condition={})
14
+ @condition = condition
15
+ end
16
+
17
+ # Is passed condition valid. Passed condition must have
18
+ # one key
19
+ #
20
+ # @return [Boolean] is condition have at least one key
21
+ #
22
+ # @example
23
+ # rule = Elasticquery::Rules::Term.new
24
+ # rule.valid? #=> false
25
+ def valid?
26
+ @condition.keys.any?
27
+ end
28
+
29
+ # Hash presentation of query.
30
+ #
31
+ # @return [Hash] presentation of filter.
32
+ #
33
+ # @example
34
+ # r = Elasticquery::filters::Term.new { name: "John" }
35
+ # r.to_hash #=> {query: {filtered: {filter: {and: {name: 'John'}}}}}
36
+ def to_hash
37
+ valid? ? {query: {filtered: {filter: {and: {term: @condition}}}}} : {}
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ require 'elasticquery/filters/term'
2
+ require 'elasticquery/filters/search'
3
+ require 'elasticquery/filters/not'
4
+ require 'elasticquery/filters/range'
5
+
6
+ require 'active_support/concern'
7
+ require 'active_support/core_ext/string/inflections'
8
+
9
+ module Elasticquery
10
+ module Queries
11
+ module All
12
+ extend ActiveSupport::Concern
13
+
14
+ filters = [
15
+ Filters::Term,
16
+ Filters::Search,
17
+ Filters::Not,
18
+ Filters::Range
19
+ ]
20
+
21
+ included do
22
+ filters.each do |filter_class|
23
+ filter_name = filter_class.to_s.split("::").last.underscore
24
+
25
+ define_method filter_name do |*args|
26
+ filter = filter_class.new *args
27
+ query << filter
28
+ self
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,72 @@
1
+ require 'active_support/core_ext/hash/deep_merge'
2
+
3
+ module Elasticquery
4
+ class Query
5
+
6
+ attr_reader :filters
7
+
8
+ DEFAULT = {query: {filtered: {query: {match_all:{}}}}}
9
+
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
+ def initialize(query = DEFAULT)
17
+ @query = query
18
+ @filters = []
19
+ end
20
+
21
+ # Convery query object ot hash
22
+ #
23
+ # @return [Hash] hash presentation of query
24
+ def to_hash
25
+ q = @query.dup
26
+ if Enumerable === _filters
27
+ flatted_filters = _filters.flat_map do |type, filters|
28
+ filters.flat_map do |key, value|
29
+ {type => {key => value}}
30
+ end
31
+ end
32
+ q[:query][:filtered][:filter][:and] = flatted_filters
33
+ end
34
+ q
35
+ end
36
+
37
+ # Merge filter to query. If current query is `matche_all`ed
38
+ # then clear it and use new value. Populate #filters array
39
+ # with given classes
40
+ #
41
+ # @param [Elasticquery::filter::Base] filter passed
42
+ #
43
+ # @see #match_all?
44
+ def <<(filter)
45
+ @query = {} if match_all?
46
+ merge filter
47
+ @filters << filter
48
+ end
49
+
50
+ # Ckeck current object to default elasticsearch param
51
+ #
52
+ # @return [Boolean] is passed query undefined
53
+ def match_all?
54
+ @query == DEFAULT
55
+ end
56
+
57
+ private
58
+
59
+ def merge(filter)
60
+ hash = filter.to_hash
61
+ subquery = hash.respond_to?(:call) ? instance_exec(&hash) : hash
62
+ @query.deep_merge! subquery
63
+ end
64
+
65
+ def _filters
66
+ @query[:query] &&
67
+ @query[:query][:filtered] &&
68
+ @query[:query][:filtered][:filter] &&
69
+ @query[:query][:filtered][:filter][:and]
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,4 @@
1
+ module Elasticquery
2
+ # current gem version
3
+ VERSION = "0.1.0"
4
+ end
data/test/base_test.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'test_helper'
2
+ require 'elasticquery/base'
3
+
4
+ class TestBase < MiniTest::Test
5
+
6
+ def setup
7
+ @klass = Class.new(Elasticquery::Base)
8
+ @klass.filtered { 1 + 1 }
9
+
10
+ @query = @klass.new
11
+ end
12
+
13
+ def test_queries_with_filters_are_filterable
14
+ assert @query.filterable?
15
+ end
16
+
17
+ def test_queries_without_filters_are_not_filtrable
18
+ klass = Class.new(Elasticquery::Base)
19
+ refute klass.new.filterable?
20
+ end
21
+
22
+ def test_filters_is_empty_by_default
23
+ query = Elasticquery::Base.new
24
+ assert_empty query.filters
25
+ end
26
+
27
+ def test_extract_params
28
+ query = @klass.new a: 1
29
+ assert_equal 1, query.params[:a]
30
+ end
31
+
32
+ def test_default_params_as_empty
33
+ assert_empty @query.params
34
+ end
35
+
36
+ def test_filters_proc
37
+ assert_equal 2, @query.filters.first.call
38
+ end
39
+
40
+ def test_should_have_term_rule_included
41
+ assert_respond_to @query, :term
42
+ end
43
+
44
+ def test_filters_as_array
45
+ klass = Class.new(Elasticquery::Base)
46
+ klass.filtered { 1 + 1 }
47
+ klass.filtered { 2 + 2 }
48
+
49
+ query = klass.new
50
+ assert_equal 2, query.filters.count
51
+ end
52
+
53
+ def test_should_execute_code_in_instance_context
54
+ klass = Class.new(Elasticquery::Base)
55
+ klass.send(:define_method, :_error_) { raise StandardError, 'from instance context'}
56
+ klass.filtered { _error_ }
57
+
58
+ assert_raises(StandardError) { klass.new.build }
59
+ end
60
+
61
+ def test_build_should_build_on_instance
62
+ @klass.stub :build, {query: 1} do
63
+ assert_equal @klass.build({a: 1}), query: 1
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+ require 'elasticquery/filters/not'
3
+
4
+ class NotFilter < MiniTest::Test
5
+ attr_reader :filters
6
+
7
+ def setup
8
+ @filter = Elasticquery::Filters::Not.new a: 1
9
+ end
10
+
11
+ def test_always_valid
12
+ filter = Elasticquery::Filters::Not.new
13
+ assert filter.valid?
14
+ end
15
+
16
+ def test_to_hash_return_proc
17
+ assert_instance_of Proc, @filter.to_hash
18
+ end
19
+
20
+ def test_to_hash_change_last_term_filter_value
21
+ filter_class = MiniTest::Mock.new
22
+ filter_class.expect :valid?, true
23
+ filter_class.expect :to_hash, {query: {filtered: {filter: {and: {term: {a: 1, b: 2}}}}}}
24
+ previous_filter = MiniTest::Mock.new
25
+ previous_filter.expect :dup_with, filter_class, [{a: 1}]
26
+
27
+ @filters = [previous_filter]
28
+ result = instance_exec &@filter.to_hash
29
+ assert_equal result, query: {filtered: {filter: {and: {not: {filter: {term: {a: 1, b: 2}}}}}}}
30
+
31
+ assert filter_class.verify
32
+ assert previous_filter.verify
33
+ end
34
+
35
+ def test_to_hash_change_last_range_filter_value
36
+ filter_class = MiniTest::Mock.new
37
+ filter_class.expect :valid?, true
38
+ filter_class.expect :to_hash, {query: {filtered: {filter: {and: {range: {a: {lte: 1}}}}}}}
39
+ previous_filter = MiniTest::Mock.new
40
+ previous_filter.expect :dup_with, filter_class, [{a: 1}]
41
+
42
+ @filters = [previous_filter]
43
+ result = instance_exec &@filter.to_hash
44
+ assert_equal result, query: {filtered: {filter: {and: {not: {filter: {range: {a: {lte: 1}}}}}}}}
45
+
46
+ assert filter_class.verify
47
+ assert previous_filter.verify
48
+ end
49
+
50
+ def test_based_new_raise_exception
51
+ assert_raises StandardError do
52
+ @filter.dup_with a: 1
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,44 @@
1
+ require 'test_helper'
2
+ require 'elasticquery/filters/range'
3
+
4
+ class RangeFilter < MiniTest::Test
5
+ def setup
6
+ @filter = Elasticquery::Filters::Range.new 'year', lte: 2015, gte: 1990
7
+ @lte_filter = Elasticquery::Filters::Range.new 'age', lte: 58
8
+ @gte_filter = Elasticquery::Filters::Range.new 'age', gte: 18
9
+ end
10
+
11
+ def test_filter_valid_if_has_at_least_lte_or_gte_option
12
+ filter = Elasticquery::Filters::Range.new 'year', lte: 1
13
+ assert filter.valid?
14
+ filter = Elasticquery::Filters::Range.new 'year', gte: 0
15
+ assert filter.valid?
16
+ end
17
+
18
+ def test_filter_invalid_if_has_no_options
19
+ filter = Elasticquery::Filters::Range.new 'year'
20
+ refute filter.valid?
21
+ end
22
+
23
+ def test_to_hash_should_return_query
24
+ filter = Elasticquery::Filters::Range.new :year, lte: 1, gte: 0
25
+ assert_equal({query: {filtered: {filter: {and: {range: {year: {lte: 1, gte: 0}}}}}}}, filter.to_hash)
26
+ end
27
+
28
+ def test_to_hash_with_one_param
29
+ filter = Elasticquery::Filters::Range.new :year, lte: 1
30
+ assert_equal({query: {filtered: {filter: {and: {range: {year: {lte: 1}}}}}}}, filter.to_hash)
31
+ end
32
+
33
+ def test_to_hash_is_empty_if_invalid
34
+ filter = Elasticquery::Filters::Range.new 'year'
35
+ refute filter.valid?
36
+ assert_equal filter.to_hash, {}
37
+ end
38
+
39
+ def test_dup_with_return_new_range_filter
40
+ filter = Elasticquery::Filters::Range.new 'year'
41
+ new_filter = filter.dup_with 'month', gte: 12
42
+ assert_kind_of Elasticquery::Filters::Range, new_filter
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+ require 'elasticquery/filters/search'
3
+
4
+ class TestSearchFilter < MiniTest::Test
5
+
6
+ def test_default_values_of_filter
7
+ filter = Elasticquery::Filters::Search.new 'hi'
8
+ actual = filter.to_hash[:query][:filtered][:query][:multi_match]
9
+ assert_equal actual, {fields: "_all", operator: "and", type: "best_fields", query: "hi"}
10
+ end
11
+
12
+ def test_passed_parameters
13
+ assert_raises ArgumentError do
14
+ Elasticquery::Filters::Search.new
15
+ end
16
+ end
17
+
18
+ def test_default_query_should_be_valid
19
+ filter = Elasticquery::Filters::Search.new 'hi'
20
+ assert filter.valid?
21
+ end
22
+
23
+ def test_query_fields_validation
24
+ filter = Elasticquery::Filters::Search.new 'hi', fields: 42
25
+ refute filter.valid?
26
+ filter = Elasticquery::Filters::Search.new 'hi', fields: %w(name body)
27
+ assert filter.valid?
28
+ end
29
+
30
+ def test_operator_validation
31
+ filter = Elasticquery::Filters::Search.new 'hi', operator: 'without'
32
+ refute filter.valid?
33
+ filter = Elasticquery::Filters::Search.new 'hi', operator: 'or'
34
+ assert filter.valid?
35
+ end
36
+
37
+ def test_types_validation
38
+ %w(most_fields cross_fields phrase pharse_prefix).each do |type|
39
+ filter = Elasticquery::Filters::Search.new 'hi', type: type
40
+ assert filter.valid?
41
+ end
42
+ filter = Elasticquery::Filters::Search.new 'hi', type: 'random'
43
+ refute filter.valid?
44
+ end
45
+
46
+ def test_to_hash_is_empty_if_invalid
47
+ filter = Elasticquery::Filters::Search.new 'hi', fields: 42
48
+ refute filter.valid?
49
+ assert_equal filter.to_hash, {}
50
+ end
51
+
52
+ def test_dup_with_return_new_search_query
53
+ filter = Elasticquery::Filters::Search.new 'hi', fields: 42
54
+ new_filter = filter.dup_with 'who'
55
+ assert_kind_of Elasticquery::Filters::Search, new_filter
56
+ end
57
+ end
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+ require 'elasticquery/filters/term'
3
+
4
+ class TestTermfilter < MiniTest::Test
5
+
6
+ def test_valid_filter_should_have_one_key
7
+ filter = Elasticquery::Filters::Term.new a: 1
8
+ assert filter.valid?
9
+ end
10
+
11
+ def test_invalid_filter_should_have_no_keys
12
+ filter = Elasticquery::Filters::Term.new
13
+ refute filter.valid?
14
+ end
15
+
16
+ def test_valid_filter_should_have_2_and_more_keys
17
+ filter = Elasticquery::Filters::Term.new a: 1, b: 2
18
+ assert filter.valid?
19
+ end
20
+
21
+ def test_to_hash_should_return_es_query
22
+ filter = Elasticquery::Filters::Term.new a: 1, b: 2
23
+ assert_equal({query: {filtered: {filter: {and: {term: {a: 1, b: 2}}}}}}, filter.to_hash)
24
+ end
25
+
26
+ def test_to_hash_is_empty_if_invalid
27
+ filter = Elasticquery::Filters::Term.new
28
+ refute filter.valid?
29
+ assert_equal filter.to_hash, {}
30
+ end
31
+
32
+ def test_dup_with_return_new_term_filter
33
+ filter = Elasticquery::Filters::Term.new a: 1
34
+ new_filter = filter.dup_with b: 2
35
+ assert_kind_of Elasticquery::Filters::Term, new_filter
36
+ end
37
+ end