searchkick 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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