stretchy 0.4.0 → 0.4.1

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 (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