stretchy 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.ruby-version +1 -0
  4. data/lib/stretchy/boosts/base.rb +2 -3
  5. data/lib/stretchy/boosts/field_decay_boost.rb +23 -19
  6. data/lib/stretchy/boosts/filter_boost.rb +6 -3
  7. data/lib/stretchy/boosts/random_boost.rb +7 -4
  8. data/lib/stretchy/builders/boost_builder.rb +1 -1
  9. data/lib/stretchy/builders/filter_builder.rb +55 -0
  10. data/lib/stretchy/builders/match_builder.rb +41 -43
  11. data/lib/stretchy/builders/query_builder.rb +37 -0
  12. data/lib/stretchy/builders/shell_builder.rb +53 -0
  13. data/lib/stretchy/builders/where_builder.rb +88 -159
  14. data/lib/stretchy/builders.rb +3 -0
  15. data/lib/stretchy/clauses/base.rb +33 -78
  16. data/lib/stretchy/clauses/boost_clause.rb +10 -10
  17. data/lib/stretchy/clauses/boost_match_clause.rb +3 -3
  18. data/lib/stretchy/clauses/boost_where_clause.rb +5 -5
  19. data/lib/stretchy/clauses/match_clause.rb +5 -24
  20. data/lib/stretchy/clauses/where_clause.rb +23 -42
  21. data/lib/stretchy/errors/validation_error.rb +17 -0
  22. data/lib/stretchy/errors.rb +1 -1
  23. data/lib/stretchy/filters/and_filter.rb +5 -1
  24. data/lib/stretchy/filters/base.rb +2 -2
  25. data/lib/stretchy/filters/bool_filter.rb +10 -4
  26. data/lib/stretchy/filters/exists_filter.rb +5 -1
  27. data/lib/stretchy/filters/geo_filter.rb +13 -7
  28. data/lib/stretchy/filters/not_filter.rb +5 -2
  29. data/lib/stretchy/filters/or_filter.rb +4 -1
  30. data/lib/stretchy/filters/query_filter.rb +5 -1
  31. data/lib/stretchy/filters/range_filter.rb +10 -5
  32. data/lib/stretchy/filters/terms_filter.rb +8 -2
  33. data/lib/stretchy/queries/base.rb +2 -2
  34. data/lib/stretchy/queries/bool_query.rb +12 -0
  35. data/lib/stretchy/queries/filtered_query.rb +8 -3
  36. data/lib/stretchy/queries/function_score_query.rb +25 -32
  37. data/lib/stretchy/queries/match_query.rb +10 -3
  38. data/lib/stretchy/results/base.rb +9 -23
  39. data/lib/stretchy/types/base.rb +2 -2
  40. data/lib/stretchy/types/geo_point.rb +15 -6
  41. data/lib/stretchy/types/range.rb +24 -6
  42. data/lib/stretchy/utils/logger.rb +10 -5
  43. data/lib/stretchy/utils/validation.rb +77 -0
  44. data/lib/stretchy/utils.rb +1 -1
  45. data/lib/stretchy/version.rb +1 -1
  46. data/lib/stretchy.rb +3 -0
  47. data/lib/stretchy_validations.rb +10 -0
  48. data/lib/validation/rule/decay.rb +20 -0
  49. data/lib/validation/rule/distance.rb +20 -0
  50. data/lib/validation/rule/field.rb +22 -0
  51. data/lib/validation/rule/inclusion.rb +33 -0
  52. data/lib/validation/rule/latitude.rb +21 -0
  53. data/lib/validation/rule/longitude.rb +22 -0
  54. data/lib/validation/rule/one_of.rb +22 -0
  55. data/lib/validation/rule/required.rb +26 -0
  56. data/lib/validation/rule/responds_to.rb +23 -0
  57. data/lib/validation/rule/type.rb +41 -0
  58. data/stretchy.gemspec +2 -0
  59. metadata +48 -5
  60. data/lib/stretchy/errors/contract_error.rb +0 -5
  61. data/lib/stretchy/utils/contract.rb +0 -120
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9296a4a37e2bf84e553b9b5757132c71d0bfca0c
4
- data.tar.gz: 8e9eea9e347fb728f3a779bc0ba32713c52549c9
3
+ metadata.gz: 53b93c00801430b33a5fe52f6ad1327b25049979
4
+ data.tar.gz: 238e3d9edb267bd9ddfe03706fc6ffc28f4ce820
5
5
  SHA512:
6
- metadata.gz: 23721cc1bee9c018e53c3ca34f68e5c9c46d6e9e593c59fcdb3eb27b32bc869fccd36e6ac64736a62e5993d9ddd2da4eaff3d1d43e70ec48cd2a2d2f166c9b4e
7
- data.tar.gz: 020882eeb4e78a3bc16a6749d963ab9850cfc6d2fd2727dc218312078e917f6a4c2738fb4b66b16dc861a89fa141f9ca88d063c94c21bc838f870d67ba85fc46
6
+ metadata.gz: e9699a839cbbb7808d1fcedd217744bb45661804be0d14f5d729cce0986f43f1653698db9fb3aa2267ce9a9cc19450a02e21e390acd6e3772b8ee4d7545b40fe
7
+ data.tar.gz: 7f865b3f1d4c7fb0e61d58b33bbe808aa0c6cc1c44d93545c2000f4819cc43b27b909e29e751745b0d8d002ade78c24b41d04099f10be7f5b4c15e75b2a45984
data/.gitignore CHANGED
@@ -8,4 +8,5 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  .rspec
11
- *.gem
11
+ *.gem
12
+ TODO*
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.2
@@ -1,13 +1,12 @@
1
- require 'stretchy/utils/contract'
1
+ require 'stretchy/utils/validation'
2
2
 
3
3
  module Stretchy
4
4
  module Boosts
5
5
  class Base
6
6
 
7
- include Stretchy::Utils::Contract
7
+ include Stretchy::Utils::Validation
8
8
 
9
9
  DEFAULT_WEIGHT = 1.2
10
- DEFAULT_DECAY_FN = :gauss
11
10
 
12
11
  def initialize
13
12
  raise "Override this in subclass"
@@ -5,39 +5,43 @@ module Stretchy
5
5
  module Boosts
6
6
  class FieldDecayBoost < Base
7
7
 
8
- attr_reader :field, :origin, :scale, :offset, :decay, :decay_amount, :weight
8
+ DEFAULT_DECAY_FN = :gauss
9
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}
10
+ attribute :field
11
+ attribute :origin
12
+ attribute :scale
13
+ attribute :offset
14
+ attribute :decay, Symbol, default: DEFAULT_DECAY_FN
15
+ attribute :decay_amount, Float, default: DEFAULT_WEIGHT
16
+ attribute :weight
17
+
18
+ validations do
19
+ rule :field, :field
20
+ rule :origin, type: {classes: [Numeric, Time, Date, Stretchy::Types::GeoPoint], required: true}
21
+ rule :scale, :required
22
+ rule :decay, :decay
23
+ rule :decay_amount, type: {classes: Numeric}
24
+ end
15
25
 
16
26
  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
-
27
+ self.class.attribute_set.set(self, options) if options
28
+ set_default_attributes
29
+
26
30
  validate!
27
31
  end
28
32
 
29
33
  def to_search
30
34
  json = {scale: @scale}
31
- if @origin.is_a?(Stretchy::Types::Base)
35
+ if @origin.is_a?(Types::Base)
32
36
  json[:origin] = @origin.to_search
33
37
  else
34
38
  json[:origin] = @origin
35
39
  end
36
- json[:offset] = @offset if @offset
37
- json[:decay] = @decay if @decay
40
+ json[:offset] = @offset if @offset
41
+ json[:decay] = @decay_amount if @decay_amount
38
42
 
39
43
  {
40
- @type => {
44
+ @decay => {
41
45
  @field => json
42
46
  },
43
47
  weight: @weight
@@ -5,10 +5,13 @@ module Stretchy
5
5
  module Boosts
6
6
  class FilterBoost < Base
7
7
 
8
- attr_reader :filter, :weight
8
+ attribute :filter
9
+ attribute :weight, Numeric, default: DEFAULT_WEIGHT
9
10
 
10
- contract filter: {type: Stretchy::Filters::Base},
11
- weight: {type: Numeric}
11
+ validations do
12
+ rule :filter, type: {classes: Filters::Base}
13
+ rule :weight, type: {classes: Numeric}
14
+ end
12
15
 
13
16
  def initialize(options = {})
14
17
  @filter = options[:filter]
@@ -4,14 +4,17 @@ module Stretchy
4
4
  module Boosts
5
5
  class RandomBoost < Base
6
6
 
7
- DEFAULT_WEIGHT = 1.2
7
+ attribute :seed, Numeric
8
+ attribute :weight, Numeric, default: DEFAULT_WEIGHT
8
9
 
9
- contract seed: {type: Numeric},
10
- weight: {type: Numeric}
10
+ validations do
11
+ rule :seed, type: {classes: Numeric, required: true}
12
+ rule :weight, type: {classes: Numeric, required: true}
13
+ end
11
14
 
12
15
  # randomizes order (somewhat) consistently per-user
13
16
  # http://www.elastic.co/guide/en/elasticsearch/guide/current/random-scoring.html
14
-
17
+
15
18
  def initialize(seed, weight = nil)
16
19
  @seed = seed
17
20
  @weight = weight || DEFAULT_WEIGHT
@@ -16,7 +16,7 @@ module Stretchy
16
16
  @functions.any?
17
17
  end
18
18
 
19
- def build(query_or_filter)
19
+ def to_search(query_or_filter)
20
20
  options = {
21
21
  functions: @functions,
22
22
  score_mode: @score_mode,
@@ -0,0 +1,55 @@
1
+ module Stretchy
2
+ module Builders
3
+ class FilterBuilder
4
+
5
+ attr_reader :terms, :ranges, :geos, :exists, :inverse, :should
6
+
7
+ def initialize(options = {})
8
+ @terms = Hash.new { [] }
9
+ @ranges = Hash.new { [] }
10
+ @geos = Hash.new { [] }
11
+ @exists = []
12
+ @inverse = !!options[:inverse]
13
+ @should = !!options[:should]
14
+ end
15
+
16
+ def any?
17
+ @terms.any? || @ranges.any? || @geos.any? || @exists.any?
18
+ end
19
+
20
+ def add_terms(field, terms)
21
+ @terms[field] += Array(terms)
22
+ @terms[field].uniq!
23
+ end
24
+
25
+ def add_range(field, options)
26
+ @ranges[field] = Filters::RangeFilter.new(field, options)
27
+ end
28
+
29
+ def add_geo(field, distance, geo_point)
30
+ @geos[field] = Filters::GeoFilter.new(field, distance, geo_point)
31
+ end
32
+
33
+ def add_exists(fields)
34
+ @exists += clean_string_array(fields)
35
+ @exists.uniq!
36
+ end
37
+
38
+ def to_filters
39
+ filters = @ranges.values + @geos.values
40
+ filters += @terms.map do |field, values|
41
+ Filters::TermsFilter.new(field, values)
42
+ end
43
+
44
+ filters += @exists.map {|field| Filters::ExistsFilter.new(field) }
45
+ filters
46
+ end
47
+
48
+ private
49
+ def clean_string_array(values)
50
+ Array(values).map(&:to_s).map(&:strip).reject(&:empty?)
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -2,69 +2,67 @@ module Stretchy
2
2
  module Builders
3
3
  class MatchBuilder
4
4
 
5
- attr_accessor :matches, :matchops,
6
- :antimatches, :antimatchops,
7
- :shouldmatches, :shouldmatchops,
8
- :shouldnotmatches, :shouldnotmatchops
5
+ attr_accessor :must, :must_not, :should, :should_not
9
6
 
10
7
  def initialize
11
- @matches = Hash.new { [] }
12
- @matchops = Hash.new { 'and' }
13
-
14
- @antimatches = Hash.new { [] }
15
- @antimatchops = Hash.new { 'and' }
16
-
17
- @shouldmatches = Hash.new { [] }
18
- @shouldmatchops = Hash.new { 'and' }
19
-
20
- @shouldnotmatches = Hash.new { [] }
21
- @shouldnotmatchops = Hash.new { 'and' }
8
+ @must = QueryBuilder.new
9
+ @must_not = QueryBuilder.new
10
+ @should = QueryBuilder.new
11
+ @should_not = QueryBuilder.new
22
12
  end
23
13
 
24
14
  def any?
25
- @matches.any? || @antimatches.any? || @shouldmatches.any? || @shouldnotmatches.any?
15
+ must.any? || must_not.any? || should.any? || should_not.any?
16
+ end
17
+
18
+ def add_matches(field, matches, options = {})
19
+ builder_from_options(options).add_matches(field, matches, options)
26
20
  end
27
21
 
28
- def build
22
+ def to_query
29
23
  return Stretchy::Queries::MatchAllQuery.new unless any?
30
24
 
31
- if @matches.count > 1 || @antimatches.any? ||
32
- @shouldmatches.any? || @shouldnotmatches.any?
33
-
25
+ if use_bool?
34
26
  bool_query
35
27
  else
36
- to_queries(@matches, @matchops).first
28
+ must.to_query.first
37
29
  end
38
30
  end
39
31
 
40
- def bool_query
41
- Stretchy::Queries::BoolQuery.new(
42
- must: to_queries(@matches, @matchops),
43
- must_not: to_queries(@antimatches, @antimatchops),
44
- should: build_should
45
- )
46
- end
32
+ private
33
+
34
+ def builder_from_options(options = {})
35
+ if options[:inverse] && options[:should]
36
+ should_not
37
+ elsif options[:inverse]
38
+ must_not
39
+ elsif options[:should]
40
+ should
41
+ else
42
+ must
43
+ end
44
+ end
45
+
46
+ def use_bool?
47
+ must.count > 1 || must_not.any? || should.any? || should_not.any?
48
+ end
47
49
 
48
- def build_should
49
- if @shouldnotmatches.any?
50
+ def bool_query
50
51
  Stretchy::Queries::BoolQuery.new(
51
- must: to_queries(@shouldmatches, @shouldmatchops),
52
- must_not: to_queries(@shouldnotmatches, @shouldnotmatchops)
52
+ must: must.to_query,
53
+ must_not: must_not.to_query,
54
+ should: build_should
53
55
  )
54
- else
55
- to_queries(@shouldmatches, @shouldmatchops)
56
56
  end
57
- end
58
-
59
- private
60
57
 
61
- def to_queries(matches, operators)
62
- matches.map do |field, strings|
63
- Stretchy::Queries::MatchQuery.new(
64
- field: field,
65
- string: strings.join(' '),
66
- operator: operators[field]
58
+ def build_should
59
+ if should.count > 1 || should_not.any?
60
+ Stretchy::Queries::BoolQuery.new(
61
+ must: should.to_query,
62
+ must_not: should_not.to_query
67
63
  )
64
+ else
65
+ should.to_query.first
68
66
  end
69
67
  end
70
68
 
@@ -0,0 +1,37 @@
1
+ module Stretchy
2
+ module Builders
3
+ class QueryBuilder
4
+
5
+ extend Forwardable
6
+
7
+ delegate [:any?, :count, :length] => :matches
8
+
9
+ attr_reader :matches, :operators
10
+
11
+ def initialize
12
+ @matches = Hash.new { [] }
13
+ @operators = Hash.new { 'and' }
14
+ end
15
+
16
+ def add_matches(field, new_matches, options)
17
+ @matches[field] += Array(new_matches).map(&:to_s).map(&:strip).reject(&:empty?)
18
+ if options[:operator]
19
+ @operators[field] = options[:operator]
20
+ elsif options[:or]
21
+ @operators[field] = 'or'
22
+ end
23
+ end
24
+
25
+ def to_query
26
+ matches.map do |field, matches_for_field|
27
+ Queries::MatchQuery.new(
28
+ field: field,
29
+ string: matches_for_field.join(' '),
30
+ operator: operators[field]
31
+ )
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,53 @@
1
+ module Stretchy
2
+ module Builders
3
+ class ShellBuilder
4
+
5
+ DEFAULT_LIMIT = 30
6
+ DEFAULT_OFFSET = 0
7
+
8
+ attr_accessor :index, :type, :limit, :offset, :explain,
9
+ :match_builder, :where_builder, :boost_builder,
10
+ :aggregate_builder, :fields
11
+
12
+ def initialize(options = {})
13
+ @index = options[:index] || Stretchy.index_name
14
+ @match_builder = Stretchy::Builders::MatchBuilder.new
15
+ @where_builder = Stretchy::Builders::WhereBuilder.new
16
+ @boost_builder = Stretchy::Builders::BoostBuilder.new
17
+ @aggregate_builder = {}
18
+ @limit = options[:limit] || DEFAULT_LIMIT
19
+ @offset = options[:offset] || DEFAULT_OFFSET
20
+ @fields = options[:fields]
21
+ @type = options[:type]
22
+ end
23
+
24
+ def page
25
+ (offset.to_f / limit).ceil + 1
26
+ end
27
+ alias :current_page :page
28
+
29
+ #
30
+ # Compiles the internal representation of your filters,
31
+ # full-text queries, and boosts into the JSON to be
32
+ # passed to Elastic. If you want to know exactly what
33
+ # your query generated, you can call this method.
34
+ #
35
+ # @return [Hash] the query hash to be compiled to json
36
+ # and sent to Elastic
37
+ def to_search
38
+ _to_search = if where_builder.any?
39
+ Stretchy::Queries::FilteredQuery.new(
40
+ query: match_builder.to_query,
41
+ filter: where_builder.to_filter
42
+ )
43
+ else
44
+ match_builder.to_query
45
+ end
46
+
47
+ _to_search = boost_builder.to_search(_to_search) if boost_builder.any?
48
+ _to_search.to_search
49
+ end
50
+
51
+ end
52
+ end
53
+ end