stretchy 0.1.2 → 0.2.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.
@@ -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