searchkick-hooopo 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +35 -0
  4. data/CHANGELOG.md +491 -0
  5. data/Gemfile +12 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +1908 -0
  8. data/Rakefile +20 -0
  9. data/benchmark/Gemfile +23 -0
  10. data/benchmark/benchmark.rb +97 -0
  11. data/lib/searchkick/bulk_reindex_job.rb +17 -0
  12. data/lib/searchkick/index.rb +500 -0
  13. data/lib/searchkick/index_options.rb +333 -0
  14. data/lib/searchkick/indexer.rb +28 -0
  15. data/lib/searchkick/logging.rb +242 -0
  16. data/lib/searchkick/middleware.rb +12 -0
  17. data/lib/searchkick/model.rb +156 -0
  18. data/lib/searchkick/process_batch_job.rb +23 -0
  19. data/lib/searchkick/process_queue_job.rb +23 -0
  20. data/lib/searchkick/query.rb +901 -0
  21. data/lib/searchkick/reindex_queue.rb +38 -0
  22. data/lib/searchkick/reindex_v2_job.rb +39 -0
  23. data/lib/searchkick/results.rb +216 -0
  24. data/lib/searchkick/tasks.rb +33 -0
  25. data/lib/searchkick/version.rb +3 -0
  26. data/lib/searchkick.rb +215 -0
  27. data/searchkick.gemspec +28 -0
  28. data/test/aggs_test.rb +197 -0
  29. data/test/autocomplete_test.rb +75 -0
  30. data/test/boost_test.rb +175 -0
  31. data/test/callbacks_test.rb +59 -0
  32. data/test/ci/before_install.sh +17 -0
  33. data/test/errors_test.rb +19 -0
  34. data/test/gemfiles/activerecord31.gemfile +7 -0
  35. data/test/gemfiles/activerecord32.gemfile +7 -0
  36. data/test/gemfiles/activerecord40.gemfile +8 -0
  37. data/test/gemfiles/activerecord41.gemfile +8 -0
  38. data/test/gemfiles/activerecord42.gemfile +7 -0
  39. data/test/gemfiles/activerecord50.gemfile +7 -0
  40. data/test/gemfiles/apartment.gemfile +8 -0
  41. data/test/gemfiles/cequel.gemfile +8 -0
  42. data/test/gemfiles/mongoid2.gemfile +7 -0
  43. data/test/gemfiles/mongoid3.gemfile +6 -0
  44. data/test/gemfiles/mongoid4.gemfile +7 -0
  45. data/test/gemfiles/mongoid5.gemfile +7 -0
  46. data/test/gemfiles/mongoid6.gemfile +8 -0
  47. data/test/gemfiles/nobrainer.gemfile +8 -0
  48. data/test/gemfiles/parallel_tests.gemfile +8 -0
  49. data/test/geo_shape_test.rb +172 -0
  50. data/test/highlight_test.rb +78 -0
  51. data/test/index_test.rb +153 -0
  52. data/test/inheritance_test.rb +83 -0
  53. data/test/marshal_test.rb +8 -0
  54. data/test/match_test.rb +276 -0
  55. data/test/misspellings_test.rb +56 -0
  56. data/test/model_test.rb +42 -0
  57. data/test/multi_search_test.rb +22 -0
  58. data/test/multi_tenancy_test.rb +22 -0
  59. data/test/order_test.rb +46 -0
  60. data/test/pagination_test.rb +53 -0
  61. data/test/partial_reindex_test.rb +58 -0
  62. data/test/query_test.rb +35 -0
  63. data/test/records_test.rb +10 -0
  64. data/test/reindex_test.rb +52 -0
  65. data/test/reindex_v2_job_test.rb +32 -0
  66. data/test/routing_test.rb +23 -0
  67. data/test/should_index_test.rb +32 -0
  68. data/test/similar_test.rb +28 -0
  69. data/test/sql_test.rb +198 -0
  70. data/test/suggest_test.rb +85 -0
  71. data/test/synonyms_test.rb +67 -0
  72. data/test/test_helper.rb +527 -0
  73. data/test/where_test.rb +223 -0
  74. metadata +250 -0
@@ -0,0 +1,527 @@
1
+ require "bundler/setup"
2
+ Bundler.require(:default)
3
+ require "minitest/autorun"
4
+ require "minitest/pride"
5
+ require "logger"
6
+ require "active_support/core_ext" if defined?(NoBrainer)
7
+ require "active_support/notifications"
8
+
9
+ Searchkick.index_suffix = ENV["TEST_ENV_NUMBER"]
10
+
11
+ ENV["RACK_ENV"] = "test"
12
+
13
+ Minitest::Test = Minitest::Unit::TestCase unless defined?(Minitest::Test)
14
+
15
+ if !defined?(ParallelTests) || ParallelTests.first_process?
16
+ File.delete("elasticsearch.log") if File.exist?("elasticsearch.log")
17
+ end
18
+
19
+ Searchkick.client.transport.logger = Logger.new("elasticsearch.log")
20
+ Searchkick.search_timeout = 5
21
+
22
+ if defined?(Redis)
23
+ if defined?(ConnectionPool)
24
+ Searchkick.redis = ConnectionPool.new { Redis.new }
25
+ else
26
+ Searchkick.redis = Redis.new
27
+ end
28
+ end
29
+
30
+ puts "Running against Elasticsearch #{Searchkick.server_version}"
31
+
32
+ I18n.config.enforce_available_locales = true
33
+
34
+ if defined?(ActiveJob)
35
+ ActiveJob::Base.logger = nil
36
+ ActiveJob::Base.queue_adapter = :inline
37
+ end
38
+
39
+ ActiveSupport::LogSubscriber.logger = ActiveSupport::Logger.new(STDOUT) if ENV["NOTIFICATIONS"]
40
+
41
+ def elasticsearch_below50?
42
+ Searchkick.server_below?("5.0.0-alpha1")
43
+ end
44
+
45
+ def elasticsearch_below22?
46
+ Searchkick.server_below?("2.2.0")
47
+ end
48
+
49
+ def nobrainer?
50
+ defined?(NoBrainer)
51
+ end
52
+
53
+ def cequel?
54
+ defined?(Cequel)
55
+ end
56
+
57
+ if defined?(Mongoid)
58
+ Mongoid.logger.level = Logger::INFO
59
+ Mongo::Logger.logger.level = Logger::INFO if defined?(Mongo::Logger)
60
+
61
+ Mongoid.configure do |config|
62
+ config.connect_to "searchkick_test"
63
+ end
64
+
65
+ class Product
66
+ include Mongoid::Document
67
+ include Mongoid::Timestamps
68
+
69
+ field :name
70
+ field :store_id, type: Integer
71
+ field :in_stock, type: Boolean
72
+ field :backordered, type: Boolean
73
+ field :orders_count, type: Integer
74
+ field :found_rate, type: BigDecimal
75
+ field :price, type: Integer
76
+ field :color
77
+ field :latitude, type: BigDecimal
78
+ field :longitude, type: BigDecimal
79
+ field :description
80
+ field :alt_description
81
+ end
82
+
83
+ class Store
84
+ include Mongoid::Document
85
+ has_many :products
86
+
87
+ field :name
88
+ end
89
+
90
+ class Region
91
+ include Mongoid::Document
92
+
93
+ field :name
94
+ field :text
95
+ end
96
+
97
+ class Speaker
98
+ include Mongoid::Document
99
+
100
+ field :name
101
+ end
102
+
103
+ class Animal
104
+ include Mongoid::Document
105
+
106
+ field :name
107
+ end
108
+
109
+ class Dog < Animal
110
+ end
111
+
112
+ class Cat < Animal
113
+ end
114
+ elsif defined?(NoBrainer)
115
+ NoBrainer.configure do |config|
116
+ config.app_name = :searchkick
117
+ config.environment = :test
118
+ end
119
+
120
+ class Product
121
+ include NoBrainer::Document
122
+ include NoBrainer::Document::Timestamps
123
+
124
+ field :id, type: Object
125
+ field :name, type: String
126
+ field :in_stock, type: Boolean
127
+ field :backordered, type: Boolean
128
+ field :orders_count, type: Integer
129
+ field :found_rate
130
+ field :price, type: Integer
131
+ field :color, type: String
132
+ field :latitude
133
+ field :longitude
134
+ field :description, type: String
135
+ field :alt_description, type: String
136
+
137
+ belongs_to :store, validates: false
138
+ end
139
+
140
+ class Store
141
+ include NoBrainer::Document
142
+
143
+ field :id, type: Object
144
+ field :name, type: String
145
+ end
146
+
147
+ class Region
148
+ include NoBrainer::Document
149
+
150
+ field :id, type: Object
151
+ field :name, type: String
152
+ field :text, type: Text
153
+ end
154
+
155
+ class Speaker
156
+ include NoBrainer::Document
157
+
158
+ field :id, type: Object
159
+ field :name, type: String
160
+ end
161
+
162
+ class Animal
163
+ include NoBrainer::Document
164
+
165
+ field :id, type: Object
166
+ field :name, type: String
167
+ end
168
+
169
+ class Dog < Animal
170
+ end
171
+
172
+ class Cat < Animal
173
+ end
174
+ elsif defined?(Cequel)
175
+ cequel =
176
+ Cequel.connect(
177
+ host: "127.0.0.1",
178
+ port: 9042,
179
+ keyspace: "searchkick_test",
180
+ default_consistency: :all
181
+ )
182
+ # cequel.logger = ActiveSupport::Logger.new(STDOUT)
183
+ cequel.schema.drop! if cequel.schema.exists?
184
+ cequel.schema.create!
185
+ Cequel::Record.connection = cequel
186
+
187
+ class Product
188
+ include Cequel::Record
189
+
190
+ key :id, :uuid, auto: true
191
+ column :name, :text, index: true
192
+ column :store_id, :int
193
+ column :in_stock, :boolean
194
+ column :backordered, :boolean
195
+ column :orders_count, :int
196
+ column :found_rate, :decimal
197
+ column :price, :int
198
+ column :color, :text
199
+ column :latitude, :decimal
200
+ column :longitude, :decimal
201
+ column :description, :text
202
+ column :alt_description, :text
203
+ column :created_at, :timestamp
204
+ end
205
+
206
+ class Store
207
+ include Cequel::Record
208
+
209
+ key :id, :timeuuid, auto: true
210
+ column :name, :text
211
+
212
+ # has issue with id serialization
213
+ def search_data
214
+ {
215
+ name: name
216
+ }
217
+ end
218
+ end
219
+
220
+ class Region
221
+ include Cequel::Record
222
+
223
+ key :id, :timeuuid, auto: true
224
+ column :name, :text
225
+ column :text, :text
226
+ end
227
+
228
+ class Speaker
229
+ include Cequel::Record
230
+
231
+ key :id, :timeuuid, auto: true
232
+ column :name, :text
233
+ end
234
+
235
+ class Animal
236
+ include Cequel::Record
237
+
238
+ key :id, :timeuuid, auto: true
239
+ column :name, :text
240
+
241
+ # has issue with id serialization
242
+ def search_data
243
+ {
244
+ name: name
245
+ }
246
+ end
247
+ end
248
+
249
+ class Dog < Animal
250
+ end
251
+
252
+ class Cat < Animal
253
+ end
254
+
255
+ [Product, Store, Region, Speaker, Animal].each(&:synchronize_schema)
256
+ else
257
+ require "active_record"
258
+
259
+ # for debugging
260
+ # ActiveRecord::Base.logger = Logger.new(STDOUT)
261
+
262
+ # rails does this in activerecord/lib/active_record/railtie.rb
263
+ ActiveRecord::Base.default_timezone = :utc
264
+ ActiveRecord::Base.time_zone_aware_attributes = true
265
+
266
+ # migrations
267
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
268
+
269
+ ActiveRecord::Base.raise_in_transactional_callbacks = true if ActiveRecord::VERSION::STRING.start_with?("4.2.")
270
+
271
+ if defined?(Apartment)
272
+ class Rails
273
+ def self.env
274
+ ENV["RACK_ENV"]
275
+ end
276
+ end
277
+
278
+ tenants = ["tenant1", "tenant2"]
279
+ Apartment.configure do |config|
280
+ config.tenant_names = tenants
281
+ config.database_schema_file = false
282
+ config.excluded_models = ["Product", "Store", "Animal", "Dog", "Cat"]
283
+ end
284
+
285
+ class Tenant < ActiveRecord::Base
286
+ searchkick index_prefix: -> { Apartment::Tenant.current }
287
+ end
288
+
289
+ tenants.each do |tenant|
290
+ begin
291
+ Apartment::Tenant.create(tenant)
292
+ rescue Apartment::TenantExists
293
+ # do nothing
294
+ end
295
+ Apartment::Tenant.switch!(tenant)
296
+
297
+ ActiveRecord::Migration.create_table :tenants, force: true do |t|
298
+ t.string :name
299
+ t.timestamps null: true
300
+ end
301
+
302
+ Tenant.reindex
303
+ end
304
+
305
+ Apartment::Tenant.reset
306
+ end
307
+
308
+ ActiveRecord::Migration.create_table :products do |t|
309
+ t.string :name
310
+ t.integer :store_id
311
+ t.boolean :in_stock
312
+ t.boolean :backordered
313
+ t.integer :orders_count
314
+ t.decimal :found_rate
315
+ t.integer :price
316
+ t.string :color
317
+ t.decimal :latitude, precision: 10, scale: 7
318
+ t.decimal :longitude, precision: 10, scale: 7
319
+ t.text :description
320
+ t.text :alt_description
321
+ t.timestamps null: true
322
+ end
323
+
324
+ ActiveRecord::Migration.create_table :stores do |t|
325
+ t.string :name
326
+ end
327
+
328
+ ActiveRecord::Migration.create_table :regions do |t|
329
+ t.string :name
330
+ t.text :text
331
+ end
332
+
333
+ ActiveRecord::Migration.create_table :speakers do |t|
334
+ t.string :name
335
+ end
336
+
337
+ ActiveRecord::Migration.create_table :animals do |t|
338
+ t.string :name
339
+ t.string :type
340
+ end
341
+
342
+ class Product < ActiveRecord::Base
343
+ belongs_to :store
344
+ end
345
+
346
+ class Store < ActiveRecord::Base
347
+ has_many :products
348
+ end
349
+
350
+ class Region < ActiveRecord::Base
351
+ end
352
+
353
+ class Speaker < ActiveRecord::Base
354
+ end
355
+
356
+ class Animal < ActiveRecord::Base
357
+ end
358
+
359
+ class Dog < Animal
360
+ end
361
+
362
+ class Cat < Animal
363
+ end
364
+ end
365
+
366
+ class Product
367
+ searchkick \
368
+ synonyms: [
369
+ ["clorox", "bleach"],
370
+ ["scallion", "greenonion"],
371
+ ["saranwrap", "plasticwrap"],
372
+ ["qtip", "cottonswab"],
373
+ ["burger", "hamburger"],
374
+ ["bandaid", "bandag"],
375
+ ["UPPERCASE", "lowercase"],
376
+ "lightbulb => led,lightbulb",
377
+ "lightbulb => halogenlamp"
378
+ ],
379
+ suggest: [:name, :color],
380
+ conversions: [:conversions],
381
+ locations: [:location, :multiple_locations],
382
+ text_start: [:name],
383
+ text_middle: [:name],
384
+ text_end: [:name],
385
+ word_start: [:name],
386
+ word_middle: [:name],
387
+ word_end: [:name],
388
+ highlight: [:name],
389
+ searchable: [:name, :color],
390
+ filterable: [:name, :color, :description],
391
+ similarity: "BM25",
392
+ match: ENV["MATCH"] ? ENV["MATCH"].to_sym : nil
393
+
394
+ attr_accessor :conversions, :user_ids, :aisle, :details
395
+
396
+ def search_data
397
+ serializable_hash.except("id").merge(
398
+ conversions: conversions,
399
+ user_ids: user_ids,
400
+ location: {lat: latitude, lon: longitude},
401
+ multiple_locations: [{lat: latitude, lon: longitude}, {lat: 0, lon: 0}],
402
+ aisle: aisle,
403
+ details: details
404
+ )
405
+ end
406
+
407
+ def should_index?
408
+ name != "DO NOT INDEX"
409
+ end
410
+
411
+ def search_name
412
+ {
413
+ name: name
414
+ }
415
+ end
416
+ end
417
+
418
+ class Store
419
+ searchkick \
420
+ routing: true,
421
+ merge_mappings: true,
422
+ mappings: {
423
+ store: {
424
+ properties: {
425
+ name: elasticsearch_below50? ? {type: "string", analyzer: "keyword"} : {type: "keyword"}
426
+ }
427
+ }
428
+ }
429
+
430
+ def search_document_id
431
+ id
432
+ end
433
+
434
+ def search_routing
435
+ name
436
+ end
437
+ end
438
+
439
+ class Region
440
+ searchkick \
441
+ geo_shape: {
442
+ territory: {tree: "quadtree", precision: "10km"}
443
+ }
444
+
445
+ attr_accessor :territory
446
+
447
+ def search_data
448
+ {
449
+ name: name,
450
+ text: text,
451
+ territory: territory
452
+ }
453
+ end
454
+ end
455
+
456
+ class Speaker
457
+ searchkick \
458
+ conversions: ["conversions_a", "conversions_b"]
459
+
460
+ attr_accessor :conversions_a, :conversions_b, :aisle
461
+
462
+ def search_data
463
+ serializable_hash.except("id").merge(
464
+ conversions_a: conversions_a,
465
+ conversions_b: conversions_b,
466
+ aisle: aisle
467
+ )
468
+ end
469
+ end
470
+
471
+ class Animal
472
+ searchkick \
473
+ text_start: [:name],
474
+ suggest: [:name],
475
+ index_name: -> { "#{name.tableize}-#{Date.today.year}#{Searchkick.index_suffix}" },
476
+ callbacks: defined?(ActiveJob) ? :async : true
477
+ # wordnet: true
478
+ end
479
+
480
+ Product.searchkick_index.delete if Product.searchkick_index.exists?
481
+ Product.reindex
482
+ Product.reindex # run twice for both index paths
483
+ Product.create!(name: "Set mapping")
484
+
485
+ Store.reindex
486
+ Animal.reindex
487
+ Speaker.reindex
488
+ Region.reindex
489
+
490
+ class Minitest::Test
491
+ def setup
492
+ Product.destroy_all
493
+ Store.destroy_all
494
+ Animal.destroy_all
495
+ Speaker.destroy_all
496
+ end
497
+
498
+ protected
499
+
500
+ def store(documents, klass = Product)
501
+ documents.shuffle.each do |document|
502
+ klass.create!(document)
503
+ end
504
+ klass.searchkick_index.refresh
505
+ end
506
+
507
+ def store_names(names, klass = Product)
508
+ store names.map { |name| {name: name} }, klass
509
+ end
510
+
511
+ # no order
512
+ def assert_search(term, expected, options = {}, klass = Product)
513
+ assert_equal expected.sort, klass.search(term, options).map(&:name).sort
514
+ end
515
+
516
+ def assert_order(term, expected, options = {}, klass = Product)
517
+ assert_equal expected, klass.search(term, options).map(&:name)
518
+ end
519
+
520
+ def assert_equal_scores(term, options = {}, klass = Product)
521
+ assert_equal 1, klass.search(term, options).hits.map { |a| a["_score"] }.uniq.size
522
+ end
523
+
524
+ def assert_first(term, expected, options = {}, klass = Product)
525
+ assert_equal expected, klass.search(term, options).map(&:name).first
526
+ end
527
+ end