stretchy 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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(field: 'coords', distance: '50km', lat:, lng:)
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(field:, min:, max:)
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(field:, values:)
9
- @field = field
10
- @values = Array(values)
11
- validate!
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(query: nil, filter:)
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: {responds_to: :to_search, array: true},
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(string, field: '_all', operator: 'and')
12
- @field = field
13
- @operator = operator
14
- @string = string
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: @string,
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)
@@ -1,3 +1,3 @@
1
1
  module Stretchy
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
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
- Dir[File.join(File.dirname(__FILE__), 'stretchy', '**', '*.rb')].each do |path|
14
- require path unless path =~ /utils/ || path =~ /base/
15
- end
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", "~> 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"
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.1.2
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-04-21 00:00:00.000000000 Z
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/query.rb
133
- - lib/stretchy/request_body.rb
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
@@ -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