searchkick 2.4.0 → 2.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4bbb1863e16cb46d246eb067805b4a2cc1471c68
4
- data.tar.gz: ea1bc2af0dcb8eb2e059aae9586697086a3cbc0e
3
+ metadata.gz: 48b011f59eaeb3f3b2213e20bf2206d4963f7bb0
4
+ data.tar.gz: d3ffbd07eda7ca733915c251ce8b1e1c9c146e44
5
5
  SHA512:
6
- metadata.gz: 50d82877150dc9a875e8d67539f1992d29870b8cef227b7e81eb1c05f8e6e8346b952914035af8d2b1b5177cd8c730f5e55eb57c0a725feeaf0a2d6bfad6a29a
7
- data.tar.gz: 45bea9181b909710ff9f446df5151f536e002caf11249d3d53c40ea7a1eecb465e797acf896c23ab15fc9b54f7028ce9d0244c1680dcb6f5281d7f3dc4f6d754
6
+ metadata.gz: 910eaa5810045780efabe7874e0c4e6bb669ad30c3f1bead3c37ab1f817e21a5e73c55acfcf7ffeacc63be95e163ddb5470b4912967379062afab45413d9d8ea
7
+ data.tar.gz: eae561c188d98071116695b8997f95ac31f1d0229fc56f968193aaac423020ee1219344568c61e2e1d7ddeee94f583189300845b5f5927a7122ab2cf5305f127
data/.travis.yml CHANGED
@@ -20,7 +20,7 @@ gemfile:
20
20
  - test/gemfiles/mongoid5.gemfile
21
21
  - test/gemfiles/mongoid6.gemfile
22
22
  env:
23
- - ELASTICSEARCH_VERSION=6.0.0
23
+ - ELASTICSEARCH_VERSION=6.2.1
24
24
  jdk: oraclejdk8
25
25
  matrix:
26
26
  include:
@@ -36,3 +36,6 @@ matrix:
36
36
  - gemfile: Gemfile
37
37
  env: ELASTICSEARCH_VERSION=5.6.4
38
38
  jdk: oraclejdk8
39
+ - gemfile: Gemfile
40
+ env: ELASTICSEARCH_VERSION=6.0.0
41
+ jdk: oraclejdk8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 2.5.0
2
+
3
+ - Try requests 3 times before raising error
4
+ - Better exception when trying to access results for failed multi-search query
5
+ - More efficient aggregations with `where` clauses
6
+ - Added support for `faraday_middleware-aws-sigv4`
7
+ - Added `credentials` option to `aws_credentials`
8
+ - Added `modifier` option to `boost_by`
9
+ - Added `scope_results` option
10
+ - Added `factor` option to `boost_by_distance`
11
+
1
12
  ## 2.4.0
2
13
 
3
14
  - Fixed `similar` for Elasticsearch 6
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,51 @@
1
+ # Contributing
2
+
3
+ First, thanks for wanting to contribute. You’re awesome! :heart:
4
+
5
+ ## Questions
6
+
7
+ Use [Stack Overflow](https://stackoverflow.com/) with the tag `searchkick`.
8
+
9
+ ## Feature Requests
10
+
11
+ Create an issue. Start the title with `[Idea]`.
12
+
13
+ ## Issues
14
+
15
+ Think you’ve discovered an issue?
16
+
17
+ 1. Search existing issues to see if it’s been reported.
18
+ 2. Try the `master` branch to make sure it hasn’t been fixed.
19
+
20
+ ```rb
21
+ gem "searchkick", github: "ankane/searchkick"
22
+ ```
23
+
24
+ 3. Try the `debug` option when searching. This can reveal useful info.
25
+
26
+ ```ruby
27
+ Product.search("something", debug: true)
28
+ ```
29
+
30
+ If the above steps don’t help, create an issue.
31
+
32
+ - For incorrect search results, recreate the problem by forking [this gist](https://gist.github.com/ankane/f80b0923d9ae2c077f41997f7b704e5c). Include a link to your gist and the output in the issue.
33
+ - For exceptions, include the complete backtrace.
34
+
35
+ ## Pull Requests
36
+
37
+ Fork the project and create a pull request. A few tips:
38
+
39
+ - Keep changes to a minimum. If you have multiple features or fixes, submit multiple pull requests.
40
+ - Follow the existing style. The code should read like it’s written by a single person.
41
+ - Add one or more tests if possible. Make sure existing tests pass with:
42
+
43
+ ```sh
44
+ bundle exec rake test
45
+ ```
46
+
47
+ Feel free to open an issue to get feedback on your idea before spending too much time on it.
48
+
49
+ ---
50
+
51
+ This contributing guide is released under [CCO](https://creativecommons.org/publicdomain/zero/1.0/) (public domain). Use it for your own project without attribution.
data/README.md CHANGED
@@ -299,6 +299,8 @@ end
299
299
 
300
300
  Call `Product.reindex` after changing synonyms.
301
301
 
302
+ Synonyms cannot be more than two words at the moment.
303
+
302
304
  To read synonyms from a file, use:
303
305
 
304
306
  ```ruby
@@ -759,6 +761,12 @@ Date histogram
759
761
  Product.search "pear", aggs: {products_per_year: {date_histogram: {field: :created_at, interval: :year}}}
760
762
  ```
761
763
 
764
+ For other aggregation types, including sub-aggregations, use `body_options`:
765
+
766
+ ```ruby
767
+ Product.search "orange", body_options: {aggs: {price: {histogram: {field: :price, interval: 10}}}
768
+ ```
769
+
762
770
  #### Moving From Facets
763
771
 
764
772
  1. Replace `facets` with `aggs` in searches. **Note:** Stats facets are not supported at this time.
@@ -895,6 +903,8 @@ Bounded by a box
895
903
  Restaurant.search "sushi", where: {location: {top_left: {lat: 38, lon: -123}, bottom_right: {lat: 37, lon: -122}}}
896
904
  ```
897
905
 
906
+ **Note:** `top_right` and `bottom_left` also work
907
+
898
908
  Bounded by a polygon
899
909
 
900
910
  ```ruby
@@ -1047,20 +1057,32 @@ Searchkick uses `ENV["ELASTICSEARCH_URL"]` for the Elasticsearch server. This de
1047
1057
 
1048
1058
  ### Heroku
1049
1059
 
1050
- Choose an add-on: [SearchBox](https://elements.heroku.com/addons/searchbox), [Bonsai](https://elements.heroku.com/addons/bonsai), or [Elastic Cloud](https://elements.heroku.com/addons/foundelasticsearch).
1060
+ Choose an add-on: [Bonsai](https://elements.heroku.com/addons/bonsai) or [Elastic Cloud](https://elements.heroku.com/addons/foundelasticsearch). [SearchBox](https://elements.heroku.com/addons/searchbox) does not work at the moment.
1051
1061
 
1052
- ```sh
1053
- # SearchBox
1054
- heroku addons:create searchbox:starter
1055
- heroku config:set ELASTICSEARCH_URL=`heroku config:get SEARCHBOX_URL`
1062
+ For Bonsai:
1056
1063
 
1057
- # Bonsai
1064
+ ```sh
1058
1065
  heroku addons:create bonsai
1059
1066
  heroku config:set ELASTICSEARCH_URL=`heroku config:get BONSAI_URL`
1067
+ ```
1068
+
1069
+ For Found:
1060
1070
 
1061
- # Found
1071
+ ```sh
1062
1072
  heroku addons:create foundelasticsearch
1063
- heroku config:set ELASTICSEARCH_URL=`heroku config:get FOUNDELASTICSEARCH_URL`
1073
+ heroku addons:open foundelasticsearch
1074
+ ```
1075
+
1076
+ Visit the Shield page and reset your password. You’ll need to add the username and password to your url. Get the existing url with:
1077
+
1078
+ ```sh
1079
+ heroku config:get FOUNDELASTICSEARCH_URL
1080
+ ```
1081
+
1082
+ And add `elastic:password@` right after `https://`:
1083
+
1084
+ ```sh
1085
+ heroku config:set ELASTICSEARCH_URL=https://elastic:password@12345.us-east-1.aws.found.io
1064
1086
  ```
1065
1087
 
1066
1088
  Then deploy and reindex:
@@ -1415,7 +1437,7 @@ class Product < ApplicationRecord
1415
1437
  searchkick mappings: {
1416
1438
  product: {
1417
1439
  properties: {
1418
- name: {type: "string", analyzer: "keyword"}
1440
+ name: {type: "keyword"}
1419
1441
  }
1420
1442
  }
1421
1443
  }
@@ -1506,20 +1528,6 @@ To query nested data, use dot notation.
1506
1528
  User.search "san", fields: ["address.city"], where: {"address.zip_code" => 12345}
1507
1529
  ```
1508
1530
 
1509
- ## Search Concepts
1510
-
1511
- ### Precision and Recall
1512
-
1513
- [Precision and recall](https://en.wikipedia.org/wiki/Precision_and_recall) are two key concepts in search (also known as *information retrieval*). To help illustrate, let’s walk through an example.
1514
-
1515
- You have a store with 16 types of apples. A user searches for `apples` gets 10 results. 8 of the results are for apples, and 2 are for apple juice.
1516
-
1517
- **Precision** is the fraction of documents in the results that are relevant. There are 10 results and 8 are relevant, so precision is 80%.
1518
-
1519
- **Recall** is the fraction of relevant documents in the results out of all relevant documents. There are 16 apples and only 8 in the results, so recall is 50%.
1520
-
1521
- There’s typically a trade-off between the two. As you tweak your search to increase precision (not return irrelevant documents), there’s are greater chance a relevant document also isn’t returned, which decreases recall. The opposite also applies. As you try to increase recall (return a higher number of relevent documents), there’s a greater chance you also return an irrelevant document, decreasing precision.
1522
-
1523
1531
  ## Reference
1524
1532
 
1525
1533
  Reindex one record
@@ -1658,6 +1666,12 @@ Eager load different associations by model
1658
1666
  Searchkick.search("*", index_name: [Product, Store], model_includes: {Product => [:store], Store => [:product]})
1659
1667
  ```
1660
1668
 
1669
+ Run additional scopes on results [master]
1670
+
1671
+ ```ruby
1672
+ Product.search "milk", scope_results: ->(r) { r.with_attached_images }
1673
+ ```
1674
+
1661
1675
  Specify default fields to search
1662
1676
 
1663
1677
  ```ruby
@@ -1,6 +1,6 @@
1
1
  module Searchkick
2
2
  class BulkReindexJob < ActiveJob::Base
3
- queue_as :searchkick
3
+ queue_as { Searchkick.queue_name }
4
4
 
5
5
  def perform(class_name:, record_ids: nil, index_name: nil, method_name: nil, batch_id: nil, min_id: nil, max_id: nil)
6
6
  klass = class_name.constantize
@@ -0,0 +1,12 @@
1
+ module Searchkick
2
+ # Subclass of `Hashie::Mash` to wrap Hash-like structures
3
+ # (responses from Elasticsearch)
4
+ #
5
+ # The primary goal of the subclass is to disable the
6
+ # warning being printed by Hashie for re-defined
7
+ # methods, such as `sort`.
8
+ #
9
+ class HashWrapper < ::Hashie::Mash
10
+ disable_warnings if respond_to?(:disable_warnings)
11
+ end
12
+ end
@@ -174,7 +174,8 @@ module Searchkick
174
174
  if synonyms.any?
175
175
  settings[:analysis][:filter][:searchkick_synonym] = {
176
176
  type: "synonym",
177
- synonyms: synonyms.select { |s| s.size > 1 }.map { |s| s.is_a?(Array) ? s.join(",") : s }.map(&:downcase)
177
+ # only remove a single space from synonyms so three-word synonyms will fail noisily instead of silently
178
+ synonyms: synonyms.select { |s| s.size > 1 }.map { |s| s.is_a?(Array) ? s.map { |s| s.sub(/\s+/, "") }.join(",") : s }.map(&:downcase)
178
179
  }
179
180
  # choosing a place for the synonym filter when stemming is not easy
180
181
  # https://groups.google.com/forum/#!topic/elasticsearch/p7qcQlgHdB8
@@ -1,6 +1,6 @@
1
1
  module Searchkick
2
2
  class ProcessBatchJob < ActiveJob::Base
3
- queue_as :searchkick
3
+ queue_as { Searchkick.queue_name }
4
4
 
5
5
  def perform(class_name:, record_ids:)
6
6
  klass = class_name.constantize
@@ -1,6 +1,6 @@
1
1
  module Searchkick
2
2
  class ProcessQueueJob < ActiveJob::Base
3
- queue_as :searchkick
3
+ queue_as { Searchkick.queue_name }
4
4
 
5
5
  def perform(class_name:)
6
6
  model = class_name.constantize
@@ -19,7 +19,7 @@ module Searchkick
19
19
  :boost_by, :boost_by_distance, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :execute, :explain,
20
20
  :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load,
21
21
  :match, :misspellings, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile,
22
- :request_params, :routing, :select, :similar, :smart_aggs, :suggest, :track, :type, :where]
22
+ :request_params, :routing, :scope_results, :select, :similar, :smart_aggs, :suggest, :track, :type, :where]
23
23
  raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
24
24
 
25
25
  term = term.to_s
@@ -98,7 +98,7 @@ module Searchkick
98
98
  # no easy way to tell which host the client will use
99
99
  host = Searchkick.client.transport.hosts.first
100
100
  credentials = host[:user] || host[:password] ? "#{host[:user]}:#{host[:password]}@" : nil
101
- "curl #{host[:protocol]}://#{credentials}#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map { |t| CGI.escape(t) }.join(',')}" : ''}/_search?pretty -d '#{query[:body].to_json}'"
101
+ "curl #{host[:protocol]}://#{credentials}#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map { |t| CGI.escape(t) }.join(',')}" : ''}/_search?pretty -H 'Content-Type: application/json' -d '#{query[:body].to_json}'"
102
102
  end
103
103
 
104
104
  def handle_response(response)
@@ -112,7 +112,9 @@ module Searchkick
112
112
  json: !@json.nil?,
113
113
  match_suffix: @match_suffix,
114
114
  highlighted_fields: @highlighted_fields || [],
115
- misspellings: @misspellings
115
+ misspellings: @misspellings,
116
+ term: term,
117
+ scope_results: options[:scope_results]
116
118
  }
117
119
 
118
120
  if options[:debug]
@@ -455,12 +457,16 @@ module Searchkick
455
457
  where[:type] = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v, true) }
456
458
  end
457
459
 
458
- # filters
460
+ # start everything as efficient filters
461
+ # move to post_filters as aggs demand
459
462
  filters = where_filters(where)
460
- set_filters(payload, filters) if filters.any?
463
+ post_filters = []
461
464
 
462
465
  # aggregations
463
- set_aggregations(payload) if options[:aggs]
466
+ set_aggregations(payload, filters, post_filters) if options[:aggs]
467
+
468
+ # filters
469
+ set_filters(payload, filters, post_filters)
464
470
 
465
471
  # suggestions
466
472
  set_suggestions(payload, options[:suggest]) if options[:suggest]
@@ -545,6 +551,7 @@ module Searchkick
545
551
  function_params = attributes.select { |k, _| [:origin, :scale, :offset, :decay].include?(k) }
546
552
  function_params[:origin] = location_value(function_params[:origin])
547
553
  custom_filters << {
554
+ weight: attributes[:factor] || 1,
548
555
  attributes[:function] => {
549
556
  field => function_params
550
557
  }
@@ -653,12 +660,11 @@ module Searchkick
653
660
  @highlighted_fields = payload[:highlight][:fields].keys
654
661
  end
655
662
 
656
- def set_aggregations(payload)
663
+ def set_aggregations(payload, filters, post_filters)
657
664
  aggs = options[:aggs]
658
665
  payload[:aggs] = {}
659
666
 
660
667
  aggs = Hash[aggs.map { |f| [f, {}] }] if aggs.is_a?(Array) # convert to more advanced syntax
661
-
662
668
  aggs.each do |field, agg_options|
663
669
  size = agg_options[:limit] ? agg_options[:limit] : 1_000
664
670
  shared_agg_options = agg_options.slice(:order, :min_doc_count)
@@ -703,6 +709,17 @@ module Searchkick
703
709
  where = {}
704
710
  where = (options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false
705
711
  agg_filters = where_filters(where.merge(agg_options[:where] || {}))
712
+
713
+ # only do one level comparison for simplicity
714
+ filters.select! do |filter|
715
+ if agg_filters.include?(filter)
716
+ true
717
+ else
718
+ post_filters << filter
719
+ false
720
+ end
721
+ end
722
+
706
723
  if agg_filters.any?
707
724
  payload[:aggs][field] = {
708
725
  filter: {
@@ -718,14 +735,16 @@ module Searchkick
718
735
  end
719
736
  end
720
737
 
721
- def set_filters(payload, filters)
722
- if options[:aggs]
738
+ def set_filters(payload, filters, post_filters)
739
+ if post_filters.any?
723
740
  payload[:post_filter] = {
724
741
  bool: {
725
- filter: filters
742
+ filter: post_filters
726
743
  }
727
744
  }
728
- else
745
+ end
746
+
747
+ if filters.any?
729
748
  # more efficient query if no aggs
730
749
  payload[:query] = {
731
750
  bool: {
@@ -769,7 +788,7 @@ module Searchkick
769
788
  if value.is_a?(Hash)
770
789
  value.each do |op, op_value|
771
790
  case op
772
- when :within, :bottom_right
791
+ when :within, :bottom_right, :bottom_left
773
792
  # do nothing
774
793
  when :near
775
794
  filters << {
@@ -804,6 +823,15 @@ module Searchkick
804
823
  }
805
824
  }
806
825
  }
826
+ when :top_right
827
+ filters << {
828
+ geo_bounding_box: {
829
+ field => {
830
+ top_right: location_value(op_value),
831
+ bottom_left: location_value(value[:bottom_left])
832
+ }
833
+ }
834
+ }
807
835
  when :regexp # support for regexp queries without using a regexp ruby object
808
836
  filters << {regexp: {field => {value: op_value}}}
809
837
  when :not # not equal
@@ -886,7 +914,7 @@ module Searchkick
886
914
  field_value_factor: {
887
915
  field: field,
888
916
  factor: value[:factor].to_f,
889
- modifier: log ? "ln2p" : nil
917
+ modifier: value[:modifier] || (log ? "ln2p" : nil)
890
918
  }
891
919
  }
892
920
 
@@ -68,7 +68,7 @@ module Searchkick
68
68
  end
69
69
 
70
70
  result["id"] ||= result["_id"] # needed for legacy reasons
71
- Hashie::Mash.new(result)
71
+ HashWrapper.new(result)
72
72
  end
73
73
  end
74
74
  end
@@ -77,6 +77,8 @@ module Searchkick
77
77
  def suggestions
78
78
  if response["suggest"]
79
79
  response["suggest"].values.flat_map { |v| v.first["options"] }.sort_by { |o| -o["score"] }.map { |o| o["text"] }.uniq
80
+ elsif options[:term] == "*"
81
+ []
80
82
  else
81
83
  raise "Pass `suggest: true` to the search method for suggestions"
82
84
  end
@@ -187,7 +189,11 @@ module Searchkick
187
189
  end
188
190
 
189
191
  def hits
190
- @response["hits"]["hits"]
192
+ if error
193
+ raise Searchkick::Error, "Query error - use the error method to view it"
194
+ else
195
+ @response["hits"]["hits"]
196
+ end
191
197
  end
192
198
 
193
199
  def misspellings?
@@ -215,6 +221,10 @@ module Searchkick
215
221
  end
216
222
  end
217
223
 
224
+ if options[:scope_results]
225
+ records = options[:scope_results].call(records)
226
+ end
227
+
218
228
  Searchkick.load_records(records, ids)
219
229
  end
220
230
 
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "2.4.0"
2
+ VERSION = "2.5.0"
3
3
  end
data/lib/searchkick.rb CHANGED
@@ -6,6 +6,7 @@ require "searchkick/index_options"
6
6
  require "searchkick/index"
7
7
  require "searchkick/indexer"
8
8
  require "searchkick/reindex_queue"
9
+ require "searchkick/hash_wrapper"
9
10
  require "searchkick/results"
10
11
  require "searchkick/query"
11
12
  require "searchkick/multi_search"
@@ -54,14 +55,11 @@ module Searchkick
54
55
 
55
56
  Elasticsearch::Client.new({
56
57
  url: ENV["ELASTICSEARCH_URL"],
57
- transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}}
58
+ transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}},
59
+ retry_on_failure: 2
58
60
  }.deep_merge(client_options)) do |f|
59
61
  f.use Searchkick::Middleware
60
- f.request :aws_signers_v4, {
61
- credentials: Aws::Credentials.new(aws_credentials[:access_key_id], aws_credentials[:secret_access_key]),
62
- service_name: "es",
63
- region: aws_credentials[:region] || "us-east-1"
64
- } if aws_credentials
62
+ f.request signer_middleware_key, signer_middleware_aws_params if aws_credentials
65
63
  end
66
64
  end
67
65
  end
@@ -136,7 +134,11 @@ module Searchkick
136
134
  end
137
135
 
138
136
  def self.aws_credentials=(creds)
139
- require "faraday_middleware/aws_signers_v4"
137
+ begin
138
+ require "faraday_middleware/aws_signers_v4"
139
+ rescue LoadError
140
+ require "faraday_middleware/aws_sigv4"
141
+ end
140
142
  @aws_credentials = creds
141
143
  @client = nil # reset client
142
144
  end
@@ -200,6 +202,24 @@ module Searchkick
200
202
  def self.callbacks_value=(value)
201
203
  Thread.current[:searchkick_callbacks_enabled] = value
202
204
  end
205
+
206
+ # private
207
+ def self.signer_middleware_key
208
+ defined?(FaradayMiddleware::AwsSignersV4) ? :aws_signers_v4 : :aws_sigv4
209
+ end
210
+
211
+ # private
212
+ def self.signer_middleware_aws_params
213
+ if signer_middleware_key == :aws_sigv4
214
+ {service: "es", region: "us-east-1"}.merge(aws_credentials)
215
+ else
216
+ {
217
+ credentials: aws_credentials[:credentials] || Aws::Credentials.new(aws_credentials[:access_key_id], aws_credentials[:secret_access_key]),
218
+ service_name: "es",
219
+ region: aws_credentials[:region] || "us-east-1"
220
+ }
221
+ end
222
+ end
203
223
  end
204
224
 
205
225
  # TODO find better ActiveModel hook
data/test/aggs_test.rb CHANGED
@@ -178,6 +178,26 @@ class AggsTest < Minitest::Test
178
178
  assert_equal 66, products.aggs["sum_price"]["value"]
179
179
  end
180
180
 
181
+ def test_body_options
182
+ products =
183
+ Product.search("*",
184
+ body_options: {
185
+ aggs: {
186
+ price: {
187
+ histogram: {field: :price, interval: 10}
188
+ }
189
+ }
190
+ }
191
+ )
192
+
193
+ expected = [
194
+ {"key" => 0.0, "doc_count" => 1},
195
+ {"key" => 10.0, "doc_count" => 1},
196
+ {"key" => 20.0, "doc_count" => 2}
197
+ ]
198
+ assert_equal products.aggs["price"]["buckets"], expected
199
+ end
200
+
181
201
  protected
182
202
 
183
203
  def buckets_as_hash(agg)
data/test/boost_test.rb CHANGED
@@ -191,6 +191,17 @@ class BoostTest < Minitest::Test
191
191
  assert_order "san", ["San Francisco", "San Antonio", "San Marino"], boost_by_distance: {location: {origin: {lat: 37, lon: -122}, scale: "1000mi"}}
192
192
  end
193
193
 
194
+ def test_boost_by_distance_v2_factor
195
+ store [
196
+ {name: "San Francisco", latitude: 37.7833, longitude: -122.4167, found_rate: 0.1},
197
+ {name: "San Antonio", latitude: 29.4167, longitude: -98.5000, found_rate: 0.99},
198
+ {name: "San Marino", latitude: 43.9333, longitude: 12.4667, found_rate: 0.2}
199
+ ]
200
+
201
+ assert_order "san", ["San Antonio","San Francisco", "San Marino"], boost_by: {found_rate: {factor: 100}}, boost_by_distance: {location: {origin: [37, -122], scale: "1000mi"}}
202
+ assert_order "san", ["San Francisco", "San Antonio", "San Marino"], boost_by: {found_rate: {factor: 100}}, boost_by_distance: {location: {origin: [37, -122], scale: "1000mi", factor: 100}}
203
+ end
204
+
194
205
  def test_boost_by_indices
195
206
  skip if cequel?
196
207
 
@@ -14,4 +14,4 @@ fi
14
14
  tar -xvf elasticsearch-$ELASTICSEARCH_VERSION.tar.gz
15
15
  cd elasticsearch-$ELASTICSEARCH_VERSION/bin
16
16
  ./elasticsearch -d
17
- wget -O- --waitretry=1 --tries=30 --retry-connrefused -v http://127.0.0.1:9200/
17
+ wget -O- --waitretry=1 --tries=60 --retry-connrefused -v http://127.0.0.1:9200/
@@ -33,4 +33,12 @@ class MultiSearchTest < Minitest::Test
33
33
  Searchkick.multi_search([products], retry_misspellings: true)
34
34
  assert_equal ["abc", "abd"], products.map(&:name)
35
35
  end
36
+
37
+ def test_error
38
+ products = Product.search("*", order: {bad_column: :asc}, execute: false)
39
+ Searchkick.multi_search([products])
40
+ assert products.error
41
+ error = assert_raises(Searchkick::Error) { products.results }
42
+ assert_equal error.message, "Query error - use the error method to view it"
43
+ end
36
44
  end
data/test/sql_test.rb CHANGED
@@ -211,4 +211,11 @@ class SqlTest < Minitest::Test
211
211
  assert records.first.association(associations[klass].first).loaded?
212
212
  end
213
213
  end
214
+
215
+ def test_scope_results
216
+ skip unless defined?(ActiveRecord)
217
+
218
+ store_names ["Product A", "Product B"]
219
+ assert_search "product", ["Product A"], scope_results: ->(r) { r.where(name: "Product A") }
220
+ end
214
221
  end
data/test/suggest_test.rb CHANGED
@@ -68,6 +68,7 @@ class SuggestTest < Minitest::Test
68
68
  end
69
69
 
70
70
  def test_multiple_models
71
+ skip # flaky test
71
72
  store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
72
73
  assert_equal "how big is a tiger shark", Searchkick.search("How Big is a Tigre Shar", suggest: [:name], fields: [:name]).suggestions.first
73
74
  end
@@ -77,6 +78,10 @@ class SuggestTest < Minitest::Test
77
78
  assert_raises(ArgumentError) { Searchkick.search("How Big is a Tigre Shar", suggest: true) }
78
79
  end
79
80
 
81
+ def test_star
82
+ assert_equal [], Product.search("*", suggest: true).suggestions
83
+ end
84
+
80
85
  protected
81
86
 
82
87
  def assert_suggest(term, expected, options = {})
data/test/test_helper.rb CHANGED
@@ -399,7 +399,7 @@ class Product
399
399
  synonyms: [
400
400
  ["clorox", "bleach"],
401
401
  ["scallion", "greenonion"],
402
- ["saranwrap", "plasticwrap"],
402
+ ["saran wrap", "plastic wrap"],
403
403
  ["qtip", "cottonswab"],
404
404
  ["burger", "hamburger"],
405
405
  ["bandaid", "bandag"],
data/test/where_test.rb CHANGED
@@ -189,6 +189,22 @@ class WhereTest < Minitest::Test
189
189
  assert_search "san", ["San Francisco"], where: {location: {top_left: {lat: 38, lon: -123}, bottom_right: {lat: 37, lon: -122}}}
190
190
  end
191
191
 
192
+ def test_top_right_bottom_left
193
+ store [
194
+ {name: "San Francisco", latitude: 37.7833, longitude: -122.4167},
195
+ {name: "San Antonio", latitude: 29.4167, longitude: -98.5000}
196
+ ]
197
+ assert_search "san", ["San Francisco"], where: {location: {top_right: [38, -122], bottom_left: [37, -123]}}
198
+ end
199
+
200
+ def test_top_right_bottom_left_hash
201
+ store [
202
+ {name: "San Francisco", latitude: 37.7833, longitude: -122.4167},
203
+ {name: "San Antonio", latitude: 29.4167, longitude: -98.5000}
204
+ ]
205
+ assert_search "san", ["San Francisco"], where: {location: {top_right: {lat: 38, lon: -122}, bottom_left: {lat: 37, lon: -123}}}
206
+ end
207
+
192
208
  def test_multiple_locations
193
209
  store [
194
210
  {name: "San Francisco", latitude: 37.7833, longitude: -122.4167},
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchkick
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-15 00:00:00.000000000 Z
11
+ date: 2018-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -104,6 +104,7 @@ files:
104
104
  - ".gitignore"
105
105
  - ".travis.yml"
106
106
  - CHANGELOG.md
107
+ - CONTRIBUTING.md
107
108
  - Gemfile
108
109
  - LICENSE.txt
109
110
  - README.md
@@ -112,6 +113,7 @@ files:
112
113
  - benchmark/benchmark.rb
113
114
  - lib/searchkick.rb
114
115
  - lib/searchkick/bulk_reindex_job.rb
116
+ - lib/searchkick/hash_wrapper.rb
115
117
  - lib/searchkick/index.rb
116
118
  - lib/searchkick/index_options.rb
117
119
  - lib/searchkick/indexer.rb