stretchy 0.1.2 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 28c98af055eaf32fb6c23460780f168e66c894bb
4
- data.tar.gz: 54af860b9fca236b64c2d95f3acbd13bde7b5f47
3
+ metadata.gz: 02cee68659326cb0fcbb36e25b584efd846149cc
4
+ data.tar.gz: 4af82a79df5998f4bf7beface88f4467425fd86a
5
5
  SHA512:
6
- metadata.gz: b1a8077918cd57cb1294f9340716d2b20d14ce4ebaf01c96eb6e535011ebe4dafcccc013ae4ce201120f5fabe75498c823b29825a1471bf8cdc11cbe99204b23
7
- data.tar.gz: ab509f01cfc5f0a97dd522a3ad1bd9ba34bd1c0bbdc4b863d81b78ec386f8f86fc1d3d947d53c748af5ba71f4e27ed195b80f5000e64da8be8ec8c098381064a
6
+ metadata.gz: 432060e10838aa8438d545ff402214d9b4048d5848f1dbefde4d0a4fff4be366b51dfe52642d02628b61462679c0a7ee513035acd9dd2cd6c5604cb2ac1b64ec
7
+ data.tar.gz: 98a4e9114c0b8a99687773994ab45bbc32a07621cb48a7cb41f6e132e0d3dc975211ed091a99a686739efee9010742219ede298b3d0402da75a92e353f718e90
@@ -4,6 +4,8 @@ module Stretchy
4
4
 
5
5
  include Stretchy::Utils::Contract
6
6
 
7
+ DEFAULT_WEIGHT = 1.2
8
+
7
9
  def initialize
8
10
  raise "Override this in subclass"
9
11
  end
@@ -2,14 +2,14 @@ module Stretchy
2
2
  module Boosts
3
3
  class FilterBoost < Base
4
4
 
5
- DEFAULT_WEIGHT = 1.2
5
+ attr_reader :filter, :weight
6
6
 
7
7
  contract filter: {type: Stretchy::Filters::Base},
8
8
  weight: {type: Numeric}
9
9
 
10
- def initialize(filter:, weight: DEFAULT_WEIGHT)
11
- @filter = filter
12
- @weight = weight
10
+ def initialize(options = {})
11
+ @filter = options[:filter]
12
+ @weight = options[:weight] || DEFAULT_WEIGHT
13
13
  validate!
14
14
  end
15
15
 
@@ -10,9 +10,9 @@ module Stretchy
10
10
  # randomizes order (somewhat) consistently per-user
11
11
  # http://www.elastic.co/guide/en/elasticsearch/guide/current/random-scoring.html
12
12
 
13
- def initialize(seed, weight = DEFAULT_WEIGHT)
13
+ def initialize(seed, weight = nil)
14
14
  @seed = seed
15
- @weight = weight
15
+ @weight = weight || DEFAULT_WEIGHT
16
16
  validate!
17
17
  end
18
18
 
@@ -0,0 +1,36 @@
1
+ module Stretchy
2
+ module Builders
3
+ class BoostBuilder
4
+
5
+ attr_accessor :functions, :overall_boost, :max_boost, :score_mode, :boost_mode
6
+
7
+ def initialize
8
+ @functions = []
9
+ @overall_boost = nil
10
+ @max_boost = nil
11
+ @score_mode = 'sum'
12
+ @boost_mode = 'sum'
13
+ end
14
+
15
+ def any?
16
+ @functions.any?
17
+ end
18
+
19
+ def build(query_or_filter)
20
+ options = {
21
+ functions: @functions,
22
+ score_mode: @score_mode,
23
+ boost_mode: @boost_mode
24
+ }
25
+
26
+ options[:overall_boost] = @overall_boost if @overall_boost
27
+ options[:max_boost] = @max_boost if @max_boost
28
+ options[:query] = query_or_filter if query_or_filter.is_a?(Stretchy::Queries::Base)
29
+ options[:filter] = query_or_filter if query_or_filter.is_a?(Stretchy::Filters::Base)
30
+
31
+ Stretchy::Queries::FunctionScoreQuery.new(options)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ module Stretchy
2
+ module Builders
3
+ class MatchBuilder
4
+
5
+ attr_accessor :matches, :antimatches
6
+
7
+ def initialize
8
+ @matches = Hash.new { [] }
9
+ @antimatches = Hash.new { [] }
10
+ end
11
+
12
+ def any?
13
+ @matches.any? || @antimatches.any?
14
+ end
15
+
16
+ def build
17
+ return Stretchy::Queries::MatchAllQuery.new unless any?
18
+
19
+ if @matches.count > 1 || @antimatches.any?
20
+ bool_query
21
+ else
22
+ field, strings = @matches.first
23
+ Stretchy::Queries::MatchQuery.new(field: field, string: strings.join(' '))
24
+ end
25
+ end
26
+
27
+ def bool_query
28
+ Stretchy::Queries::BoolQuery.new(
29
+ must: to_queries(@matches),
30
+ must_not: to_queries(@antimatches)
31
+ )
32
+ end
33
+
34
+ private
35
+
36
+ def to_queries(matches)
37
+ matches.map do |field, strings|
38
+ Stretchy::Queries::MatchQuery.new(field: field, string: strings.join(' '))
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,126 @@
1
+ module Stretchy
2
+ module Builders
3
+ class WhereBuilder
4
+
5
+ attr_accessor :terms, :antiterms, :exists, :empties,
6
+ :ranges, :antiranges, :geos, :antigeos
7
+
8
+ def initialize(options = {})
9
+ @terms = Hash.new { [] }
10
+ @antiterms = Hash.new { [] }
11
+ @ranges = {}
12
+ @antiranges = {}
13
+ @geos = {}
14
+ @antigeos = {}
15
+
16
+ @exists = []
17
+ @empties = []
18
+ end
19
+
20
+ def build
21
+ if use_bool?
22
+ bool_filter
23
+ elsif musts?
24
+ and_filter
25
+ elsif must_nots?
26
+ not_filter
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ def musts?
33
+ @terms.any? || @exists.any? || @ranges.any? || @geos.any?
34
+ end
35
+
36
+ def must_nots?
37
+ @antiterms.any? || @empties.any? ||
38
+ @antiranges.any? || @antigeos.any?
39
+ end
40
+
41
+ def use_bool?
42
+ musts? && must_nots?
43
+ end
44
+
45
+ def any?
46
+ musts? || must_nots?
47
+ end
48
+
49
+ def bool_filter
50
+ Stretchy::Filters::BoolFilter.new(
51
+ must: build_filters(
52
+ terms: @terms,
53
+ exists: @exists,
54
+ ranges: @ranges,
55
+ geos: @geos
56
+ ),
57
+ must_not: build_filters(
58
+ terms: @antiterms,
59
+ exists: @empties,
60
+ ranges: @antiranges,
61
+ geos: @antigeos
62
+ )
63
+ )
64
+ end
65
+
66
+ def and_filter
67
+ filter = nil
68
+ filters = build_filters(
69
+ terms: @terms,
70
+ exists: @exists,
71
+ ranges: @ranges,
72
+ geos: @geos
73
+ )
74
+ if filters.count > 1
75
+ filter = Stretchy::Filters::AndFilter.new(filters)
76
+ else
77
+ filter = filters.first
78
+ end
79
+ filter
80
+ end
81
+
82
+ def not_filter
83
+ filter = build_filters(
84
+ terms: @antiterms,
85
+ exists: @empties,
86
+ ranges: @antiranges,
87
+ geos: @antigeos
88
+ )
89
+ filter = Stretchy::Filters::AndFilter.new(filter) if filter.count > 1
90
+ Stretchy::Filters::NotFilter.new(filter)
91
+ end
92
+
93
+ def build_filters(options = {})
94
+ filters = []
95
+ terms = Hash(options[:terms])
96
+ ranges = Hash(options[:ranges])
97
+ geos = Hash(options[:geos])
98
+ exists = Array(options[:exists])
99
+
100
+ filters << Stretchy::Filters::TermsFilter.new(terms) if terms.any?
101
+
102
+ filters += exists.map do |field|
103
+ Stretchy::Filters::ExistsFilter.new(field)
104
+ end
105
+
106
+ filters += ranges.map do |field, values|
107
+ Stretchy::Filters::RangeFilter.new(
108
+ field: field,
109
+ min: values[:min],
110
+ max: values[:max]
111
+ )
112
+ end
113
+
114
+ filters += geos.map do |field, values|
115
+ Stretchy::Filters::GeoFilter.new(
116
+ field: field,
117
+ distance: values[:distance],
118
+ lat: values[:lat],
119
+ lng: values[:lng]
120
+ )
121
+ end
122
+ filters
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,95 @@
1
+ module Stretchy
2
+ module Clauses
3
+ class Base
4
+
5
+ extend Forwardable
6
+
7
+ DEFAULT_LIMIT = 30
8
+ DEFAULT_OFFSET = 0
9
+
10
+ attr_accessor :match_builder, :where_builder, :boost_builder,
11
+ :aggregate_builder, :inverse, :type, :index_name
12
+
13
+ alias :inverse? :inverse
14
+
15
+ delegate [:response, :results, :ids, :hits, :took, :shards, :total, :max_score] => :query_results
16
+
17
+ def initialize(base_or_opts = nil, options = {})
18
+ if base_or_opts && !base_or_opts.is_a?(Hash)
19
+ base = base_or_opts
20
+ @index_name = base.index_name
21
+ @type = base.type
22
+ @match_builder = base.match_builder
23
+ @where_builder = base.where_builder
24
+ @boost_builder = base.boost_builder
25
+ @aggregate_builder = base.aggregate_builder
26
+ @inverse = options[:inverse] || base.inverse
27
+ @limit = base.get_limit
28
+ @offset = base.get_offset
29
+ else
30
+ options = Hash(base_or_opts).merge(options)
31
+ @index_name = options[:index] || Stretchy.index_name
32
+ @type = options[:type]
33
+ @match_builder = Stretchy::Builders::MatchBuilder.new
34
+ @where_builder = Stretchy::Builders::WhereBuilder.new
35
+ @boost_builder = Stretchy::Builders::BoostBuilder.new
36
+ @aggregate_builder = nil
37
+ @inverse = options[:inverse]
38
+ @limit = DEFAULT_LIMIT
39
+ @offset = DEFAULT_OFFSET
40
+ end
41
+ end
42
+
43
+ def limit(num)
44
+ @limit = num
45
+ self
46
+ end
47
+
48
+ def get_limit
49
+ @limit
50
+ end
51
+
52
+ def offset(num)
53
+ @offset = num
54
+ self
55
+ end
56
+
57
+ def get_offset
58
+ @offset
59
+ end
60
+
61
+ def match(options = {})
62
+ MatchClause.new(self, options)
63
+ end
64
+
65
+ def where(options = {})
66
+ WhereClause.new(self, options)
67
+ end
68
+
69
+ def boost(options = {})
70
+ BoostClause.new(self, options)
71
+ end
72
+
73
+ def to_search
74
+ return @to_search if @to_search
75
+
76
+ @to_search = if @where_builder.any?
77
+ Stretchy::Queries::FilteredQuery.new(
78
+ query: @match_builder.build,
79
+ filter: @where_builder.build
80
+ )
81
+ else
82
+ @match_builder.build
83
+ end
84
+
85
+ @to_search = @boost_builder.build(@to_search) if @boost_builder.any?
86
+ @to_search = @to_search.to_search
87
+ end
88
+
89
+ def query_results
90
+ @query_results ||= Stretchy::Results::Base.new(to_search.merge(from: @limit, size: @offset), type: @type)
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,50 @@
1
+ module Stretchy
2
+ module Clauses
3
+ class BoostClause < Base
4
+
5
+ def initialize(base, options = {})
6
+ super(base)
7
+ @inverse = options.delete(:inverse)
8
+ end
9
+
10
+ def match(options = {})
11
+ BoostMatchClause.new(self, options)
12
+ end
13
+
14
+ def where(options = {})
15
+ BoostWhereClause.new(self, options)
16
+ end
17
+
18
+ def random(*args)
19
+ @boost_builder.functions << Stretchy::Boosts::RandomBoost.new(*args)
20
+ self
21
+ end
22
+
23
+ def all(num)
24
+ @boost_builder.overall_boost = num
25
+ self
26
+ end
27
+
28
+ def max(num)
29
+ @boost_builder.max_boost = num
30
+ self
31
+ end
32
+
33
+ def score_mode(mode)
34
+ @boost_builder.score_mode = mode
35
+ self
36
+ end
37
+
38
+ def boost_mode(mode)
39
+ @boost_builder.boost_mode = mode
40
+ self
41
+ end
42
+
43
+ def not(options = {})
44
+ inst = self.class.new(self, options.merge(inverse: !inverse?))
45
+ inst
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ module Stretchy
2
+ module Clauses
3
+ class BoostMatchClause < BoostClause
4
+
5
+ def initialize(base, opts_or_string = {}, options = {})
6
+ super(base)
7
+ if opts_or_string.is_a?(Hash)
8
+ @inverse = opts_or_string.delete(:inverse) || options.delete(:inverse)
9
+ match_function(opts_or_string.merge(options))
10
+ else
11
+ @inverse = options.delete(:inverse)
12
+ match_function(options.merge('_all' => opts_or_string))
13
+ end
14
+ end
15
+
16
+ def not(opts_or_string = {}, options = {})
17
+ self.class.new(self, opts_or_string, options.merge(inverse: !inverse?))
18
+ end
19
+
20
+ private
21
+
22
+ def match_function(options = {})
23
+ weight = options.delete(:weight)
24
+ clause = MatchClause.tmp(options)
25
+ boost = clause.to_boost(weight)
26
+ @boost_builder.functions << boost if boost
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,44 @@
1
+ module Stretchy
2
+ module Clauses
3
+ class BoostWhereClause < BoostClause
4
+
5
+ def initialize(base, options = {})
6
+ super(base, options)
7
+ where_function(:init, options)
8
+ self
9
+ end
10
+
11
+ def range(*args)
12
+ where_function(:range, *args)
13
+ self
14
+ end
15
+
16
+ def geo(*args)
17
+ where_function(:geo, *args)
18
+ self
19
+ end
20
+
21
+ private
22
+
23
+ def add_params(params = {})
24
+ where_function(:init, params)
25
+ end
26
+
27
+ def where_function(method, *args)
28
+ options = args.last.is_a?(Hash) ? args.pop : {}
29
+ weight = options.delete(:weight)
30
+
31
+ clause = nil
32
+ if method == :init
33
+ clause = WhereClause.tmp(options.merge(inverse: inverse?))
34
+ else
35
+ args.push(options)
36
+ clause = WhereClause.tmp(inverse: inverse?).send(method, *args)
37
+ end
38
+ boost = clause.to_boost(weight)
39
+
40
+ @boost_builder.functions << boost if boost
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ module Stretchy
2
+ module Clauses
3
+ class MatchClause < Base
4
+
5
+ def self.tmp(options = {})
6
+ self.new(Base.new, options)
7
+ end
8
+
9
+ def initialize(base, opts_or_str = {}, options = {})
10
+ super(base)
11
+ if opts_or_str.is_a?(Hash)
12
+ @inverse = opts_or_str.delete(:inverse) || options.delete(:inverse)
13
+ add_params(options.merge(opts_or_str))
14
+ else
15
+ @inverse = options.delete(:inverse)
16
+ add_params(options.merge('_all' => opts_or_str))
17
+ end
18
+ end
19
+
20
+ def not(opts_or_str = {}, options = {})
21
+ self.class.new(self, opts_or_str, options.merge(inverse: !@inverse))
22
+ end
23
+
24
+ def to_boost(weight = nil)
25
+ weight ||= Stretchy::Boosts::FilterBoost::DEFAULT_WEIGHT
26
+ Stretchy::Boosts::FilterBoost.new(
27
+ filter: Stretchy::Filters::QueryFilter.new(
28
+ @match_builder.build
29
+ ),
30
+ weight: weight
31
+ )
32
+ end
33
+
34
+ private
35
+
36
+ def add_params(params = {})
37
+ case params
38
+ when Hash
39
+ params.each do |field, params|
40
+ add_param(field, params)
41
+ end
42
+ else
43
+ add_param('_all', params)
44
+ end
45
+ end
46
+
47
+ def add_param(field, param)
48
+ if inverse?
49
+ @match_builder.antimatches[field] += Array(param)
50
+ else
51
+ @match_builder.matches[field] += Array(param)
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,113 @@
1
+ module Stretchy
2
+ module Clauses
3
+ class WhereClause < Base
4
+
5
+ def self.tmp(options = {})
6
+ self.new(Base.new, options)
7
+ end
8
+
9
+ def initialize(base, options = {})
10
+ super(base)
11
+ @inverse = options.delete(:inverse)
12
+ add_params(options)
13
+ end
14
+
15
+ def range(field, options = {})
16
+ min = max = nil
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
27
+
28
+ self
29
+ end
30
+
31
+ def geo(field, options = {})
32
+ add_geo(field, options)
33
+ self
34
+ end
35
+
36
+ def not(options = {})
37
+ self.class.new(self, options.merge(inverse: !@inverse))
38
+ end
39
+
40
+ def to_boost(weight = nil)
41
+ weight ||= Stretchy::Boosts::FilterBoost::DEFAULT_WEIGHT
42
+
43
+ if @match_builder.any? && @where_builder.any?
44
+ Stretchy::Boosts::FilterBoost.new(
45
+ filter: Stretchy::Filters::QueryFilter.new(
46
+ Stretchy::Queries::FilteredQuery.new(
47
+ query: @match_builder.build,
48
+ filter: @where_builder.build
49
+ )
50
+ ),
51
+ weight: weight
52
+ )
53
+
54
+ elsif @match_builder.any?
55
+ Stretchy::Boosts::FilterBoost.new(
56
+ filter: Stretchy::Filters::QueryFilter.new(
57
+ @match_builder.build
58
+ ),
59
+ weight: weight
60
+ )
61
+
62
+ elsif @where_builder.any?
63
+ Stretchy::Boosts::FilterBoost.new(
64
+ filter: @where_builder.build,
65
+ weight: weight
66
+ )
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def add_params(options = {})
73
+ options.each do |field, param|
74
+ # if it is an array, process each param
75
+ # separately - ensures string & symbols
76
+ # always go into .match_builder
77
+
78
+ if param.is_a?(Array)
79
+ param.each{|p| add_param(field, p) }
80
+ else
81
+ add_param(field, param)
82
+ end
83
+ end
84
+ end
85
+
86
+ def add_geo(field, options = {})
87
+ store = inverse? ? @where_builder.antigeos : @where_builder.geos
88
+ store[field] = options
89
+ end
90
+
91
+ def add_param(field, param)
92
+ case param
93
+ when nil
94
+ store = inverse? ? @where_builder.exists : @where_builder.empties
95
+ store << field
96
+ when String, Symbol
97
+ if inverse?
98
+ @match_builder.antimatches[field] += Array(param)
99
+ else
100
+ @match_builder.matches[field] += Array(param)
101
+ end
102
+ when Range
103
+ store = inverse? ? @where_builder.antiranges : @where_builder.ranges
104
+ store[field] = { min: param.min, max: param.max }
105
+ else
106
+ store = inverse? ? @where_builder.antiterms : @where_builder.terms
107
+ store[field] += Array(param)
108
+ end
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -4,8 +4,12 @@ module Stretchy
4
4
 
5
5
  contract filters: {type: Stretchy::Filters::Base, array: true}
6
6
 
7
- def initialize(filters)
8
- @filters = Array(filters)
7
+ def initialize(*args)
8
+ if args.count == 1 && args.first.is_a?(Array)
9
+ @filters = args.first
10
+ else
11
+ @filters = args
12
+ end
9
13
  validate!
10
14
  end
11
15
 
@@ -6,18 +6,19 @@ module Stretchy
6
6
  must_not: {type: Base, array: true},
7
7
  should: {type: Base, array: true}
8
8
 
9
- def initialize(must:, must_not:, should: nil)
10
- @must = Array(must)
11
- @must_not = Array(must_not)
12
- @should = Array(should)
9
+ def initialize(options = {})
10
+ @must = Array(options[:must])
11
+ @must_not = Array(options[:must_not])
12
+ @should = Array(options[:should])
13
13
  validate!
14
+ require_one(must: @must, must_not: @must_not, should: @should)
14
15
  end
15
16
 
16
17
  def to_search
17
18
  json = {}
18
- json[:must] = @must.map(&:to_search) if @must
19
- json[:must_not] = @must_not.map(&:to_search) if @must_not
20
- json[:should] = @should.map(&:to_search) if @should
19
+ json[:must] = @must.map(&:to_search) if @must.any?
20
+ json[:must_not] = @must_not.map(&:to_search) if @must_not.any?
21
+ json[:should] = @should.map(&:to_search) if @should.any?
21
22
  { bool: json }
22
23
  end
23
24
  end