searchkick_evichat 0.0.2

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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE.md +7 -0
  3. data/.gitignore +22 -0
  4. data/.travis.yml +33 -0
  5. data/CHANGELOG.md +571 -0
  6. data/CONTRIBUTING.md +51 -0
  7. data/Gemfile +16 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +1895 -0
  10. data/Rakefile +16 -0
  11. data/benchmark/Gemfile +24 -0
  12. data/benchmark/index.rb +99 -0
  13. data/benchmark/search.rb +48 -0
  14. data/docs/Searchkick-3-Upgrade.md +57 -0
  15. data/lib/searchkick.rb +255 -0
  16. data/lib/searchkick/bulk_indexer.rb +171 -0
  17. data/lib/searchkick/bulk_reindex_job.rb +27 -0
  18. data/lib/searchkick/hash_wrapper.rb +12 -0
  19. data/lib/searchkick/index.rb +331 -0
  20. data/lib/searchkick/index_options.rb +414 -0
  21. data/lib/searchkick/indexer.rb +28 -0
  22. data/lib/searchkick/logging.rb +243 -0
  23. data/lib/searchkick/middleware.rb +12 -0
  24. data/lib/searchkick/model.rb +105 -0
  25. data/lib/searchkick/multi_search.rb +41 -0
  26. data/lib/searchkick/process_batch_job.rb +23 -0
  27. data/lib/searchkick/process_queue_job.rb +23 -0
  28. data/lib/searchkick/query.rb +978 -0
  29. data/lib/searchkick/record_data.rb +128 -0
  30. data/lib/searchkick/record_indexer.rb +59 -0
  31. data/lib/searchkick/reindex_queue.rb +38 -0
  32. data/lib/searchkick/reindex_v2_job.rb +36 -0
  33. data/lib/searchkick/results.rb +235 -0
  34. data/lib/searchkick/tasks.rb +29 -0
  35. data/lib/searchkick/version.rb +3 -0
  36. data/searchkick.gemspec +30 -0
  37. data/test/aggs_test.rb +217 -0
  38. data/test/autocomplete_test.rb +81 -0
  39. data/test/boost_test.rb +207 -0
  40. data/test/callbacks_test.rb +59 -0
  41. data/test/ci/before_install.sh +17 -0
  42. data/test/errors_test.rb +19 -0
  43. data/test/gemfiles/activerecord42.gemfile +7 -0
  44. data/test/gemfiles/activerecord50.gemfile +7 -0
  45. data/test/gemfiles/activerecord51.gemfile +7 -0
  46. data/test/gemfiles/apartment.gemfile +8 -0
  47. data/test/gemfiles/cequel.gemfile +8 -0
  48. data/test/gemfiles/mongoid5.gemfile +7 -0
  49. data/test/gemfiles/mongoid6.gemfile +12 -0
  50. data/test/gemfiles/nobrainer.gemfile +8 -0
  51. data/test/gemfiles/parallel_tests.gemfile +8 -0
  52. data/test/geo_shape_test.rb +171 -0
  53. data/test/highlight_test.rb +94 -0
  54. data/test/index_test.rb +165 -0
  55. data/test/inheritance_test.rb +82 -0
  56. data/test/language_test.rb +79 -0
  57. data/test/marshal_test.rb +13 -0
  58. data/test/match_test.rb +293 -0
  59. data/test/misspellings_test.rb +56 -0
  60. data/test/model_test.rb +40 -0
  61. data/test/multi_search_test.rb +37 -0
  62. data/test/multi_tenancy_test.rb +22 -0
  63. data/test/order_test.rb +40 -0
  64. data/test/pagination_test.rb +96 -0
  65. data/test/partial_reindex_test.rb +65 -0
  66. data/test/query_test.rb +43 -0
  67. data/test/reindex_test.rb +87 -0
  68. data/test/reindex_v2_job_test.rb +27 -0
  69. data/test/routing_test.rb +23 -0
  70. data/test/should_index_test.rb +32 -0
  71. data/test/similar_test.rb +28 -0
  72. data/test/sql_test.rb +190 -0
  73. data/test/suggest_test.rb +100 -0
  74. data/test/support/kaminari.yml +21 -0
  75. data/test/synonyms_test.rb +69 -0
  76. data/test/test_helper.rb +593 -0
  77. data/test/where_test.rb +249 -0
  78. metadata +250 -0
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ begin
5
+ require "parallel_tests/tasks"
6
+ require "shellwords"
7
+ rescue LoadError
8
+ # do nothing
9
+ end
10
+
11
+ task default: :test
12
+ Rake::TestTask.new do |t|
13
+ t.libs << "test"
14
+ t.pattern = "test/**/*_test.rb"
15
+ t.warning = false
16
+ end
@@ -0,0 +1,24 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in searchkick.gemspec
4
+ gemspec path: "../"
5
+
6
+ gem "sqlite3"
7
+ gem "pg"
8
+ gem "activerecord", "~> 5.1.0"
9
+ gem "activerecord-import"
10
+ gem "activejob"
11
+ gem "redis"
12
+ gem "sidekiq"
13
+
14
+ # performance
15
+ gem "typhoeus"
16
+ gem "oj"
17
+
18
+ # profiling
19
+ gem "ruby-prof"
20
+ gem "allocation_stats"
21
+ gem "get_process_mem"
22
+ gem "memory_profiler"
23
+ gem "allocation_tracer"
24
+ gem "benchmark-ips"
@@ -0,0 +1,99 @@
1
+ require "bundler/setup"
2
+ Bundler.require(:default)
3
+ require "active_record"
4
+ require "benchmark"
5
+ require "active_support/notifications"
6
+
7
+ ActiveSupport::Notifications.subscribe "request.searchkick" do |*args|
8
+ event = ActiveSupport::Notifications::Event.new(*args)
9
+ puts "Import: #{event.duration.round}ms"
10
+ end
11
+
12
+ ActiveJob::Base.queue_adapter = :sidekiq
13
+
14
+ Searchkick.redis = Redis.new
15
+
16
+ ActiveRecord::Base.default_timezone = :utc
17
+ ActiveRecord::Base.time_zone_aware_attributes = true
18
+ # ActiveRecord::Base.establish_connection adapter: "sqlite3", database: "/tmp/searchkick"
19
+ ActiveRecord::Base.establish_connection "postgresql://localhost/searchkick_demo_development"
20
+ # ActiveRecord::Base.logger = Logger.new(STDOUT)
21
+
22
+ ActiveJob::Base.logger = nil
23
+
24
+ class Product < ActiveRecord::Base
25
+ searchkick batch_size: 1000
26
+
27
+ def search_data
28
+ {
29
+ name: name,
30
+ color: color,
31
+ store_id: store_id
32
+ }
33
+ end
34
+ end
35
+
36
+ if ENV["SETUP"]
37
+ total_docs = 100000
38
+
39
+ ActiveRecord::Migration.create_table :products, force: :cascade do |t|
40
+ t.string :name
41
+ t.string :color
42
+ t.integer :store_id
43
+ end
44
+
45
+ Product.import ["name", "color", "store_id"], total_docs.times.map { |i| ["Product #{i}", ["red", "blue"].sample, rand(10)] }
46
+
47
+ puts "Imported"
48
+ end
49
+
50
+ result = nil
51
+ report = nil
52
+ stats = nil
53
+
54
+ Product.searchkick_index.delete rescue nil
55
+
56
+ GC.start
57
+ GC.disable
58
+ start_mem = GetProcessMem.new.mb
59
+
60
+ time =
61
+ Benchmark.realtime do
62
+ # result = RubyProf.profile do
63
+ # report = MemoryProfiler.report do
64
+ # stats = AllocationStats.trace do
65
+ reindex = Product.reindex #(async: true)
66
+ # p reindex
67
+ # end
68
+
69
+ # 60.times do |i|
70
+ # if reindex.is_a?(Hash)
71
+ # docs = Searchkick::Index.new(reindex[:index_name]).total_docs
72
+ # else
73
+ # docs = Product.searchkick_index.total_docs
74
+ # end
75
+ # puts "#{i}: #{docs}"
76
+ # if docs == total_docs
77
+ # break
78
+ # end
79
+ # p Searchkick.reindex_status(reindex[:index_name]) if reindex.is_a?(Hash)
80
+ # sleep(1)
81
+ # # Product.searchkick_index.refresh
82
+ # end
83
+ end
84
+
85
+ puts
86
+ puts "Time: #{time.round(1)}s"
87
+
88
+ if result
89
+ printer = RubyProf::GraphPrinter.new(result)
90
+ printer.print(STDOUT, min_percent: 5)
91
+ end
92
+
93
+ if report
94
+ puts report.pretty_print
95
+ end
96
+
97
+ if stats
98
+ puts result.allocations(alias_paths: true).group_by(:sourcefile, :class).to_text
99
+ end
@@ -0,0 +1,48 @@
1
+ require "bundler/setup"
2
+ Bundler.require(:default)
3
+ require "active_record"
4
+ require "benchmark/ips"
5
+
6
+ ActiveRecord::Base.default_timezone = :utc
7
+ ActiveRecord::Base.time_zone_aware_attributes = true
8
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: "/tmp/searchkick"
9
+
10
+ class Product < ActiveRecord::Base
11
+ searchkick batch_size: 1000
12
+
13
+ def search_data
14
+ {
15
+ name: name,
16
+ color: color,
17
+ store_id: store_id
18
+ }
19
+ end
20
+ end
21
+
22
+ if ENV["SETUP"]
23
+ total_docs = 1000000
24
+
25
+ ActiveRecord::Migration.create_table :products, force: :cascade do |t|
26
+ t.string :name
27
+ t.string :color
28
+ t.integer :store_id
29
+ end
30
+
31
+ Product.import ["name", "color", "store_id"], total_docs.times.map { |i| ["Product #{i}", ["red", "blue"].sample, rand(10)] }
32
+
33
+ puts "Imported"
34
+
35
+ Product.reindex
36
+
37
+ puts "Reindexed"
38
+ end
39
+
40
+ query = Product.search("product", fields: [:name], where: {color: "red", store_id: 5}, limit: 10000, load: false, execute: false)
41
+
42
+ require "pp"
43
+ pp query.body.as_json
44
+ puts
45
+
46
+ Benchmark.ips do |x|
47
+ x.report { query.dup.execute }
48
+ end
@@ -0,0 +1,57 @@
1
+ # Searchkick 3 Upgrade
2
+
3
+ ## Before You Upgrade
4
+
5
+ Searchkick 3 no longer uses types, since they are deprecated in Elasticsearch 6.
6
+
7
+ If you use inheritance, add to your parent model:
8
+
9
+ ```ruby
10
+ class Animal < ApplicationRecord
11
+ searchkick inheritance: true
12
+ end
13
+ ```
14
+
15
+ And do a full reindex before upgrading.
16
+
17
+ ## Upgrading
18
+
19
+ Update your Gemfile:
20
+
21
+ ```ruby
22
+ gem 'searchkick', '~> 3'
23
+ ```
24
+
25
+ And run:
26
+
27
+ ```sh
28
+ bundle update searchkick
29
+ ```
30
+
31
+ We recommend you don’t stem conversions anymore, so conversions for `pepper` don’t affect `peppers`, but if you want to keep the old behavior, use:
32
+
33
+ ```ruby
34
+ Searchkick.model_options = {
35
+ stem_conversions: true
36
+ }
37
+ ```
38
+
39
+ Searchkick 3 disables the `_all` field by default, since Elasticsearch 6 removes the ability to reindex with it. If you’re on Elasticsearch 5 and still need it, add to your model:
40
+
41
+ ```ruby
42
+ class Product < ApplicationRecord
43
+ searchkick _all: true
44
+ end
45
+ ```
46
+
47
+ If you use `record.reindex_async` or `record.reindex(async: true)`, replace it with:
48
+
49
+ ```ruby
50
+ record.reindex(mode: :async)
51
+ ```
52
+
53
+ If you use `log: true` with `boost_by`, replace it with `modifier: "ln2p"`.
54
+
55
+ If you use the `body` option and have warnings about incompatible options, remove them, as they now throw an `ArgumentError`.
56
+
57
+ Check out the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md) for the full list of changes.
@@ -0,0 +1,255 @@
1
+ require "active_model"
2
+ require "active_support/core_ext/hash/deep_merge"
3
+ require "elasticsearch"
4
+ require "hashie"
5
+
6
+ require "searchkick/bulk_indexer"
7
+ require "searchkick/index"
8
+ require "searchkick/indexer"
9
+ require "searchkick/hash_wrapper"
10
+ require "searchkick/middleware"
11
+ require "searchkick/model"
12
+ require "searchkick/multi_search"
13
+ require "searchkick/query"
14
+ require "searchkick/reindex_queue"
15
+ require "searchkick/record_data"
16
+ require "searchkick/record_indexer"
17
+ require "searchkick/results"
18
+ require "searchkick/version"
19
+
20
+ require "searchkick/logging" if defined?(ActiveSupport::Notifications)
21
+
22
+ begin
23
+ require "rake"
24
+ rescue LoadError
25
+ # do nothing
26
+ end
27
+ require "searchkick/tasks" if defined?(Rake)
28
+
29
+ begin
30
+ require "rake"
31
+ rescue LoadError
32
+ # do nothing
33
+ end
34
+ require "searchkick/tasks" if defined?(Rake)
35
+
36
+ # background jobs
37
+ begin
38
+ require "active_job"
39
+ rescue LoadError
40
+ # do nothing
41
+ end
42
+ if defined?(ActiveJob)
43
+ require "searchkick/bulk_reindex_job"
44
+ require "searchkick/process_batch_job"
45
+ require "searchkick/process_queue_job"
46
+ require "searchkick/reindex_v2_job"
47
+ end
48
+
49
+ module Searchkick
50
+ class Error < StandardError; end
51
+ class MissingIndexError < Error; end
52
+ class UnsupportedVersionError < Error; end
53
+ class InvalidQueryError < Elasticsearch::Transport::Transport::Errors::BadRequest; end
54
+ class DangerousOperation < Error; end
55
+ class ImportError < Error; end
56
+
57
+ class << self
58
+ attr_accessor :search_method_name, :wordnet_path, :timeout, :models, :client_options, :redis, :index_prefix, :index_suffix, :queue_name, :model_options
59
+ attr_writer :client, :env, :search_timeout
60
+ attr_reader :aws_credentials
61
+ end
62
+ self.search_method_name = :search
63
+ self.wordnet_path = "/var/lib/wn_s.pl"
64
+ self.timeout = 10
65
+ self.models = []
66
+ self.client_options = {}
67
+ self.queue_name = :searchkick
68
+ self.model_options = {}
69
+
70
+ def self.client
71
+ @client ||= begin
72
+ require "typhoeus/adapters/faraday" if defined?(Typhoeus)
73
+
74
+ Elasticsearch::Client.new({
75
+ url: ENV["ELASTICSEARCH_URL"],
76
+ transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}},
77
+ retry_on_failure: 2
78
+ }.deep_merge(client_options)) do |f|
79
+ f.use Searchkick::Middleware
80
+ f.request signer_middleware_key, signer_middleware_aws_params if aws_credentials
81
+ end
82
+ end
83
+ end
84
+
85
+ def self.env
86
+ @env ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
87
+ end
88
+
89
+ def self.search_timeout
90
+ @search_timeout || timeout
91
+ end
92
+
93
+ def self.server_version
94
+ @server_version ||= client.info["version"]["number"]
95
+ end
96
+
97
+ def self.server_below?(version)
98
+ Gem::Version.new(server_version.split("-")[0]) < Gem::Version.new(version.split("-")[0])
99
+ end
100
+
101
+ def self.search(term = "*", model: nil, **options, &block)
102
+ options = options.dup
103
+ klass = model
104
+
105
+ # make Searchkick.search(index_name: [Product]) and Product.search equivalent
106
+ unless klass
107
+ index_name = Array(options[:index_name])
108
+ if index_name.size == 1 && index_name.first.respond_to?(:searchkick_index)
109
+ klass = index_name.first
110
+ options.delete(:index_name)
111
+ end
112
+ end
113
+
114
+ query = Searchkick::Query.new(klass, term, options)
115
+ block.call(query.body) if block
116
+ if options[:execute] == false
117
+ query
118
+ else
119
+ query.execute
120
+ end
121
+ end
122
+
123
+ def self.multi_search(queries)
124
+ Searchkick::MultiSearch.new(queries).perform
125
+ end
126
+
127
+ # callbacks
128
+
129
+ def self.enable_callbacks
130
+ self.callbacks_value = nil
131
+ end
132
+
133
+ def self.disable_callbacks
134
+ self.callbacks_value = false
135
+ end
136
+
137
+ def self.callbacks?(default: true)
138
+ if callbacks_value.nil?
139
+ default
140
+ else
141
+ callbacks_value != false
142
+ end
143
+ end
144
+
145
+ def self.callbacks(value)
146
+ if block_given?
147
+ previous_value = callbacks_value
148
+ begin
149
+ self.callbacks_value = value
150
+ result = yield
151
+ indexer.perform if callbacks_value == :bulk
152
+ result
153
+ ensure
154
+ self.callbacks_value = previous_value
155
+ end
156
+ else
157
+ self.callbacks_value = value
158
+ end
159
+ end
160
+
161
+ def self.aws_credentials=(creds)
162
+ begin
163
+ require "faraday_middleware/aws_signers_v4"
164
+ rescue LoadError
165
+ require "faraday_middleware/aws_sigv4"
166
+ end
167
+ @aws_credentials = creds
168
+ @client = nil # reset client
169
+ end
170
+
171
+ def self.reindex_status(index_name)
172
+ if redis
173
+ batches_left = Searchkick::Index.new(index_name).batches_left
174
+ {
175
+ completed: batches_left == 0,
176
+ batches_left: batches_left
177
+ }
178
+ else
179
+ raise Searchkick::Error, "Redis not configured"
180
+ end
181
+ end
182
+
183
+ def self.with_redis
184
+ if redis
185
+ if redis.respond_to?(:with)
186
+ redis.with do |r|
187
+ yield r
188
+ end
189
+ else
190
+ yield redis
191
+ end
192
+ end
193
+ end
194
+
195
+ # private
196
+ def self.load_records(records, ids)
197
+ records =
198
+ if records.respond_to?(:primary_key)
199
+ # ActiveRecord
200
+ records.where(records.primary_key => ids) if records.primary_key
201
+ elsif records.respond_to?(:queryable)
202
+ # Mongoid 3+
203
+ records.queryable.for_ids(ids)
204
+ elsif records.respond_to?(:unscoped) && :id.respond_to?(:in)
205
+ # Nobrainer
206
+ records.unscoped.where(:id.in => ids)
207
+ elsif records.respond_to?(:key_column_names)
208
+ records.where(records.key_column_names.first => ids)
209
+ end
210
+
211
+ raise Searchkick::Error, "Not sure how to load records" if !records
212
+
213
+ records
214
+ end
215
+
216
+ # private
217
+ def self.indexer
218
+ Thread.current[:searchkick_indexer] ||= Searchkick::Indexer.new
219
+ end
220
+
221
+ # private
222
+ def self.callbacks_value
223
+ Thread.current[:searchkick_callbacks_enabled]
224
+ end
225
+
226
+ # private
227
+ def self.callbacks_value=(value)
228
+ Thread.current[:searchkick_callbacks_enabled] = value
229
+ end
230
+
231
+ # private
232
+ def self.signer_middleware_key
233
+ defined?(FaradayMiddleware::AwsSignersV4) ? :aws_signers_v4 : :aws_sigv4
234
+ end
235
+
236
+ # private
237
+ def self.signer_middleware_aws_params
238
+ if signer_middleware_key == :aws_sigv4
239
+ {service: "es", region: "us-east-1"}.merge(aws_credentials)
240
+ else
241
+ {
242
+ credentials: aws_credentials[:credentials] || Aws::Credentials.new(aws_credentials[:access_key_id], aws_credentials[:secret_access_key]),
243
+ service_name: "es",
244
+ region: aws_credentials[:region] || "us-east-1"
245
+ }
246
+ end
247
+ end
248
+ end
249
+
250
+ # TODO find better ActiveModel hook
251
+ ActiveModel::Callbacks.include(Searchkick::Model)
252
+
253
+ ActiveSupport.on_load(:active_record) do
254
+ extend Searchkick::Model
255
+ end