Commit 7ce0440ab4f2fdd02a52b2bcf42fb4f90e668667

Authored by Andrew Kane
1 parent 4054147c

Added support for Elasticsearch 7

.travis.yml
... ... @@ -14,30 +14,24 @@ cache:
14 14 directories:
15 15 - $HOME/elasticsearch
16 16 env:
17   - - ELASTICSEARCH_VERSION=6.7.0
  17 + - ELASTICSEARCH_VERSION=7.0.0
18 18 jdk: openjdk10
19 19 matrix:
20 20 include:
21 21 - gemfile: Gemfile
22 22 - gemfile: test/gemfiles/activerecord51.gemfile
23   - env: ELASTICSEARCH_VERSION=6.0.0
  23 + env: ELASTICSEARCH_VERSION=7.0.0
24 24 - gemfile: test/gemfiles/activerecord50.gemfile
25   - env: ELASTICSEARCH_VERSION=5.6.10
26   - - gemfile: test/gemfiles/activerecord42.gemfile
27   - env: ELASTICSEARCH_VERSION=5.0.1
28   - - gemfile: test/gemfiles/mongoid6.gemfile
  25 + env: ELASTICSEARCH_VERSION=6.7.0
  26 + - gemfile: test/gemfiles/mongoid7.gemfile
  27 + env: ELASTICSEARCH_VERSION=6.0.0
29 28 services:
30 29 - mongodb
31 30 - redis-server
32   - - gemfile: test/gemfiles/mongoid5.gemfile
  31 + - gemfile: test/gemfiles/mongoid6.gemfile
33 32 services:
34 33 - mongodb
35 34 - redis-server
36   - - gemfile: Gemfile
37   - env: ELASTICSEARCH_VERSION=7.0.0-rc1
38   - allow_failures:
39   - - gemfile: Gemfile
40   - env: ELASTICSEARCH_VERSION=7.0.0-rc1
41 35 notifications:
42 36 email:
43 37 on_success: never
... ...
CHANGELOG.md
  1 +## 4.0.0 [unreleased]
  2 +
  3 +- Added support for Elasticsearch 7
  4 +- Added `models` option
  5 +
  6 +Breaking changes
  7 +
  8 +- Removed support for Elasticsearch 5
  9 +- Removed support for multi-word synonyms (they no longer work with shingles)
  10 +
1 11 ## 3.1.3
2 12  
3 13 - Added support for endless ranges
... ...
README.md
... ... @@ -53,7 +53,7 @@ Add this line to your application’s Gemfile:
53 53 gem 'searchkick'
54 54 ```
55 55  
56   -The latest version works with Elasticsearch 5 and 6. For Elasticsearch 2, use version 2.5.0 and [this readme](https://github.com/ankane/searchkick/blob/v2.5.0/README.md).
  56 +The latest version works with Elasticsearch 6 and 7. For Elasticsearch 5, use version 3.1.3 and [this readme](https://github.com/ankane/searchkick/blob/v3.1.3/README.md).
57 57  
58 58 Add searchkick to models you want to search.
59 59  
... ... @@ -312,13 +312,13 @@ A few languages require plugins:
312 312  
313 313 ```ruby
314 314 class Product < ApplicationRecord
315   - searchkick synonyms: [["scallion", "green onion"], ["qtip", "cotton swab"]]
  315 + searchkick synonyms: [["burger", "hamburger"], ["sneakers", "shoes"]]
316 316 end
317 317 ```
318 318  
319 319 Call `Product.reindex` after changing synonyms.
320 320  
321   -Synonyms cannot be more than two words at the moment.
  321 +Synonyms cannot be multiple words at the moment.
322 322  
323 323 To read synonyms from a file, use:
324 324  
... ... @@ -796,8 +796,6 @@ Script support
796 796 Product.search "*", aggs: {color: {script: {source: "'Color: ' + _value"}}}
797 797 ```
798 798  
799   -**Note:** Use `inline` instead of `source` before Elasticsearch 5.6
800   -
801 799 Date histogram
802 800  
803 801 ```ruby
... ... @@ -924,9 +922,7 @@ You can also index and search geo shapes.
924 922  
925 923 ```ruby
926 924 class Restaurant < ApplicationRecord
927   - searchkick geo_shape: {
928   - bounds: {tree: "geohash", precision: "1km"}
929   - }
  925 + searchkick geo_shape: [:bounds]
930 926  
931 927 def search_data
932 928 attributes.merge(
... ... @@ -959,12 +955,6 @@ Not touching the query shape
959 955 Restaurant.search "burger", where: {bounds: {geo_shape: {type: "envelope", relation: "disjoint", coordinates: [{lat: 38, lon: -123}, {lat: 37, lon: -122}]}}}
960 956 ```
961 957  
962   -Containing the query shape
963   -
964   -```ruby
965   -Restaurant.search "fries", where: {bounds: {geo_shape: {type: "envelope", relation: "contains", coordinates: [{lat: 38, lon: -123}, {lat: 37, lon: -122}]}}}
966   -```
967   -
968 958 ## Inheritance
969 959  
970 960 Searchkick supports single table inheritance.
... ... @@ -1496,21 +1486,15 @@ Then use `products` and `coupons` as typical results.
1496 1486  
1497 1487 **Note:** Errors are not raised as with single requests. Use the `error` method on each query to check for errors.
1498 1488  
1499   -## Multiple Indices
  1489 +## Multiple Models
1500 1490  
1501   -Search across multiple models/indices with:
  1491 +Search across multiple models with:
1502 1492  
1503 1493 ```ruby
1504   -Searchkick.search "milk", index_name: [Product, Category]
  1494 +Searchkick.search "milk", models: [Product, Category]
1505 1495 ```
1506 1496  
1507   -Specify conditions for different indices
1508   -
1509   -```ruby
1510   -where: {_or: [{_type: "product", in_stock: true}, {_type: "category", active: true}]}
1511   -```
1512   -
1513   -Boost specific indices with:
  1497 +Boost specific models with:
1514 1498  
1515 1499 ```ruby
1516 1500 indices_boost: {Category => 2, Product => 1}
... ... @@ -1657,7 +1641,7 @@ Product.search &quot;milk&quot;, includes: [:brand, :stores]
1657 1641 Eager load different associations by model
1658 1642  
1659 1643 ```ruby
1660   -Searchkick.search("*", index_name: [Product, Store], model_includes: {Product => [:store], Store => [:product]})
  1644 +Searchkick.search("*", models: [Product, Store], model_includes: {Product => [:store], Store => [:product]})
1661 1645 ```
1662 1646  
1663 1647 Run additional scopes on results
... ... @@ -1900,6 +1884,11 @@ Check out [this great post](https://www.tiagoamaro.com.br/2014/12/11/multi-tenan
1900 1884  
1901 1885 See [how to upgrade to Searchkick 3](docs/Searchkick-3-Upgrade.md)
1902 1886  
  1887 +## Elasticsearch 6 to 7 Upgrade
  1888 +
  1889 +1. Install Searchkick 4
  1890 +2. Upgrade your Elasticsearch cluster
  1891 +
1903 1892 ## Elasticsearch 5 to 6 Upgrade
1904 1893  
1905 1894 Elasticsearch 6 removes the ability to reindex with the `_all` field. Before you upgrade, we recommend disabling this field manually and specifying default fields on your models.
... ...
lib/searchkick.rb
... ... @@ -78,16 +78,36 @@ module Searchkick
78 78 Gem::Version.new(server_version.split("-")[0]) < Gem::Version.new(version.split("-")[0])
79 79 end
80 80  
  81 + # memoize for performance
  82 + def self.server_below7?
  83 + unless defined?(@server_below7)
  84 + @server_below7 = server_below?("7.0.0")
  85 + end
  86 + @server_below7
  87 + end
  88 +
81 89 def self.search(term = "*", model: nil, **options, &block)
82 90 options = options.dup
83 91 klass = model
84 92  
85   - # make Searchkick.search(index_name: [Product]) and Product.search equivalent
  93 + # convert index_name into models if possible
  94 + # this should allow for easier upgrade
  95 + if options[:index_name] && !options[:models] && Array(options[:index_name]).all? { |v| v.respond_to?(:searchkick_index) }
  96 + options[:models] = options.delete(:index_name)
  97 + end
  98 +
  99 + # make Searchkick.search(models: [Product]) and Product.search equivalent
86 100 unless klass
87   - index_name = Array(options[:index_name])
88   - if index_name.size == 1 && index_name.first.respond_to?(:searchkick_index)
89   - klass = index_name.first
90   - options.delete(:index_name)
  101 + models = Array(options[:models])
  102 + if models.size == 1
  103 + klass = models.first
  104 + options.delete(:models)
  105 + end
  106 + end
  107 +
  108 + if klass
  109 + if (options[:models] && Array(options[:models]) != [klass]) || Array(options[:index_name]).any? { |v| v.respond_to?(:searchkick_index) && v != klass }
  110 + raise ArgumentError, "Use Searchkick.search to search multiple models"
91 111 end
92 112 end
93 113  
... ...
lib/searchkick/index.rb
... ... @@ -17,7 +17,7 @@ module Searchkick
17 17 end
18 18  
19 19 def delete
20   - if !Searchkick.server_below?("6.0.0") && alias_exists?
  20 + if alias_exists?
21 21 # can't call delete directly on aliases in ES 6
22 22 indices = client.indices.get_alias(name: name).keys
23 23 client.indices.delete index: indices
... ... @@ -68,7 +68,7 @@ module Searchkick
68 68 }
69 69 )
70 70  
71   - response["hits"]["total"]
  71 + Searchkick::Results.new(nil, response).total_count
72 72 end
73 73  
74 74 def promote(new_name, update_refresh_interval: false)
... ...
lib/searchkick/index_options.rb
... ... @@ -4,15 +4,13 @@ module Searchkick
4 4 options = @options
5 5 language = options[:language]
6 6 language = language.call if language.respond_to?(:call)
7   - index_type = options[:_type]
8   - index_type = index_type.call if index_type.respond_to?(:call)
9 7  
10 8 if options[:mappings] && !options[:merge_mappings]
11 9 settings = options[:settings] || {}
12 10 mappings = options[:mappings]
13 11 else
14   - below60 = Searchkick.server_below?("6.0.0")
15 12 below62 = Searchkick.server_below?("6.2.0")
  13 + below70 = Searchkick.server_below?("7.0.0")
16 14  
17 15 default_type = "text"
18 16 default_analyzer = :searchkick_index
... ... @@ -144,15 +142,6 @@ module Searchkick
144 142 }
145 143 }
146 144  
147   - if below60
148   - # ES docs say standard token filter does nothing in ES 5
149   - # (and therefore isn't needed at at), but tests say otherwise
150   - # https://www.elastic.co/guide/en/elasticsearch/reference/5.0/analysis-standard-tokenfilter.html
151   - [default_analyzer, :searchkick_search, :searchkick_search2].each do |analyzer|
152   - settings[:analysis][:analyzer][analyzer][:filter].unshift("standard")
153   - end
154   - end
155   -
156 145 stem = options[:stem]
157 146  
158 147 case language
... ... @@ -279,8 +268,7 @@ module Searchkick
279 268 # - Only apply the synonym expansion at index time
280 269 # - Don't have the synonym filter applied search
281 270 # - Use directional synonyms where appropriate. You want to make sure that you're not injecting terms that are too general.
282   - settings[:analysis][:analyzer][default_analyzer][:filter].insert(4, "searchkick_synonym") if below60
283   - settings[:analysis][:analyzer][default_analyzer][:filter] << "searchkick_synonym"
  271 + settings[:analysis][:analyzer][default_analyzer][:filter].insert(2, "searchkick_synonym")
284 272  
285 273 %w(word_start word_middle word_end).each do |type|
286 274 settings[:analysis][:analyzer]["searchkick_#{type}_index".to_sym][:filter].insert(2, "searchkick_synonym")
... ... @@ -391,10 +379,6 @@ module Searchkick
391 379 "{name}" => keyword_mapping
392 380 }
393 381  
394   - if below60 && all
395   - dynamic_fields["{name}"][:include_in_all] = !options[:searchable]
396   - end
397   -
398 382 if options.key?(:filterable)
399 383 dynamic_fields["{name}"] = {type: default_type, index: index_false_value}
400 384 end
... ... @@ -413,25 +397,24 @@ module Searchkick
413 397 multi_field = dynamic_fields["{name}"].merge(fields: dynamic_fields.except("{name}"))
414 398  
415 399 mappings = {
416   - index_type => {
417   - properties: mapping,
418   - _routing: routing,
419   - # https://gist.github.com/kimchy/2898285
420   - dynamic_templates: [
421   - {
422   - string_template: {
423   - match: "*",
424   - match_mapping_type: "string",
425   - mapping: multi_field
426   - }
  400 + properties: mapping,
  401 + _routing: routing,
  402 + # https://gist.github.com/kimchy/2898285
  403 + dynamic_templates: [
  404 + {
  405 + string_template: {
  406 + match: "*",
  407 + match_mapping_type: "string",
  408 + mapping: multi_field
427 409 }
428   - ]
429   - }
  410 + }
  411 + ]
430 412 }
431 413  
432   - if below60
433   - all_enabled = all && (!options[:searchable] || options[:searchable].to_a.map(&:to_s).include?("_all"))
434   - mappings[index_type][:_all] = all_enabled ? analyzed_field_options : {enabled: false}
  414 + if below70
  415 + index_type = options[:_type]
  416 + index_type = index_type.call if index_type.respond_to?(:call)
  417 + mappings = {index_type => mappings}
435 418 end
436 419  
437 420 mappings = mappings.symbolize_keys.deep_merge((options[:mappings] || {}).symbolize_keys)
... ...
lib/searchkick/query.rb
... ... @@ -18,7 +18,7 @@ module Searchkick
18 18 unknown_keywords = options.keys - [:aggs, :block, :body, :body_options, :boost,
19 19 :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :execute, :explain,
20 20 :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load,
21   - :match, :misspellings, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile,
  21 + :match, :misspellings, :models, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile,
22 22 :request_params, :routing, :scope_results, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where]
23 23 raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
24 24  
... ... @@ -39,6 +39,7 @@ module Searchkick
39 39 @misspellings = false
40 40 @misspellings_below = nil
41 41 @highlighted_fields = nil
  42 + @index_mapping = nil
42 43  
43 44 prepare
44 45 end
... ... @@ -56,9 +57,18 @@ module Searchkick
56 57 end
57 58  
58 59 def params
  60 + if options[:models]
  61 + @index_mapping = {}
  62 + Array(options[:models]).each do |model|
  63 + @index_mapping[model.searchkick_index.name] = model
  64 + end
  65 + end
  66 +
59 67 index =
60 68 if options[:index_name]
61 69 Array(options[:index_name]).map { |v| v.respond_to?(:searchkick_index) ? v.searchkick_index.name : v }.join(",")
  70 + elsif options[:models]
  71 + @index_mapping.keys.join(",")
62 72 elsif searchkick_index
63 73 searchkick_index.name
64 74 else
... ... @@ -116,8 +126,8 @@ module Searchkick
116 126 misspellings: @misspellings,
117 127 term: term,
118 128 scope_results: options[:scope_results],
119   - index_name: options[:index_name],
120   - total_entries: options[:total_entries]
  129 + total_entries: options[:total_entries],
  130 + index_mapping: @index_mapping
121 131 }
122 132  
123 133 if options[:debug]
... ... @@ -166,7 +176,7 @@ module Searchkick
166 176 end
167 177  
168 178 def retry_misspellings?(response)
169   - @misspellings_below && response["hits"]["total"] < @misspellings_below
  179 + @misspellings_below && Searchkick::Results.new(searchkick_klass, response).total_count < @misspellings_below
170 180 end
171 181  
172 182 private
... ... @@ -377,7 +387,7 @@ module Searchkick
377 387 queries_to_add.concat(q2)
378 388 end
379 389  
380   - queries.concat(queries_to_add)
  390 + queries << queries_to_add
381 391  
382 392 if options[:exclude]
383 393 must_not.concat(set_exclude(exclude_field, exclude_analyzer))
... ... @@ -392,9 +402,10 @@ module Searchkick
392 402  
393 403 should = []
394 404 else
  405 + # higher score for matching more fields
395 406 payload = {
396   - dis_max: {
397   - queries: queries
  407 + bool: {
  408 + should: queries.map { |qs| {dis_max: {queries: qs}} }
398 409 }
399 410 }
400 411  
... ... @@ -663,20 +674,9 @@ module Searchkick
663 674 def set_boost_by_indices(payload)
664 675 return unless options[:indices_boost]
665 676  
666   - if below52?
667   - indices_boost = options[:indices_boost].each_with_object({}) do |(key, boost), memo|
668   - index = key.respond_to?(:searchkick_index) ? key.searchkick_index.name : key
669   - # try to use index explicitly instead of alias: https://github.com/elasticsearch/elasticsearch/issues/4756
670   - index_by_alias = Searchkick.client.indices.get_alias(index: index).keys.first
671   - memo[index_by_alias || index] = boost
672   - end
673   - else
674   - # array format supports alias resolution
675   - # https://github.com/elastic/elasticsearch/pull/21393
676   - indices_boost = options[:indices_boost].map do |key, boost|
677   - index = key.respond_to?(:searchkick_index) ? key.searchkick_index.name : key
678   - {index => boost}
679   - end
  677 + indices_boost = options[:indices_boost].map do |key, boost|
  678 + index = key.respond_to?(:searchkick_index) ? key.searchkick_index.name : key
  679 + {index => boost}
680 680 end
681 681  
682 682 payload[:indices_boost] = indices_boost
... ... @@ -713,7 +713,7 @@ module Searchkick
713 713 def set_highlights(payload, fields)
714 714 payload[:highlight] = {
715 715 fields: Hash[fields.map { |f| [f, {}] }],
716   - fragment_size: below60? ? 30000 : 0
  716 + fragment_size: 0
717 717 }
718 718  
719 719 if options[:highlight].is_a?(Hash)
... ... @@ -824,7 +824,7 @@ module Searchkick
824 824 # TODO id transformation for arrays
825 825 def set_order(payload)
826 826 order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
827   - id_field = below60? ? :_uid : :_id
  827 + id_field = :_id
828 828 payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? id_field : k, v] }]
829 829 end
830 830  
... ... @@ -1021,16 +1021,12 @@ module Searchkick
1021 1021 k.sub(/\.(analyzed|word_start|word_middle|word_end|text_start|text_middle|text_end|exact)\z/, "")
1022 1022 end
1023 1023  
1024   - def below52?
1025   - Searchkick.server_below?("5.2.0")
1026   - end
1027   -
1028   - def below60?
1029   - Searchkick.server_below?("6.0.0")
1030   - end
1031   -
1032 1024 def below61?
1033 1025 Searchkick.server_below?("6.1.0")
1034 1026 end
  1027 +
  1028 + def below70?
  1029 + Searchkick.server_below?("7.0.0")
  1030 + end
1035 1031 end
1036 1032 end
... ...
lib/searchkick/record_data.rb
... ... @@ -34,18 +34,13 @@ module Searchkick
34 34 index.klass_document_type(record.class, ignore_type)
35 35 end
36 36  
37   - # memoize
38   - def self.routing_key
39   - @routing_key ||= Searchkick.server_below?("6.0.0") ? :_routing : :routing
40   - end
41   -
42 37 def record_data
43 38 data = {
44 39 _index: index.name,
45   - _id: search_id,
46   - _type: document_type
  40 + _id: search_id
47 41 }
48   - data[self.class.routing_key] = record.search_routing if record.respond_to?(:search_routing)
  42 + data[:_type] = document_type if Searchkick.server_below7?
  43 + data[:routing] = record.search_routing if record.respond_to?(:search_routing)
49 44 data
50 45 end
51 46  
... ...
lib/searchkick/results.rb
... ... @@ -25,9 +25,16 @@ module Searchkick
25 25 # results can have different types
26 26 results = {}
27 27  
28   - hits.group_by { |hit, _| hit["_type"] }.each do |type, grouped_hits|
29   - klass = (!options[:index_name] && @klass) || type.camelize.constantize
30   - results[type] = results_query(klass, grouped_hits).to_a.index_by { |r| r.id.to_s }
  28 + hits.group_by { |hit, _| hit["_index"] }.each do |index, grouped_hits|
  29 + klass =
  30 + if @klass
  31 + @klass
  32 + else
  33 + index_alias = index.split("_")[0..-2].join("_")
  34 + (options[:index_mapping] || {})[index_alias]
  35 + end
  36 + raise Searchkick::Error, "Unknown model for index: #{index}" unless klass
  37 + results[index] = results_query(klass, grouped_hits).to_a.index_by { |r| r.id.to_s }
31 38 end
32 39  
33 40 missing_ids = []
... ... @@ -35,7 +42,7 @@ module Searchkick
35 42 # sort
36 43 results =
37 44 hits.map do |hit|
38   - result = results[hit["_type"]][hit["_id"].to_s]
  45 + result = results[hit["_index"]][hit["_id"].to_s]
39 46 if result && !(options[:load].is_a?(Hash) && options[:load][:dumpable])
40 47 if (hit["highlight"] || options[:highlight]) && !result.respond_to?(:search_highlights)
41 48 highlights = hit_highlights(hit)
... ... @@ -132,7 +139,13 @@ module Searchkick
132 139 end
133 140  
134 141 def total_count
135   - options[:total_entries] || response["hits"]["total"]
  142 + if options[:total_entries]
  143 + options[:total_entries]
  144 + elsif response["hits"]["total"].is_a?(Hash)
  145 + response["hits"]["total"]["value"]
  146 + else
  147 + response["hits"]["total"]
  148 + end
136 149 end
137 150 alias_method :total_entries, :total_count
138 151  
... ...
searchkick.gemspec
... ... @@ -16,10 +16,10 @@ Gem::Specification.new do |spec|
16 16 spec.files = Dir["*.{md,txt}", "{lib}/**/*"]
17 17 spec.require_path = "lib"
18 18  
19   - spec.required_ruby_version = ">= 2.2"
  19 + spec.required_ruby_version = ">= 2.4"
20 20  
21   - spec.add_dependency "activemodel", ">= 4.2"
22   - spec.add_dependency "elasticsearch", ">= 5"
  21 + spec.add_dependency "activemodel", ">= 5"
  22 + spec.add_dependency "elasticsearch", ">= 6"
23 23 spec.add_dependency "hashie"
24 24  
25 25 spec.add_development_dependency "bundler"
... ...
test/aggs_test.rb
... ... @@ -20,8 +20,7 @@ class AggsTest &lt; Minitest::Test
20 20 end
21 21  
22 22 def test_order
23   - order_key = Searchkick.server_below?("6.0") ? "_term" : "_key"
24   - agg = Product.search("Product", aggs: {color: {order: {order_key => "desc"}}}).aggs["color"]
  23 + agg = Product.search("Product", aggs: {color: {order: {_key: "desc"}}}).aggs["color"]
25 24 assert_equal %w(red green blue), agg["buckets"].map { |b| b["key"] }
26 25 end
27 26  
... ... @@ -37,8 +36,7 @@ class AggsTest &lt; Minitest::Test
37 36  
38 37 def test_script
39 38 source = "'Color: ' + _value"
40   - script = Searchkick.server_below?("5.6") ? {inline: source} : {source: source}
41   - agg = Product.search("Product", aggs: {color: {script: script}}).aggs["color"]
  39 + agg = Product.search("Product", aggs: {color: {script: {source: source}}}).aggs["color"]
42 40 assert_equal ({"Color: blue" => 1, "Color: green" => 1, "Color: red" => 1}), buckets_as_hash(agg)
43 41 end
44 42  
... ...
test/boost_test.rb
... ... @@ -67,6 +67,7 @@ class BoostTest &lt; Minitest::Test
67 67 end
68 68  
69 69 def test_conversions_weight
  70 + Product.reindex
70 71 store [
71 72 {name: "Product Boost", orders_count: 20},
72 73 {name: "Product Conversions", conversions: {"product" => 10}}
... ... @@ -229,6 +230,6 @@ class BoostTest &lt; Minitest::Test
229 230 store_names ["Rex"], Animal
230 231 store_names ["Rexx"], Product
231 232  
232   - assert_order "Rex", ["Rexx", "Rex"], {index_name: [Animal, Product], indices_boost: {Animal => 1, Product => 200}, fields: [:name]}, Store
  233 + assert_order "Rex", ["Rexx", "Rex"], {models: [Animal, Product], indices_boost: {Animal => 1, Product => 200}, fields: [:name]}, Searchkick
233 234 end
234 235 end
... ...
test/ci/install_elasticsearch.sh
... ... @@ -5,15 +5,13 @@ set -e
5 5 CACHE_DIR=$HOME/elasticsearch/$ELASTICSEARCH_VERSION
6 6  
7 7 if [ ! -d "$CACHE_DIR" ]; then
8   - if [[ $ELASTICSEARCH_VERSION == 1* ]]; then
9   - URL=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-$ELASTICSEARCH_VERSION.tar.gz
10   - elif [[ $ELASTICSEARCH_VERSION == 2* ]]; then
11   - URL=https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/$ELASTICSEARCH_VERSION/elasticsearch-$ELASTICSEARCH_VERSION.tar.gz
  8 + if [[ $ELASTICSEARCH_VERSION == 7* ]]; then
  9 + URL=https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-$ELASTICSEARCH_VERSION-linux-x86_64.tar.gz
12 10 else
13 11 URL=https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-$ELASTICSEARCH_VERSION.tar.gz
14 12 fi
15 13  
16   - wget $URL
  14 + wget -O elasticsearch-$ELASTICSEARCH_VERSION.tar.gz $URL
17 15 tar xvfz elasticsearch-$ELASTICSEARCH_VERSION.tar.gz
18 16 mv elasticsearch-$ELASTICSEARCH_VERSION $CACHE_DIR
19 17 else
... ...
test/errors_test.rb
... ... @@ -4,13 +4,15 @@ class ErrorsTest &lt; Minitest::Test
4 4 def test_bulk_import_raises_error
5 5 valid_dog = Product.create(name: "2016-01-02")
6 6 invalid_dog = Product.create(name: "Ol' One-Leg")
7   - index = Searchkick::Index.new "dogs", mappings: {
8   - dog: {
9   - properties: {
10   - name: {type: "date"}
11   - }
  7 + mapping = {
  8 + properties: {
  9 + name: {type: "date"}
12 10 }
13 11 }
  12 + mapping = {product: mapping} if Searchkick.server_below?("7.0.0")
  13 + index = Searchkick::Index.new "dogs", mappings: mapping
  14 + index.delete if index.exists?
  15 + index.create_index
14 16 index.store valid_dog
15 17 assert_raises(Searchkick::ImportError) do
16 18 index.bulk_index [valid_dog, invalid_dog]
... ...
test/gemfiles/activerecord42.gemfile
... ... @@ -1,7 +0,0 @@
1   -source 'https://rubygems.org'
2   -
3   -# Specify your gem's dependencies in searchkick.gemspec
4   -gemspec path: "../../"
5   -
6   -gem "sqlite3", "~> 1.3.0"
7   -gem "activerecord", "~> 4.2.0"
test/gemfiles/mongoid5.gemfile
... ... @@ -1,7 +0,0 @@
1   -source 'https://rubygems.org'
2   -
3   -# Specify your gem's dependencies in searchkick.gemspec
4   -gemspec path: "../../"
5   -
6   -gem "mongoid", "~> 5.0.0"
7   -gem "activejob"
test/geo_shape_test.rb
... ... @@ -32,6 +32,9 @@ class GeoShapeTest &lt; Minitest::Test
32 32 end
33 33  
34 34 def test_circle
  35 + # https://github.com/elastic/elasticsearch/issues/39237
  36 + skip unless Searchkick.server_below?("6.6.0")
  37 +
35 38 assert_search "*", ["Region A"], {
36 39 where: {
37 40 territory: {
... ... @@ -142,6 +145,9 @@ class GeoShapeTest &lt; Minitest::Test
142 145 end
143 146  
144 147 def test_contains
  148 + # CONTAINS query relation not supported
  149 + skip unless Searchkick.server_below?("6.6.0")
  150 +
145 151 assert_search "*", ["Region C"], {
146 152 where: {
147 153 territory: {
... ...
test/inheritance_test.rb
... ... @@ -77,6 +77,20 @@ class InheritanceTest &lt; Minitest::Test
77 77 def test_multiple_indices
78 78 store_names ["Product A"]
79 79 store_names ["Product B"], Animal
80   - assert_search "product", ["Product A", "Product B"], index_name: [Product.searchkick_index.name, Animal.searchkick_index.name], conversions: false
  80 + assert_search "product", ["Product A", "Product B"], {models: [Product, Animal], conversions: false}, Searchkick
  81 + assert_search "product", ["Product A", "Product B"], {index_name: [Product, Animal], conversions: false}, Searchkick
  82 + end
  83 +
  84 + def test_index_name_model
  85 + store_names ["Product A"]
  86 + assert_equal ["Product A"], Searchkick.search("product", index_name: [Product]).map(&:name)
  87 + end
  88 +
  89 + def test_index_name_string
  90 + store_names ["Product A"]
  91 + error = assert_raises Searchkick::Error do
  92 + Searchkick.search("product", index_name: [Product.searchkick_index.name]).map(&:name)
  93 + end
  94 + assert_includes error.message, "Unknown model"
81 95 end
82 96 end
... ...
test/models/product.rb
... ... @@ -2,11 +2,8 @@ class Product
2 2 searchkick \
3 3 synonyms: [
4 4 ["clorox", "bleach"],
5   - ["scallion", "greenonion"],
6   - ["saran wrap", "plastic wrap"],
7   - ["qtip", "cottonswab"],
8 5 ["burger", "hamburger"],
9   - ["bandaid", "bandag"],
  6 + ["bandaid", "bandages"],
10 7 ["UPPERCASE", "lowercase"],
11 8 "lightbulb => led,lightbulb",
12 9 "lightbulb => halogenlamp"
... ...
test/models/region.rb
1 1 class Region
2 2 searchkick \
3   - geo_shape: {
4   - territory: {tree: "quadtree", precision: "10km"}
5   - }
  3 + geo_shape: [:territory]
6 4  
7 5 attr_accessor :territory
8 6  
... ...
test/models/store.rb
1 1 class Store
  2 + mappings = {
  3 + properties: {
  4 + name: {type: "keyword"}
  5 + }
  6 + }
  7 + mappings = {store: mappings} if Searchkick.server_below?("7.0.0")
  8 +
2 9 searchkick \
3 10 routing: true,
4 11 merge_mappings: true,
5   - mappings: {
6   - store: {
7   - properties: {
8   - name: {type: "keyword"}
9   - }
10   - }
11   - }
  12 + mappings: mappings
12 13  
13 14 def search_document_id
14 15 id
... ...
test/multi_indices_test.rb
... ... @@ -7,16 +7,44 @@ class MultiIndicesTest &lt; Minitest::Test
7 7 assert_search_multi "product", ["Product A", "Product B"]
8 8 end
9 9  
10   - def test_where
11   - store [{name: "Product A", color: "red"}, {name: "Product B", color: "blue"}]
12   - store_names ["Product C"], Speaker
13   - assert_search_multi "product", ["Product A", "Product C"], where: {_or: [{_type: "product", color: "red"}, {_type: "speaker"}]}
  10 + def test_index_name
  11 + store_names ["Product A"]
  12 + assert_equal ["Product A"], Product.search("product", index_name: Product.searchkick_index.name).map(&:name)
  13 + assert_equal ["Product A"], Product.search("product", index_name: Product).map(&:name)
  14 + assert_equal [], Product.search("product", index_name: Speaker.searchkick_index.name, conversions: false).map(&:name)
  15 + end
  16 +
  17 + def test_models_and_index_name
  18 + store_names ["Product A"]
  19 + store_names ["Product B"], Speaker
  20 + assert_equal ["Product A"], Searchkick.search("product", models: [Product, Store], index_name: Product.searchkick_index.name).map(&:name)
  21 + error = assert_raises(Searchkick::Error) do
  22 + Searchkick.search("product", models: [Product, Store], index_name: Speaker.searchkick_index.name).map(&:name)
  23 + end
  24 + assert_includes error.message, "Unknown model"
  25 + # legacy
  26 + assert_equal ["Product A"], Searchkick.search("product", index_name: [Product, Store]).map(&:name)
  27 + end
  28 +
  29 + def test_model_with_another_model
  30 + error = assert_raises(ArgumentError) do
  31 + Product.search(models: [Store])
  32 + end
  33 + assert_includes error.message, "Use Searchkick.search"
  34 + end
  35 +
  36 + def test_model_with_another_model_in_index_name
  37 + error = assert_raises(ArgumentError) do
  38 + # legacy protection
  39 + Product.search(index_name: [Store, "another"])
  40 + end
  41 + assert_includes error.message, "Use Searchkick.search"
14 42 end
15 43  
16 44 private
17 45  
18 46 def assert_search_multi(term, expected, options = {})
19   - options[:index_name] = [Product, Speaker]
  47 + options[:models] = [Product, Speaker]
20 48 options[:fields] = [:name]
21 49 assert_search(term, expected, options, Searchkick)
22 50 end
... ...
test/query_test.rb
... ... @@ -4,18 +4,11 @@ class QueryTest &lt; Minitest::Test
4 4 def test_basic
5 5 store_names ["Milk", "Apple"]
6 6 query = Product.search("milk", execute: false)
7   - # query.body = {query: {match_all: {}}}
8   - # query.body = {query: {match: {name: "Apple"}}}
9 7 query.body[:query] = {match_all: {}}
10 8 assert_equal ["Apple", "Milk"], query.map(&:name).sort
11 9 assert_equal ["Apple", "Milk"], query.execute.map(&:name).sort
12 10 end
13 11  
14   - def test_with_effective_min_score
15   - store_names ["Milk", "Milk2"]
16   - assert_search "milk", ["Milk"], body_options: {min_score: 1}
17   - end
18   -
19 12 def test_with_uneffective_min_score
20 13 store_names ["Milk", "Milk2"]
21 14 assert_search "milk", ["Milk", "Milk2"], body_options: {min_score: 0.0001}
... ...
test/routing_test.rb
... ... @@ -7,8 +7,11 @@ class RoutingTest &lt; Minitest::Test
7 7 end
8 8  
9 9 def test_routing_mappings
10   - index_options = Store.searchkick_index.index_options
11   - assert_equal index_options[:mappings][:store][:_routing], required: true
  10 + mappings = Store.searchkick_index.index_options[:mappings]
  11 + if Searchkick.server_below?("7.0.0")
  12 + mappings = mappings[:store]
  13 + end
  14 + assert_equal mappings[:_routing], required: true
12 15 end
13 16  
14 17 def test_routing_correct_node
... ...
test/sql_test.rb
... ... @@ -37,6 +37,7 @@ class SqlTest &lt; Minitest::Test
37 37 end
38 38  
39 39 def test_fields_both_match
  40 + # have same score due to dismax
40 41 store [
41 42 {name: "Blue A", color: "red"},
42 43 {name: "Blue B", color: "light blue"}
... ... @@ -172,7 +173,7 @@ class SqlTest &lt; Minitest::Test
172 173 store_names ["Store A"], Store
173 174  
174 175 associations = {Product => [:store], Store => [:products]}
175   - result = Searchkick.search("*", index_name: [Product, Store], model_includes: associations)
  176 + result = Searchkick.search("*", models: [Product, Store], model_includes: associations)
176 177  
177 178 assert_equal 2, result.length
178 179  
... ...
test/suggest_test.rb
1 1 require_relative "test_helper"
2 2  
3 3 class SuggestTest < Minitest::Test
  4 + def setup
  5 + super
  6 + Product.reindex
  7 + end
  8 +
4 9 def test_basic
5 10 store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
6 11 assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [:name]
... ...
test/synonyms_test.rb
... ... @@ -6,11 +6,6 @@ class SynonymsTest &lt; Minitest::Test
6 6 assert_search "clorox", ["Clorox Bleach", "Kroger Bleach"]
7 7 end
8 8  
9   - def test_saran_wrap
10   - store_names ["Saran Wrap", "Kroger Plastic Wrap"]
11   - assert_search "saran wrap", ["Saran Wrap", "Kroger Plastic Wrap"]
12   - end
13   -
14 9 def test_burger_buns
15 10 store_names ["Hamburger Buns"]
16 11 assert_search "burger buns", ["Hamburger Buns"]
... ... @@ -21,24 +16,14 @@ class SynonymsTest &lt; Minitest::Test
21 16 assert_search "bandaids", ["Band-Aid", "Kroger 12-Pack Bandages"]
22 17 end
23 18  
24   - def test_qtips
25   - store_names ["Q Tips", "Kroger Cotton Swabs"]
26   - assert_search "q tips", ["Q Tips", "Kroger Cotton Swabs"]
27   - end
28   -
29 19 def test_reverse
30   - store_names ["Scallions"]
31   - assert_search "green onions", ["Scallions"]
32   - end
33   -
34   - def test_exact
35   - store_names ["Green Onions", "Yellow Onions"]
36   - assert_search "scallion", ["Green Onions"]
  20 + store_names ["Hamburger"]
  21 + assert_search "burger", ["Hamburger"]
37 22 end
38 23  
39 24 def test_stemmed
40   - store_names ["Green Onions", "Yellow Onions"]
41   - assert_search "scallions", ["Green Onions"]
  25 + store_names ["Burger"]
  26 + assert_search "hamburgers", ["Burger"]
42 27 end
43 28  
44 29 def test_word_start
... ...