searchkick 0.8.7 → 0.9.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: 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: