stretchy 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|