stretchy 0.1.2 → 0.2.0

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