searchkick 2.3.2 → 5.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +377 -84
  3. data/LICENSE.txt +1 -1
  4. data/README.md +859 -602
  5. data/lib/searchkick/bulk_reindex_job.rb +13 -9
  6. data/lib/searchkick/controller_runtime.rb +40 -0
  7. data/lib/searchkick/hash_wrapper.rb +12 -0
  8. data/lib/searchkick/index.rb +281 -356
  9. data/lib/searchkick/index_cache.rb +30 -0
  10. data/lib/searchkick/index_options.rb +487 -281
  11. data/lib/searchkick/indexer.rb +15 -8
  12. data/lib/searchkick/log_subscriber.rb +57 -0
  13. data/lib/searchkick/middleware.rb +9 -2
  14. data/lib/searchkick/model.rb +72 -118
  15. data/lib/searchkick/multi_search.rb +9 -10
  16. data/lib/searchkick/process_batch_job.rb +12 -15
  17. data/lib/searchkick/process_queue_job.rb +22 -13
  18. data/lib/searchkick/query.rb +458 -217
  19. data/lib/searchkick/railtie.rb +7 -0
  20. data/lib/searchkick/record_data.rb +128 -0
  21. data/lib/searchkick/record_indexer.rb +164 -0
  22. data/lib/searchkick/reindex_queue.rb +51 -9
  23. data/lib/searchkick/reindex_v2_job.rb +10 -32
  24. data/lib/searchkick/relation.rb +247 -0
  25. data/lib/searchkick/relation_indexer.rb +155 -0
  26. data/lib/searchkick/results.rb +201 -82
  27. data/lib/searchkick/version.rb +1 -1
  28. data/lib/searchkick/where.rb +11 -0
  29. data/lib/searchkick.rb +269 -97
  30. data/lib/tasks/searchkick.rake +37 -0
  31. metadata +24 -178
  32. data/.gitignore +0 -22
  33. data/.travis.yml +0 -39
  34. data/Gemfile +0 -16
  35. data/Rakefile +0 -20
  36. data/benchmark/Gemfile +0 -23
  37. data/benchmark/benchmark.rb +0 -97
  38. data/lib/searchkick/logging.rb +0 -242
  39. data/lib/searchkick/tasks.rb +0 -33
  40. data/searchkick.gemspec +0 -28
  41. data/test/aggs_test.rb +0 -197
  42. data/test/autocomplete_test.rb +0 -75
  43. data/test/boost_test.rb +0 -202
  44. data/test/callbacks_test.rb +0 -59
  45. data/test/ci/before_install.sh +0 -17
  46. data/test/errors_test.rb +0 -19
  47. data/test/gemfiles/activerecord31.gemfile +0 -7
  48. data/test/gemfiles/activerecord32.gemfile +0 -7
  49. data/test/gemfiles/activerecord40.gemfile +0 -8
  50. data/test/gemfiles/activerecord41.gemfile +0 -8
  51. data/test/gemfiles/activerecord42.gemfile +0 -7
  52. data/test/gemfiles/activerecord50.gemfile +0 -7
  53. data/test/gemfiles/apartment.gemfile +0 -8
  54. data/test/gemfiles/cequel.gemfile +0 -8
  55. data/test/gemfiles/mongoid2.gemfile +0 -7
  56. data/test/gemfiles/mongoid3.gemfile +0 -6
  57. data/test/gemfiles/mongoid4.gemfile +0 -7
  58. data/test/gemfiles/mongoid5.gemfile +0 -7
  59. data/test/gemfiles/mongoid6.gemfile +0 -12
  60. data/test/gemfiles/nobrainer.gemfile +0 -8
  61. data/test/gemfiles/parallel_tests.gemfile +0 -8
  62. data/test/geo_shape_test.rb +0 -175
  63. data/test/highlight_test.rb +0 -78
  64. data/test/index_test.rb +0 -166
  65. data/test/inheritance_test.rb +0 -83
  66. data/test/marshal_test.rb +0 -8
  67. data/test/match_test.rb +0 -276
  68. data/test/misspellings_test.rb +0 -56
  69. data/test/model_test.rb +0 -42
  70. data/test/multi_search_test.rb +0 -36
  71. data/test/multi_tenancy_test.rb +0 -22
  72. data/test/order_test.rb +0 -46
  73. data/test/pagination_test.rb +0 -70
  74. data/test/partial_reindex_test.rb +0 -58
  75. data/test/query_test.rb +0 -35
  76. data/test/records_test.rb +0 -10
  77. data/test/reindex_test.rb +0 -64
  78. data/test/reindex_v2_job_test.rb +0 -32
  79. data/test/routing_test.rb +0 -23
  80. data/test/should_index_test.rb +0 -32
  81. data/test/similar_test.rb +0 -28
  82. data/test/sql_test.rb +0 -214
  83. data/test/suggest_test.rb +0 -95
  84. data/test/support/kaminari.yml +0 -21
  85. data/test/synonyms_test.rb +0 -67
  86. data/test/test_helper.rb +0 -567
  87. data/test/where_test.rb +0 -223
@@ -1,242 +0,0 @@
1
- # based on https://gist.github.com/mnutt/566725
2
- require "active_support/core_ext/module/attr_internal"
3
-
4
- module Searchkick
5
- module QueryWithInstrumentation
6
- def execute_search
7
- name = searchkick_klass ? "#{searchkick_klass.name} Search" : "Search"
8
- event = {
9
- name: name,
10
- query: params
11
- }
12
- ActiveSupport::Notifications.instrument("search.searchkick", event) do
13
- super
14
- end
15
- end
16
- end
17
-
18
- module IndexWithInstrumentation
19
- def store(record)
20
- event = {
21
- name: "#{record.searchkick_klass.name} Store",
22
- id: search_id(record)
23
- }
24
- if Searchkick.callbacks_value == :bulk
25
- super
26
- else
27
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
28
- super
29
- end
30
- end
31
- end
32
-
33
- def remove(record)
34
- name = record && record.searchkick_klass ? "#{record.searchkick_klass.name} Remove" : "Remove"
35
- event = {
36
- name: name,
37
- id: search_id(record)
38
- }
39
- if Searchkick.callbacks_value == :bulk
40
- super
41
- else
42
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
43
- super
44
- end
45
- end
46
- end
47
-
48
- def update_record(record, method_name)
49
- event = {
50
- name: "#{record.searchkick_klass.name} Update",
51
- id: search_id(record)
52
- }
53
- if Searchkick.callbacks_value == :bulk
54
- super
55
- else
56
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
57
- super
58
- end
59
- end
60
- end
61
-
62
- def bulk_index(records)
63
- if records.any?
64
- event = {
65
- name: "#{records.first.searchkick_klass.name} Import",
66
- count: records.size
67
- }
68
- event[:id] = search_id(records.first) if records.size == 1
69
- if Searchkick.callbacks_value == :bulk
70
- super
71
- else
72
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
73
- super
74
- end
75
- end
76
- end
77
- end
78
- alias_method :import, :bulk_index
79
-
80
- def bulk_update(records, *args)
81
- if records.any?
82
- event = {
83
- name: "#{records.first.searchkick_klass.name} Update",
84
- count: records.size
85
- }
86
- event[:id] = search_id(records.first) if records.size == 1
87
- if Searchkick.callbacks_value == :bulk
88
- super
89
- else
90
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
91
- super
92
- end
93
- end
94
- end
95
- end
96
-
97
- def bulk_delete(records)
98
- if records.any?
99
- event = {
100
- name: "#{records.first.searchkick_klass.name} Delete",
101
- count: records.size
102
- }
103
- event[:id] = search_id(records.first) if records.size == 1
104
- if Searchkick.callbacks_value == :bulk
105
- super
106
- else
107
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
108
- super
109
- end
110
- end
111
- end
112
- end
113
- end
114
-
115
- module IndexerWithInstrumentation
116
- def perform
117
- if Searchkick.callbacks_value == :bulk
118
- event = {
119
- name: "Bulk",
120
- count: queued_items.size
121
- }
122
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
123
- super
124
- end
125
- else
126
- super
127
- end
128
- end
129
- end
130
-
131
- module SearchkickWithInstrumentation
132
- def multi_search(searches, **options)
133
- event = {
134
- name: "Multi Search",
135
- body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
136
- }
137
- ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
138
- super
139
- end
140
- end
141
- end
142
-
143
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/log_subscriber.rb
144
- class LogSubscriber < ActiveSupport::LogSubscriber
145
- def self.runtime=(value)
146
- Thread.current[:searchkick_runtime] = value
147
- end
148
-
149
- def self.runtime
150
- Thread.current[:searchkick_runtime] ||= 0
151
- end
152
-
153
- def self.reset_runtime
154
- rt = runtime
155
- self.runtime = 0
156
- rt
157
- end
158
-
159
- def search(event)
160
- self.class.runtime += event.duration
161
- return unless logger.debug?
162
-
163
- payload = event.payload
164
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
165
- type = payload[:query][:type]
166
- index = payload[:query][:index].is_a?(Array) ? payload[:query][:index].join(",") : payload[:query][:index]
167
-
168
- # no easy way to tell which host the client will use
169
- host = Searchkick.client.transport.hosts.first
170
- 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}'"
171
- end
172
-
173
- def request(event)
174
- self.class.runtime += event.duration
175
- return unless logger.debug?
176
-
177
- payload = event.payload
178
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
179
-
180
- debug " #{color(name, YELLOW, true)} #{payload.except(:name).to_json}"
181
- end
182
-
183
- def multi_search(event)
184
- self.class.runtime += event.duration
185
- return unless logger.debug?
186
-
187
- payload = event.payload
188
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
189
-
190
- # no easy way to tell which host the client will use
191
- host = Searchkick.client.transport.hosts.first
192
- debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/_msearch?pretty -d '#{payload[:body]}'"
193
- end
194
- end
195
-
196
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/railties/controller_runtime.rb
197
- module ControllerRuntime
198
- extend ActiveSupport::Concern
199
-
200
- protected
201
-
202
- attr_internal :searchkick_runtime
203
-
204
- def process_action(action, *args)
205
- # We also need to reset the runtime before each action
206
- # because of queries in middleware or in cases we are streaming
207
- # and it won't be cleaned up by the method below.
208
- Searchkick::LogSubscriber.reset_runtime
209
- super
210
- end
211
-
212
- def cleanup_view_runtime
213
- searchkick_rt_before_render = Searchkick::LogSubscriber.reset_runtime
214
- runtime = super
215
- searchkick_rt_after_render = Searchkick::LogSubscriber.reset_runtime
216
- self.searchkick_runtime = searchkick_rt_before_render + searchkick_rt_after_render
217
- runtime - searchkick_rt_after_render
218
- end
219
-
220
- def append_info_to_payload(payload)
221
- super
222
- payload[:searchkick_runtime] = (searchkick_runtime || 0) + Searchkick::LogSubscriber.reset_runtime
223
- end
224
-
225
- module ClassMethods
226
- def log_process_action(payload)
227
- messages = super
228
- runtime = payload[:searchkick_runtime]
229
- messages << ("Searchkick: %.1fms" % runtime.to_f) if runtime.to_f > 0
230
- messages
231
- end
232
- end
233
- end
234
- end
235
- Searchkick::Query.send(:prepend, Searchkick::QueryWithInstrumentation)
236
- Searchkick::Index.send(:prepend, Searchkick::IndexWithInstrumentation)
237
- Searchkick::Indexer.send(:prepend, Searchkick::IndexerWithInstrumentation)
238
- Searchkick.singleton_class.send(:prepend, Searchkick::SearchkickWithInstrumentation)
239
- Searchkick::LogSubscriber.attach_to :searchkick
240
- ActiveSupport.on_load(:action_controller) do
241
- include Searchkick::ControllerRuntime
242
- end
@@ -1,33 +0,0 @@
1
- require "rake"
2
-
3
- namespace :searchkick do
4
- desc "reindex model"
5
- task reindex: :environment do
6
- if ENV["CLASS"]
7
- klass = ENV["CLASS"].constantize rescue nil
8
- if klass
9
- klass.reindex
10
- else
11
- abort "Could not find class: #{ENV['CLASS']}"
12
- end
13
- else
14
- abort "USAGE: rake searchkick:reindex CLASS=Product"
15
- end
16
- end
17
-
18
- if defined?(Rails)
19
-
20
- namespace :reindex do
21
- desc "reindex all models"
22
- task all: :environment do
23
- Rails.application.eager_load!
24
- Searchkick.models.each do |model|
25
- puts "Reindexing #{model.name}..."
26
- model.reindex
27
- end
28
- puts "Reindex complete"
29
- end
30
- end
31
-
32
- end
33
- end
data/searchkick.gemspec DELETED
@@ -1,28 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "searchkick/version"
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "searchkick"
8
- spec.version = Searchkick::VERSION
9
- spec.authors = ["Andrew Kane"]
10
- spec.email = ["andrew@chartkick.com"]
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
- spec.homepage = "https://github.com/ankane/searchkick"
14
- spec.license = "MIT"
15
-
16
- spec.files = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features|benchmark)/})
19
- spec.require_paths = ["lib"]
20
-
21
- spec.add_dependency "activemodel", ">= 4.1"
22
- spec.add_dependency "elasticsearch", ">= 1"
23
- spec.add_dependency "hashie"
24
-
25
- spec.add_development_dependency "bundler"
26
- spec.add_development_dependency "rake"
27
- spec.add_development_dependency "minitest"
28
- end
data/test/aggs_test.rb DELETED
@@ -1,197 +0,0 @@
1
- require_relative "test_helper"
2
-
3
- class AggsTest < Minitest::Test
4
- def setup
5
- super
6
- store [
7
- {name: "Product Show", latitude: 37.7833, longitude: 12.4167, store_id: 1, in_stock: true, color: "blue", price: 21, created_at: 2.days.ago},
8
- {name: "Product Hide", latitude: 29.4167, longitude: -98.5000, store_id: 2, in_stock: false, color: "green", price: 25, created_at: 2.days.from_now},
9
- {name: "Product B", latitude: 43.9333, longitude: -122.4667, store_id: 2, in_stock: false, color: "red", price: 5, created_at: Time.now},
10
- {name: "Foo", latitude: 43.9333, longitude: 12.4667, store_id: 3, in_stock: false, color: "yellow", price: 15, created_at: Time.now}
11
- ]
12
- end
13
-
14
- def test_basic
15
- assert_equal ({1 => 1, 2 => 2}), store_agg(aggs: [:store_id])
16
- end
17
-
18
- def test_where
19
- assert_equal ({1 => 1}), store_agg(aggs: {store_id: {where: {in_stock: true}}})
20
- end
21
-
22
- def test_order
23
- agg = Product.search("Product", aggs: {color: {order: {"_term" => "desc"}}}).aggs["color"]
24
- assert_equal %w(red green blue), agg["buckets"].map { |b| b["key"] }
25
- end
26
-
27
- def test_field
28
- assert_equal ({1 => 1, 2 => 2}), store_agg(aggs: {store_id: {}})
29
- assert_equal ({1 => 1, 2 => 2}), store_agg(aggs: {store_id: {field: "store_id"}})
30
- assert_equal ({1 => 1, 2 => 2}), store_agg({aggs: {store_id_new: {field: "store_id"}}}, "store_id_new")
31
- end
32
-
33
- def test_min_doc_count
34
- assert_equal ({2 => 2}), store_agg(aggs: {store_id: {min_doc_count: 2}})
35
- end
36
-
37
- def test_no_aggs
38
- assert_nil Product.search("*").aggs
39
- end
40
-
41
- def test_limit
42
- agg = Product.search("Product", aggs: {store_id: {limit: 1}}).aggs["store_id"]
43
- assert_equal 1, agg["buckets"].size
44
- # assert_equal 3, agg["doc_count"]
45
- assert_equal(1, agg["sum_other_doc_count"])
46
- end
47
-
48
- def test_ranges
49
- price_ranges = [{to: 10}, {from: 10, to: 20}, {from: 20}]
50
- agg = Product.search("Product", aggs: {price: {ranges: price_ranges}}).aggs["price"]
51
-
52
- assert_equal 3, agg["buckets"].size
53
- assert_equal 10.0, agg["buckets"][0]["to"]
54
- assert_equal 20.0, agg["buckets"][2]["from"]
55
- assert_equal 1, agg["buckets"][0]["doc_count"]
56
- assert_equal 0, agg["buckets"][1]["doc_count"]
57
- assert_equal 2, agg["buckets"][2]["doc_count"]
58
- end
59
-
60
- def test_date_ranges
61
- ranges = [{to: 1.day.ago}, {from: 1.day.ago, to: 1.day.from_now}, {from: 1.day.from_now}]
62
- agg = Product.search("Product", aggs: {created_at: {date_ranges: ranges}}).aggs["created_at"]
63
-
64
- assert_equal 1, agg["buckets"][0]["doc_count"]
65
- assert_equal 1, agg["buckets"][1]["doc_count"]
66
- assert_equal 1, agg["buckets"][2]["doc_count"]
67
- end
68
-
69
- def test_query_where
70
- assert_equal ({1 => 1}), store_agg(where: {in_stock: true}, aggs: [:store_id])
71
- end
72
-
73
- def test_two_wheres
74
- assert_equal ({2 => 1}), store_agg(where: {color: "red"}, aggs: {store_id: {where: {in_stock: false}}})
75
- end
76
-
77
- def test_where_override
78
- assert_equal ({}), store_agg(where: {color: "red"}, aggs: {store_id: {where: {in_stock: false, color: "blue"}}})
79
- assert_equal ({2 => 1}), store_agg(where: {color: "blue"}, aggs: {store_id: {where: {in_stock: false, color: "red"}}})
80
- end
81
-
82
- def test_skip
83
- assert_equal ({1 => 1, 2 => 2}), store_agg(where: {store_id: 2}, aggs: [:store_id])
84
- end
85
-
86
- def test_skip_complex
87
- assert_equal ({1 => 1, 2 => 1}), store_agg(where: {store_id: 2, price: {gt: 5}}, aggs: [:store_id])
88
- end
89
-
90
- def test_multiple
91
- assert_equal ({"store_id" => {1 => 1, 2 => 2}, "color" => {"blue" => 1, "green" => 1, "red" => 1}}), store_multiple_aggs(aggs: [:store_id, :color])
92
- end
93
-
94
- def test_smart_aggs_false
95
- assert_equal ({2 => 2}), store_agg(where: {color: "red"}, aggs: {store_id: {where: {in_stock: false}}}, smart_aggs: false)
96
- assert_equal ({2 => 2}), store_agg(where: {color: "blue"}, aggs: {store_id: {where: {in_stock: false}}}, smart_aggs: false)
97
- end
98
-
99
- def test_aggs_group_by_date
100
- store [{name: "Old Product", created_at: 3.years.ago}]
101
- products =
102
- Product.search("Product", {
103
- where: {
104
- created_at: {lt: Time.now}
105
- },
106
- aggs: {
107
- products_per_year: {
108
- date_histogram: {
109
- field: :created_at,
110
- interval: :year
111
- }
112
- }
113
- }
114
- })
115
-
116
- assert_equal 4, products.aggs["products_per_year"]["buckets"].size
117
- end
118
-
119
- def test_aggs_avg
120
- products =
121
- Product.search("*", {
122
- aggs: {
123
- avg_price: {
124
- avg: {
125
- field: :price
126
- }
127
- }
128
- }
129
- })
130
- assert_equal 16.5, products.aggs["avg_price"]["value"]
131
- end
132
-
133
- def test_aggs_cardinality
134
- products =
135
- Product.search("*", {
136
- aggs: {
137
- total_stores: {
138
- cardinality: {
139
- field: :store_id
140
- }
141
- }
142
- }
143
- })
144
- assert_equal 3, products.aggs["total_stores"]["value"]
145
- end
146
-
147
- def test_aggs_min_max
148
- products =
149
- Product.search("*", {
150
- aggs: {
151
- min_price: {
152
- min: {
153
- field: :price
154
- }
155
- },
156
- max_price: {
157
- max: {
158
- field: :price
159
- }
160
- }
161
- }
162
- })
163
- assert_equal 5, products.aggs["min_price"]["value"]
164
- assert_equal 25, products.aggs["max_price"]["value"]
165
- end
166
-
167
- def test_aggs_sum
168
- products =
169
- Product.search("*", {
170
- aggs: {
171
- sum_price: {
172
- sum: {
173
- field: :price
174
- }
175
- }
176
- }
177
- })
178
- assert_equal 66, products.aggs["sum_price"]["value"]
179
- end
180
-
181
- protected
182
-
183
- def buckets_as_hash(agg)
184
- Hash[agg["buckets"].map { |v| [v["key"], v["doc_count"]] }]
185
- end
186
-
187
- def store_agg(options, agg_key = "store_id")
188
- buckets = Product.search("Product", options).aggs[agg_key]
189
- buckets_as_hash(buckets)
190
- end
191
-
192
- def store_multiple_aggs(options)
193
- Hash[Product.search("Product", options).aggs.map do |field, filtered_agg|
194
- [field, buckets_as_hash(filtered_agg)]
195
- end]
196
- end
197
- end
@@ -1,75 +0,0 @@
1
- require_relative "test_helper"
2
-
3
- class AutocompleteTest < Minitest::Test
4
- def test_autocomplete
5
- store_names ["Hummus"]
6
- assert_search "hum", ["Hummus"], match: :text_start
7
- end
8
-
9
- def test_autocomplete_two_words
10
- store_names ["Organic Hummus"]
11
- assert_search "hum", [], match: :text_start
12
- end
13
-
14
- def test_autocomplete_fields
15
- store_names ["Hummus"]
16
- assert_search "hum", ["Hummus"], match: :text_start, fields: [:name]
17
- end
18
-
19
- def test_text_start
20
- store_names ["Where in the World is Carmen San Diego"]
21
- assert_search "where in the world is", ["Where in the World is Carmen San Diego"], fields: [{name: :text_start}]
22
- assert_search "in the world", [], fields: [{name: :text_start}]
23
- end
24
-
25
- def test_text_middle
26
- store_names ["Where in the World is Carmen San Diego"]
27
- assert_search "where in the world is", ["Where in the World is Carmen San Diego"], fields: [{name: :text_middle}]
28
- assert_search "n the wor", ["Where in the World is Carmen San Diego"], fields: [{name: :text_middle}]
29
- assert_search "men san diego", ["Where in the World is Carmen San Diego"], fields: [{name: :text_middle}]
30
- assert_search "world carmen", [], fields: [{name: :text_middle}]
31
- end
32
-
33
- def test_text_end
34
- store_names ["Where in the World is Carmen San Diego"]
35
- assert_search "men san diego", ["Where in the World is Carmen San Diego"], fields: [{name: :text_end}]
36
- assert_search "carmen san", [], fields: [{name: :text_end}]
37
- end
38
-
39
- def test_word_start
40
- store_names ["Where in the World is Carmen San Diego"]
41
- assert_search "car san wor", ["Where in the World is Carmen San Diego"], fields: [{name: :word_start}]
42
- end
43
-
44
- def test_word_middle
45
- store_names ["Where in the World is Carmen San Diego"]
46
- assert_search "orl", ["Where in the World is Carmen San Diego"], fields: [{name: :word_middle}]
47
- end
48
-
49
- def test_word_end
50
- store_names ["Where in the World is Carmen San Diego"]
51
- assert_search "rld men ego", ["Where in the World is Carmen San Diego"], fields: [{name: :word_end}]
52
- end
53
-
54
- def test_word_start_multiple_words
55
- store_names ["Dark Grey", "Dark Blue"]
56
- assert_search "dark grey", ["Dark Grey"], fields: [{name: :word_start}]
57
- end
58
-
59
- def test_word_start_exact
60
- store_names ["Back Scratcher", "Backpack"]
61
- assert_order "back", ["Back Scratcher", "Backpack"], fields: [{name: :word_start}]
62
- end
63
-
64
- def test_word_start_exact_martin
65
- store_names ["Martina", "Martin"]
66
- assert_order "martin", ["Martin", "Martina"], fields: [{name: :word_start}]
67
- end
68
-
69
- # TODO find a better place
70
-
71
- def test_exact
72
- store_names ["hi@example.org"]
73
- assert_search "hi@example.org", ["hi@example.org"], fields: [{name: :exact}]
74
- end
75
- end