elasticquery 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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