stretchy 0.2.0 → 0.3.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.
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