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 +4 -4
- data/lib/stretchy/boosts/base.rb +2 -0
- data/lib/stretchy/boosts/filter_boost.rb +4 -4
- data/lib/stretchy/boosts/random_boost.rb +2 -2
- data/lib/stretchy/builders/boost_builder.rb +36 -0
- data/lib/stretchy/builders/match_builder.rb +44 -0
- data/lib/stretchy/builders/where_builder.rb +126 -0
- data/lib/stretchy/clauses/base.rb +95 -0
- data/lib/stretchy/clauses/boost_clause.rb +50 -0
- data/lib/stretchy/clauses/boost_match_clause.rb +31 -0
- data/lib/stretchy/clauses/boost_where_clause.rb +44 -0
- data/lib/stretchy/clauses/match_clause.rb +57 -0
- data/lib/stretchy/clauses/where_clause.rb +113 -0
- data/lib/stretchy/filters/and_filter.rb +6 -2
- data/lib/stretchy/filters/bool_filter.rb +8 -7
- data/lib/stretchy/filters/exists_filter.rb +3 -0
- data/lib/stretchy/filters/geo_filter.rb +9 -9
- data/lib/stretchy/filters/range_filter.rb +8 -6
- data/lib/stretchy/filters/terms_filter.rb +17 -9
- data/lib/stretchy/queries/bool_query.rb +21 -0
- data/lib/stretchy/queries/filtered_query.rb +4 -3
- data/lib/stretchy/queries/function_score_query.rb +1 -1
- data/lib/stretchy/queries/match_query.rb +12 -5
- data/lib/stretchy/results/base.rb +45 -0
- data/lib/stretchy/results/null_results.rb +22 -0
- data/lib/stretchy/utils/contract.rb +16 -1
- data/lib/stretchy/version.rb +1 -1
- data/lib/stretchy.rb +12 -3
- data/stretchy.gemspec +6 -4
- metadata +42 -5
- data/lib/stretchy/null_query.rb +0 -30
- data/lib/stretchy/query.rb +0 -241
- data/lib/stretchy/request_body.rb +0 -67
@@ -2,10 +2,13 @@ module Stretchy
|
|
2
2
|
module Filters
|
3
3
|
class ExistsFilter < Base
|
4
4
|
|
5
|
+
contract field: {type: :field, required: true}
|
6
|
+
|
5
7
|
# CAUTION: this will match empty strings
|
6
8
|
# see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-filter.html
|
7
9
|
def initialize(field)
|
8
10
|
@field = field
|
11
|
+
validate!
|
9
12
|
end
|
10
13
|
|
11
14
|
def to_search
|
@@ -2,16 +2,16 @@ module Stretchy
|
|
2
2
|
module Filters
|
3
3
|
class GeoFilter < Base
|
4
4
|
|
5
|
-
contract distance: {type: :distance},
|
6
|
-
lat: {type: :lat},
|
7
|
-
lng: {type: :lng},
|
8
|
-
field: {type: :field}
|
5
|
+
contract distance: {type: :distance, required: true},
|
6
|
+
lat: {type: :lat, required: true},
|
7
|
+
lng: {type: :lng, required: true},
|
8
|
+
field: {type: :field, required: true}
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@field = field
|
12
|
-
@distance = distance
|
13
|
-
@lat = lat
|
14
|
-
@lng = lng
|
10
|
+
def initialize(options = {})
|
11
|
+
@field = options[:field]
|
12
|
+
@distance = options[:distance]
|
13
|
+
@lat = options[:lat]
|
14
|
+
@lng = options[:lng]
|
15
15
|
validate!
|
16
16
|
end
|
17
17
|
|
@@ -3,13 +3,15 @@ module Stretchy
|
|
3
3
|
class RangeFilter < Base
|
4
4
|
|
5
5
|
contract field: {type: :field},
|
6
|
-
min: {type: Numeric},
|
7
|
-
max: {type: Numeric}
|
6
|
+
min: {type: [Numeric, Time]},
|
7
|
+
max: {type: [Numeric, Time]}
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@field = field
|
11
|
-
@min = min
|
12
|
-
@max = max
|
9
|
+
def initialize(options = {})
|
10
|
+
@field = options[:field]
|
11
|
+
@min = options[:min]
|
12
|
+
@max = options[:max]
|
13
|
+
validate!
|
14
|
+
require_one(min: @min, max: @max)
|
13
15
|
end
|
14
16
|
|
15
17
|
def to_search
|
@@ -2,20 +2,28 @@ module Stretchy
|
|
2
2
|
module Filters
|
3
3
|
class TermsFilter < Base
|
4
4
|
|
5
|
-
contract field: {type: :field},
|
6
|
-
values: {type: Array}
|
5
|
+
contract field: {type: :field, required: true},
|
6
|
+
values: {type: Array, required: true}
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
def initialize(field_or_opts = {}, terms = [])
|
9
|
+
if field_or_opts.is_a?(Hash)
|
10
|
+
@terms = field_or_opts
|
11
|
+
else
|
12
|
+
@terms = { field_or_opts => Array(terms) }
|
13
|
+
end
|
14
|
+
validate_terms!
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_terms!
|
18
|
+
exc = Stretchy::Errors::ContractError.new("Terms cannot be blank")
|
19
|
+
raise exc if @terms.none? || @terms.any? do |field, terms|
|
20
|
+
terms.none?
|
21
|
+
end
|
12
22
|
end
|
13
23
|
|
14
24
|
def to_search
|
15
25
|
{
|
16
|
-
terms:
|
17
|
-
@field => @values
|
18
|
-
}
|
26
|
+
terms: @terms
|
19
27
|
}
|
20
28
|
end
|
21
29
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Queries
|
3
|
+
class BoolQuery < Base
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@must = Array(options[:must])
|
7
|
+
@must_not = Array(options[:must_not])
|
8
|
+
@should = Array(options[:should])
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_search
|
12
|
+
json = {}
|
13
|
+
json[:must] = @must.map(&:to_search) if @must.any?
|
14
|
+
json[:must_not] = @must_not.map(&:to_search) if @must_not.any?
|
15
|
+
json[:should] = @should.map(&:to_search) if @should.any?
|
16
|
+
{ bool: json }
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -5,10 +5,11 @@ module Stretchy
|
|
5
5
|
contract query: {type: Base},
|
6
6
|
filter: {type: Stretchy::Filters::Base}
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@query = query
|
10
|
-
@filter = filter
|
8
|
+
def initialize(options = {})
|
9
|
+
@query = options[:query]
|
10
|
+
@filter = options[:filter]
|
11
11
|
validate!
|
12
|
+
require_one(query: @query, filter: @filter)
|
12
13
|
end
|
13
14
|
|
14
15
|
def to_search
|
@@ -5,7 +5,7 @@ module Stretchy
|
|
5
5
|
SCORE_MODES = %w(multiply sum avg first max min)
|
6
6
|
BOOST_MODES = %w(multiply replace sum avg max min)
|
7
7
|
|
8
|
-
contract functions: {
|
8
|
+
contract functions: {type: Stretchy::Boosts::Base, array: true},
|
9
9
|
query: {type: Base},
|
10
10
|
filter: {type: Stretchy::Filters::Base},
|
11
11
|
score_mode: {type: String, in: SCORE_MODES},
|
@@ -8,10 +8,17 @@ module Stretchy
|
|
8
8
|
operator: {type: String, in: OPERATORS},
|
9
9
|
string: {type: String}
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def initialize(options = {})
|
12
|
+
case options
|
13
|
+
when String
|
14
|
+
@field = '_all'
|
15
|
+
@string = options
|
16
|
+
@operator = 'and'
|
17
|
+
when Hash
|
18
|
+
@field = options[:field] || '_all'
|
19
|
+
@string = options[:string]
|
20
|
+
@operator = options[:operator] || 'and'
|
21
|
+
end
|
15
22
|
validate!
|
16
23
|
end
|
17
24
|
|
@@ -19,7 +26,7 @@ module Stretchy
|
|
19
26
|
{
|
20
27
|
match: {
|
21
28
|
@field => {
|
22
|
-
query:
|
29
|
+
query: @string,
|
23
30
|
operator: @operator
|
24
31
|
}
|
25
32
|
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Results
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def initialize(request_json, options = {})
|
6
|
+
@request = request_json
|
7
|
+
@index = options[:index]
|
8
|
+
@type = options[:type]
|
9
|
+
end
|
10
|
+
|
11
|
+
def response
|
12
|
+
@response ||= Stretchy.search(type: @type, body: request_json)
|
13
|
+
end
|
14
|
+
|
15
|
+
def ids
|
16
|
+
@ids ||= response['hits']['hits'].map{|h| h['_id'] =~ /\d+(\.\d+)?/ ? h['_id'].to_i : h['_id'] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def hits
|
20
|
+
@hits ||= response['hits']['hits'].map do |hit|
|
21
|
+
merge_fields = hit.reject{|field, _| field == '_source' }
|
22
|
+
hit['_source'].merge(merge_fields)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
alias :results :hits
|
26
|
+
|
27
|
+
def took
|
28
|
+
@took ||= response['took']
|
29
|
+
end
|
30
|
+
|
31
|
+
def shards
|
32
|
+
@shards ||= response['_shards']
|
33
|
+
end
|
34
|
+
|
35
|
+
def total
|
36
|
+
@total ||= response['hits']['total']
|
37
|
+
end
|
38
|
+
|
39
|
+
def max_score
|
40
|
+
@max_score ||= response['hits']['max_score']
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Results
|
3
|
+
class NullResults < Base
|
4
|
+
# use this class when you don't want to actually run
|
5
|
+
# a search, or catch an exception or something
|
6
|
+
|
7
|
+
def response
|
8
|
+
@response ||= {
|
9
|
+
'took' => 0,
|
10
|
+
'timed_out' => false,
|
11
|
+
'_shards' => {},
|
12
|
+
'hits' => {
|
13
|
+
'total' => 0,
|
14
|
+
'max_score' => 0,
|
15
|
+
'hits' => []
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -29,10 +29,22 @@ module Stretchy
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
def require_one(options = {})
|
33
|
+
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
|
+
end
|
36
|
+
end
|
37
|
+
|
32
38
|
module ClassMethods
|
33
39
|
|
34
40
|
attr_reader :contracts
|
35
41
|
|
42
|
+
def is_empty?(value)
|
43
|
+
value.nil? ||
|
44
|
+
(value.respond_to?(:any?) && !value.any?) ||
|
45
|
+
(value.respond_to?(:length) && value.length == 0)
|
46
|
+
end
|
47
|
+
|
36
48
|
def contract(var, options = {})
|
37
49
|
@contracts ||= {}
|
38
50
|
if var.is_a?(Hash)
|
@@ -56,7 +68,7 @@ module Stretchy
|
|
56
68
|
case type
|
57
69
|
when :distance
|
58
70
|
msg = "Expected #{name} to be a distance measure, but #{value} is not a valid distance"
|
59
|
-
fail_assertion(msg) unless value.match(DISTANCE_FORMAT)
|
71
|
+
fail_assertion(msg) unless String(value).match(DISTANCE_FORMAT)
|
60
72
|
when :lat
|
61
73
|
msg = "Expected #{name} to be between 90 and -90, but was #{value}"
|
62
74
|
value = Float(value) rescue nil
|
@@ -71,6 +83,9 @@ module Stretchy
|
|
71
83
|
|
72
84
|
msg = "Expected #{name} to be a string, symbol, or number, but was blank"
|
73
85
|
fail_assertion(msg) if value.is_a?(String) && value.empty?
|
86
|
+
when Array
|
87
|
+
msg = "Expected #{name} to be one of #{type.map{|t| t.name}}, but got #{value.class.name}"
|
88
|
+
fail_assertion(msg) unless type.any?{|t| value.is_a?(t)}
|
74
89
|
else
|
75
90
|
msg = "Expected #{name} to be of type #{type}, but found #{value.class.name}"
|
76
91
|
fail_assertion(msg) unless value.is_a?(type)
|
data/lib/stretchy/version.rb
CHANGED
data/lib/stretchy.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'logger'
|
3
|
+
require 'forwardable'
|
3
4
|
require 'excon'
|
4
5
|
require 'elasticsearch'
|
5
6
|
|
@@ -9,10 +10,18 @@ require 'stretchy/utils/client_actions'
|
|
9
10
|
require 'stretchy/boosts/base'
|
10
11
|
require 'stretchy/filters/base'
|
11
12
|
require 'stretchy/queries/base'
|
13
|
+
require 'stretchy/clauses/base'
|
14
|
+
require 'stretchy/results/base'
|
12
15
|
|
13
|
-
|
14
|
-
|
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)/)
|
16
25
|
|
17
26
|
module Stretchy
|
18
27
|
|
data/stretchy.gemspec
CHANGED
@@ -22,8 +22,10 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_dependency "elasticsearch", "~> 1.0"
|
23
23
|
spec.add_dependency "excon", "~> 0.45"
|
24
24
|
|
25
|
-
spec.add_development_dependency "bundler",
|
26
|
-
spec.add_development_dependency "rake",
|
27
|
-
spec.add_development_dependency "rspec",
|
28
|
-
spec.add_development_dependency "fuubar",
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.8"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
28
|
+
spec.add_development_dependency "fuubar", "~> 2.0"
|
29
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
30
|
+
spec.add_development_dependency "awesome_print", "~> 1.6"
|
29
31
|
end
|
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.
|
4
|
+
version: 0.2.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-
|
11
|
+
date: 2015-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elasticsearch
|
@@ -94,6 +94,34 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '2.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.10'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.10'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: awesome_print
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.6'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.6'
|
97
125
|
description: Build queries for Elasticsearch with a chainable interface like ActiveRecord's.
|
98
126
|
email:
|
99
127
|
- andrew@atevans.com
|
@@ -113,6 +141,15 @@ files:
|
|
113
141
|
- lib/stretchy/boosts/filter_boost.rb
|
114
142
|
- lib/stretchy/boosts/geo_boost.rb
|
115
143
|
- lib/stretchy/boosts/random_boost.rb
|
144
|
+
- lib/stretchy/builders/boost_builder.rb
|
145
|
+
- lib/stretchy/builders/match_builder.rb
|
146
|
+
- lib/stretchy/builders/where_builder.rb
|
147
|
+
- lib/stretchy/clauses/base.rb
|
148
|
+
- lib/stretchy/clauses/boost_clause.rb
|
149
|
+
- lib/stretchy/clauses/boost_match_clause.rb
|
150
|
+
- lib/stretchy/clauses/boost_where_clause.rb
|
151
|
+
- lib/stretchy/clauses/match_clause.rb
|
152
|
+
- lib/stretchy/clauses/where_clause.rb
|
116
153
|
- lib/stretchy/errors/contract_error.rb
|
117
154
|
- lib/stretchy/filters/and_filter.rb
|
118
155
|
- lib/stretchy/filters/base.rb
|
@@ -123,14 +160,14 @@ files:
|
|
123
160
|
- lib/stretchy/filters/query_filter.rb
|
124
161
|
- lib/stretchy/filters/range_filter.rb
|
125
162
|
- lib/stretchy/filters/terms_filter.rb
|
126
|
-
- lib/stretchy/null_query.rb
|
127
163
|
- lib/stretchy/queries/base.rb
|
164
|
+
- lib/stretchy/queries/bool_query.rb
|
128
165
|
- lib/stretchy/queries/filtered_query.rb
|
129
166
|
- lib/stretchy/queries/function_score_query.rb
|
130
167
|
- lib/stretchy/queries/match_all_query.rb
|
131
168
|
- lib/stretchy/queries/match_query.rb
|
132
|
-
- lib/stretchy/
|
133
|
-
- lib/stretchy/
|
169
|
+
- lib/stretchy/results/base.rb
|
170
|
+
- lib/stretchy/results/null_results.rb
|
134
171
|
- lib/stretchy/utils/client_actions.rb
|
135
172
|
- lib/stretchy/utils/configuration.rb
|
136
173
|
- lib/stretchy/utils/contract.rb
|
data/lib/stretchy/null_query.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
module Stretchy
|
2
|
-
class NullQuery
|
3
|
-
def initialize(options = {})
|
4
|
-
end
|
5
|
-
|
6
|
-
def response
|
7
|
-
{}
|
8
|
-
end
|
9
|
-
|
10
|
-
def id_response
|
11
|
-
{}
|
12
|
-
end
|
13
|
-
|
14
|
-
def results
|
15
|
-
[]
|
16
|
-
end
|
17
|
-
|
18
|
-
def ids
|
19
|
-
[]
|
20
|
-
end
|
21
|
-
|
22
|
-
def shards
|
23
|
-
[]
|
24
|
-
end
|
25
|
-
|
26
|
-
def total
|
27
|
-
0
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|