stretchy 0.4.0 → 0.4.1
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/.gitignore +2 -1
- data/.ruby-version +1 -0
- data/lib/stretchy/boosts/base.rb +2 -3
- data/lib/stretchy/boosts/field_decay_boost.rb +23 -19
- data/lib/stretchy/boosts/filter_boost.rb +6 -3
- data/lib/stretchy/boosts/random_boost.rb +7 -4
- data/lib/stretchy/builders/boost_builder.rb +1 -1
- data/lib/stretchy/builders/filter_builder.rb +55 -0
- data/lib/stretchy/builders/match_builder.rb +41 -43
- data/lib/stretchy/builders/query_builder.rb +37 -0
- data/lib/stretchy/builders/shell_builder.rb +53 -0
- data/lib/stretchy/builders/where_builder.rb +88 -159
- data/lib/stretchy/builders.rb +3 -0
- data/lib/stretchy/clauses/base.rb +33 -78
- data/lib/stretchy/clauses/boost_clause.rb +10 -10
- data/lib/stretchy/clauses/boost_match_clause.rb +3 -3
- data/lib/stretchy/clauses/boost_where_clause.rb +5 -5
- data/lib/stretchy/clauses/match_clause.rb +5 -24
- data/lib/stretchy/clauses/where_clause.rb +23 -42
- data/lib/stretchy/errors/validation_error.rb +17 -0
- data/lib/stretchy/errors.rb +1 -1
- data/lib/stretchy/filters/and_filter.rb +5 -1
- data/lib/stretchy/filters/base.rb +2 -2
- data/lib/stretchy/filters/bool_filter.rb +10 -4
- data/lib/stretchy/filters/exists_filter.rb +5 -1
- data/lib/stretchy/filters/geo_filter.rb +13 -7
- data/lib/stretchy/filters/not_filter.rb +5 -2
- data/lib/stretchy/filters/or_filter.rb +4 -1
- data/lib/stretchy/filters/query_filter.rb +5 -1
- data/lib/stretchy/filters/range_filter.rb +10 -5
- data/lib/stretchy/filters/terms_filter.rb +8 -2
- data/lib/stretchy/queries/base.rb +2 -2
- data/lib/stretchy/queries/bool_query.rb +12 -0
- data/lib/stretchy/queries/filtered_query.rb +8 -3
- data/lib/stretchy/queries/function_score_query.rb +25 -32
- data/lib/stretchy/queries/match_query.rb +10 -3
- data/lib/stretchy/results/base.rb +9 -23
- data/lib/stretchy/types/base.rb +2 -2
- data/lib/stretchy/types/geo_point.rb +15 -6
- data/lib/stretchy/types/range.rb +24 -6
- data/lib/stretchy/utils/logger.rb +10 -5
- data/lib/stretchy/utils/validation.rb +77 -0
- data/lib/stretchy/utils.rb +1 -1
- data/lib/stretchy/version.rb +1 -1
- data/lib/stretchy.rb +3 -0
- data/lib/stretchy_validations.rb +10 -0
- data/lib/validation/rule/decay.rb +20 -0
- data/lib/validation/rule/distance.rb +20 -0
- data/lib/validation/rule/field.rb +22 -0
- data/lib/validation/rule/inclusion.rb +33 -0
- data/lib/validation/rule/latitude.rb +21 -0
- data/lib/validation/rule/longitude.rb +22 -0
- data/lib/validation/rule/one_of.rb +22 -0
- data/lib/validation/rule/required.rb +26 -0
- data/lib/validation/rule/responds_to.rb +23 -0
- data/lib/validation/rule/type.rb +41 -0
- data/stretchy.gemspec +2 -0
- metadata +48 -5
- data/lib/stretchy/errors/contract_error.rb +0 -5
- data/lib/stretchy/utils/contract.rb +0 -120
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53b93c00801430b33a5fe52f6ad1327b25049979
|
4
|
+
data.tar.gz: 238e3d9edb267bd9ddfe03706fc6ffc28f4ce820
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9699a839cbbb7808d1fcedd217744bb45661804be0d14f5d729cce0986f43f1653698db9fb3aa2267ce9a9cc19450a02e21e390acd6e3772b8ee4d7545b40fe
|
7
|
+
data.tar.gz: 7f865b3f1d4c7fb0e61d58b33bbe808aa0c6cc1c44d93545c2000f4819cc43b27b909e29e751745b0d8d002ade78c24b41d04099f10be7f5b4c15e75b2a45984
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.2
|
data/lib/stretchy/boosts/base.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
require 'stretchy/utils/
|
1
|
+
require 'stretchy/utils/validation'
|
2
2
|
|
3
3
|
module Stretchy
|
4
4
|
module Boosts
|
5
5
|
class Base
|
6
6
|
|
7
|
-
include Stretchy::Utils::
|
7
|
+
include Stretchy::Utils::Validation
|
8
8
|
|
9
9
|
DEFAULT_WEIGHT = 1.2
|
10
|
-
DEFAULT_DECAY_FN = :gauss
|
11
10
|
|
12
11
|
def initialize
|
13
12
|
raise "Override this in subclass"
|
@@ -5,39 +5,43 @@ module Stretchy
|
|
5
5
|
module Boosts
|
6
6
|
class FieldDecayBoost < Base
|
7
7
|
|
8
|
-
|
8
|
+
DEFAULT_DECAY_FN = :gauss
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
attribute :field
|
11
|
+
attribute :origin
|
12
|
+
attribute :scale
|
13
|
+
attribute :offset
|
14
|
+
attribute :decay, Symbol, default: DEFAULT_DECAY_FN
|
15
|
+
attribute :decay_amount, Float, default: DEFAULT_WEIGHT
|
16
|
+
attribute :weight
|
17
|
+
|
18
|
+
validations do
|
19
|
+
rule :field, :field
|
20
|
+
rule :origin, type: {classes: [Numeric, Time, Date, Stretchy::Types::GeoPoint], required: true}
|
21
|
+
rule :scale, :required
|
22
|
+
rule :decay, :decay
|
23
|
+
rule :decay_amount, type: {classes: Numeric}
|
24
|
+
end
|
15
25
|
|
16
26
|
def initialize(options = {})
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@offset = options[:offset]
|
22
|
-
@type = options[:type] || DEFAULT_DECAY_FN
|
23
|
-
@weight = options[:weight] || DEFAULT_WEIGHT
|
24
|
-
@decay = options[:decay]
|
25
|
-
|
27
|
+
self.class.attribute_set.set(self, options) if options
|
28
|
+
set_default_attributes
|
29
|
+
|
26
30
|
validate!
|
27
31
|
end
|
28
32
|
|
29
33
|
def to_search
|
30
34
|
json = {scale: @scale}
|
31
|
-
if @origin.is_a?(
|
35
|
+
if @origin.is_a?(Types::Base)
|
32
36
|
json[:origin] = @origin.to_search
|
33
37
|
else
|
34
38
|
json[:origin] = @origin
|
35
39
|
end
|
36
|
-
json[:offset]
|
37
|
-
json[:decay]
|
40
|
+
json[:offset] = @offset if @offset
|
41
|
+
json[:decay] = @decay_amount if @decay_amount
|
38
42
|
|
39
43
|
{
|
40
|
-
@
|
44
|
+
@decay => {
|
41
45
|
@field => json
|
42
46
|
},
|
43
47
|
weight: @weight
|
@@ -5,10 +5,13 @@ module Stretchy
|
|
5
5
|
module Boosts
|
6
6
|
class FilterBoost < Base
|
7
7
|
|
8
|
-
|
8
|
+
attribute :filter
|
9
|
+
attribute :weight, Numeric, default: DEFAULT_WEIGHT
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
validations do
|
12
|
+
rule :filter, type: {classes: Filters::Base}
|
13
|
+
rule :weight, type: {classes: Numeric}
|
14
|
+
end
|
12
15
|
|
13
16
|
def initialize(options = {})
|
14
17
|
@filter = options[:filter]
|
@@ -4,14 +4,17 @@ module Stretchy
|
|
4
4
|
module Boosts
|
5
5
|
class RandomBoost < Base
|
6
6
|
|
7
|
-
|
7
|
+
attribute :seed, Numeric
|
8
|
+
attribute :weight, Numeric, default: DEFAULT_WEIGHT
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
validations do
|
11
|
+
rule :seed, type: {classes: Numeric, required: true}
|
12
|
+
rule :weight, type: {classes: Numeric, required: true}
|
13
|
+
end
|
11
14
|
|
12
15
|
# randomizes order (somewhat) consistently per-user
|
13
16
|
# http://www.elastic.co/guide/en/elasticsearch/guide/current/random-scoring.html
|
14
|
-
|
17
|
+
|
15
18
|
def initialize(seed, weight = nil)
|
16
19
|
@seed = seed
|
17
20
|
@weight = weight || DEFAULT_WEIGHT
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Builders
|
3
|
+
class FilterBuilder
|
4
|
+
|
5
|
+
attr_reader :terms, :ranges, :geos, :exists, :inverse, :should
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@terms = Hash.new { [] }
|
9
|
+
@ranges = Hash.new { [] }
|
10
|
+
@geos = Hash.new { [] }
|
11
|
+
@exists = []
|
12
|
+
@inverse = !!options[:inverse]
|
13
|
+
@should = !!options[:should]
|
14
|
+
end
|
15
|
+
|
16
|
+
def any?
|
17
|
+
@terms.any? || @ranges.any? || @geos.any? || @exists.any?
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_terms(field, terms)
|
21
|
+
@terms[field] += Array(terms)
|
22
|
+
@terms[field].uniq!
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_range(field, options)
|
26
|
+
@ranges[field] = Filters::RangeFilter.new(field, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_geo(field, distance, geo_point)
|
30
|
+
@geos[field] = Filters::GeoFilter.new(field, distance, geo_point)
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_exists(fields)
|
34
|
+
@exists += clean_string_array(fields)
|
35
|
+
@exists.uniq!
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_filters
|
39
|
+
filters = @ranges.values + @geos.values
|
40
|
+
filters += @terms.map do |field, values|
|
41
|
+
Filters::TermsFilter.new(field, values)
|
42
|
+
end
|
43
|
+
|
44
|
+
filters += @exists.map {|field| Filters::ExistsFilter.new(field) }
|
45
|
+
filters
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def clean_string_array(values)
|
50
|
+
Array(values).map(&:to_s).map(&:strip).reject(&:empty?)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -2,69 +2,67 @@ module Stretchy
|
|
2
2
|
module Builders
|
3
3
|
class MatchBuilder
|
4
4
|
|
5
|
-
attr_accessor :
|
6
|
-
:antimatches, :antimatchops,
|
7
|
-
:shouldmatches, :shouldmatchops,
|
8
|
-
:shouldnotmatches, :shouldnotmatchops
|
5
|
+
attr_accessor :must, :must_not, :should, :should_not
|
9
6
|
|
10
7
|
def initialize
|
11
|
-
@
|
12
|
-
@
|
13
|
-
|
14
|
-
@
|
15
|
-
@antimatchops = Hash.new { 'and' }
|
16
|
-
|
17
|
-
@shouldmatches = Hash.new { [] }
|
18
|
-
@shouldmatchops = Hash.new { 'and' }
|
19
|
-
|
20
|
-
@shouldnotmatches = Hash.new { [] }
|
21
|
-
@shouldnotmatchops = Hash.new { 'and' }
|
8
|
+
@must = QueryBuilder.new
|
9
|
+
@must_not = QueryBuilder.new
|
10
|
+
@should = QueryBuilder.new
|
11
|
+
@should_not = QueryBuilder.new
|
22
12
|
end
|
23
13
|
|
24
14
|
def any?
|
25
|
-
|
15
|
+
must.any? || must_not.any? || should.any? || should_not.any?
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_matches(field, matches, options = {})
|
19
|
+
builder_from_options(options).add_matches(field, matches, options)
|
26
20
|
end
|
27
21
|
|
28
|
-
def
|
22
|
+
def to_query
|
29
23
|
return Stretchy::Queries::MatchAllQuery.new unless any?
|
30
24
|
|
31
|
-
if
|
32
|
-
@shouldmatches.any? || @shouldnotmatches.any?
|
33
|
-
|
25
|
+
if use_bool?
|
34
26
|
bool_query
|
35
27
|
else
|
36
|
-
|
28
|
+
must.to_query.first
|
37
29
|
end
|
38
30
|
end
|
39
31
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
32
|
+
private
|
33
|
+
|
34
|
+
def builder_from_options(options = {})
|
35
|
+
if options[:inverse] && options[:should]
|
36
|
+
should_not
|
37
|
+
elsif options[:inverse]
|
38
|
+
must_not
|
39
|
+
elsif options[:should]
|
40
|
+
should
|
41
|
+
else
|
42
|
+
must
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def use_bool?
|
47
|
+
must.count > 1 || must_not.any? || should.any? || should_not.any?
|
48
|
+
end
|
47
49
|
|
48
|
-
|
49
|
-
if @shouldnotmatches.any?
|
50
|
+
def bool_query
|
50
51
|
Stretchy::Queries::BoolQuery.new(
|
51
|
-
must:
|
52
|
-
must_not:
|
52
|
+
must: must.to_query,
|
53
|
+
must_not: must_not.to_query,
|
54
|
+
should: build_should
|
53
55
|
)
|
54
|
-
else
|
55
|
-
to_queries(@shouldmatches, @shouldmatchops)
|
56
56
|
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
57
|
|
61
|
-
def
|
62
|
-
|
63
|
-
Stretchy::Queries::
|
64
|
-
|
65
|
-
|
66
|
-
operator: operators[field]
|
58
|
+
def build_should
|
59
|
+
if should.count > 1 || should_not.any?
|
60
|
+
Stretchy::Queries::BoolQuery.new(
|
61
|
+
must: should.to_query,
|
62
|
+
must_not: should_not.to_query
|
67
63
|
)
|
64
|
+
else
|
65
|
+
should.to_query.first
|
68
66
|
end
|
69
67
|
end
|
70
68
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Builders
|
3
|
+
class QueryBuilder
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
delegate [:any?, :count, :length] => :matches
|
8
|
+
|
9
|
+
attr_reader :matches, :operators
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@matches = Hash.new { [] }
|
13
|
+
@operators = Hash.new { 'and' }
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_matches(field, new_matches, options)
|
17
|
+
@matches[field] += Array(new_matches).map(&:to_s).map(&:strip).reject(&:empty?)
|
18
|
+
if options[:operator]
|
19
|
+
@operators[field] = options[:operator]
|
20
|
+
elsif options[:or]
|
21
|
+
@operators[field] = 'or'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_query
|
26
|
+
matches.map do |field, matches_for_field|
|
27
|
+
Queries::MatchQuery.new(
|
28
|
+
field: field,
|
29
|
+
string: matches_for_field.join(' '),
|
30
|
+
operator: operators[field]
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Builders
|
3
|
+
class ShellBuilder
|
4
|
+
|
5
|
+
DEFAULT_LIMIT = 30
|
6
|
+
DEFAULT_OFFSET = 0
|
7
|
+
|
8
|
+
attr_accessor :index, :type, :limit, :offset, :explain,
|
9
|
+
:match_builder, :where_builder, :boost_builder,
|
10
|
+
:aggregate_builder, :fields
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@index = options[:index] || Stretchy.index_name
|
14
|
+
@match_builder = Stretchy::Builders::MatchBuilder.new
|
15
|
+
@where_builder = Stretchy::Builders::WhereBuilder.new
|
16
|
+
@boost_builder = Stretchy::Builders::BoostBuilder.new
|
17
|
+
@aggregate_builder = {}
|
18
|
+
@limit = options[:limit] || DEFAULT_LIMIT
|
19
|
+
@offset = options[:offset] || DEFAULT_OFFSET
|
20
|
+
@fields = options[:fields]
|
21
|
+
@type = options[:type]
|
22
|
+
end
|
23
|
+
|
24
|
+
def page
|
25
|
+
(offset.to_f / limit).ceil + 1
|
26
|
+
end
|
27
|
+
alias :current_page :page
|
28
|
+
|
29
|
+
#
|
30
|
+
# Compiles the internal representation of your filters,
|
31
|
+
# full-text queries, and boosts into the JSON to be
|
32
|
+
# passed to Elastic. If you want to know exactly what
|
33
|
+
# your query generated, you can call this method.
|
34
|
+
#
|
35
|
+
# @return [Hash] the query hash to be compiled to json
|
36
|
+
# and sent to Elastic
|
37
|
+
def to_search
|
38
|
+
_to_search = if where_builder.any?
|
39
|
+
Stretchy::Queries::FilteredQuery.new(
|
40
|
+
query: match_builder.to_query,
|
41
|
+
filter: where_builder.to_filter
|
42
|
+
)
|
43
|
+
else
|
44
|
+
match_builder.to_query
|
45
|
+
end
|
46
|
+
|
47
|
+
_to_search = boost_builder.to_search(_to_search) if boost_builder.any?
|
48
|
+
_to_search.to_search
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|