searchkick 0.8.7 → 0.9.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: 5df7a84637481daab3ecd7dc97ff013ff1845e9c
4
- data.tar.gz: ab0dd0e7603936240bcd3c9a24bd7b3b031f8516
3
+ metadata.gz: 086a7f84d9047b486d127d05cc3d20b7b6d66b04
4
+ data.tar.gz: f0896d8115143f72cf088f69f82c923c6e4f67cf
5
5
  SHA512:
6
- metadata.gz: cf49fbcdae22e3c416191fc04efaf2cb9ce48723d7be55a9e1862e3e6b1241c446057d64e0230a08c5a622fb85b8e2fe6454a707d9dca5ef4606b700e4597334
7
- data.tar.gz: 410648692e8aeffa3559f1f5244860d7be1305c1497e19e4b9584feca17b3f9747a463bc236207d743a6a200a975ba701d4f4dfbb7a89a1e3d3b0fc3e646d81d
6
+ metadata.gz: f084aa390d924d64c610ea86845433bb521b8ad319e50350936f56d57f3db770e01e87c34c55d38f88b58599b03d6176a8af6e4fee8793fc5f1b8fb01cfeea6e
7
+ data.tar.gz: e26a6809754329de5cf67a473f3df2d5b8b19b1981bdb22c041421b4916a1efcf5d4f78501cd9656ba297ccdcb0e3fe1742d0d57ffee1e0bf8475104045367fb
data/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
+ ## 0.9.0
2
+
3
+ - Much better performance for where queries if no facets
4
+ - Added basic support for regex
5
+ - Added support for routing
6
+ - Made `Searchkick.disable_callbacks` thread-safe
7
+
1
8
  ## 0.8.7
2
9
 
3
- - Fixed MongoDB import
10
+ - Fixed Mongoid import
4
11
 
5
12
  ## 0.8.6
6
13
 
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in searchkick.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -23,9 +23,9 @@ Plus:
23
23
 
24
24
  :speech_balloon: Get [handcrafted updates](http://chartkick.us7.list-manage.com/subscribe?u=952c861f99eb43084e0a49f98&id=6ea6541e8e&group[0][4]=true) for new features
25
25
 
26
- :tangerine: Battle-tested at [Instacart](https://www.instacart.com)
26
+ :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
27
27
 
28
- [![Build Status](https://travis-ci.org/ankane/searchkick.png?branch=master)](https://travis-ci.org/ankane/searchkick)
28
+ [![Build Status](https://travis-ci.org/ankane/searchkick.svg?branch=master)](https://travis-ci.org/ankane/searchkick)
29
29
 
30
30
  We highly recommend tracking queries and conversions
31
31
 
@@ -606,7 +606,7 @@ Find similar items.
606
606
 
607
607
  ```ruby
608
608
  product = Product.first
609
- product.similar(fields: ["name"])
609
+ product.similar(fields: ["name"], where: {size: "12 oz"})
610
610
  ```
611
611
 
612
612
  ### Geospatial Searches
@@ -647,6 +647,22 @@ Also supports [additional options](http://www.elasticsearch.org/guide/en/elastic
647
647
  City.search "san", boost_by_distance: {field: :location, origin: [37, -122], function: :linear, scale: "30mi", decay: 0.5}
648
648
  ```
649
649
 
650
+ ### Routing
651
+
652
+ Searchkick supports [Elasticsearch’s routing feature](https://www.elastic.co/blog/customizing-your-document-routing).
653
+
654
+ ```ruby
655
+ class Contact < ActiveRecord::Base
656
+ searchkick routing: :user_id
657
+ end
658
+ ```
659
+
660
+ Reindex and search with:
661
+
662
+ ```ruby
663
+ Contact.search "John", routing: current_user.id
664
+ ```
665
+
650
666
  ## Inheritance
651
667
 
652
668
  Searchkick supports single table inheritance.
@@ -849,6 +865,25 @@ product.reindex
849
865
  product.reindex_async
850
866
  ```
851
867
 
868
+ Reindex more than one record without recreating the index
869
+
870
+ ```ruby
871
+ # do this ...
872
+ some_company.products.each { |p| p.reindex }
873
+ # or this ...
874
+ Product.searchkick_index.import(some_company.products)
875
+ # don't do the following as it will recreate the index with some_company's products only
876
+ some_company.products.reindex
877
+ ```
878
+
879
+ Reindex large set of records in batches
880
+
881
+ ```ruby
882
+ Product.where("id > 100000").find_in_batches do |batch|
883
+ Product.searchkick_index.import(batch)
884
+ end
885
+ ```
886
+
852
887
  Remove old indices
853
888
 
854
889
  ```ruby
@@ -955,6 +990,34 @@ Reindex all models - Rails only
955
990
  rake searchkick:reindex:all
956
991
  ```
957
992
 
993
+ ## Large Data Sets
994
+
995
+ For large data sets, check out [Keeping Elasticsearch in Sync](https://www.found.no/foundation/keeping-elasticsearch-in-sync/). Searchkick will make this easy in the future.
996
+
997
+ ## Testing
998
+
999
+ This section could use some love.
1000
+
1001
+ ### RSpec
1002
+
1003
+ ```ruby
1004
+ describe Product do
1005
+ it "searches" do
1006
+ Product.reindex
1007
+ Product.searchkick_index.refresh # don't forget this
1008
+ # test goes here...
1009
+ end
1010
+ end
1011
+ ```
1012
+
1013
+ ### Factory Girl
1014
+
1015
+ ```ruby
1016
+ product = FactoryGirl.create(:product)
1017
+ product.reindex # don't forget this
1018
+ Product.searchkick_index.refresh # or this
1019
+ ```
1020
+
958
1021
  ## Migrating from Tire
959
1022
 
960
1023
  1. Change `search` methods to `tire.search` and add index name in existing search calls
@@ -1019,11 +1082,12 @@ Thanks to Karel Minarik for [Elasticsearch Ruby](https://github.com/elasticsearc
1019
1082
 
1020
1083
  ## Roadmap
1021
1084
 
1085
+ - More features for large data sets
1086
+ - Improve section on testing
1022
1087
  - Semantic search features
1023
1088
  - Search multiple fields for different terms
1024
1089
  - Search across models
1025
1090
  - Search nested objects
1026
- - Add section on testing
1027
1091
  - Much finer customization
1028
1092
 
1029
1093
  ## Contributing
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
- task :default => :test
4
+ task default: :test
5
5
  Rake::TestTask.new do |t|
6
6
  t.libs << "test"
7
7
  t.pattern = "test/**/*_test.rb"
@@ -3,4 +3,4 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in searchkick.gemspec
4
4
  gemspec path: "../"
5
5
 
6
- gem "nobrainer", ">= 0.19.0"
6
+ gem "nobrainer", "0.22.0"
@@ -34,7 +34,7 @@ module Searchkick
34
34
  rescue Elasticsearch::Transport::Transport::Errors::NotFound
35
35
  []
36
36
  end
37
- actions = old_indices.map{|old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
37
+ actions = old_indices.map { |old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
38
38
  client.indices.update_aliases body: {actions: actions}
39
39
  end
40
40
 
@@ -50,19 +50,22 @@ module Searchkick
50
50
  end
51
51
 
52
52
  def remove(record)
53
- client.delete(
54
- index: name,
55
- type: document_type(record),
56
- id: search_id(record)
57
- )
53
+ id = search_id(record)
54
+ unless id.blank?
55
+ client.delete(
56
+ index: name,
57
+ type: document_type(record),
58
+ id: id
59
+ )
60
+ end
58
61
  end
59
62
 
60
63
  def import(records)
61
- records.group_by{|r| document_type(r) }.each do |type, batch|
64
+ records.group_by { |r| document_type(r) }.each do |type, batch|
62
65
  client.bulk(
63
66
  index: name,
64
67
  type: type,
65
- body: batch.map{|r| {index: {_id: search_id(r), data: search_data(r)}} }
68
+ body: batch.map { |r| {index: {_id: search_id(r), data: search_data(r)}} }
66
69
  )
67
70
  end
68
71
  end
@@ -71,12 +74,12 @@ module Searchkick
71
74
  client.get(
72
75
  index: name,
73
76
  type: document_type(record),
74
- id: record.id
77
+ id: search_id(record)
75
78
  )["_source"]
76
79
  end
77
80
 
78
81
  def reindex_record(record)
79
- if record.destroyed? or !record.should_index?
82
+ if record.destroyed? || !record.should_index?
80
83
  begin
81
84
  remove(record)
82
85
  rescue Elasticsearch::Transport::Transport::Errors::NotFound
@@ -89,7 +92,7 @@ module Searchkick
89
92
 
90
93
  def reindex_record_async(record)
91
94
  if defined?(Searchkick::ReindexV2Job)
92
- Searchkick::ReindexV2Job.perform_later(record.class.name, record.id.to_s)
95
+ Searchkick::ReindexV2Job.perform_later(record.class.name, record.id.to_s)
93
96
  else
94
97
  Delayed::Job.enqueue Searchkick::ReindexJob.new(record.class.name, record.id.to_s)
95
98
  end
@@ -97,7 +100,7 @@ module Searchkick
97
100
 
98
101
  def similar_record(record, options = {})
99
102
  like_text = retrieve(record).to_hash
100
- .keep_if{|k,v| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
103
+ .keep_if { |k, v| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
101
104
  .values.compact.join(" ")
102
105
 
103
106
  # TODO deep merge method
@@ -136,7 +139,7 @@ module Searchkick
136
139
  # remove old indices that start w/ index_name
137
140
  def clean_indices
138
141
  all_indices = client.indices.get_aliases
139
- indices = all_indices.select{|k, v| (v.empty? || v["aliases"].empty?) && k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
142
+ indices = all_indices.select { |k, v| (v.empty? || v["aliases"].empty?) && k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
140
143
  indices.each do |index|
141
144
  Searchkick::Index.new(index).delete
142
145
  end
@@ -180,7 +183,7 @@ module Searchkick
180
183
  scope = scope.search_import if scope.respond_to?(:search_import)
181
184
  if scope.respond_to?(:find_in_batches)
182
185
  scope.find_in_batches batch_size: batch_size do |batch|
183
- import batch.select{|item| item.should_index? }
186
+ import batch.select(&:should_index?)
184
187
  end
185
188
  else
186
189
  # https://github.com/karmi/tire/blob/master/lib/tire/model/import.rb
@@ -200,7 +203,7 @@ module Searchkick
200
203
  def index_options
201
204
  options = @options
202
205
 
203
- if options[:mappings] and !options[:merge_mappings]
206
+ if options[:mappings] && !options[:merge_mappings]
204
207
  settings = options[:settings] || {}
205
208
  mappings = options[:mappings]
206
209
  else
@@ -333,7 +336,7 @@ module Searchkick
333
336
  if synonyms.any?
334
337
  settings[:analysis][:filter][:searchkick_synonym] = {
335
338
  type: "synonym",
336
- synonyms: synonyms.select{|s| s.size > 1 }.map{|s| s.join(",") }
339
+ synonyms: synonyms.select { |s| s.size > 1 }.map { |s| s.join(",") }
337
340
  }
338
341
  # choosing a place for the synonym filter when stemming is not easy
339
342
  # https://groups.google.com/forum/#!topic/elasticsearch/p7qcQlgHdB8
@@ -361,7 +364,7 @@ module Searchkick
361
364
 
362
365
  if options[:special_characters] == false
363
366
  settings[:analysis][:analyzer].each do |analyzer, analyzer_settings|
364
- analyzer_settings[:filter].reject!{|f| f == "asciifolding" }
367
+ analyzer_settings[:filter].reject! { |f| f == "asciifolding" }
365
368
  end
366
369
  end
367
370
 
@@ -380,7 +383,7 @@ module Searchkick
380
383
 
381
384
  mapping_options = Hash[
382
385
  [:autocomplete, :suggest, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight]
383
- .map{|type| [type, (options[type] || []).map(&:to_s)] }
386
+ .map { |type| [type, (options[type] || []).map(&:to_s)] }
384
387
  ]
385
388
 
386
389
  mapping_options.values.flatten.uniq.each do |field|
@@ -420,9 +423,15 @@ module Searchkick
420
423
  }
421
424
  end
422
425
 
426
+ routing = {}
427
+ if options[:routing]
428
+ routing = {required: true, path: options[:routing].to_s}
429
+ end
430
+
423
431
  mappings = {
424
432
  _default_: {
425
433
  properties: mapping,
434
+ _routing: routing,
426
435
  # https://gist.github.com/kimchy/2898285
427
436
  dynamic_templates: [
428
437
  {
@@ -457,7 +466,7 @@ module Searchkick
457
466
  # other
458
467
 
459
468
  def tokens(text, options = {})
460
- client.indices.analyze({text: text, index: name}.merge(options))["tokens"].map{|t| t["token"] }
469
+ client.indices.analyze({text: text, index: name}.merge(options))["tokens"].map { |t| t["token"] }
461
470
  end
462
471
 
463
472
  def klass_document_type(klass)
@@ -488,24 +497,24 @@ module Searchkick
488
497
 
489
498
  # stringify fields
490
499
  # remove _id since search_id is used instead
491
- source = source.inject({}){|memo,(k,v)| memo[k.to_s] = v; memo}.except("_id")
500
+ source = source.inject({}) { |memo, (k, v)| memo[k.to_s] = v; memo }.except("_id")
492
501
 
493
502
  # conversions
494
503
  conversions_field = options[:conversions]
495
- if conversions_field and source[conversions_field]
496
- source[conversions_field] = source[conversions_field].map{|k, v| {query: k, count: v} }
504
+ if conversions_field && source[conversions_field]
505
+ source[conversions_field] = source[conversions_field].map { |k, v| {query: k, count: v} }
497
506
  end
498
507
 
499
508
  # hack to prevent generator field doesn't exist error
500
509
  (options[:suggest] || []).map(&:to_s).each do |field|
501
- source[field] = nil if !source[field]
510
+ source[field] = nil unless source[field]
502
511
  end
503
512
 
504
513
  # locations
505
514
  (options[:locations] || []).map(&:to_s).each do |field|
506
515
  if source[field]
507
516
  if source[field].first.is_a?(Array) # array of arrays
508
- source[field] = source[field].map{|a| a.map(&:to_f).reverse }
517
+ source[field] = source[field].map { |a| a.map(&:to_f).reverse }
509
518
  else
510
519
  source[field] = source[field].map(&:to_f).reverse
511
520
  end
@@ -77,7 +77,7 @@ module Searchkick
77
77
 
78
78
  # no easy way to tell which host the client will use
79
79
  host = Searchkick.client.transport.hosts.first
80
- debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map{|t| CGI.escape(t) }.join(",")}" : ""}/_search?pretty -d '#{payload[:query][:body].to_json}'"
80
+ debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map { |t| CGI.escape(t) }.join(',')}" : ''}/_search?pretty -d '#{payload[:query][:body].to_json}'"
81
81
  end
82
82
 
83
83
  def request(event)
@@ -11,14 +11,14 @@ module Searchkick
11
11
  class_eval do
12
12
  cattr_reader :searchkick_options, :searchkick_klass
13
13
 
14
- callbacks = options.has_key?(:callbacks) ? options[:callbacks] : true
14
+ callbacks = options.key?(:callbacks) ? options[:callbacks] : true
15
15
 
16
16
  class_variable_set :@@searchkick_options, options.dup
17
17
  class_variable_set :@@searchkick_klass, self
18
18
  class_variable_set :@@searchkick_callbacks, callbacks
19
19
  class_variable_set :@@searchkick_index, options[:index_name] || [options[:index_prefix], model_name.plural, Searchkick.env].compact.join("_")
20
20
 
21
- define_singleton_method(Searchkick.search_method_name) do |term = nil, options={}, &block|
21
+ define_singleton_method(Searchkick.search_method_name) do |term = nil, options = {}, &block|
22
22
  searchkick_index.search_model(self, term, options, &block)
23
23
  end
24
24
  extend Searchkick::Reindex # legacy for Searchjoy
@@ -68,10 +68,10 @@ module Searchkick
68
68
  if callbacks
69
69
  callback_name = callbacks == :async ? :reindex_async : :reindex
70
70
  if respond_to?(:after_commit)
71
- after_commit callback_name, if: proc{ self.class.search_callbacks? }
71
+ after_commit callback_name, if: proc { self.class.search_callbacks? }
72
72
  else
73
- after_save callback_name, if: proc{ self.class.search_callbacks? }
74
- after_destroy callback_name, if: proc{ self.class.search_callbacks? }
73
+ after_save callback_name, if: proc { self.class.search_callbacks? }
74
+ after_destroy callback_name, if: proc { self.class.search_callbacks? }
75
75
  end
76
76
  end
77
77
 
@@ -15,26 +15,26 @@ module Searchkick
15
15
  @term = term
16
16
  @options = options
17
17
 
18
- below12 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.2")
19
- below14 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.4")
18
+ below12 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.2.0")
19
+ below14 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.4.0")
20
20
 
21
21
  boost_fields = {}
22
22
  fields =
23
23
  if options[:fields]
24
24
  if options[:autocomplete]
25
- options[:fields].map{|f| "#{f}.autocomplete" }
25
+ options[:fields].map { |f| "#{f}.autocomplete" }
26
26
  else
27
27
  options[:fields].map do |value|
28
28
  k, v = value.is_a?(Hash) ? value.to_a.first : [value, :word]
29
29
  k2, boost = k.to_s.split("^", 2)
30
- field = "#{k2}.#{v == :word ? "analyzed" : v}"
30
+ field = "#{k2}.#{v == :word ? 'analyzed' : v}"
31
31
  boost_fields[field] = boost.to_f if boost
32
32
  field
33
33
  end
34
34
  end
35
35
  else
36
36
  if options[:autocomplete]
37
- (searchkick_options[:autocomplete] || []).map{|f| "#{f}.autocomplete" }
37
+ (searchkick_options[:autocomplete] || []).map { |f| "#{f}.autocomplete" }
38
38
  else
39
39
  ["_all"]
40
40
  end
@@ -44,7 +44,7 @@ module Searchkick
44
44
 
45
45
  # pagination
46
46
  page = [options[:page].to_i, 1].max
47
- per_page = (options[:limit] || options[:per_page] || 100000).to_i
47
+ per_page = (options[:limit] || options[:per_page] || 100_000).to_i
48
48
  padding = [options[:padding].to_i, 0].max
49
49
  offset = options[:offset] || (page - 1) * per_page + padding
50
50
 
@@ -98,13 +98,13 @@ module Searchkick
98
98
  boost: factor
99
99
  }
100
100
 
101
- if field == "_all" or field.end_with?(".analyzed")
101
+ if field == "_all" || field.end_with?(".analyzed")
102
102
  shared_options[:cutoff_frequency] = 0.001 unless operator == "and"
103
103
  qs.concat [
104
104
  shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search"),
105
105
  shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search2")
106
106
  ]
107
- misspellings = options.has_key?(:misspellings) ? options[:misspellings] : options[:mispellings] # why not?
107
+ misspellings = options.key?(:misspellings) ? options[:misspellings] : options[:mispellings] # why not?
108
108
  if misspellings != false
109
109
  edit_distance = (misspellings.is_a?(Hash) && (misspellings[:edit_distance] || misspellings[:distance])) || 1
110
110
  qs.concat [
@@ -120,7 +120,7 @@ module Searchkick
120
120
  qs << shared_options.merge(analyzer: analyzer)
121
121
  end
122
122
 
123
- queries.concat(qs.map{|q| {match: {field => q}} })
123
+ queries.concat(qs.map { |q| {match: {field => q}} })
124
124
  end
125
125
 
126
126
  payload = {
@@ -130,7 +130,7 @@ module Searchkick
130
130
  }
131
131
  end
132
132
 
133
- if conversions_field and options[:conversions] != false
133
+ if conversions_field && options[:conversions] != false
134
134
  # wrap payload in a bool query
135
135
  script_score =
136
136
  if below12
@@ -167,7 +167,7 @@ module Searchkick
167
167
 
168
168
  boost_by = options[:boost_by] || {}
169
169
  if boost_by.is_a?(Array)
170
- boost_by = Hash[ boost_by.map{|f| [f, {factor: 1}] } ]
170
+ boost_by = Hash[boost_by.map { |f| [f, {factor: 1}] }]
171
171
  end
172
172
  if options[:boost]
173
173
  boost_by[options[:boost]] = {factor: 1}
@@ -191,14 +191,14 @@ module Searchkick
191
191
  end
192
192
 
193
193
  boost_where = options[:boost_where] || {}
194
- if options[:user_id] and personalize_field
194
+ if options[:user_id] && personalize_field
195
195
  boost_where[personalize_field] = options[:user_id]
196
196
  end
197
197
  if options[:personalize]
198
198
  boost_where = boost_where.merge(options[:personalize])
199
199
  end
200
200
  boost_where.each do |field, value|
201
- if value.is_a?(Array) and value.first.is_a?(Hash)
201
+ if value.is_a?(Array) && value.first.is_a?(Hash)
202
202
  value.each do |value_factor|
203
203
  value, factor = value_factor[:value], value_factor[:factor]
204
204
  custom_filters << custom_filter(field, value, factor)
@@ -215,10 +215,10 @@ module Searchkick
215
215
  boost_by_distance = options[:boost_by_distance]
216
216
  if boost_by_distance
217
217
  boost_by_distance = {function: :gauss, scale: "5mi"}.merge(boost_by_distance)
218
- if !boost_by_distance[:field] or !boost_by_distance[:origin]
218
+ if !boost_by_distance[:field] || !boost_by_distance[:origin]
219
219
  raise ArgumentError, "boost_by_distance requires :field and :origin"
220
220
  end
221
- function_params = boost_by_distance.select{|k,v| [:origin, :scale, :offset, :decay].include?(k) }
221
+ function_params = boost_by_distance.select { |k, v| [:origin, :scale, :offset, :decay].include?(k) }
222
222
  function_params[:origin] = function_params[:origin].reverse
223
223
  custom_filters << {
224
224
  boost_by_distance[:function] => {
@@ -248,29 +248,41 @@ module Searchkick
248
248
  if options[:order]
249
249
  order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
250
250
  # TODO id transformation for arrays
251
- payload[:sort] = order.is_a?(Array) ? order : Hash[ order.map{|k, v| [k.to_s == "id" ? :_id : k, v] } ]
251
+ payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? :_id : k, v] }]
252
252
  end
253
253
 
254
254
  # filters
255
255
  filters = where_filters(options[:where])
256
256
  if filters.any?
257
- payload[:filter] = {
258
- and: filters
259
- }
257
+ if options[:facets]
258
+ payload[:filter] = {
259
+ and: filters
260
+ }
261
+ else
262
+ # more efficient query if no facets
263
+ payload[:query] = {
264
+ filtered: {
265
+ query: payload[:query],
266
+ filter: {
267
+ and: filters
268
+ }
269
+ }
270
+ }
271
+ end
260
272
  end
261
273
 
262
274
  # facets
263
275
  if options[:facets]
264
276
  facets = options[:facets] || {}
265
277
  if facets.is_a?(Array) # convert to more advanced syntax
266
- facets = Hash[ facets.map{|f| [f, {}] } ]
278
+ facets = Hash[facets.map { |f| [f, {}] }]
267
279
  end
268
280
 
269
281
  payload[:facets] = {}
270
282
  facets.each do |field, facet_options|
271
283
  # ask for extra facets due to
272
284
  # https://github.com/elasticsearch/elasticsearch/issues/1305
273
- size = facet_options[:limit] ? facet_options[:limit] + 150 : 100000
285
+ size = facet_options[:limit] ? facet_options[:limit] + 150 : 100_000
274
286
 
275
287
  if facet_options[:ranges]
276
288
  payload[:facets][field] = {
@@ -289,7 +301,7 @@ module Searchkick
289
301
  else
290
302
  payload[:facets][field] = {
291
303
  terms: {
292
- field: field,
304
+ field: facet_options[:field] || field,
293
305
  size: size
294
306
  }
295
307
  }
@@ -300,7 +312,7 @@ module Searchkick
300
312
  # offset is not possible
301
313
  # http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html
302
314
 
303
- facet_options.deep_merge!(where: options[:where].reject{|k| k == field}) if options[:smart_facets] == true
315
+ facet_options.deep_merge!(where: options[:where].reject { |k| k == field }) if options[:smart_facets] == true
304
316
  facet_filters = where_filters(facet_options[:where])
305
317
  if facet_filters.any?
306
318
  payload[:facets][field][:facet_filter] = {
@@ -318,7 +330,7 @@ module Searchkick
318
330
 
319
331
  # intersection
320
332
  if options[:fields]
321
- suggest_fields = suggest_fields & options[:fields].map{|v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
333
+ suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
322
334
  end
323
335
 
324
336
  if suggest_fields.any?
@@ -336,11 +348,11 @@ module Searchkick
336
348
  # highlight
337
349
  if options[:highlight]
338
350
  payload[:highlight] = {
339
- fields: Hash[ fields.map{|f| [f, {}] } ]
351
+ fields: Hash[fields.map { |f| [f, {}] }]
340
352
  }
341
353
 
342
354
  if options[:highlight].is_a?(Hash)
343
- if tag = options[:highlight][:tag]
355
+ if (tag = options[:highlight][:tag])
344
356
  payload[:highlight][:pre_tags] = [tag]
345
357
  payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
346
358
  end
@@ -364,8 +376,13 @@ module Searchkick
364
376
  payload[:fields] = []
365
377
  end
366
378
 
367
- if options[:type] or klass != searchkick_klass
368
- @type = [options[:type] || klass].flatten.map{|v| searchkick_index.klass_document_type(v) }
379
+ if options[:type] || klass != searchkick_klass
380
+ @type = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v) }
381
+ end
382
+
383
+ # routing
384
+ if options[:routing]
385
+ @routing = options[:routing]
369
386
  end
370
387
  end
371
388
 
@@ -395,6 +412,7 @@ module Searchkick
395
412
  body: body
396
413
  }
397
414
  params.merge!(type: @type) if @type
415
+ params.merge!(routing: @routing) if @routing
398
416
  params
399
417
  end
400
418
 
@@ -405,10 +423,10 @@ module Searchkick
405
423
  status_code = e.message[1..3].to_i
406
424
  if status_code == 404
407
425
  raise MissingIndexError, "Index missing - run #{searchkick_klass.name}.reindex"
408
- elsif status_code == 500 and (
409
- e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") or
410
- e.message.include?("No query registered for [multi_match]") or
411
- e.message.include?("[match] query does not support [cutoff_frequency]]") or
426
+ elsif status_code == 500 && (
427
+ e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") ||
428
+ e.message.include?("No query registered for [multi_match]") ||
429
+ e.message.include?("[match] query does not support [cutoff_frequency]]") ||
412
430
  e.message.include?("No query registered for [function_score]]")
413
431
  )
414
432
 
@@ -430,7 +448,7 @@ module Searchkick
430
448
  field = field.to_s
431
449
  facet = response["facets"][field]
432
450
  response["facets"][field]["terms"] = facet["terms"].first(limit)
433
- response["facets"][field]["other"] = facet["total"] - facet["terms"].sum{|term| term["count"] }
451
+ response["facets"][field]["other"] = facet["total"] - facet["terms"].sum { |term| term["count"] }
434
452
  end
435
453
 
436
454
  opts = {
@@ -453,7 +471,7 @@ module Searchkick
453
471
 
454
472
  if field == :or
455
473
  value.each do |or_clause|
456
- filters << {or: or_clause.map{|or_statement| {and: where_filters(or_statement)} }}
474
+ filters << {or: or_clause.map { |or_statement| {and: where_filters(or_statement)} }}
457
475
  end
458
476
  else
459
477
  # expand ranges
@@ -507,7 +525,7 @@ module Searchkick
507
525
  raise "Unknown where operator"
508
526
  end
509
527
  # issue 132
510
- if existing = filters.find{ |f| f[:range] && f[:range][field] }
528
+ if (existing = filters.find { |f| f[:range] && f[:range][field] })
511
529
  existing[:range][field].merge!(range_query)
512
530
  else
513
531
  filters << {range: {field => range_query}}
@@ -524,13 +542,15 @@ module Searchkick
524
542
 
525
543
  def term_filters(field, value)
526
544
  if value.is_a?(Array) # in query
527
- if value.any?
528
- {or: value.map{|v| term_filters(field, v) }}
545
+ if value.any?(&:nil?)
546
+ {or: [term_filters(field, nil), term_filters(field, value.compact)]}
529
547
  else
530
- {terms: {field => value}} # match nothing
548
+ {in: {field => value}}
531
549
  end
532
550
  elsif value.nil?
533
551
  {missing: {"field" => field, existence: true, null_value: true}}
552
+ elsif value.is_a?(Regexp)
553
+ {regexp: {field => {value: value.source}}}
534
554
  else
535
555
  {term: {field => value}}
536
556
  end
@@ -10,7 +10,7 @@ module Searchkick
10
10
  model = @klass.constantize
11
11
  record = model.find(@id) rescue nil # TODO fix lazy coding
12
12
  index = model.searchkick_index
13
- if !record or !record.should_index?
13
+ if !record || !record.should_index?
14
14
  # hacky
15
15
  record ||= model.new
16
16
  record.id = @id
@@ -6,7 +6,7 @@ module Searchkick
6
6
  model = klass.constantize
7
7
  record = model.find(id) rescue nil # TODO fix lazy coding
8
8
  index = model.searchkick_index
9
- if !record or !record.should_index?
9
+ if !record || !record.should_index?
10
10
  # hacky
11
11
  record ||= model.new
12
12
  record.id = id
@@ -21,10 +21,10 @@ module Searchkick
21
21
  # results can have different types
22
22
  results = {}
23
23
 
24
- hits.group_by{|hit, i| hit["_type"] }.each do |type, grouped_hits|
24
+ hits.group_by { |hit, i| hit["_type"] }.each do |type, grouped_hits|
25
25
  records = type.camelize.constantize
26
26
  if options[:includes]
27
- if defined?(NoBrainer::Document) and records < NoBrainer::Document
27
+ if defined?(NoBrainer::Document) && records < NoBrainer::Document
28
28
  records = records.preload(options[:includes])
29
29
  else
30
30
  records = records.includes(options[:includes])
@@ -35,7 +35,7 @@ module Searchkick
35
35
 
36
36
  # sort
37
37
  hits.map do |hit|
38
- results[hit["_type"]].find{|r| r.id.to_s == hit["_id"].to_s }
38
+ results[hit["_type"]].find { |r| r.id.to_s == hit["_id"].to_s }
39
39
  end.compact
40
40
  else
41
41
  hits.map do |hit|
@@ -54,7 +54,7 @@ module Searchkick
54
54
 
55
55
  def suggestions
56
56
  if response["suggest"]
57
- response["suggest"].values.flat_map{|v| v.first["options"] }.sort_by{|o| -o["score"] }.map{|o| o["text"] }.uniq
57
+ response["suggest"].values.flat_map { |v| v.first["options"] }.sort_by { |o| -o["score"] }.map { |o| o["text"] }.uniq
58
58
  else
59
59
  raise "Pass `suggest: true` to the search method for suggestions"
60
60
  end
@@ -68,7 +68,7 @@ module Searchkick
68
68
  each_with_hit.map do |model, hit|
69
69
  details = {}
70
70
  if hit["highlight"]
71
- details[:highlight] = Hash[ hit["highlight"].map{|k, v| [(options[:json] ? k : k.sub(/\.analyzed\z/, "")).to_sym, v.first] } ]
71
+ details[:highlight] = Hash[hit["highlight"].map { |k, v| [(options[:json] ? k : k.sub(/\.analyzed\z/, "")).to_sym, v.first] }]
72
72
  end
73
73
  [model, details]
74
74
  end
@@ -138,18 +138,18 @@ module Searchkick
138
138
  private
139
139
 
140
140
  def results_query(records, grouped_hits)
141
- if records.respond_to?(:primary_key) and records.primary_key
141
+ if records.respond_to?(:primary_key) && records.primary_key
142
142
  # ActiveRecord
143
- records.where(records.primary_key => grouped_hits.map{|hit| hit["_id"] }).to_a
144
- elsif records.respond_to?(:all) and records.all.respond_to?(:for_ids)
143
+ records.where(records.primary_key => grouped_hits.map { |hit| hit["_id"] }).to_a
144
+ elsif records.respond_to?(:all) && records.all.respond_to?(:for_ids)
145
145
  # Mongoid 2
146
- records.all.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
146
+ records.all.for_ids(grouped_hits.map { |hit| hit["_id"] }).to_a
147
147
  elsif records.respond_to?(:queryable)
148
148
  # Mongoid 3+
149
- records.queryable.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
150
- elsif records.respond_to?(:unscoped) and records.all.respond_to?(:preload)
149
+ records.queryable.for_ids(grouped_hits.map { |hit| hit["_id"] }).to_a
150
+ elsif records.respond_to?(:unscoped) && records.all.respond_to?(:preload)
151
151
  # Nobrainer
152
- records.unscoped.where(:id.in => grouped_hits.map{|hit| hit["_id"] }).to_a
152
+ records.unscoped.where(:id.in => grouped_hits.map { |hit| hit["_id"] }).to_a
153
153
  else
154
154
  raise "Not sure how to load records"
155
155
  end
@@ -3,13 +3,13 @@ require "rake"
3
3
  namespace :searchkick do
4
4
 
5
5
  desc "reindex model"
6
- task :reindex => :environment do
6
+ task reindex: :environment do
7
7
  if ENV["CLASS"]
8
8
  klass = ENV["CLASS"].constantize rescue nil
9
9
  if klass
10
10
  klass.reindex
11
11
  else
12
- abort "Could not find class: #{ENV["CLASS"]}"
12
+ abort "Could not find class: #{ENV['CLASS']}"
13
13
  end
14
14
  else
15
15
  abort "USAGE: rake searchkick:reindex CLASS=Product"
@@ -20,7 +20,7 @@ namespace :searchkick do
20
20
 
21
21
  namespace :reindex do
22
22
  desc "reindex all models"
23
- task :all => :environment do
23
+ task all: :environment do
24
24
  Rails.application.eager_load!
25
25
  Searchkick.models.each do |model|
26
26
  puts "Reindexing #{model.name}..."
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "0.8.7"
2
+ VERSION = "0.9.0"
3
3
  end
data/lib/searchkick.rb CHANGED
@@ -24,13 +24,11 @@ module Searchkick
24
24
  class InvalidQueryError < Elasticsearch::Transport::Transport::Errors::BadRequest; end
25
25
 
26
26
  class << self
27
- attr_accessor :callbacks
28
27
  attr_accessor :search_method_name
29
28
  attr_accessor :wordnet_path
30
29
  attr_accessor :timeout
31
30
  attr_accessor :models
32
31
  end
33
- self.callbacks = true
34
32
  self.search_method_name = :search
35
33
  self.wordnet_path = "/var/lib/wn_s.pl"
36
34
  self.timeout = 10
@@ -44,8 +42,8 @@ module Searchkick
44
42
  )
45
43
  end
46
44
 
47
- def self.client=(client)
48
- @client = client
45
+ class << self
46
+ attr_writer :client
49
47
  end
50
48
 
51
49
  def self.server_version
@@ -53,15 +51,15 @@ module Searchkick
53
51
  end
54
52
 
55
53
  def self.enable_callbacks
56
- self.callbacks = true
54
+ Thread.current[:searchkick_callbacks_enabled] = true
57
55
  end
58
56
 
59
57
  def self.disable_callbacks
60
- self.callbacks = false
58
+ Thread.current[:searchkick_callbacks_enabled] = false
61
59
  end
62
60
 
63
61
  def self.callbacks?
64
- callbacks
62
+ Thread.current[:searchkick_callbacks_enabled].nil? || Thread.current[:searchkick_callbacks_enabled]
65
63
  end
66
64
 
67
65
  def self.env
data/searchkick.gemspec CHANGED
@@ -1,19 +1,19 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'searchkick/version'
4
+ require "searchkick/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "searchkick"
8
8
  spec.version = Searchkick::VERSION
9
9
  spec.authors = ["Andrew Kane"]
10
10
  spec.email = ["andrew@chartkick.com"]
11
- spec.description = %q{Intelligent search made easy}
12
- spec.summary = %q{Searchkick learns what your users are looking for. As more people search, it gets smarter and the results get better. It’s friendly for developers - and magical for your users.}
11
+ spec.description = "Intelligent search made easy"
12
+ spec.summary = "Searchkick learns what your users are looking for. As more people search, it gets smarter and the results get better. It’s friendly for developers - and magical for your users."
13
13
  spec.homepage = "https://github.com/ankane/searchkick"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.files = `git ls-files`.split($/)
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
data/test/facets_test.rb CHANGED
@@ -21,6 +21,12 @@ class TestFacets < Minitest::Test
21
21
  assert_equal ({1 => 1}), store_facet(facets: {store_id: {where: {in_stock: true}}})
22
22
  end
23
23
 
24
+ def test_field
25
+ assert_equal ({1 => 1, 2 => 2}), store_facet(facets: {store_id: {}})
26
+ assert_equal ({1 => 1, 2 => 2}), store_facet(facets: {store_id: {field: "store_id"}})
27
+ assert_equal ({1 => 1, 2 => 2}), store_facet({facets: {store_id_new: {field: "store_id"}}}, "store_id_new")
28
+ end
29
+
24
30
  def test_limit
25
31
  facet = Product.search("Product", facets: {store_id: {limit: 1}}).facets["store_id"]
26
32
  assert_equal 1, facet["terms"].size
@@ -78,8 +84,8 @@ class TestFacets < Minitest::Test
78
84
 
79
85
  protected
80
86
 
81
- def store_facet(options)
82
- Hash[ Product.search("Product", options).facets["store_id"]["terms"].map{|v| [v["term"], v["count"]] } ]
87
+ def store_facet(options, facet_key="store_id")
88
+ Hash[Product.search("Product", options).facets[facet_key]["terms"].map { |v| [v["term"], v["count"]] }]
83
89
  end
84
90
 
85
91
  end
data/test/index_test.rb CHANGED
@@ -75,24 +75,32 @@ class TestIndex < Minitest::Test
75
75
  def test_bad_mapping
76
76
  Product.searchkick_index.delete
77
77
  store_names ["Product A"]
78
- assert_raises(Searchkick::InvalidQueryError){ Product.search "test" }
78
+ assert_raises(Searchkick::InvalidQueryError) { Product.search "test" }
79
+ ensure
80
+ Product.reindex
81
+ end
82
+
83
+ def test_remove_blank_id
84
+ store_names ["Product A"]
85
+ Product.searchkick_index.remove(OpenStruct.new)
86
+ assert_search "product", ["Product A"]
79
87
  ensure
80
88
  Product.reindex
81
89
  end
82
90
 
83
91
  def test_missing_index
84
- assert_raises(Searchkick::MissingIndexError){ Product.search "test", index_name: "not_found" }
92
+ assert_raises(Searchkick::MissingIndexError) { Product.search "test", index_name: "not_found" }
85
93
  end
86
94
 
87
95
  def test_unsupported_version
88
- raises_exception = lambda { |s| raise Elasticsearch::Transport::Transport::Error.new("[500] No query registered for [multi_match]") }
96
+ raises_exception = ->(s) { raise Elasticsearch::Transport::Transport::Error.new("[500] No query registered for [multi_match]") }
89
97
  Searchkick.client.stub :search, raises_exception do
90
- assert_raises(Searchkick::UnsupportedVersionError){ Product.search("test") }
98
+ assert_raises(Searchkick::UnsupportedVersionError) { Product.search("test") }
91
99
  end
92
100
  end
93
101
 
94
102
  def test_invalid_query
95
- assert_raises(Searchkick::InvalidQueryError){ Product.search(query: {}) }
103
+ assert_raises(Searchkick::InvalidQueryError) { Product.search(query: {}) }
96
104
  end
97
105
 
98
106
  if defined?(ActiveRecord)
data/test/model_test.rb CHANGED
@@ -18,6 +18,9 @@ class TestModel < Minitest::Test
18
18
  end
19
19
 
20
20
  def test_disable_callbacks_global
21
+ # make sure callbacks default to on
22
+ assert Searchkick.callbacks?
23
+
21
24
  store_names ["product a"]
22
25
 
23
26
  Searchkick.disable_callbacks
@@ -3,7 +3,7 @@ require_relative "test_helper"
3
3
  class TestReindexV2Job < Minitest::Test
4
4
 
5
5
  def setup
6
- skip if !defined?(ActiveJob)
6
+ skip unless defined?(ActiveJob)
7
7
  super
8
8
  Searchkick.disable_callbacks
9
9
  end
@@ -0,0 +1,14 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestRouting < Minitest::Test
4
+
5
+ def test_routing_query
6
+ query = Store.search("Dollar Tree", routing: "Dollar Tree", execute: false)
7
+ assert_equal query.params[:routing], "Dollar Tree"
8
+ end
9
+
10
+ def test_routing_mappings
11
+ index_options = Store.searchkick_index.index_options
12
+ assert_equal index_options[:mappings][:_default_][:_routing], {required: true, path: "name"}
13
+ end
14
+ end
data/test/sql_test.rb CHANGED
@@ -8,7 +8,7 @@ class TestSql < Minitest::Test
8
8
  end
9
9
 
10
10
  def test_no_limit
11
- names = 20.times.map{|i| "Product #{i}" }
11
+ names = 20.times.map { |i| "Product #{i}" }
12
12
  store_names names
13
13
  assert_search "product", names
14
14
  end
@@ -57,7 +57,7 @@ class TestSql < Minitest::Test
57
57
  {name: "Product A", store_id: 1, in_stock: true, backordered: true, created_at: now, orders_count: 4, user_ids: [1, 2, 3]},
58
58
  {name: "Product B", store_id: 2, in_stock: true, backordered: false, created_at: now - 1, orders_count: 3, user_ids: [1]},
59
59
  {name: "Product C", store_id: 3, in_stock: false, backordered: true, created_at: now - 2, orders_count: 2, user_ids: [1, 3]},
60
- {name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1},
60
+ {name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1}
61
61
  ]
62
62
  assert_search "product", ["Product A", "Product B"], where: {in_stock: true}
63
63
  # date
@@ -85,7 +85,7 @@ class TestSql < Minitest::Test
85
85
  assert_search "product", ["Product A", "Product C"], where: {user_ids: {all: [1, 3]}}
86
86
  assert_search "product", [], where: {user_ids: {all: [1, 2, 3, 4]}}
87
87
  # any / nested terms
88
- assert_search "product", ["Product B", "Product C"], where: {user_ids: {not: [2], in: [1,3]}}
88
+ assert_search "product", ["Product B", "Product C"], where: {user_ids: {not: [2], in: [1, 3]}}
89
89
  # not / exists
90
90
  assert_search "product", ["Product D"], where: {user_ids: nil}
91
91
  assert_search "product", ["Product A", "Product B", "Product C"], where: {user_ids: {not: nil}}
@@ -93,6 +93,11 @@ class TestSql < Minitest::Test
93
93
  assert_search "product", ["Product B"], where: {user_ids: {not: [3, nil]}}
94
94
  end
95
95
 
96
+ def test_regexp
97
+ store_names ["Product A"]
98
+ assert_search "*", ["Product A"], where: {name: /Pro.+/}
99
+ end
100
+
96
101
  def test_where_string
97
102
  store [
98
103
  {name: "Product A", color: "RED"}
@@ -288,7 +293,7 @@ class TestSql < Minitest::Test
288
293
  def test_select
289
294
  store [{name: "Product A", store_id: 1}]
290
295
  result = Product.search("product", load: false, select: [:name, :store_id]).first
291
- assert_equal %w[id name store_id], result.keys.reject{|k| k.start_with?("_") }.sort
296
+ assert_equal %w[id name store_id], result.keys.reject { |k| k.start_with?("_") }.sort
292
297
  assert_equal ["Product A"], result.name # this is not great
293
298
  end
294
299
 
@@ -312,7 +317,7 @@ class TestSql < Minitest::Test
312
317
  end
313
318
 
314
319
  # TODO see if Mongoid is loaded
315
- unless defined?(Mongoid) or defined?(NoBrainer)
320
+ unless defined?(Mongoid) || defined?(NoBrainer)
316
321
  def test_include
317
322
  store_names ["Product A"]
318
323
  assert Product.search("product", include: [:store]).first.association(:store).loaded?
data/test/suggest_test.rb CHANGED
@@ -19,13 +19,13 @@ class TestSuggest < Minitest::Test
19
19
 
20
20
  def test_without_option
21
21
  store_names ["hi"] # needed to prevent ElasticsearchException - seed 668
22
- assert_raises(RuntimeError){ Product.search("hi").suggestions }
22
+ assert_raises(RuntimeError) { Product.search("hi").suggestions }
23
23
  end
24
24
 
25
25
  def test_multiple_fields
26
26
  store [
27
27
  {name: "Shark", color: "Sharp"},
28
- {name: "Shark", color: "Sharp"},
28
+ {name: "Shark", color: "Sharp"}
29
29
  ]
30
30
  assert_suggest_all "shar", ["shark", "sharp"]
31
31
  end
data/test/test_helper.rb CHANGED
@@ -8,9 +8,11 @@ ENV["RACK_ENV"] = "test"
8
8
 
9
9
  Minitest::Test = Minitest::Unit::TestCase unless defined?(Minitest::Test)
10
10
 
11
- File.delete("elasticsearch.log") if File.exists?("elasticsearch.log")
11
+ File.delete("elasticsearch.log") if File.exist?("elasticsearch.log")
12
12
  Searchkick.client.transport.logger = Logger.new("elasticsearch.log")
13
13
 
14
+ puts "Running against Elasticsearch #{Searchkick.server_version}"
15
+
14
16
  I18n.config.enforce_available_locales = true
15
17
 
16
18
  ActiveJob::Base.logger = nil if defined?(ActiveJob)
@@ -26,7 +28,7 @@ if defined?(Mongoid)
26
28
  module BSON
27
29
  class ObjectId
28
30
  def <=>(other)
29
- self.data <=> other.data
31
+ data <=> other.data
30
32
  end
31
33
  end
32
34
  end
@@ -210,12 +212,15 @@ class Product
210
212
  end
211
213
 
212
214
  class Store
213
- searchkick mappings: {
214
- store: {
215
- properties: {
216
- name: {type: "string", analyzer: "keyword"}
215
+ searchkick \
216
+ routing: :name,
217
+ merge_mappings: true,
218
+ mappings: {
219
+ store: {
220
+ properties: {
221
+ name: {type: "string", analyzer: "keyword"},
222
+ }
217
223
  }
218
- }
219
224
  }
220
225
  end
221
226
 
@@ -223,7 +228,7 @@ class Animal
223
228
  searchkick \
224
229
  autocomplete: [:name],
225
230
  suggest: [:name],
226
- index_name: -> { "#{self.name.tableize}-#{Date.today.year}" }
231
+ index_name: -> { "#{name.tableize}-#{Date.today.year}" }
227
232
  # wordnet: true
228
233
  end
229
234
 
@@ -252,7 +257,7 @@ class Minitest::Test
252
257
  end
253
258
 
254
259
  def store_names(names, klass = Product)
255
- store names.map{|name| {name: name} }, klass
260
+ store names.map { |name| {name: name} }, klass
256
261
  end
257
262
 
258
263
  # no order
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: 0.8.7
4
+ version: 0.9.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: 2015-02-14 00:00:00.000000000 Z
11
+ date: 2015-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -139,6 +139,7 @@ files:
139
139
  - test/query_test.rb
140
140
  - test/reindex_job_test.rb
141
141
  - test/reindex_v2_job_test.rb
142
+ - test/routing_test.rb
142
143
  - test/should_index_test.rb
143
144
  - test/similar_test.rb
144
145
  - test/sql_test.rb
@@ -183,9 +184,11 @@ test_files:
183
184
  - test/query_test.rb
184
185
  - test/reindex_job_test.rb
185
186
  - test/reindex_v2_job_test.rb
187
+ - test/routing_test.rb
186
188
  - test/should_index_test.rb
187
189
  - test/similar_test.rb
188
190
  - test/sql_test.rb
189
191
  - test/suggest_test.rb
190
192
  - test/synonyms_test.rb
191
193
  - test/test_helper.rb
194
+ has_rdoc: