stretchy 0.3.6 → 0.3.7
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 +4 -4
- data/.yardopts +1 -0
- data/README.md +76 -46
- data/lib/stretchy/clauses/base.rb +152 -0
- data/lib/stretchy/clauses/boost_clause.rb +139 -0
- data/lib/stretchy/clauses/boost_match_clause.rb +59 -0
- data/lib/stretchy/clauses/boost_where_clause.rb +68 -0
- data/lib/stretchy/clauses/match_clause.rb +100 -0
- data/lib/stretchy/clauses/where_clause.rb +148 -0
- data/lib/stretchy/results/base.rb +20 -1
- data/lib/stretchy/utils/client_actions.rb +24 -4
- data/lib/stretchy/utils/colorize.rb +71 -0
- data/lib/stretchy/utils/configuration.rb +15 -2
- data/lib/stretchy/utils/logger.rb +59 -0
- data/lib/stretchy/utils.rb +4 -2
- data/lib/stretchy/version.rb +1 -1
- data/lib/stretchy.rb +2 -0
- data/stretchy.gemspec +1 -0
- metadata +19 -2
@@ -2,10 +2,31 @@ require 'stretchy/clauses/boost_clause'
|
|
2
2
|
|
3
3
|
module Stretchy
|
4
4
|
module Clauses
|
5
|
+
#
|
6
|
+
# Boost documents that match a free-text query. Most
|
7
|
+
# options will be passed into {#initialize}, but you
|
8
|
+
# can also chain `.not` onto it. Calling `.where` or
|
9
|
+
# `.match` from here will apply filters (*not boosts*)
|
10
|
+
# and return to the base state
|
11
|
+
#
|
12
|
+
# @author [atevans]
|
13
|
+
#
|
5
14
|
class BoostMatchClause < BoostClause
|
6
15
|
|
7
16
|
delegate [:range, :geo] => :where
|
8
17
|
|
18
|
+
#
|
19
|
+
# Adds a match query to the boost functions.
|
20
|
+
#
|
21
|
+
# @overload initialize(base, opts_or_string)
|
22
|
+
# @param base [Base] Base query to copy data from
|
23
|
+
# @param opts_or_string [String] String to do a free-text match across the document
|
24
|
+
#
|
25
|
+
# @overload initialize(base, opts_or_string)
|
26
|
+
# @param base [Base] Base query to copy data from
|
27
|
+
# @param options = {} [Hash] Fields and values to match via full-text search
|
28
|
+
#
|
29
|
+
# @return [BoostMatchClause] Boost clause in match context, with queries applied
|
9
30
|
def initialize(base, opts_or_string = {}, options = {})
|
10
31
|
super(base)
|
11
32
|
if opts_or_string.is_a?(Hash)
|
@@ -17,14 +38,52 @@ module Stretchy
|
|
17
38
|
end
|
18
39
|
end
|
19
40
|
|
41
|
+
#
|
42
|
+
# Switches to inverse context, and applies filters as inverse
|
43
|
+
# options (ie, documents that *do not* match the query will
|
44
|
+
# be boosted)
|
45
|
+
#
|
46
|
+
# @overload not(opts_or_string)
|
47
|
+
# @param [String] String that must not match anywhere in the document
|
48
|
+
#
|
49
|
+
# @overload not(opts_or_string)
|
50
|
+
# @param opts_or_string [Hash] Fields and values that should not match in the document
|
51
|
+
#
|
52
|
+
# @return [BoostMatchClause] Query with inverse matching boost function applied
|
20
53
|
def not(opts_or_string = {}, options = {})
|
21
54
|
self.class.new(self, opts_or_string, options.merge(inverse: !inverse?))
|
22
55
|
end
|
23
56
|
|
57
|
+
#
|
58
|
+
# Returns to the base context; filters passed here
|
59
|
+
# will be used to filter documents.
|
60
|
+
#
|
61
|
+
# @example Returning to base context
|
62
|
+
# query.boost.match('string').where(other_field: 64)
|
63
|
+
#
|
64
|
+
# @example Staying in boost context
|
65
|
+
# query.boost.match('string').boost.where(other_field: 99)
|
66
|
+
#
|
67
|
+
# @see {WhereClause#initialize}
|
68
|
+
#
|
69
|
+
# @return [WhereClause] Query with where clause applied
|
24
70
|
def where(*args)
|
25
71
|
WhereClause.new(self, *args)
|
26
72
|
end
|
27
73
|
|
74
|
+
#
|
75
|
+
# Returns to the base context. Queries passed here
|
76
|
+
# will be used to filter documents.
|
77
|
+
#
|
78
|
+
# @example Returning to base context
|
79
|
+
# query.boost.match(message: 'curse word').match('username')
|
80
|
+
#
|
81
|
+
# @example Staying in boost context
|
82
|
+
# query.boost.match(message: 'happy word').boost.match('love')
|
83
|
+
#
|
84
|
+
# @see {MatchClause#initialize}
|
85
|
+
#
|
86
|
+
# @return [MatchClause] Base context with match queries applied
|
28
87
|
def match(*args)
|
29
88
|
MatchClause.new(self, *args)
|
30
89
|
end
|
@@ -2,27 +2,95 @@ require 'stretchy/clauses/boost_clause'
|
|
2
2
|
|
3
3
|
module Stretchy
|
4
4
|
module Clauses
|
5
|
+
#
|
6
|
+
# Boosts documents that match certain filters. Most filters will
|
7
|
+
# be passed into {#initialize}, but you can also use `.range` and
|
8
|
+
# `.geo` .
|
9
|
+
#
|
10
|
+
# @author [atevans]
|
11
|
+
#
|
5
12
|
class BoostWhereClause < BoostClause
|
6
13
|
|
14
|
+
#
|
15
|
+
# Generates a boost that matches a set of filters.
|
16
|
+
#
|
17
|
+
# @param base [Base] Query to copy data from.
|
18
|
+
# @param options = {} [Hash] Fields and values to filter on.
|
19
|
+
#
|
20
|
+
# @see {WhereClause#initialize}
|
21
|
+
#
|
22
|
+
# @return [BoostWhereClause] Query with filter boosts applied
|
7
23
|
def initialize(base, options = {})
|
8
24
|
super(base, options)
|
9
25
|
where_function(:init, options)
|
10
26
|
self
|
11
27
|
end
|
12
28
|
|
29
|
+
#
|
30
|
+
# Returns to the base context; filters passed here
|
31
|
+
# will be used to filter documents.
|
32
|
+
#
|
33
|
+
# @example Returning to base context
|
34
|
+
# query.boost.where(number_field: 33).where(other_field: 64)
|
35
|
+
#
|
36
|
+
# @example Staying in boost context
|
37
|
+
# query.boost.where(number_field: 33).boost.where(other_field: 99)
|
38
|
+
#
|
39
|
+
# @see {WhereClause#initialize}
|
40
|
+
#
|
41
|
+
# @return [WhereClause] Query with where clause applied
|
13
42
|
def where(*args)
|
14
43
|
WhereClause.new(self, *args)
|
15
44
|
end
|
16
45
|
|
46
|
+
#
|
47
|
+
# Returns to the base context. Queries passed here
|
48
|
+
# will be used to filter documents.
|
49
|
+
#
|
50
|
+
# @example Returning to base context
|
51
|
+
# query.boost.where(number_field: 89).match('username')
|
52
|
+
#
|
53
|
+
# @example Staying in boost context
|
54
|
+
# query.boost.where(number_field: 89).boost.match('love')
|
55
|
+
#
|
56
|
+
# @see {MatchClause#initialize}
|
57
|
+
#
|
58
|
+
# @return [MatchClause] Base context with match queries applied
|
17
59
|
def match(*args)
|
18
60
|
MatchClause.new(self, *args)
|
19
61
|
end
|
20
62
|
|
63
|
+
#
|
64
|
+
# Applies a range filter with a min or max
|
65
|
+
# as a boost.
|
66
|
+
#
|
67
|
+
# @see {WhereClause#range}
|
68
|
+
#
|
69
|
+
# @see {Filters::RangeFilter}
|
70
|
+
#
|
71
|
+
# @see http://www.elastic.co/guide/en/elasticsearch/guide/master/_ranges.html Elastic Guides - Ranges
|
72
|
+
#
|
73
|
+
# @return [Base] Query in base context with range boost applied
|
21
74
|
def range(*args)
|
22
75
|
where_function(:range, *args)
|
23
76
|
Base.new(self)
|
24
77
|
end
|
25
78
|
|
79
|
+
#
|
80
|
+
# Boosts a document if it matches a geo filter.
|
81
|
+
# This is different than {BoostClause#near} -
|
82
|
+
# while `.near` applies a decay function that boosts
|
83
|
+
# based on how close a field is to a geo point,
|
84
|
+
# `.geo` applies a filter that either boosts or doesn't
|
85
|
+
# boost the document.
|
86
|
+
#
|
87
|
+
# @see {WhereFunction#geo}
|
88
|
+
#
|
89
|
+
# @see {Filters::GeoFilter}
|
90
|
+
#
|
91
|
+
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-filter.html Elastic Docs - Geo Distance Filter
|
92
|
+
#
|
93
|
+
# @return [Base] Query in base context with geo filter boost applied
|
26
94
|
def geo(*args)
|
27
95
|
where_function(:geo, *args)
|
28
96
|
Base.new(self)
|
@@ -2,12 +2,49 @@ require 'stretchy/clauses/base'
|
|
2
2
|
|
3
3
|
module Stretchy
|
4
4
|
module Clauses
|
5
|
+
#
|
6
|
+
# A Match clause inherits the same state as any clause.
|
7
|
+
# There aren't any more specific methods to chain, as
|
8
|
+
# this clause only handles basic full-text searches.
|
9
|
+
#
|
10
|
+
# @author [atevans]
|
11
|
+
#
|
5
12
|
class MatchClause < Base
|
6
13
|
|
14
|
+
#
|
15
|
+
# Creates a temporary MatchClause outside the main
|
16
|
+
# query scope by using a new {Base}. Primarily
|
17
|
+
# used in {BoostClause} for boosting on full-text
|
18
|
+
# matches.
|
19
|
+
#
|
20
|
+
# @param options = {} [Hash] Options to pass to the full-text match
|
21
|
+
#
|
22
|
+
# @return [MatchClause] Temporary clause outside current state
|
7
23
|
def self.tmp(options = {})
|
8
24
|
self.new(Base.new, options)
|
9
25
|
end
|
10
26
|
|
27
|
+
#
|
28
|
+
# Creates a new state with a match query applied.
|
29
|
+
#
|
30
|
+
# @overload initialize(base, opts_or_string)
|
31
|
+
# @param [Base] Base clause to copy data from
|
32
|
+
# @param [String] Performs a full-text query for this string on all fields in the document.
|
33
|
+
#
|
34
|
+
# @overload initialize(base, opts_or_string)
|
35
|
+
# @param [Base] Base clause to copy data from
|
36
|
+
# @param [Hash] A hash of fields and values to perform full-text matches with
|
37
|
+
#
|
38
|
+
# @example A basic full-text match
|
39
|
+
# query.match("anywhere in document")
|
40
|
+
#
|
41
|
+
# @example A full-text search on specific fields
|
42
|
+
# query.match(
|
43
|
+
# my_field: "match in my_field",
|
44
|
+
# other_field: "match in other_field"
|
45
|
+
# )
|
46
|
+
#
|
47
|
+
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html Elastic Docs - Match Query
|
11
48
|
def initialize(base, opts_or_str = {}, options = {})
|
12
49
|
super(base)
|
13
50
|
if opts_or_str.is_a?(Hash)
|
@@ -21,14 +58,73 @@ module Stretchy
|
|
21
58
|
end
|
22
59
|
end
|
23
60
|
|
61
|
+
#
|
62
|
+
# Switches to inverted context. Matches applied here work the same way as
|
63
|
+
# {#initialize}, but returned documents must **not** match these filters.
|
64
|
+
#
|
65
|
+
# @overload not(opts_or_str)
|
66
|
+
# @param [String] A string that must not be matched anywhere in the document
|
67
|
+
# @overload not(opts_or_str)
|
68
|
+
# @param [Hash] A hash of fields and strings that must not be matched in those fields
|
69
|
+
#
|
70
|
+
# @return [MatchClause] inverted query state with match filters applied
|
71
|
+
#
|
72
|
+
# @example Inverted full-text
|
73
|
+
# query.match.not("hello")
|
74
|
+
#
|
75
|
+
# @example Inverted full-text matching for specific fields
|
76
|
+
# query.match.not(
|
77
|
+
# my_field: "not_match_1",
|
78
|
+
# other_field: "not_match_2"
|
79
|
+
# )
|
24
80
|
def not(opts_or_str = {}, options = {})
|
25
81
|
self.class.new(self, opts_or_str, options.merge(inverse: true, should: should?))
|
26
82
|
end
|
27
83
|
|
84
|
+
#
|
85
|
+
# Switches to `should` context. Applies full-text matches
|
86
|
+
# that are not required, but boost the relevance score for
|
87
|
+
# matching documents.
|
88
|
+
#
|
89
|
+
# Can be chained with {#not}
|
90
|
+
#
|
91
|
+
# @overload not(opts_or_str)
|
92
|
+
# @param [String] A string that should be matched anywhere in the document
|
93
|
+
# @overload not(opts_or_str)
|
94
|
+
# @param [Hash] A hash of fields and strings that should be matched in those fields
|
95
|
+
#
|
96
|
+
# @param opts_or_str = {} [type] [description]
|
97
|
+
# @param options = {} [type] [description]
|
98
|
+
#
|
99
|
+
# @return [MatchClause] query state with should filters added
|
100
|
+
#
|
101
|
+
# @example Should match with full-text
|
102
|
+
# query.match.should("anywhere")
|
103
|
+
#
|
104
|
+
# @example Should match specific fields
|
105
|
+
# query.match.should(
|
106
|
+
# field_one: "one",
|
107
|
+
# field_two: "two"
|
108
|
+
# )
|
109
|
+
#
|
110
|
+
# @example Should not match
|
111
|
+
# query.match.should.not(
|
112
|
+
# field_one: "one",
|
113
|
+
# field_two: "two"
|
114
|
+
# )
|
115
|
+
#
|
116
|
+
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html Elastic Docs - Bool Query
|
28
117
|
def should(opts_or_str = {}, options = {})
|
29
118
|
self.class.new(self, opts_or_str, options.merge(should: true))
|
30
119
|
end
|
31
120
|
|
121
|
+
#
|
122
|
+
# Converts this match context to a set of boosts
|
123
|
+
# to use in a {Stretchy::Queries::FunctionScoreQuery}
|
124
|
+
#
|
125
|
+
# @param weight = nil [Numeric] Weight of generated boost
|
126
|
+
#
|
127
|
+
# @return [Stretchy::Boosts::FilterBoost] boost containing these match parameters
|
32
128
|
def to_boost(weight = nil)
|
33
129
|
weight ||= Stretchy::Boosts::FilterBoost::DEFAULT_WEIGHT
|
34
130
|
Stretchy::Boosts::FilterBoost.new(
|
@@ -39,6 +135,10 @@ module Stretchy
|
|
39
135
|
)
|
40
136
|
end
|
41
137
|
|
138
|
+
#
|
139
|
+
# Accessor for `@should`
|
140
|
+
#
|
141
|
+
# @return [true, false] `@should`
|
42
142
|
def should?
|
43
143
|
!!@should
|
44
144
|
end
|
@@ -2,12 +2,62 @@ require 'stretchy/clauses/base'
|
|
2
2
|
|
3
3
|
module Stretchy
|
4
4
|
module Clauses
|
5
|
+
#
|
6
|
+
# A Where clause inherits the same state as any clause,
|
7
|
+
# but has a few possible states to transition to. You
|
8
|
+
# can call {#range} and {#geo} to add their respective
|
9
|
+
# filters, or you can transition to the inverted state
|
10
|
+
# via {#not}, or the `should` state via {#should}
|
11
|
+
#
|
12
|
+
# ### STATES:
|
13
|
+
# * **inverted:** any filters added in this state will be
|
14
|
+
# inverted, ie the document must **NOT** match said
|
15
|
+
# filters.
|
16
|
+
# * **should:** any filters added in this state will be
|
17
|
+
# applied to a `should` block. Documents which do
|
18
|
+
# not match these filters will be returned, but
|
19
|
+
# documents which do match will have a higher
|
20
|
+
# relevance score.
|
21
|
+
#
|
22
|
+
# @author [atevans]
|
23
|
+
#
|
5
24
|
class WhereClause < Base
|
6
25
|
|
26
|
+
#
|
27
|
+
# Creates a temporary context by initializing a new Base object.
|
28
|
+
# Used primarily in {BoostWhereClause}
|
29
|
+
#
|
30
|
+
# @param options = {} [Hash] Options to filter on
|
31
|
+
#
|
32
|
+
# @return [WhereClause] A clause outside the main query context
|
7
33
|
def self.tmp(options = {})
|
8
34
|
self.new(Base.new, options)
|
9
35
|
end
|
10
36
|
|
37
|
+
#
|
38
|
+
# Options passed to the initializer will be interpreted as filters
|
39
|
+
# to be added to the query. This is similar to ActiveRecord's `where`
|
40
|
+
# method.
|
41
|
+
#
|
42
|
+
# @param base [Base] Used to intialize the new state from the previous clause
|
43
|
+
# @param options = {} [Hash] filters to be applied to the new state
|
44
|
+
# @option options [true, false] :inverted (nil) Whether the new state is inverted
|
45
|
+
# @option options [true, false] :should (nil) Whether the new state is should
|
46
|
+
#
|
47
|
+
# @example Apply ActiveRecord-like filters
|
48
|
+
# query.where(
|
49
|
+
# string_field: "string",
|
50
|
+
# must_not_exist: nil,
|
51
|
+
# in_range: 27..33,
|
52
|
+
# included_in: [47, 23, 86]
|
53
|
+
# )
|
54
|
+
#
|
55
|
+
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-filter.html Elastic Docs - Terms Filter
|
56
|
+
#
|
57
|
+
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-filter.html Elastic Docs - Exists Filter
|
58
|
+
#
|
59
|
+
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-filter.html Elastic Docs - Range Filter
|
60
|
+
#
|
11
61
|
def initialize(base, options = {})
|
12
62
|
super(base)
|
13
63
|
@inverse = options.delete(:inverse)
|
@@ -15,15 +65,62 @@ module Stretchy
|
|
15
65
|
add_params(options)
|
16
66
|
end
|
17
67
|
|
68
|
+
#
|
69
|
+
# Accessor for `@should`
|
70
|
+
#
|
71
|
+
# @return [true, false] `@should`
|
18
72
|
def should?
|
19
73
|
!!@should
|
20
74
|
end
|
21
75
|
|
76
|
+
#
|
77
|
+
# Add a range filter to the current context. While
|
78
|
+
# you can pass a `Range` object to {#where}, this
|
79
|
+
# allows you to specify an open-ended range, such
|
80
|
+
# as only specifying the minimum or maximum value.
|
81
|
+
#
|
82
|
+
# @param field [String, Symbol] The field to filter with this range
|
83
|
+
# @param options = {} [Hash] Options for the range
|
84
|
+
# @option options [Numeric] :min (nil) Minimum. Ranges are _inclusive_ by default
|
85
|
+
# @option options [Numeric] :max (nil) Maximum. Ranges are _inclusive_ by default
|
86
|
+
# @option options [true, false] :exclusive (nil) Overrides default and makes the range exclusive -
|
87
|
+
# equivalent to passing both `:exclusive_min` and `:exclusive_max`
|
88
|
+
# @option options [true, false] :exclusive_min (nil) Overrides default and makes the minimum exclusive
|
89
|
+
# @option options [true, false] :exclusive_max (nil) Overrides default and makes the maximum exclusive
|
90
|
+
#
|
91
|
+
# @return [self] query state with range filter applied
|
92
|
+
#
|
93
|
+
# @example Adding a range filter
|
94
|
+
# query.where.range(:my_range_field,
|
95
|
+
# min: 33,
|
96
|
+
# exclusive: true
|
97
|
+
# )
|
22
98
|
def range(field, options = {})
|
23
99
|
get_storage(:ranges)[field] = Stretchy::Types::Range.new(options)
|
24
100
|
self
|
25
101
|
end
|
26
102
|
|
103
|
+
#
|
104
|
+
# Adds a geo distance filter to the current context.
|
105
|
+
# Documents must have a `geo_point` field that is within
|
106
|
+
# the specified distance of the passed parameters.
|
107
|
+
#
|
108
|
+
# @param field [String, Symbol] The field this filter will be applied to.
|
109
|
+
# @param options = {} [Hash] Options for the geo distance filter
|
110
|
+
# @option options [String] :distance (nil) The maximum distance from the specified origin.
|
111
|
+
# Use an Elastic distance format such as `'21mi'` or `'37km'`
|
112
|
+
# @option options [Float] :lat (nil) The latitude of the origin point. Can also be specified as `:latitude`
|
113
|
+
# @option options [Float] :lng (nil) The longitude of the origin point.
|
114
|
+
# Can also be specified as `:lon` or `:longitude`
|
115
|
+
#
|
116
|
+
# @return [self] query state with geo distance filter applied
|
117
|
+
#
|
118
|
+
# @example Searching by distance from a point
|
119
|
+
# query.where.geo(:coords,
|
120
|
+
# distance: '27km',
|
121
|
+
# lat: 33.3,
|
122
|
+
# lng: 29.2
|
123
|
+
# )
|
27
124
|
def geo(field, options = {})
|
28
125
|
get_storage(:geos)[field] = {
|
29
126
|
distance: options[:distance],
|
@@ -32,14 +129,65 @@ module Stretchy
|
|
32
129
|
self
|
33
130
|
end
|
34
131
|
|
132
|
+
#
|
133
|
+
# Switches current state to inverted. Options passed
|
134
|
+
# here are equivalent to those passed to {#initialize},
|
135
|
+
# except documents *must not* match these filters.
|
136
|
+
#
|
137
|
+
# Can be chained with {#should} to produce inverted should queries
|
138
|
+
#
|
139
|
+
# @param options = {} [Hash] Options to filter on
|
140
|
+
#
|
141
|
+
# @return [WhereClause] inverted query state with not filters applied.
|
142
|
+
#
|
143
|
+
# @example Inverting filters
|
144
|
+
# query.where.not(
|
145
|
+
# must_exist: nil,
|
146
|
+
# not_matching: "this string",
|
147
|
+
# not_in: [45, 67, 99],
|
148
|
+
# not_in_range: 89..23
|
149
|
+
# )
|
150
|
+
#
|
151
|
+
# @example Inverted should filters
|
152
|
+
# query.should.not(
|
153
|
+
# match_field: [:these, "options"]
|
154
|
+
# )
|
35
155
|
def not(options = {})
|
36
156
|
self.class.new(self, options.merge(inverse: true, should: should?))
|
37
157
|
end
|
38
158
|
|
159
|
+
#
|
160
|
+
# Switches the current state to `should`. Options passed
|
161
|
+
# here are equivalent to those passed to {#initialize},
|
162
|
+
# except documents which do not match are still returned
|
163
|
+
# with a lower score than documents which do match.
|
164
|
+
#
|
165
|
+
# Can be chained with {#not} to produce inverted should queries
|
166
|
+
#
|
167
|
+
# @param options = {} [Hash] Options to filter on
|
168
|
+
#
|
169
|
+
# @return [WhereClause] should query state with should filters applied
|
170
|
+
#
|
171
|
+
# @example Specifying should options
|
172
|
+
# query.should(
|
173
|
+
# field: [99, 27]
|
174
|
+
# )
|
175
|
+
#
|
176
|
+
# @example Inverted should options
|
177
|
+
# query.should.not(
|
178
|
+
# exists_field: nil
|
179
|
+
# )
|
39
180
|
def should(options = {})
|
40
181
|
self.class.new(self, options.merge(should: true))
|
41
182
|
end
|
42
183
|
|
184
|
+
#
|
185
|
+
# Converts the current context into a boost to
|
186
|
+
# be passed into a {FunctionScoreQuery}.
|
187
|
+
#
|
188
|
+
# @param weight = nil [Numeric] A weight for the {FunctionScoreQuery}
|
189
|
+
#
|
190
|
+
# @return [Boosts::FilterBoost] A boost including all the current filters
|
43
191
|
def to_boost(weight = nil)
|
44
192
|
weight ||= Stretchy::Boosts::FilterBoost::DEFAULT_WEIGHT
|
45
193
|
|
@@ -26,7 +26,14 @@ module Stretchy
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def response
|
29
|
-
|
29
|
+
params = {
|
30
|
+
type: type,
|
31
|
+
body: request,
|
32
|
+
from: offset,
|
33
|
+
size: limit
|
34
|
+
}
|
35
|
+
params[:explain] = true if clause.get_explain
|
36
|
+
@response ||= Stretchy.search(params)
|
30
37
|
end
|
31
38
|
|
32
39
|
def ids
|
@@ -41,6 +48,18 @@ module Stretchy
|
|
41
48
|
end
|
42
49
|
alias :results :hits
|
43
50
|
|
51
|
+
def scores
|
52
|
+
@scores ||= Hash[response['hits']['hits'].map do |hit|
|
53
|
+
[hit['_id'], hit['_score']]
|
54
|
+
end]
|
55
|
+
end
|
56
|
+
|
57
|
+
def explanations
|
58
|
+
@scores ||= Hash[response['hits']['hits'].map do |hit|
|
59
|
+
[hit['_id'], hit['_explanation']]
|
60
|
+
end]
|
61
|
+
end
|
62
|
+
|
44
63
|
def took
|
45
64
|
@took ||= response['took']
|
46
65
|
end
|
@@ -8,12 +8,14 @@ module Stretchy
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
# used for ensuring a
|
11
|
+
# used for ensuring a consistent index in specs
|
12
12
|
def refresh
|
13
|
+
Stretchy.log("Refreshing index: #{index_name}")
|
13
14
|
client.indices.refresh index: index_name
|
14
15
|
end
|
15
16
|
|
16
17
|
def count
|
18
|
+
Stretchy.log("Counting all documents in index: #{index_name}")
|
17
19
|
client.cat.count(index: index_name).split(' ')[2].to_i
|
18
20
|
end
|
19
21
|
|
@@ -30,7 +32,10 @@ module Stretchy
|
|
30
32
|
params[field] = options[field] if options[field]
|
31
33
|
end
|
32
34
|
|
33
|
-
|
35
|
+
Stretchy.log("Querying Elastic:", params)
|
36
|
+
response = client.search(params)
|
37
|
+
Stretchy.log("Received response:", response)
|
38
|
+
response
|
34
39
|
end
|
35
40
|
|
36
41
|
def index(options = {})
|
@@ -38,7 +43,16 @@ module Stretchy
|
|
38
43
|
type = options[:type]
|
39
44
|
body = options[:body]
|
40
45
|
id = options[:id] || options['id'] || body['id'] || body['_id'] || body[:id] || body[:_id]
|
41
|
-
|
46
|
+
params = {
|
47
|
+
index: index,
|
48
|
+
type: type,
|
49
|
+
id: id,
|
50
|
+
body: body
|
51
|
+
}
|
52
|
+
Stretchy.log("Indexing document:", params)
|
53
|
+
response = client.index(params)
|
54
|
+
Stretchy.log("Received response:", response)
|
55
|
+
response
|
42
56
|
end
|
43
57
|
|
44
58
|
def bulk(options = {})
|
@@ -51,23 +65,29 @@ module Stretchy
|
|
51
65
|
document
|
52
66
|
]
|
53
67
|
end
|
54
|
-
|
68
|
+
Stretchy.log("Bulk indexing documents:", {body: requests})
|
69
|
+
response = client.bulk body: requests
|
70
|
+
Stretchy.log("Received response:", response)
|
55
71
|
end
|
56
72
|
|
57
73
|
def exists(_index_name = index_name)
|
74
|
+
Stretchy.log("Checking index existence for: #{_index_name}")
|
58
75
|
client.indices.exists(index: _index_name)
|
59
76
|
end
|
60
77
|
alias :exists? :exists
|
61
78
|
|
62
79
|
def delete(_index_name = index_name)
|
80
|
+
Stretchy.log("Deleting index: #{_index_name}")
|
63
81
|
client.indices.delete(index: _index_name) if exists?(_index_name)
|
64
82
|
end
|
65
83
|
|
66
84
|
def create(_index_name = index_name)
|
85
|
+
Stretchy.log("Creating index: #{_index_name}")
|
67
86
|
client.indices.create(index: _index_name) unless exists?(_index_name)
|
68
87
|
end
|
69
88
|
|
70
89
|
def mapping(_index_name, _type, _body)
|
90
|
+
Stretchy.log("Putting mapping:", {index_name: _index_name, type: _type, body: _body})
|
71
91
|
client.indices.put_mapping(index: _index_name, type: _type, body: _body)
|
72
92
|
end
|
73
93
|
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Utils
|
3
|
+
class Colorize
|
4
|
+
COLORS = {
|
5
|
+
"default" => "38",
|
6
|
+
"black" => "30",
|
7
|
+
"red" => "31",
|
8
|
+
"green" => "32",
|
9
|
+
"brown" => "33",
|
10
|
+
"blue" => "34",
|
11
|
+
"purple" => "35",
|
12
|
+
"cyan" => "36",
|
13
|
+
"gray" => "37",
|
14
|
+
"dark gray" => "1;30",
|
15
|
+
"light red" => "1;31",
|
16
|
+
"light green" => "1;32",
|
17
|
+
"yellow" => "1;33",
|
18
|
+
"light blue" => "1;34",
|
19
|
+
"light purple" => "1;35",
|
20
|
+
"light cyan" => "1;36",
|
21
|
+
"white" => "1;37"
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
BG_COLORS = {
|
25
|
+
"default" => "0",
|
26
|
+
"black" => "40",
|
27
|
+
"red" => "41",
|
28
|
+
"green" => "42",
|
29
|
+
"brown" => "43",
|
30
|
+
"blue" => "44",
|
31
|
+
"purple" => "45",
|
32
|
+
"cyan" => "46",
|
33
|
+
"gray" => "47",
|
34
|
+
"dark gray" => "100",
|
35
|
+
"light red" => "101",
|
36
|
+
"light green" => "102",
|
37
|
+
"yellow" => "103",
|
38
|
+
"light blue" => "104",
|
39
|
+
"light purple" => "105",
|
40
|
+
"light cyan" => "106",
|
41
|
+
"white" => "107"
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
def colorize(string, color = "default", bg = "default")
|
45
|
+
color_code = COLORS[color]
|
46
|
+
bg_code = BG_COLORS[bg]
|
47
|
+
return "\033[#{bg_code};#{color_code}m#{string}\033[0m"
|
48
|
+
end
|
49
|
+
|
50
|
+
module ClassMethods
|
51
|
+
def colors
|
52
|
+
Colorize::COLORS
|
53
|
+
end
|
54
|
+
|
55
|
+
def bgs
|
56
|
+
Colorize::BG_COLORS
|
57
|
+
end
|
58
|
+
|
59
|
+
Colorize::COLORS.keys.each do |color|
|
60
|
+
define_method color do |string|
|
61
|
+
color_code = colors[color]
|
62
|
+
bg_code = bgs["default"]
|
63
|
+
string.split("\n").map{|s| "\033[#{bg_code};#{color_code}m#{s}\033[0m" }.join("\n")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
extend ClassMethods
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|