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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +165 -0
- data/Rakefile +21 -0
- data/elasticquery.gemspec +29 -0
- data/lib/elasticquery.rb +6 -0
- data/lib/elasticquery/base.rb +80 -0
- data/lib/elasticquery/filters/base.rb +18 -0
- data/lib/elasticquery/filters/not.rb +30 -0
- data/lib/elasticquery/filters/range.rb +29 -0
- data/lib/elasticquery/filters/search.rb +53 -0
- data/lib/elasticquery/filters/term.rb +41 -0
- data/lib/elasticquery/queries/all.rb +34 -0
- data/lib/elasticquery/query.rb +72 -0
- data/lib/elasticquery/version.rb +4 -0
- data/test/base_test.rb +66 -0
- data/test/filters/not_test.rb +55 -0
- data/test/filters/range_test.rb +44 -0
- data/test/filters/search_test.rb +57 -0
- data/test/filters/term_test.rb +37 -0
- data/test/integration/chainable_call_test.rb +31 -0
- data/test/integration/not_case_test.rb +28 -0
- data/test/integration/queries_inheritence_test.rb +64 -0
- data/test/integration/range_case_test.rb +25 -0
- data/test/integration/search_case_test.rb +48 -0
- data/test/integration/term_case_test.rb +24 -0
- data/test/queries/all_test.rb +41 -0
- data/test/query_test.rb +70 -0
- data/test/test_helper.rb +8 -0
- metadata +188 -0
@@ -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
|
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
|