stretchy 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/stretchy/boosts/base.rb +4 -1
- data/lib/stretchy/boosts/field_decay_boost.rb +47 -0
- data/lib/stretchy/boosts/filter_boost.rb +3 -0
- data/lib/stretchy/boosts/random_boost.rb +2 -0
- data/lib/stretchy/builders/match_builder.rb +22 -6
- data/lib/stretchy/builders/where_builder.rb +99 -32
- data/lib/stretchy/clauses/base.rb +22 -3
- data/lib/stretchy/clauses/boost_clause.rb +17 -0
- data/lib/stretchy/clauses/boost_match_clause.rb +2 -0
- data/lib/stretchy/clauses/boost_where_clause.rb +2 -0
- data/lib/stretchy/clauses/match_clause.rb +30 -6
- data/lib/stretchy/clauses/where_clause.rb +41 -30
- data/lib/stretchy/filters/and_filter.rb +3 -5
- data/lib/stretchy/filters/base.rb +3 -1
- data/lib/stretchy/filters/bool_filter.rb +2 -0
- data/lib/stretchy/filters/exists_filter.rb +2 -0
- data/lib/stretchy/filters/geo_filter.rb +8 -10
- data/lib/stretchy/filters/not_filter.rb +2 -0
- data/lib/stretchy/filters/or_filter.rb +22 -0
- data/lib/stretchy/filters/query_filter.rb +3 -0
- data/lib/stretchy/filters/range_filter.rb +7 -10
- data/lib/stretchy/filters/terms_filter.rb +2 -0
- data/lib/stretchy/queries/base.rb +3 -1
- data/lib/stretchy/queries/bool_query.rb +2 -0
- data/lib/stretchy/queries/filtered_query.rb +3 -0
- data/lib/stretchy/queries/function_score_query.rb +3 -0
- data/lib/stretchy/queries/match_all_query.rb +2 -0
- data/lib/stretchy/queries/match_query.rb +2 -0
- data/lib/stretchy/results/base.rb +3 -1
- data/lib/stretchy/results/null_results.rb +2 -0
- data/lib/stretchy/types/base.rb +19 -0
- data/lib/stretchy/types/geo_point.rb +30 -0
- data/lib/stretchy/types/range.rb +52 -0
- data/lib/stretchy/utils/client_actions.rb +4 -1
- data/lib/stretchy/utils/contract.rb +6 -2
- data/lib/stretchy/version.rb +1 -1
- data/lib/stretchy.rb +4 -22
- metadata +7 -3
- data/lib/stretchy/boosts/geo_boost.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 124aff086e4d044125b18f641bce3e440a3bec66
|
4
|
+
data.tar.gz: ee1b4ddda1669c13c7c17a9189fd1cda072f77b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a4375aca6e56a4755a693b3d346f945fe6d7aebe819064cbbeeb7e80e66588645ab17be24f8ed88b1ca89738d8eb6f9755cf2f79ccd35c9a60c8a862fd160e3
|
7
|
+
data.tar.gz: dac8575ff96c77ea601051da8fb08489c19c3b17245deed2ff6be48ed58fd2ad9054848025c52a43980a7112ac3277ad604c73ae0f1b1904a6d7eedab42d0715
|
data/lib/stretchy/boosts/base.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
+
require 'stretchy/utils/contract'
|
2
|
+
|
1
3
|
module Stretchy
|
2
4
|
module Boosts
|
3
5
|
class Base
|
4
6
|
|
5
7
|
include Stretchy::Utils::Contract
|
6
8
|
|
7
|
-
DEFAULT_WEIGHT
|
9
|
+
DEFAULT_WEIGHT = 1.2
|
10
|
+
DEFAULT_DECAY_FN = :gauss
|
8
11
|
|
9
12
|
def initialize
|
10
13
|
raise "Override this in subclass"
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'stretchy/boosts/base'
|
2
|
+
require 'stretchy/types/geo_point'
|
3
|
+
|
4
|
+
module Stretchy
|
5
|
+
module Boosts
|
6
|
+
class FieldDecayBoost < Base
|
7
|
+
|
8
|
+
attr_reader :field, :origin, :scale, :offset, :decay, :decay_amount, :weight
|
9
|
+
|
10
|
+
contract field: {type: :field, required: true},
|
11
|
+
origin: {required: true, type: [Numeric, Time, Date, Stretchy::Types::GeoPoint]},
|
12
|
+
scale: {required: true},
|
13
|
+
decay: {type: Numeric},
|
14
|
+
type: {type: :decay}
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
@field = options[:field]
|
18
|
+
@origin = options[:origin]
|
19
|
+
@scale = options[:scale]
|
20
|
+
|
21
|
+
@offset = options[:offset]
|
22
|
+
@type = options[:type] || DEFAULT_DECAY_FN
|
23
|
+
@weight = options[:weight] || DEFAULT_WEIGHT
|
24
|
+
@decay = options[:decay]
|
25
|
+
|
26
|
+
validate!
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_search
|
30
|
+
json = {
|
31
|
+
origin: @origin,
|
32
|
+
scale: @scale,
|
33
|
+
}
|
34
|
+
json[:offset] = @offset if @offset
|
35
|
+
json[:decay] = @decay if @decay
|
36
|
+
|
37
|
+
{
|
38
|
+
@type => {
|
39
|
+
@field => json
|
40
|
+
},
|
41
|
+
weight: @weight
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -2,21 +2,25 @@ module Stretchy
|
|
2
2
|
module Builders
|
3
3
|
class MatchBuilder
|
4
4
|
|
5
|
-
attr_accessor :matches, :antimatches
|
5
|
+
attr_accessor :matches, :antimatches, :shouldmatches, :shouldnotmatches
|
6
6
|
|
7
7
|
def initialize
|
8
|
-
@matches
|
9
|
-
@antimatches
|
8
|
+
@matches = Hash.new { [] }
|
9
|
+
@antimatches = Hash.new { [] }
|
10
|
+
@shouldmatches = Hash.new { [] }
|
11
|
+
@shouldnotmatches = Hash.new { [] }
|
10
12
|
end
|
11
13
|
|
12
14
|
def any?
|
13
|
-
@matches.any? || @antimatches.any?
|
15
|
+
@matches.any? || @antimatches.any? || @shouldmatches.any? || @shouldnotmatches.any?
|
14
16
|
end
|
15
17
|
|
16
18
|
def build
|
17
19
|
return Stretchy::Queries::MatchAllQuery.new unless any?
|
18
20
|
|
19
|
-
if @matches.count > 1
|
21
|
+
if @matches.count > 1 || @antimatches.any? ||
|
22
|
+
@shouldmatches.any? || @shouldnotmatches.any?
|
23
|
+
|
20
24
|
bool_query
|
21
25
|
else
|
22
26
|
field, strings = @matches.first
|
@@ -27,10 +31,22 @@ module Stretchy
|
|
27
31
|
def bool_query
|
28
32
|
Stretchy::Queries::BoolQuery.new(
|
29
33
|
must: to_queries(@matches),
|
30
|
-
must_not: to_queries(@antimatches)
|
34
|
+
must_not: to_queries(@antimatches),
|
35
|
+
should: build_should
|
31
36
|
)
|
32
37
|
end
|
33
38
|
|
39
|
+
def build_should
|
40
|
+
if @shouldnotmatches.any?
|
41
|
+
Stretchy::Queries::BoolQuery.new(
|
42
|
+
must: to_queries(@shouldmatches),
|
43
|
+
must_not: to_queries(@shouldnotmatches)
|
44
|
+
)
|
45
|
+
else
|
46
|
+
to_queries(@shouldmatches)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
34
50
|
private
|
35
51
|
|
36
52
|
def to_queries(matches)
|
@@ -2,19 +2,31 @@ module Stretchy
|
|
2
2
|
module Builders
|
3
3
|
class WhereBuilder
|
4
4
|
|
5
|
-
attr_accessor :terms,
|
6
|
-
:
|
5
|
+
attr_accessor :terms, :antiterms, :shouldterms, :shouldnotterms,
|
6
|
+
:exists, :antiexists, :shouldexists, :shouldnotexists,
|
7
|
+
:ranges, :antiranges, :shouldranges, :shouldnotranges,
|
8
|
+
:geos, :antigeos, :shouldgeos, :shouldnotgeos
|
7
9
|
|
8
10
|
def initialize(options = {})
|
9
|
-
@terms
|
10
|
-
@antiterms
|
11
|
-
@
|
12
|
-
@
|
13
|
-
|
14
|
-
@
|
15
|
-
|
16
|
-
@
|
17
|
-
@
|
11
|
+
@terms = Hash.new { [] }
|
12
|
+
@antiterms = Hash.new { [] }
|
13
|
+
@shouldterms = Hash.new { [] }
|
14
|
+
@shouldnotterms = Hash.new { [] }
|
15
|
+
|
16
|
+
@ranges = {}
|
17
|
+
@antiranges = {}
|
18
|
+
@shouldranges = {}
|
19
|
+
@shouldnotranges = {}
|
20
|
+
|
21
|
+
@geos = {}
|
22
|
+
@antigeos = {}
|
23
|
+
@shouldgeos = {}
|
24
|
+
@shouldnotgeos = {}
|
25
|
+
|
26
|
+
@exists = []
|
27
|
+
@antiexists = []
|
28
|
+
@shouldexists = []
|
29
|
+
@shouldnotexists = []
|
18
30
|
end
|
19
31
|
|
20
32
|
def build
|
@@ -34,32 +46,47 @@ module Stretchy
|
|
34
46
|
end
|
35
47
|
|
36
48
|
def must_nots?
|
37
|
-
@antiterms.any? || @
|
49
|
+
@antiterms.any? || @antiexists.any? ||
|
38
50
|
@antiranges.any? || @antigeos.any?
|
39
51
|
end
|
40
52
|
|
53
|
+
def shoulds?
|
54
|
+
@shouldterms.any? ||
|
55
|
+
@shouldranges.any? ||
|
56
|
+
@shouldgeos.any? ||
|
57
|
+
@shouldexists.any?
|
58
|
+
end
|
59
|
+
|
60
|
+
def should_nots?
|
61
|
+
@shouldnotterms.any? ||
|
62
|
+
@shouldnotranges.any? ||
|
63
|
+
@shouldnotgeos.any? ||
|
64
|
+
@shouldnotexists.any?
|
65
|
+
end
|
66
|
+
|
41
67
|
def use_bool?
|
42
|
-
musts? && must_nots?
|
68
|
+
(musts? && must_nots?) || shoulds? || should_nots?
|
43
69
|
end
|
44
70
|
|
45
71
|
def any?
|
46
|
-
musts? || must_nots?
|
72
|
+
musts? || must_nots? || shoulds? || should_nots?
|
47
73
|
end
|
48
74
|
|
49
75
|
def bool_filter
|
50
76
|
Stretchy::Filters::BoolFilter.new(
|
51
77
|
must: build_filters(
|
52
|
-
terms:
|
78
|
+
terms: @terms,
|
53
79
|
exists: @exists,
|
54
80
|
ranges: @ranges,
|
55
|
-
geos:
|
81
|
+
geos: @geos
|
56
82
|
),
|
57
83
|
must_not: build_filters(
|
58
|
-
terms:
|
59
|
-
exists: @
|
84
|
+
terms: @antiterms,
|
85
|
+
exists: @antiexists,
|
60
86
|
ranges: @antiranges,
|
61
|
-
geos:
|
62
|
-
)
|
87
|
+
geos: @antigeos
|
88
|
+
),
|
89
|
+
should: build_should
|
63
90
|
)
|
64
91
|
end
|
65
92
|
|
@@ -82,20 +109,63 @@ module Stretchy
|
|
82
109
|
def not_filter
|
83
110
|
filter = build_filters(
|
84
111
|
terms: @antiterms,
|
85
|
-
exists: @
|
112
|
+
exists: @antiexists,
|
86
113
|
ranges: @antiranges,
|
87
114
|
geos: @antigeos
|
88
115
|
)
|
89
|
-
filter = Stretchy::Filters::
|
116
|
+
filter = Stretchy::Filters::OrFilter.new(filter) if filter.count > 1
|
90
117
|
Stretchy::Filters::NotFilter.new(filter)
|
91
118
|
end
|
92
119
|
|
120
|
+
def build_should
|
121
|
+
if shoulds? && should_nots?
|
122
|
+
Stretchy::Filters::BoolFilter.new(
|
123
|
+
must: build_filters(
|
124
|
+
terms: @shouldterms,
|
125
|
+
exists: @shouldexists,
|
126
|
+
ranges: @shouldranges,
|
127
|
+
geos: @shouldgeos
|
128
|
+
),
|
129
|
+
must_not: build_filters(
|
130
|
+
terms: @shouldnotterms,
|
131
|
+
exists: @shouldnotexists,
|
132
|
+
ranges: @shouldnotranges,
|
133
|
+
geos: @shouldnotgeos
|
134
|
+
)
|
135
|
+
)
|
136
|
+
elsif should_nots?
|
137
|
+
filters = build_filters(
|
138
|
+
terms: @shouldnotterms,
|
139
|
+
exists: @shouldnotexists,
|
140
|
+
ranges: @shouldnotranges,
|
141
|
+
geos: @shouldnotgeos
|
142
|
+
)
|
143
|
+
if filters.count > 1
|
144
|
+
filters = Stretchy::Filters::OrFilter.new(filters)
|
145
|
+
else
|
146
|
+
filters = filters.first
|
147
|
+
end
|
148
|
+
|
149
|
+
Stretchy::Filters::NotFilter.new(filters)
|
150
|
+
else
|
151
|
+
filters = build_filters(
|
152
|
+
terms: @shouldterms,
|
153
|
+
exists: @shouldexists,
|
154
|
+
ranges: @shouldranges,
|
155
|
+
geos: @shouldgeos
|
156
|
+
)
|
157
|
+
filters = Stretchy::Filters::AndFilter.new(filters) if filters.count > 1
|
158
|
+
filters
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
93
162
|
def build_filters(options = {})
|
94
163
|
filters = []
|
95
|
-
terms
|
96
|
-
ranges
|
97
|
-
geos
|
98
|
-
|
164
|
+
terms = Hash(options[:terms])
|
165
|
+
ranges = Hash(options[:ranges])
|
166
|
+
geos = Hash(options[:geos])
|
167
|
+
near_fields = Hash(options[:near_fields])
|
168
|
+
exists = Array(options[:exists])
|
99
169
|
|
100
170
|
filters << Stretchy::Filters::TermsFilter.new(terms) if terms.any?
|
101
171
|
|
@@ -103,18 +173,15 @@ module Stretchy
|
|
103
173
|
Stretchy::Filters::ExistsFilter.new(field)
|
104
174
|
end
|
105
175
|
|
106
|
-
filters += ranges.map do |field,
|
107
|
-
Stretchy::Filters::RangeFilter.new(
|
108
|
-
field: field,
|
109
|
-
min: values[:min],
|
110
|
-
max: values[:max]
|
111
|
-
)
|
176
|
+
filters += ranges.map do |field, value|
|
177
|
+
Stretchy::Filters::RangeFilter.new(field: field, stretchy_range: value)
|
112
178
|
end
|
113
179
|
|
114
180
|
filters += geos.map do |field, values|
|
115
181
|
Stretchy::Filters::GeoFilter.new(
|
116
182
|
field: field,
|
117
183
|
distance: values[:distance],
|
184
|
+
geo_point: values[:geo_point],
|
118
185
|
lat: values[:lat],
|
119
186
|
lng: values[:lng]
|
120
187
|
)
|
@@ -10,9 +10,8 @@ module Stretchy
|
|
10
10
|
attr_accessor :match_builder, :where_builder, :boost_builder,
|
11
11
|
:aggregate_builder, :inverse, :type, :index_name
|
12
12
|
|
13
|
-
alias :inverse? :inverse
|
14
|
-
|
15
13
|
delegate [:response, :results, :ids, :hits, :took, :shards, :total, :max_score] => :query_results
|
14
|
+
delegate [:range, :geo] => :where
|
16
15
|
|
17
16
|
def initialize(base_or_opts = nil, options = {})
|
18
17
|
if base_or_opts && !base_or_opts.is_a?(Hash)
|
@@ -70,6 +69,26 @@ module Stretchy
|
|
70
69
|
BoostClause.new(self, options)
|
71
70
|
end
|
72
71
|
|
72
|
+
def not(opts_or_string = {}, opts = {})
|
73
|
+
if opts_or_string.is_a?(Hash)
|
74
|
+
WhereClause.new(self, opts_or_string.merge(inverse: true))
|
75
|
+
else
|
76
|
+
MatchClause.new(self, opts_or_string, opts.merge(inverse: true))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def should(opts_or_string = {}, opts = {})
|
81
|
+
if opts_or_string.is_a?(Hash)
|
82
|
+
WhereClause.new(self, opts_or_string.merge(should: true))
|
83
|
+
else
|
84
|
+
MatchClause.new(self, opts_or_string, opts.merge(should: true))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def inverse?
|
89
|
+
!!@inverse
|
90
|
+
end
|
91
|
+
|
73
92
|
def to_search
|
74
93
|
return @to_search if @to_search
|
75
94
|
|
@@ -92,4 +111,4 @@ module Stretchy
|
|
92
111
|
|
93
112
|
end
|
94
113
|
end
|
95
|
-
end
|
114
|
+
end
|
@@ -1,7 +1,13 @@
|
|
1
|
+
require 'stretchy/clauses/base'
|
2
|
+
|
1
3
|
module Stretchy
|
2
4
|
module Clauses
|
3
5
|
class BoostClause < Base
|
4
6
|
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
delegate [:geo, :range] => :where
|
10
|
+
|
5
11
|
def initialize(base, options = {})
|
6
12
|
super(base)
|
7
13
|
@inverse = options.delete(:inverse)
|
@@ -15,6 +21,17 @@ module Stretchy
|
|
15
21
|
BoostWhereClause.new(self, options)
|
16
22
|
end
|
17
23
|
|
24
|
+
def near(options = {})
|
25
|
+
if options[:lat] || options[:latitude] ||
|
26
|
+
options[:lng] || options[:longitude] || options[:lon]
|
27
|
+
|
28
|
+
options[:origin] = Stretchy::Types::GeoPoint.new(options)
|
29
|
+
end
|
30
|
+
@boost_builder.functions << Stretchy::Boosts::FieldDecayBoost.new(options)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
alias :geo :near
|
34
|
+
|
18
35
|
def random(*args)
|
19
36
|
@boost_builder.functions << Stretchy::Boosts::RandomBoost.new(*args)
|
20
37
|
self
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'stretchy/clauses/base'
|
2
|
+
|
1
3
|
module Stretchy
|
2
4
|
module Clauses
|
3
5
|
class MatchClause < Base
|
@@ -10,15 +12,21 @@ module Stretchy
|
|
10
12
|
super(base)
|
11
13
|
if opts_or_str.is_a?(Hash)
|
12
14
|
@inverse = opts_or_str.delete(:inverse) || options.delete(:inverse)
|
15
|
+
@should = opts_or_str.delete(:should) || options.delete(:should)
|
13
16
|
add_params(options.merge(opts_or_str))
|
14
17
|
else
|
15
18
|
@inverse = options.delete(:inverse)
|
19
|
+
@should = options.delete(:should)
|
16
20
|
add_params(options.merge('_all' => opts_or_str))
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
20
24
|
def not(opts_or_str = {}, options = {})
|
21
|
-
self.class.new(self, opts_or_str, options.merge(inverse:
|
25
|
+
self.class.new(self, opts_or_str, options.merge(inverse: true, should: should?))
|
26
|
+
end
|
27
|
+
|
28
|
+
def should(opts_or_str = {}, options = {})
|
29
|
+
self.class.new(self, opts_or_str, options.merge(should: true))
|
22
30
|
end
|
23
31
|
|
24
32
|
def to_boost(weight = nil)
|
@@ -31,8 +39,28 @@ module Stretchy
|
|
31
39
|
)
|
32
40
|
end
|
33
41
|
|
42
|
+
def should?
|
43
|
+
!!@should
|
44
|
+
end
|
45
|
+
|
34
46
|
private
|
35
47
|
|
48
|
+
def get_storage
|
49
|
+
if inverse?
|
50
|
+
if should?
|
51
|
+
@match_builder.shouldnotmatches
|
52
|
+
else
|
53
|
+
@match_builder.antimatches
|
54
|
+
end
|
55
|
+
else
|
56
|
+
if should?
|
57
|
+
@match_builder.shouldmatches
|
58
|
+
else
|
59
|
+
@match_builder.matches
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
36
64
|
def add_params(params = {})
|
37
65
|
case params
|
38
66
|
when Hash
|
@@ -45,11 +73,7 @@ module Stretchy
|
|
45
73
|
end
|
46
74
|
|
47
75
|
def add_param(field, param)
|
48
|
-
|
49
|
-
@match_builder.antimatches[field] += Array(param)
|
50
|
-
else
|
51
|
-
@match_builder.matches[field] += Array(param)
|
52
|
-
end
|
76
|
+
get_storage[field] += Array(param)
|
53
77
|
end
|
54
78
|
|
55
79
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'stretchy/clauses/base'
|
2
|
+
|
1
3
|
module Stretchy
|
2
4
|
module Clauses
|
3
5
|
class WhereClause < Base
|
@@ -9,32 +11,33 @@ module Stretchy
|
|
9
11
|
def initialize(base, options = {})
|
10
12
|
super(base)
|
11
13
|
@inverse = options.delete(:inverse)
|
14
|
+
@should = options.delete(:should)
|
12
15
|
add_params(options)
|
13
16
|
end
|
14
17
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
case options
|
19
|
-
when Hash
|
20
|
-
min = options[:min]
|
21
|
-
max = options[:max]
|
22
|
-
store = inverse? ? @where_builder.antiranges : @where_builder.ranges
|
23
|
-
store[field] = { min: min, max: max }
|
24
|
-
when Range
|
25
|
-
add_param(field, options)
|
26
|
-
end
|
18
|
+
def should?
|
19
|
+
!!@should
|
20
|
+
end
|
27
21
|
|
22
|
+
def range(field, options = {})
|
23
|
+
get_storage(:ranges)[field] = Stretchy::Types::Range.new(options)
|
28
24
|
self
|
29
25
|
end
|
30
26
|
|
31
27
|
def geo(field, options = {})
|
32
|
-
|
28
|
+
get_storage(:geos)[field] = {
|
29
|
+
distance: options[:distance],
|
30
|
+
geo_point: Stretchy::Types::GeoPoint.new(options)
|
31
|
+
}
|
33
32
|
self
|
34
33
|
end
|
35
34
|
|
36
35
|
def not(options = {})
|
37
|
-
self.class.new(self, options.merge(inverse:
|
36
|
+
self.class.new(self, options.merge(inverse: true, should: should?))
|
37
|
+
end
|
38
|
+
|
39
|
+
def should(options = {})
|
40
|
+
self.class.new(self, options.merge(should: true))
|
38
41
|
end
|
39
42
|
|
40
43
|
def to_boost(weight = nil)
|
@@ -68,6 +71,26 @@ module Stretchy
|
|
68
71
|
end
|
69
72
|
|
70
73
|
private
|
74
|
+
|
75
|
+
def get_storage(builder_field, is_inverse = nil)
|
76
|
+
is_inverse = inverse? if is_inverse.nil?
|
77
|
+
field = builder_field.to_s
|
78
|
+
if inverse? || is_inverse
|
79
|
+
if should?
|
80
|
+
field = "shouldnot#{field}"
|
81
|
+
else
|
82
|
+
field = "anti#{field}"
|
83
|
+
end
|
84
|
+
else
|
85
|
+
field = "should#{field}" if should?
|
86
|
+
end
|
87
|
+
|
88
|
+
if field =~ /match/
|
89
|
+
@match_builder.send(field)
|
90
|
+
else
|
91
|
+
@where_builder.send(field)
|
92
|
+
end
|
93
|
+
end
|
71
94
|
|
72
95
|
def add_params(options = {})
|
73
96
|
options.each do |field, param|
|
@@ -83,28 +106,16 @@ module Stretchy
|
|
83
106
|
end
|
84
107
|
end
|
85
108
|
|
86
|
-
def add_geo(field, options = {})
|
87
|
-
store = inverse? ? @where_builder.antigeos : @where_builder.geos
|
88
|
-
store[field] = options
|
89
|
-
end
|
90
|
-
|
91
109
|
def add_param(field, param)
|
92
110
|
case param
|
93
111
|
when nil
|
94
|
-
|
95
|
-
store << field
|
112
|
+
get_storage(:exists, true) << field
|
96
113
|
when String, Symbol
|
97
|
-
|
98
|
-
@match_builder.antimatches[field] += Array(param)
|
99
|
-
else
|
100
|
-
@match_builder.matches[field] += Array(param)
|
101
|
-
end
|
114
|
+
get_storage(:matches)[field] += Array(param)
|
102
115
|
when Range
|
103
|
-
|
104
|
-
store[field] = { min: param.min, max: param.max }
|
116
|
+
get_storage(:ranges)[field] = Stretchy::Types::Range.new(param)
|
105
117
|
else
|
106
|
-
|
107
|
-
store[field] += Array(param)
|
118
|
+
get_storage(:terms)[field] += Array(param)
|
108
119
|
end
|
109
120
|
end
|
110
121
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'stretchy/filters/base'
|
2
|
+
|
1
3
|
module Stretchy
|
2
4
|
module Filters
|
3
5
|
class AndFilter < Base
|
@@ -5,11 +7,7 @@ module Stretchy
|
|
5
7
|
contract filters: {type: Stretchy::Filters::Base, array: true}
|
6
8
|
|
7
9
|
def initialize(*args)
|
8
|
-
|
9
|
-
@filters = args.first
|
10
|
-
else
|
11
|
-
@filters = args
|
12
|
-
end
|
10
|
+
@filters = args.flatten
|
13
11
|
validate!
|
14
12
|
end
|
15
13
|
|
@@ -1,17 +1,18 @@
|
|
1
|
+
require 'stretchy/filters/base'
|
2
|
+
require 'stretchy/types/geo_point'
|
3
|
+
|
1
4
|
module Stretchy
|
2
5
|
module Filters
|
3
6
|
class GeoFilter < Base
|
4
7
|
|
5
8
|
contract distance: {type: :distance, required: true},
|
6
|
-
|
7
|
-
lng: {type: :lng, required: true},
|
9
|
+
geo_point: {type: Stretchy::Types::GeoPoint, required: true},
|
8
10
|
field: {type: :field, required: true}
|
9
11
|
|
10
12
|
def initialize(options = {})
|
11
|
-
@field
|
12
|
-
@distance
|
13
|
-
@
|
14
|
-
@lng = options[:lng]
|
13
|
+
@field = options[:field]
|
14
|
+
@distance = options[:distance]
|
15
|
+
@geo_point = options[:geo_point] || Stretchy::Types::GeoPoint.new(options)
|
15
16
|
validate!
|
16
17
|
end
|
17
18
|
|
@@ -19,10 +20,7 @@ module Stretchy
|
|
19
20
|
{
|
20
21
|
geo_distance: {
|
21
22
|
distance: @distance,
|
22
|
-
@field =>
|
23
|
-
lat: @lat,
|
24
|
-
lon: @lng
|
25
|
-
}
|
23
|
+
@field => @geo_point.to_search
|
26
24
|
}
|
27
25
|
}
|
28
26
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'stretchy/filters/base'
|
2
|
+
|
3
|
+
module Stretchy
|
4
|
+
module Filters
|
5
|
+
class OrFilter < Base
|
6
|
+
|
7
|
+
contract filters: {type: Base, array: true}
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
@filters = args.flatten
|
11
|
+
validate!
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_search
|
15
|
+
{
|
16
|
+
or: @filters.map(&:to_search)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,26 +1,23 @@
|
|
1
|
+
require 'stretchy/filters/base'
|
2
|
+
require 'stretchy/types/range'
|
3
|
+
|
1
4
|
module Stretchy
|
2
5
|
module Filters
|
3
6
|
class RangeFilter < Base
|
4
7
|
|
5
|
-
contract field: {type: :field},
|
6
|
-
|
7
|
-
max: {type: [Numeric, Time]}
|
8
|
+
contract field: {type: :field, required: true},
|
9
|
+
range: {type: Stretchy::Types::Range, required: true}
|
8
10
|
|
9
11
|
def initialize(options = {})
|
10
12
|
@field = options[:field]
|
11
|
-
@
|
12
|
-
@max = options[:max]
|
13
|
+
@range = options[:stretchy_range] || Stretchy::Types::Range.new(options)
|
13
14
|
validate!
|
14
|
-
require_one(min: @min, max: @max)
|
15
15
|
end
|
16
16
|
|
17
17
|
def to_search
|
18
|
-
range = {}
|
19
|
-
range[:gte] = @min if @min
|
20
|
-
range[:lte] = @max if @max
|
21
18
|
{
|
22
19
|
range: {
|
23
|
-
@field => range
|
20
|
+
@field => @range.to_search
|
24
21
|
}
|
25
22
|
}
|
26
23
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'stretchy/utils/contract'
|
2
|
+
|
3
|
+
module Stretchy
|
4
|
+
module Types
|
5
|
+
class Base
|
6
|
+
|
7
|
+
include Stretchy::Utils::Contract
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
raise "Override this in subclass"
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_search
|
14
|
+
raise "Override this in subclass"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'stretchy/types/base'
|
2
|
+
|
3
|
+
module Stretchy
|
4
|
+
module Types
|
5
|
+
class GeoPoint < Base
|
6
|
+
|
7
|
+
attr_reader :lat, :lon
|
8
|
+
|
9
|
+
contract lat: { type: :lat, required: true },
|
10
|
+
lon: { type: :lng, required: true }
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
@lat = options[:lat] || options[:latitude]
|
15
|
+
@lon = options[:lng] || options[:lon] ||
|
16
|
+
options[:longitude]
|
17
|
+
|
18
|
+
validate!
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_search
|
22
|
+
{
|
23
|
+
lat: @lat,
|
24
|
+
lon: @lon
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Types
|
3
|
+
class Range < Base
|
4
|
+
|
5
|
+
attr_reader :min, :max, :exclusive_min, :exclusive_max
|
6
|
+
|
7
|
+
contract min: { type: [Numeric, Date, Time] },
|
8
|
+
max: { type: [Numeric, Date, Time] },
|
9
|
+
exclusive_min: { in: [true, false] },
|
10
|
+
exclusive_max: { in: [true, false] }
|
11
|
+
|
12
|
+
def initialize(opts_or_range = {}, options = {})
|
13
|
+
|
14
|
+
case opts_or_range
|
15
|
+
when ::Range
|
16
|
+
@min = opts_or_range.min
|
17
|
+
@max = opts_or_range.max
|
18
|
+
@exclusive_min = !!(options[:exclusive_min] || options[:exclusive])
|
19
|
+
@exclusive_max = !!(options[:exclusive_max] || options[:exclusive])
|
20
|
+
when ::Hash
|
21
|
+
opts = options.merge(opts_or_range)
|
22
|
+
@min = opts[:min]
|
23
|
+
@max = opts[:max]
|
24
|
+
@exclusive_min = !!(opts[:exclusive_min] || opts[:exclusive])
|
25
|
+
@exclusive_max = !!(opts[:exclusive_max] || opts[:exclusive])
|
26
|
+
else
|
27
|
+
raise Stretchy::Errors::ContractError.new("Ranges must be a range or a hash - found #{options.class.name}")
|
28
|
+
end
|
29
|
+
|
30
|
+
require_one min: @min, max: @max
|
31
|
+
validate!
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_search
|
35
|
+
json = {}
|
36
|
+
if @exclusive_min && @min
|
37
|
+
json[:gt] = @min
|
38
|
+
elsif @min
|
39
|
+
json[:gte] = @min
|
40
|
+
end
|
41
|
+
|
42
|
+
if @exclusive_max && @max
|
43
|
+
json[:lt] = @max
|
44
|
+
elsif @max
|
45
|
+
json[:lte] = @max
|
46
|
+
end
|
47
|
+
json
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'json'
|
2
1
|
module Stretchy
|
3
2
|
module Utils
|
4
3
|
module ClientActions
|
@@ -18,6 +17,10 @@ module Stretchy
|
|
18
17
|
client.cat.count(index: index_name).split(' ')[2].to_i
|
19
18
|
end
|
20
19
|
|
20
|
+
def query(*args, &block)
|
21
|
+
Stretchy::Clauses::Base.new(*args, &block)
|
22
|
+
end
|
23
|
+
|
21
24
|
def search(type:, body:, fields: nil)
|
22
25
|
options = { index: index_name, type: type, body: body }
|
23
26
|
options[:fields] = fields if fields.is_a?(Array)
|
@@ -4,6 +4,7 @@ module Stretchy
|
|
4
4
|
|
5
5
|
ASSERTIONS = [:type, :responds_to, :in, :matches, :required]
|
6
6
|
DISTANCE_FORMAT = /^(\d+)(km|mi)$/
|
7
|
+
DECAY_FUNCTIONS = [:gauss, :linear, :exp]
|
7
8
|
|
8
9
|
def self.included(base)
|
9
10
|
base.send(:extend, ClassMethods)
|
@@ -31,7 +32,7 @@ module Stretchy
|
|
31
32
|
|
32
33
|
def require_one(options = {})
|
33
34
|
if options.values.all?{|v| self.class.is_empty?(v) }
|
34
|
-
raise Stretchy::Errors::ContractError.new("One of #{options.keys.join(', ')} must be present")
|
35
|
+
raise Stretchy::Errors::ContractError.new("One of #{options.keys.join(', ')} must be present, found: #{options}")
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
@@ -60,7 +61,7 @@ module Stretchy
|
|
60
61
|
|
61
62
|
def assert_required(name, value, options)
|
62
63
|
msg = "Expected to have param #{name}, but got nil"
|
63
|
-
fail_assertion(msg) if
|
64
|
+
fail_assertion(msg) if is_empty?(value)
|
64
65
|
end
|
65
66
|
|
66
67
|
def assert_type(name, value, options)
|
@@ -83,6 +84,9 @@ module Stretchy
|
|
83
84
|
|
84
85
|
msg = "Expected #{name} to be a string, symbol, or number, but was blank"
|
85
86
|
fail_assertion(msg) if value.is_a?(String) && value.empty?
|
87
|
+
when :decay
|
88
|
+
msg = "Expected #{name} to be one of #{DECAY_FUNCTIONS.join(', ')}, but got #{value}"
|
89
|
+
fail_assertion(msg) unless DECAY_FUNCTIONS.any?{|f| f == value || f.to_s == value }
|
86
90
|
when Array
|
87
91
|
msg = "Expected #{name} to be one of #{type.map{|t| t.name}}, but got #{value.class.name}"
|
88
92
|
fail_assertion(msg) unless type.any?{|t| value.is_a?(t)}
|
data/lib/stretchy/version.rb
CHANGED
data/lib/stretchy.rb
CHANGED
@@ -4,28 +4,10 @@ require 'forwardable'
|
|
4
4
|
require 'excon'
|
5
5
|
require 'elasticsearch'
|
6
6
|
|
7
|
-
require 'stretchy/utils/contract'
|
8
|
-
require 'stretchy/utils/configuration'
|
9
|
-
require 'stretchy/utils/client_actions'
|
10
|
-
require 'stretchy/boosts/base'
|
11
|
-
require 'stretchy/filters/base'
|
12
|
-
require 'stretchy/queries/base'
|
13
|
-
require 'stretchy/clauses/base'
|
14
|
-
require 'stretchy/results/base'
|
15
|
-
|
16
|
-
grep_require = ->(matches){
|
17
|
-
Dir[File.join(File.dirname(__FILE__), 'stretchy', '**', '*.rb')].each do |path|
|
18
|
-
require path if path =~ matches
|
19
|
-
end
|
20
|
-
}
|
21
|
-
|
22
|
-
grep_require.call(/utils/)
|
23
|
-
grep_require.call(/base/)
|
24
|
-
grep_require.call(/(?!utils|base)/)
|
25
|
-
|
26
7
|
module Stretchy
|
8
|
+
end
|
27
9
|
|
28
|
-
|
29
|
-
extend Utils::ClientActions
|
10
|
+
Gem.find_files('stretchy/**/*.rb').reject{|f| f =~ /spec/ }.each {|f| require f }
|
30
11
|
|
31
|
-
|
12
|
+
Stretchy.send(:extend, Stretchy::Utils::Configuration)
|
13
|
+
Stretchy.send(:extend, Stretchy::Utils::ClientActions)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stretchy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- agius
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elasticsearch
|
@@ -138,8 +138,8 @@ files:
|
|
138
138
|
- bin/setup
|
139
139
|
- lib/stretchy.rb
|
140
140
|
- lib/stretchy/boosts/base.rb
|
141
|
+
- lib/stretchy/boosts/field_decay_boost.rb
|
141
142
|
- lib/stretchy/boosts/filter_boost.rb
|
142
|
-
- lib/stretchy/boosts/geo_boost.rb
|
143
143
|
- lib/stretchy/boosts/random_boost.rb
|
144
144
|
- lib/stretchy/builders/boost_builder.rb
|
145
145
|
- lib/stretchy/builders/match_builder.rb
|
@@ -157,6 +157,7 @@ files:
|
|
157
157
|
- lib/stretchy/filters/exists_filter.rb
|
158
158
|
- lib/stretchy/filters/geo_filter.rb
|
159
159
|
- lib/stretchy/filters/not_filter.rb
|
160
|
+
- lib/stretchy/filters/or_filter.rb
|
160
161
|
- lib/stretchy/filters/query_filter.rb
|
161
162
|
- lib/stretchy/filters/range_filter.rb
|
162
163
|
- lib/stretchy/filters/terms_filter.rb
|
@@ -168,6 +169,9 @@ files:
|
|
168
169
|
- lib/stretchy/queries/match_query.rb
|
169
170
|
- lib/stretchy/results/base.rb
|
170
171
|
- lib/stretchy/results/null_results.rb
|
172
|
+
- lib/stretchy/types/base.rb
|
173
|
+
- lib/stretchy/types/geo_point.rb
|
174
|
+
- lib/stretchy/types/range.rb
|
171
175
|
- lib/stretchy/utils/client_actions.rb
|
172
176
|
- lib/stretchy/utils/configuration.rb
|
173
177
|
- lib/stretchy/utils/contract.rb
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Stretchy
|
2
|
-
module Boosts
|
3
|
-
class GeoBoost < Base
|
4
|
-
|
5
|
-
DEFAULTS = {
|
6
|
-
field: 'coords',
|
7
|
-
offset: '10km',
|
8
|
-
scale: '50km',
|
9
|
-
decay: 0.75,
|
10
|
-
weight: 1.2
|
11
|
-
}.freeze
|
12
|
-
|
13
|
-
contract offset: {type: :distance},
|
14
|
-
scale: {type: :distance},
|
15
|
-
decay: {type: Numeric},
|
16
|
-
weight: {type: Numeric},
|
17
|
-
lat: {type: :lat},
|
18
|
-
lng: {type: :lng}
|
19
|
-
|
20
|
-
def initialize(options = {})
|
21
|
-
@field = options[:field] || DEFAULTS[:field]
|
22
|
-
@offset = options[:offset] || DEFAULTS[:offset]
|
23
|
-
@scale = options[:scale] || DEFAULTS[:scale]
|
24
|
-
@decay = options[:decay] || DEFAULTS[:decay]
|
25
|
-
@weight = options[:weight] || DEFAULTS[:weight]
|
26
|
-
@lat = options[:lat]
|
27
|
-
@lng = options[:lng]
|
28
|
-
validate!
|
29
|
-
end
|
30
|
-
|
31
|
-
def to_search
|
32
|
-
{
|
33
|
-
gauss: {
|
34
|
-
@field => {
|
35
|
-
origin: { lat: @lat, lon: @lng },
|
36
|
-
offset: @offset,
|
37
|
-
scale: @scale,
|
38
|
-
decay: @decay
|
39
|
-
}
|
40
|
-
},
|
41
|
-
weight: @weight
|
42
|
-
}
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|