stretchy 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/stretchy/boosts/base.rb +4 -1
  3. data/lib/stretchy/boosts/field_decay_boost.rb +47 -0
  4. data/lib/stretchy/boosts/filter_boost.rb +3 -0
  5. data/lib/stretchy/boosts/random_boost.rb +2 -0
  6. data/lib/stretchy/builders/match_builder.rb +22 -6
  7. data/lib/stretchy/builders/where_builder.rb +99 -32
  8. data/lib/stretchy/clauses/base.rb +22 -3
  9. data/lib/stretchy/clauses/boost_clause.rb +17 -0
  10. data/lib/stretchy/clauses/boost_match_clause.rb +2 -0
  11. data/lib/stretchy/clauses/boost_where_clause.rb +2 -0
  12. data/lib/stretchy/clauses/match_clause.rb +30 -6
  13. data/lib/stretchy/clauses/where_clause.rb +41 -30
  14. data/lib/stretchy/filters/and_filter.rb +3 -5
  15. data/lib/stretchy/filters/base.rb +3 -1
  16. data/lib/stretchy/filters/bool_filter.rb +2 -0
  17. data/lib/stretchy/filters/exists_filter.rb +2 -0
  18. data/lib/stretchy/filters/geo_filter.rb +8 -10
  19. data/lib/stretchy/filters/not_filter.rb +2 -0
  20. data/lib/stretchy/filters/or_filter.rb +22 -0
  21. data/lib/stretchy/filters/query_filter.rb +3 -0
  22. data/lib/stretchy/filters/range_filter.rb +7 -10
  23. data/lib/stretchy/filters/terms_filter.rb +2 -0
  24. data/lib/stretchy/queries/base.rb +3 -1
  25. data/lib/stretchy/queries/bool_query.rb +2 -0
  26. data/lib/stretchy/queries/filtered_query.rb +3 -0
  27. data/lib/stretchy/queries/function_score_query.rb +3 -0
  28. data/lib/stretchy/queries/match_all_query.rb +2 -0
  29. data/lib/stretchy/queries/match_query.rb +2 -0
  30. data/lib/stretchy/results/base.rb +3 -1
  31. data/lib/stretchy/results/null_results.rb +2 -0
  32. data/lib/stretchy/types/base.rb +19 -0
  33. data/lib/stretchy/types/geo_point.rb +30 -0
  34. data/lib/stretchy/types/range.rb +52 -0
  35. data/lib/stretchy/utils/client_actions.rb +4 -1
  36. data/lib/stretchy/utils/contract.rb +6 -2
  37. data/lib/stretchy/version.rb +1 -1
  38. data/lib/stretchy.rb +4 -22
  39. metadata +7 -3
  40. data/lib/stretchy/boosts/geo_boost.rb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 02cee68659326cb0fcbb36e25b584efd846149cc
4
- data.tar.gz: 4af82a79df5998f4bf7beface88f4467425fd86a
3
+ metadata.gz: 124aff086e4d044125b18f641bce3e440a3bec66
4
+ data.tar.gz: ee1b4ddda1669c13c7c17a9189fd1cda072f77b6
5
5
  SHA512:
6
- metadata.gz: 432060e10838aa8438d545ff402214d9b4048d5848f1dbefde4d0a4fff4be366b51dfe52642d02628b61462679c0a7ee513035acd9dd2cd6c5604cb2ac1b64ec
7
- data.tar.gz: 98a4e9114c0b8a99687773994ab45bbc32a07621cb48a7cb41f6e132e0d3dc975211ed091a99a686739efee9010742219ede298b3d0402da75a92e353f718e90
6
+ metadata.gz: 7a4375aca6e56a4755a693b3d346f945fe6d7aebe819064cbbeeb7e80e66588645ab17be24f8ed88b1ca89738d8eb6f9755cf2f79ccd35c9a60c8a862fd160e3
7
+ data.tar.gz: dac8575ff96c77ea601051da8fb08489c19c3b17245deed2ff6be48ed58fd2ad9054848025c52a43980a7112ac3277ad604c73ae0f1b1904a6d7eedab42d0715
@@ -1,10 +1,13 @@
1
+ require 'stretchy/utils/contract'
2
+
1
3
  module Stretchy
2
4
  module Boosts
3
5
  class Base
4
6
 
5
7
  include Stretchy::Utils::Contract
6
8
 
7
- DEFAULT_WEIGHT = 1.2
9
+ DEFAULT_WEIGHT = 1.2
10
+ DEFAULT_DECAY_FN = :gauss
8
11
 
9
12
  def initialize
10
13
  raise "Override this in subclass"
@@ -0,0 +1,47 @@
1
+ require 'stretchy/boosts/base'
2
+ require 'stretchy/types/geo_point'
3
+
4
+ module Stretchy
5
+ module Boosts
6
+ class FieldDecayBoost < Base
7
+
8
+ attr_reader :field, :origin, :scale, :offset, :decay, :decay_amount, :weight
9
+
10
+ contract field: {type: :field, required: true},
11
+ origin: {required: true, type: [Numeric, Time, Date, Stretchy::Types::GeoPoint]},
12
+ scale: {required: true},
13
+ decay: {type: Numeric},
14
+ type: {type: :decay}
15
+
16
+ def initialize(options = {})
17
+ @field = options[:field]
18
+ @origin = options[:origin]
19
+ @scale = options[:scale]
20
+
21
+ @offset = options[:offset]
22
+ @type = options[:type] || DEFAULT_DECAY_FN
23
+ @weight = options[:weight] || DEFAULT_WEIGHT
24
+ @decay = options[:decay]
25
+
26
+ validate!
27
+ end
28
+
29
+ def to_search
30
+ json = {
31
+ origin: @origin,
32
+ scale: @scale,
33
+ }
34
+ json[:offset] = @offset if @offset
35
+ json[:decay] = @decay if @decay
36
+
37
+ {
38
+ @type => {
39
+ @field => json
40
+ },
41
+ weight: @weight
42
+ }
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -1,3 +1,6 @@
1
+ require 'stretchy/boosts/base'
2
+ require 'stretchy/filters/base'
3
+
1
4
  module Stretchy
2
5
  module Boosts
3
6
  class FilterBoost < Base
@@ -1,3 +1,5 @@
1
+ require 'stretchy/boosts/base'
2
+
1
3
  module Stretchy
2
4
  module Boosts
3
5
  class RandomBoost < Base
@@ -2,21 +2,25 @@ module Stretchy
2
2
  module Builders
3
3
  class MatchBuilder
4
4
 
5
- attr_accessor :matches, :antimatches
5
+ attr_accessor :matches, :antimatches, :shouldmatches, :shouldnotmatches
6
6
 
7
7
  def initialize
8
- @matches = Hash.new { [] }
9
- @antimatches = Hash.new { [] }
8
+ @matches = Hash.new { [] }
9
+ @antimatches = Hash.new { [] }
10
+ @shouldmatches = Hash.new { [] }
11
+ @shouldnotmatches = Hash.new { [] }
10
12
  end
11
13
 
12
14
  def any?
13
- @matches.any? || @antimatches.any?
15
+ @matches.any? || @antimatches.any? || @shouldmatches.any? || @shouldnotmatches.any?
14
16
  end
15
17
 
16
18
  def build
17
19
  return Stretchy::Queries::MatchAllQuery.new unless any?
18
20
 
19
- if @matches.count > 1 || @antimatches.any?
21
+ if @matches.count > 1 || @antimatches.any? ||
22
+ @shouldmatches.any? || @shouldnotmatches.any?
23
+
20
24
  bool_query
21
25
  else
22
26
  field, strings = @matches.first
@@ -27,10 +31,22 @@ module Stretchy
27
31
  def bool_query
28
32
  Stretchy::Queries::BoolQuery.new(
29
33
  must: to_queries(@matches),
30
- must_not: to_queries(@antimatches)
34
+ must_not: to_queries(@antimatches),
35
+ should: build_should
31
36
  )
32
37
  end
33
38
 
39
+ def build_should
40
+ if @shouldnotmatches.any?
41
+ Stretchy::Queries::BoolQuery.new(
42
+ must: to_queries(@shouldmatches),
43
+ must_not: to_queries(@shouldnotmatches)
44
+ )
45
+ else
46
+ to_queries(@shouldmatches)
47
+ end
48
+ end
49
+
34
50
  private
35
51
 
36
52
  def to_queries(matches)
@@ -2,19 +2,31 @@ module Stretchy
2
2
  module Builders
3
3
  class WhereBuilder
4
4
 
5
- attr_accessor :terms, :antiterms, :exists, :empties,
6
- :ranges, :antiranges, :geos, :antigeos
5
+ attr_accessor :terms, :antiterms, :shouldterms, :shouldnotterms,
6
+ :exists, :antiexists, :shouldexists, :shouldnotexists,
7
+ :ranges, :antiranges, :shouldranges, :shouldnotranges,
8
+ :geos, :antigeos, :shouldgeos, :shouldnotgeos
7
9
 
8
10
  def initialize(options = {})
9
- @terms = Hash.new { [] }
10
- @antiterms = Hash.new { [] }
11
- @ranges = {}
12
- @antiranges = {}
13
- @geos = {}
14
- @antigeos = {}
15
-
16
- @exists = []
17
- @empties = []
11
+ @terms = Hash.new { [] }
12
+ @antiterms = Hash.new { [] }
13
+ @shouldterms = Hash.new { [] }
14
+ @shouldnotterms = Hash.new { [] }
15
+
16
+ @ranges = {}
17
+ @antiranges = {}
18
+ @shouldranges = {}
19
+ @shouldnotranges = {}
20
+
21
+ @geos = {}
22
+ @antigeos = {}
23
+ @shouldgeos = {}
24
+ @shouldnotgeos = {}
25
+
26
+ @exists = []
27
+ @antiexists = []
28
+ @shouldexists = []
29
+ @shouldnotexists = []
18
30
  end
19
31
 
20
32
  def build
@@ -34,32 +46,47 @@ module Stretchy
34
46
  end
35
47
 
36
48
  def must_nots?
37
- @antiterms.any? || @empties.any? ||
49
+ @antiterms.any? || @antiexists.any? ||
38
50
  @antiranges.any? || @antigeos.any?
39
51
  end
40
52
 
53
+ def shoulds?
54
+ @shouldterms.any? ||
55
+ @shouldranges.any? ||
56
+ @shouldgeos.any? ||
57
+ @shouldexists.any?
58
+ end
59
+
60
+ def should_nots?
61
+ @shouldnotterms.any? ||
62
+ @shouldnotranges.any? ||
63
+ @shouldnotgeos.any? ||
64
+ @shouldnotexists.any?
65
+ end
66
+
41
67
  def use_bool?
42
- musts? && must_nots?
68
+ (musts? && must_nots?) || shoulds? || should_nots?
43
69
  end
44
70
 
45
71
  def any?
46
- musts? || must_nots?
72
+ musts? || must_nots? || shoulds? || should_nots?
47
73
  end
48
74
 
49
75
  def bool_filter
50
76
  Stretchy::Filters::BoolFilter.new(
51
77
  must: build_filters(
52
- terms: @terms,
78
+ terms: @terms,
53
79
  exists: @exists,
54
80
  ranges: @ranges,
55
- geos: @geos
81
+ geos: @geos
56
82
  ),
57
83
  must_not: build_filters(
58
- terms: @antiterms,
59
- exists: @empties,
84
+ terms: @antiterms,
85
+ exists: @antiexists,
60
86
  ranges: @antiranges,
61
- geos: @antigeos
62
- )
87
+ geos: @antigeos
88
+ ),
89
+ should: build_should
63
90
  )
64
91
  end
65
92
 
@@ -82,20 +109,63 @@ module Stretchy
82
109
  def not_filter
83
110
  filter = build_filters(
84
111
  terms: @antiterms,
85
- exists: @empties,
112
+ exists: @antiexists,
86
113
  ranges: @antiranges,
87
114
  geos: @antigeos
88
115
  )
89
- filter = Stretchy::Filters::AndFilter.new(filter) if filter.count > 1
116
+ filter = Stretchy::Filters::OrFilter.new(filter) if filter.count > 1
90
117
  Stretchy::Filters::NotFilter.new(filter)
91
118
  end
92
119
 
120
+ def build_should
121
+ if shoulds? && should_nots?
122
+ Stretchy::Filters::BoolFilter.new(
123
+ must: build_filters(
124
+ terms: @shouldterms,
125
+ exists: @shouldexists,
126
+ ranges: @shouldranges,
127
+ geos: @shouldgeos
128
+ ),
129
+ must_not: build_filters(
130
+ terms: @shouldnotterms,
131
+ exists: @shouldnotexists,
132
+ ranges: @shouldnotranges,
133
+ geos: @shouldnotgeos
134
+ )
135
+ )
136
+ elsif should_nots?
137
+ filters = build_filters(
138
+ terms: @shouldnotterms,
139
+ exists: @shouldnotexists,
140
+ ranges: @shouldnotranges,
141
+ geos: @shouldnotgeos
142
+ )
143
+ if filters.count > 1
144
+ filters = Stretchy::Filters::OrFilter.new(filters)
145
+ else
146
+ filters = filters.first
147
+ end
148
+
149
+ Stretchy::Filters::NotFilter.new(filters)
150
+ else
151
+ filters = build_filters(
152
+ terms: @shouldterms,
153
+ exists: @shouldexists,
154
+ ranges: @shouldranges,
155
+ geos: @shouldgeos
156
+ )
157
+ filters = Stretchy::Filters::AndFilter.new(filters) if filters.count > 1
158
+ filters
159
+ end
160
+ end
161
+
93
162
  def build_filters(options = {})
94
163
  filters = []
95
- terms = Hash(options[:terms])
96
- ranges = Hash(options[:ranges])
97
- geos = Hash(options[:geos])
98
- exists = Array(options[:exists])
164
+ terms = Hash(options[:terms])
165
+ ranges = Hash(options[:ranges])
166
+ geos = Hash(options[:geos])
167
+ near_fields = Hash(options[:near_fields])
168
+ exists = Array(options[:exists])
99
169
 
100
170
  filters << Stretchy::Filters::TermsFilter.new(terms) if terms.any?
101
171
 
@@ -103,18 +173,15 @@ module Stretchy
103
173
  Stretchy::Filters::ExistsFilter.new(field)
104
174
  end
105
175
 
106
- filters += ranges.map do |field, values|
107
- Stretchy::Filters::RangeFilter.new(
108
- field: field,
109
- min: values[:min],
110
- max: values[:max]
111
- )
176
+ filters += ranges.map do |field, value|
177
+ Stretchy::Filters::RangeFilter.new(field: field, stretchy_range: value)
112
178
  end
113
179
 
114
180
  filters += geos.map do |field, values|
115
181
  Stretchy::Filters::GeoFilter.new(
116
182
  field: field,
117
183
  distance: values[:distance],
184
+ geo_point: values[:geo_point],
118
185
  lat: values[:lat],
119
186
  lng: values[:lng]
120
187
  )
@@ -10,9 +10,8 @@ module Stretchy
10
10
  attr_accessor :match_builder, :where_builder, :boost_builder,
11
11
  :aggregate_builder, :inverse, :type, :index_name
12
12
 
13
- alias :inverse? :inverse
14
-
15
13
  delegate [:response, :results, :ids, :hits, :took, :shards, :total, :max_score] => :query_results
14
+ delegate [:range, :geo] => :where
16
15
 
17
16
  def initialize(base_or_opts = nil, options = {})
18
17
  if base_or_opts && !base_or_opts.is_a?(Hash)
@@ -70,6 +69,26 @@ module Stretchy
70
69
  BoostClause.new(self, options)
71
70
  end
72
71
 
72
+ def not(opts_or_string = {}, opts = {})
73
+ if opts_or_string.is_a?(Hash)
74
+ WhereClause.new(self, opts_or_string.merge(inverse: true))
75
+ else
76
+ MatchClause.new(self, opts_or_string, opts.merge(inverse: true))
77
+ end
78
+ end
79
+
80
+ def should(opts_or_string = {}, opts = {})
81
+ if opts_or_string.is_a?(Hash)
82
+ WhereClause.new(self, opts_or_string.merge(should: true))
83
+ else
84
+ MatchClause.new(self, opts_or_string, opts.merge(should: true))
85
+ end
86
+ end
87
+
88
+ def inverse?
89
+ !!@inverse
90
+ end
91
+
73
92
  def to_search
74
93
  return @to_search if @to_search
75
94
 
@@ -92,4 +111,4 @@ module Stretchy
92
111
 
93
112
  end
94
113
  end
95
- end
114
+ end
@@ -1,7 +1,13 @@
1
+ require 'stretchy/clauses/base'
2
+
1
3
  module Stretchy
2
4
  module Clauses
3
5
  class BoostClause < Base
4
6
 
7
+ extend Forwardable
8
+
9
+ delegate [:geo, :range] => :where
10
+
5
11
  def initialize(base, options = {})
6
12
  super(base)
7
13
  @inverse = options.delete(:inverse)
@@ -15,6 +21,17 @@ module Stretchy
15
21
  BoostWhereClause.new(self, options)
16
22
  end
17
23
 
24
+ def near(options = {})
25
+ if options[:lat] || options[:latitude] ||
26
+ options[:lng] || options[:longitude] || options[:lon]
27
+
28
+ options[:origin] = Stretchy::Types::GeoPoint.new(options)
29
+ end
30
+ @boost_builder.functions << Stretchy::Boosts::FieldDecayBoost.new(options)
31
+ self
32
+ end
33
+ alias :geo :near
34
+
18
35
  def random(*args)
19
36
  @boost_builder.functions << Stretchy::Boosts::RandomBoost.new(*args)
20
37
  self
@@ -1,3 +1,5 @@
1
+ require 'stretchy/clauses/boost_clause'
2
+
1
3
  module Stretchy
2
4
  module Clauses
3
5
  class BoostMatchClause < BoostClause
@@ -1,3 +1,5 @@
1
+ require 'stretchy/clauses/boost_clause'
2
+
1
3
  module Stretchy
2
4
  module Clauses
3
5
  class BoostWhereClause < BoostClause
@@ -1,3 +1,5 @@
1
+ require 'stretchy/clauses/base'
2
+
1
3
  module Stretchy
2
4
  module Clauses
3
5
  class MatchClause < Base
@@ -10,15 +12,21 @@ module Stretchy
10
12
  super(base)
11
13
  if opts_or_str.is_a?(Hash)
12
14
  @inverse = opts_or_str.delete(:inverse) || options.delete(:inverse)
15
+ @should = opts_or_str.delete(:should) || options.delete(:should)
13
16
  add_params(options.merge(opts_or_str))
14
17
  else
15
18
  @inverse = options.delete(:inverse)
19
+ @should = options.delete(:should)
16
20
  add_params(options.merge('_all' => opts_or_str))
17
21
  end
18
22
  end
19
23
 
20
24
  def not(opts_or_str = {}, options = {})
21
- self.class.new(self, opts_or_str, options.merge(inverse: !@inverse))
25
+ self.class.new(self, opts_or_str, options.merge(inverse: true, should: should?))
26
+ end
27
+
28
+ def should(opts_or_str = {}, options = {})
29
+ self.class.new(self, opts_or_str, options.merge(should: true))
22
30
  end
23
31
 
24
32
  def to_boost(weight = nil)
@@ -31,8 +39,28 @@ module Stretchy
31
39
  )
32
40
  end
33
41
 
42
+ def should?
43
+ !!@should
44
+ end
45
+
34
46
  private
35
47
 
48
+ def get_storage
49
+ if inverse?
50
+ if should?
51
+ @match_builder.shouldnotmatches
52
+ else
53
+ @match_builder.antimatches
54
+ end
55
+ else
56
+ if should?
57
+ @match_builder.shouldmatches
58
+ else
59
+ @match_builder.matches
60
+ end
61
+ end
62
+ end
63
+
36
64
  def add_params(params = {})
37
65
  case params
38
66
  when Hash
@@ -45,11 +73,7 @@ module Stretchy
45
73
  end
46
74
 
47
75
  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
76
+ get_storage[field] += Array(param)
53
77
  end
54
78
 
55
79
  end
@@ -1,3 +1,5 @@
1
+ require 'stretchy/clauses/base'
2
+
1
3
  module Stretchy
2
4
  module Clauses
3
5
  class WhereClause < Base
@@ -9,32 +11,33 @@ module Stretchy
9
11
  def initialize(base, options = {})
10
12
  super(base)
11
13
  @inverse = options.delete(:inverse)
14
+ @should = options.delete(:should)
12
15
  add_params(options)
13
16
  end
14
17
 
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
18
+ def should?
19
+ !!@should
20
+ end
27
21
 
22
+ def range(field, options = {})
23
+ get_storage(:ranges)[field] = Stretchy::Types::Range.new(options)
28
24
  self
29
25
  end
30
26
 
31
27
  def geo(field, options = {})
32
- add_geo(field, options)
28
+ get_storage(:geos)[field] = {
29
+ distance: options[:distance],
30
+ geo_point: Stretchy::Types::GeoPoint.new(options)
31
+ }
33
32
  self
34
33
  end
35
34
 
36
35
  def not(options = {})
37
- self.class.new(self, options.merge(inverse: !@inverse))
36
+ self.class.new(self, options.merge(inverse: true, should: should?))
37
+ end
38
+
39
+ def should(options = {})
40
+ self.class.new(self, options.merge(should: true))
38
41
  end
39
42
 
40
43
  def to_boost(weight = nil)
@@ -68,6 +71,26 @@ module Stretchy
68
71
  end
69
72
 
70
73
  private
74
+
75
+ def get_storage(builder_field, is_inverse = nil)
76
+ is_inverse = inverse? if is_inverse.nil?
77
+ field = builder_field.to_s
78
+ if inverse? || is_inverse
79
+ if should?
80
+ field = "shouldnot#{field}"
81
+ else
82
+ field = "anti#{field}"
83
+ end
84
+ else
85
+ field = "should#{field}" if should?
86
+ end
87
+
88
+ if field =~ /match/
89
+ @match_builder.send(field)
90
+ else
91
+ @where_builder.send(field)
92
+ end
93
+ end
71
94
 
72
95
  def add_params(options = {})
73
96
  options.each do |field, param|
@@ -83,28 +106,16 @@ module Stretchy
83
106
  end
84
107
  end
85
108
 
86
- def add_geo(field, options = {})
87
- store = inverse? ? @where_builder.antigeos : @where_builder.geos
88
- store[field] = options
89
- end
90
-
91
109
  def add_param(field, param)
92
110
  case param
93
111
  when nil
94
- store = inverse? ? @where_builder.exists : @where_builder.empties
95
- store << field
112
+ get_storage(:exists, true) << field
96
113
  when String, Symbol
97
- if inverse?
98
- @match_builder.antimatches[field] += Array(param)
99
- else
100
- @match_builder.matches[field] += Array(param)
101
- end
114
+ get_storage(:matches)[field] += Array(param)
102
115
  when Range
103
- store = inverse? ? @where_builder.antiranges : @where_builder.ranges
104
- store[field] = { min: param.min, max: param.max }
116
+ get_storage(:ranges)[field] = Stretchy::Types::Range.new(param)
105
117
  else
106
- store = inverse? ? @where_builder.antiterms : @where_builder.terms
107
- store[field] += Array(param)
118
+ get_storage(:terms)[field] += Array(param)
108
119
  end
109
120
  end
110
121
 
@@ -1,3 +1,5 @@
1
+ require 'stretchy/filters/base'
2
+
1
3
  module Stretchy
2
4
  module Filters
3
5
  class AndFilter < Base
@@ -5,11 +7,7 @@ module Stretchy
5
7
  contract filters: {type: Stretchy::Filters::Base, array: true}
6
8
 
7
9
  def initialize(*args)
8
- if args.count == 1 && args.first.is_a?(Array)
9
- @filters = args.first
10
- else
11
- @filters = args
12
- end
10
+ @filters = args.flatten
13
11
  validate!
14
12
  end
15
13
 
@@ -1,3 +1,5 @@
1
+ require 'stretchy/utils/contract'
2
+
1
3
  module Stretchy
2
4
  module Filters
3
5
  class Base
@@ -14,4 +16,4 @@ module Stretchy
14
16
 
15
17
  end
16
18
  end
17
- end
19
+ end
@@ -1,3 +1,5 @@
1
+ require 'stretchy/filters/base'
2
+
1
3
  module Stretchy
2
4
  module Filters
3
5
  class BoolFilter < Base
@@ -1,3 +1,5 @@
1
+ require 'stretchy/filters/base'
2
+
1
3
  module Stretchy
2
4
  module Filters
3
5
  class ExistsFilter < Base
@@ -1,17 +1,18 @@
1
+ require 'stretchy/filters/base'
2
+ require 'stretchy/types/geo_point'
3
+
1
4
  module Stretchy
2
5
  module Filters
3
6
  class GeoFilter < Base
4
7
 
5
8
  contract distance: {type: :distance, required: true},
6
- lat: {type: :lat, required: true},
7
- lng: {type: :lng, required: true},
9
+ geo_point: {type: Stretchy::Types::GeoPoint, required: true},
8
10
  field: {type: :field, required: true}
9
11
 
10
12
  def initialize(options = {})
11
- @field = options[:field]
12
- @distance = options[:distance]
13
- @lat = options[:lat]
14
- @lng = options[:lng]
13
+ @field = options[:field]
14
+ @distance = options[:distance]
15
+ @geo_point = options[:geo_point] || Stretchy::Types::GeoPoint.new(options)
15
16
  validate!
16
17
  end
17
18
 
@@ -19,10 +20,7 @@ module Stretchy
19
20
  {
20
21
  geo_distance: {
21
22
  distance: @distance,
22
- @field => {
23
- lat: @lat,
24
- lon: @lng
25
- }
23
+ @field => @geo_point.to_search
26
24
  }
27
25
  }
28
26
  end
@@ -1,3 +1,5 @@
1
+ require 'stretchy/filters/base'
2
+
1
3
  module Stretchy
2
4
  module Filters
3
5
  class NotFilter < Base
@@ -0,0 +1,22 @@
1
+ require 'stretchy/filters/base'
2
+
3
+ module Stretchy
4
+ module Filters
5
+ class OrFilter < Base
6
+
7
+ contract filters: {type: Base, array: true}
8
+
9
+ def initialize(*args)
10
+ @filters = args.flatten
11
+ validate!
12
+ end
13
+
14
+ def to_search
15
+ {
16
+ or: @filters.map(&:to_search)
17
+ }
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,6 @@
1
+ require 'stretchy/filters/base'
2
+ require 'stretchy/queries/base'
3
+
1
4
  module Stretchy
2
5
  module Filters
3
6
  class QueryFilter < Base
@@ -1,26 +1,23 @@
1
+ require 'stretchy/filters/base'
2
+ require 'stretchy/types/range'
3
+
1
4
  module Stretchy
2
5
  module Filters
3
6
  class RangeFilter < Base
4
7
 
5
- contract field: {type: :field},
6
- min: {type: [Numeric, Time]},
7
- max: {type: [Numeric, Time]}
8
+ contract field: {type: :field, required: true},
9
+ range: {type: Stretchy::Types::Range, required: true}
8
10
 
9
11
  def initialize(options = {})
10
12
  @field = options[:field]
11
- @min = options[:min]
12
- @max = options[:max]
13
+ @range = options[:stretchy_range] || Stretchy::Types::Range.new(options)
13
14
  validate!
14
- require_one(min: @min, max: @max)
15
15
  end
16
16
 
17
17
  def to_search
18
- range = {}
19
- range[:gte] = @min if @min
20
- range[:lte] = @max if @max
21
18
  {
22
19
  range: {
23
- @field => range
20
+ @field => @range.to_search
24
21
  }
25
22
  }
26
23
  end
@@ -1,3 +1,5 @@
1
+ require 'stretchy/filters/base'
2
+
1
3
  module Stretchy
2
4
  module Filters
3
5
  class TermsFilter < Base
@@ -1,3 +1,5 @@
1
+ require 'stretchy/utils/contract'
2
+
1
3
  module Stretchy
2
4
  module Queries
3
5
  class Base
@@ -14,4 +16,4 @@ module Stretchy
14
16
 
15
17
  end
16
18
  end
17
- end
19
+ end
@@ -1,3 +1,5 @@
1
+ require 'stretchy/queries/base'
2
+
1
3
  module Stretchy
2
4
  module Queries
3
5
  class BoolQuery < Base
@@ -1,3 +1,6 @@
1
+ require 'stretchy/queries/base'
2
+ require 'stretchy/filters/base'
3
+
1
4
  module Stretchy
2
5
  module Queries
3
6
  class FilteredQuery < Base
@@ -1,3 +1,6 @@
1
+ require 'stretchy/queries/base'
2
+ require 'stretchy/boosts/base'
3
+
1
4
  module Stretchy
2
5
  module Queries
3
6
  class FunctionScoreQuery < Base
@@ -1,3 +1,5 @@
1
+ require 'stretchy/queries/base'
2
+
1
3
  module Stretchy
2
4
  module Queries
3
5
  class MatchAllQuery < Base
@@ -1,3 +1,5 @@
1
+ require 'stretchy/queries/base'
2
+
1
3
  module Stretchy
2
4
  module Queries
3
5
  class MatchQuery < Base
@@ -42,4 +42,6 @@ module Stretchy
42
42
 
43
43
  end
44
44
  end
45
- end
45
+ end
46
+
47
+ require 'stretchy/results/null_results'
@@ -1,3 +1,5 @@
1
+ require 'stretchy/results/base'
2
+
1
3
  module Stretchy
2
4
  module Results
3
5
  class NullResults < Base
@@ -0,0 +1,19 @@
1
+ require 'stretchy/utils/contract'
2
+
3
+ module Stretchy
4
+ module Types
5
+ class Base
6
+
7
+ include Stretchy::Utils::Contract
8
+
9
+ def initialize
10
+ raise "Override this in subclass"
11
+ end
12
+
13
+ def to_search
14
+ raise "Override this in subclass"
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ require 'stretchy/types/base'
2
+
3
+ module Stretchy
4
+ module Types
5
+ class GeoPoint < Base
6
+
7
+ attr_reader :lat, :lon
8
+
9
+ contract lat: { type: :lat, required: true },
10
+ lon: { type: :lng, required: true }
11
+
12
+
13
+ def initialize(options = {})
14
+ @lat = options[:lat] || options[:latitude]
15
+ @lon = options[:lng] || options[:lon] ||
16
+ options[:longitude]
17
+
18
+ validate!
19
+ end
20
+
21
+ def to_search
22
+ {
23
+ lat: @lat,
24
+ lon: @lon
25
+ }
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,52 @@
1
+ module Stretchy
2
+ module Types
3
+ class Range < Base
4
+
5
+ attr_reader :min, :max, :exclusive_min, :exclusive_max
6
+
7
+ contract min: { type: [Numeric, Date, Time] },
8
+ max: { type: [Numeric, Date, Time] },
9
+ exclusive_min: { in: [true, false] },
10
+ exclusive_max: { in: [true, false] }
11
+
12
+ def initialize(opts_or_range = {}, options = {})
13
+
14
+ case opts_or_range
15
+ when ::Range
16
+ @min = opts_or_range.min
17
+ @max = opts_or_range.max
18
+ @exclusive_min = !!(options[:exclusive_min] || options[:exclusive])
19
+ @exclusive_max = !!(options[:exclusive_max] || options[:exclusive])
20
+ when ::Hash
21
+ opts = options.merge(opts_or_range)
22
+ @min = opts[:min]
23
+ @max = opts[:max]
24
+ @exclusive_min = !!(opts[:exclusive_min] || opts[:exclusive])
25
+ @exclusive_max = !!(opts[:exclusive_max] || opts[:exclusive])
26
+ else
27
+ raise Stretchy::Errors::ContractError.new("Ranges must be a range or a hash - found #{options.class.name}")
28
+ end
29
+
30
+ require_one min: @min, max: @max
31
+ validate!
32
+ end
33
+
34
+ def to_search
35
+ json = {}
36
+ if @exclusive_min && @min
37
+ json[:gt] = @min
38
+ elsif @min
39
+ json[:gte] = @min
40
+ end
41
+
42
+ if @exclusive_max && @max
43
+ json[:lt] = @max
44
+ elsif @max
45
+ json[:lte] = @max
46
+ end
47
+ json
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -1,4 +1,3 @@
1
- require 'json'
2
1
  module Stretchy
3
2
  module Utils
4
3
  module ClientActions
@@ -18,6 +17,10 @@ module Stretchy
18
17
  client.cat.count(index: index_name).split(' ')[2].to_i
19
18
  end
20
19
 
20
+ def query(*args, &block)
21
+ Stretchy::Clauses::Base.new(*args, &block)
22
+ end
23
+
21
24
  def search(type:, body:, fields: nil)
22
25
  options = { index: index_name, type: type, body: body }
23
26
  options[:fields] = fields if fields.is_a?(Array)
@@ -4,6 +4,7 @@ module Stretchy
4
4
 
5
5
  ASSERTIONS = [:type, :responds_to, :in, :matches, :required]
6
6
  DISTANCE_FORMAT = /^(\d+)(km|mi)$/
7
+ DECAY_FUNCTIONS = [:gauss, :linear, :exp]
7
8
 
8
9
  def self.included(base)
9
10
  base.send(:extend, ClassMethods)
@@ -31,7 +32,7 @@ module Stretchy
31
32
 
32
33
  def require_one(options = {})
33
34
  if options.values.all?{|v| self.class.is_empty?(v) }
34
- raise Stretchy::Errors::ContractError.new("One of #{options.keys.join(', ')} must be present")
35
+ raise Stretchy::Errors::ContractError.new("One of #{options.keys.join(', ')} must be present, found: #{options}")
35
36
  end
36
37
  end
37
38
 
@@ -60,7 +61,7 @@ module Stretchy
60
61
 
61
62
  def assert_required(name, value, options)
62
63
  msg = "Expected to have param #{name}, but got nil"
63
- fail_assertion(msg) if msg.nil?
64
+ fail_assertion(msg) if is_empty?(value)
64
65
  end
65
66
 
66
67
  def assert_type(name, value, options)
@@ -83,6 +84,9 @@ module Stretchy
83
84
 
84
85
  msg = "Expected #{name} to be a string, symbol, or number, but was blank"
85
86
  fail_assertion(msg) if value.is_a?(String) && value.empty?
87
+ when :decay
88
+ msg = "Expected #{name} to be one of #{DECAY_FUNCTIONS.join(', ')}, but got #{value}"
89
+ fail_assertion(msg) unless DECAY_FUNCTIONS.any?{|f| f == value || f.to_s == value }
86
90
  when Array
87
91
  msg = "Expected #{name} to be one of #{type.map{|t| t.name}}, but got #{value.class.name}"
88
92
  fail_assertion(msg) unless type.any?{|t| value.is_a?(t)}
@@ -1,3 +1,3 @@
1
1
  module Stretchy
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/stretchy.rb CHANGED
@@ -4,28 +4,10 @@ require 'forwardable'
4
4
  require 'excon'
5
5
  require 'elasticsearch'
6
6
 
7
- require 'stretchy/utils/contract'
8
- require 'stretchy/utils/configuration'
9
- require 'stretchy/utils/client_actions'
10
- require 'stretchy/boosts/base'
11
- require 'stretchy/filters/base'
12
- require 'stretchy/queries/base'
13
- require 'stretchy/clauses/base'
14
- require 'stretchy/results/base'
15
-
16
- grep_require = ->(matches){
17
- Dir[File.join(File.dirname(__FILE__), 'stretchy', '**', '*.rb')].each do |path|
18
- require path if path =~ matches
19
- end
20
- }
21
-
22
- grep_require.call(/utils/)
23
- grep_require.call(/base/)
24
- grep_require.call(/(?!utils|base)/)
25
-
26
7
  module Stretchy
8
+ end
27
9
 
28
- extend Utils::Configuration
29
- extend Utils::ClientActions
10
+ Gem.find_files('stretchy/**/*.rb').reject{|f| f =~ /spec/ }.each {|f| require f }
30
11
 
31
- end
12
+ Stretchy.send(:extend, Stretchy::Utils::Configuration)
13
+ Stretchy.send(:extend, Stretchy::Utils::ClientActions)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stretchy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - agius
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-05-01 00:00:00.000000000 Z
11
+ date: 2015-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: elasticsearch
@@ -138,8 +138,8 @@ files:
138
138
  - bin/setup
139
139
  - lib/stretchy.rb
140
140
  - lib/stretchy/boosts/base.rb
141
+ - lib/stretchy/boosts/field_decay_boost.rb
141
142
  - lib/stretchy/boosts/filter_boost.rb
142
- - lib/stretchy/boosts/geo_boost.rb
143
143
  - lib/stretchy/boosts/random_boost.rb
144
144
  - lib/stretchy/builders/boost_builder.rb
145
145
  - lib/stretchy/builders/match_builder.rb
@@ -157,6 +157,7 @@ files:
157
157
  - lib/stretchy/filters/exists_filter.rb
158
158
  - lib/stretchy/filters/geo_filter.rb
159
159
  - lib/stretchy/filters/not_filter.rb
160
+ - lib/stretchy/filters/or_filter.rb
160
161
  - lib/stretchy/filters/query_filter.rb
161
162
  - lib/stretchy/filters/range_filter.rb
162
163
  - lib/stretchy/filters/terms_filter.rb
@@ -168,6 +169,9 @@ files:
168
169
  - lib/stretchy/queries/match_query.rb
169
170
  - lib/stretchy/results/base.rb
170
171
  - lib/stretchy/results/null_results.rb
172
+ - lib/stretchy/types/base.rb
173
+ - lib/stretchy/types/geo_point.rb
174
+ - lib/stretchy/types/range.rb
171
175
  - lib/stretchy/utils/client_actions.rb
172
176
  - lib/stretchy/utils/configuration.rb
173
177
  - lib/stretchy/utils/contract.rb
@@ -1,46 +0,0 @@
1
- module Stretchy
2
- module Boosts
3
- class GeoBoost < Base
4
-
5
- DEFAULTS = {
6
- field: 'coords',
7
- offset: '10km',
8
- scale: '50km',
9
- decay: 0.75,
10
- weight: 1.2
11
- }.freeze
12
-
13
- contract offset: {type: :distance},
14
- scale: {type: :distance},
15
- decay: {type: Numeric},
16
- weight: {type: Numeric},
17
- lat: {type: :lat},
18
- lng: {type: :lng}
19
-
20
- def initialize(options = {})
21
- @field = options[:field] || DEFAULTS[:field]
22
- @offset = options[:offset] || DEFAULTS[:offset]
23
- @scale = options[:scale] || DEFAULTS[:scale]
24
- @decay = options[:decay] || DEFAULTS[:decay]
25
- @weight = options[:weight] || DEFAULTS[:weight]
26
- @lat = options[:lat]
27
- @lng = options[:lng]
28
- validate!
29
- end
30
-
31
- def to_search
32
- {
33
- gauss: {
34
- @field => {
35
- origin: { lat: @lat, lon: @lng },
36
- offset: @offset,
37
- scale: @scale,
38
- decay: @decay
39
- }
40
- },
41
- weight: @weight
42
- }
43
- end
44
- end
45
- end
46
- end