searchkick_bharthur 0.0.1

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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +44 -0
  4. data/CHANGELOG.md +360 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +1443 -0
  8. data/Rakefile +8 -0
  9. data/lib/searchkick/index.rb +662 -0
  10. data/lib/searchkick/logging.rb +185 -0
  11. data/lib/searchkick/middleware.rb +12 -0
  12. data/lib/searchkick/model.rb +105 -0
  13. data/lib/searchkick/query.rb +845 -0
  14. data/lib/searchkick/reindex_job.rb +26 -0
  15. data/lib/searchkick/reindex_v2_job.rb +23 -0
  16. data/lib/searchkick/results.rb +211 -0
  17. data/lib/searchkick/tasks.rb +33 -0
  18. data/lib/searchkick/version.rb +3 -0
  19. data/lib/searchkick.rb +159 -0
  20. data/searchkick.gemspec +28 -0
  21. data/test/aggs_test.rb +115 -0
  22. data/test/autocomplete_test.rb +65 -0
  23. data/test/boost_test.rb +144 -0
  24. data/test/callbacks_test.rb +27 -0
  25. data/test/ci/before_install.sh +21 -0
  26. data/test/dangerous_reindex_test.rb +27 -0
  27. data/test/facets_test.rb +90 -0
  28. data/test/gemfiles/activerecord31.gemfile +7 -0
  29. data/test/gemfiles/activerecord32.gemfile +7 -0
  30. data/test/gemfiles/activerecord40.gemfile +8 -0
  31. data/test/gemfiles/activerecord41.gemfile +8 -0
  32. data/test/gemfiles/activerecord50.gemfile +7 -0
  33. data/test/gemfiles/apartment.gemfile +8 -0
  34. data/test/gemfiles/mongoid2.gemfile +7 -0
  35. data/test/gemfiles/mongoid3.gemfile +6 -0
  36. data/test/gemfiles/mongoid4.gemfile +7 -0
  37. data/test/gemfiles/mongoid5.gemfile +7 -0
  38. data/test/gemfiles/nobrainer.gemfile +6 -0
  39. data/test/highlight_test.rb +63 -0
  40. data/test/index_test.rb +120 -0
  41. data/test/inheritance_test.rb +78 -0
  42. data/test/match_test.rb +227 -0
  43. data/test/misspellings_test.rb +46 -0
  44. data/test/model_test.rb +42 -0
  45. data/test/multi_search_test.rb +22 -0
  46. data/test/multi_tenancy_test.rb +22 -0
  47. data/test/order_test.rb +44 -0
  48. data/test/pagination_test.rb +53 -0
  49. data/test/query_test.rb +13 -0
  50. data/test/records_test.rb +8 -0
  51. data/test/reindex_job_test.rb +31 -0
  52. data/test/reindex_v2_job_test.rb +32 -0
  53. data/test/routing_test.rb +13 -0
  54. data/test/should_index_test.rb +32 -0
  55. data/test/similar_test.rb +28 -0
  56. data/test/sql_test.rb +196 -0
  57. data/test/suggest_test.rb +80 -0
  58. data/test/synonyms_test.rb +54 -0
  59. data/test/test_helper.rb +361 -0
  60. data/test/where_test.rb +171 -0
  61. metadata +231 -0
@@ -0,0 +1,185 @@
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 import(records)
49
+ if records.any?
50
+ event = {
51
+ name: "#{records.first.searchkick_klass.name} Import",
52
+ count: records.size
53
+ }
54
+ ActiveSupport::Notifications.instrument("request.searchkick", event) do
55
+ super(records)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ module SearchkickWithInstrumentation
62
+ def multi_search(searches)
63
+ event = {
64
+ name: "Multi Search",
65
+ body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
66
+ }
67
+ ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
68
+ super
69
+ end
70
+ end
71
+
72
+ def perform_items(items)
73
+ if callbacks_value == :bulk
74
+ event = {
75
+ name: "Bulk",
76
+ count: items.size
77
+ }
78
+ ActiveSupport::Notifications.instrument("request.searchkick", event) do
79
+ super
80
+ end
81
+ else
82
+ super
83
+ end
84
+ end
85
+ end
86
+
87
+ # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/log_subscriber.rb
88
+ class LogSubscriber < ActiveSupport::LogSubscriber
89
+ def self.runtime=(value)
90
+ Thread.current[:searchkick_runtime] = value
91
+ end
92
+
93
+ def self.runtime
94
+ Thread.current[:searchkick_runtime] ||= 0
95
+ end
96
+
97
+ def self.reset_runtime
98
+ rt = runtime
99
+ self.runtime = 0
100
+ rt
101
+ end
102
+
103
+ def search(event)
104
+ self.class.runtime += event.duration
105
+ return unless logger.debug?
106
+
107
+ payload = event.payload
108
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
109
+ type = payload[:query][:type]
110
+ index = payload[:query][:index].is_a?(Array) ? payload[:query][:index].join(",") : payload[:query][:index]
111
+
112
+ # no easy way to tell which host the client will use
113
+ host = Searchkick.client.transport.hosts.first
114
+ 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}'"
115
+ end
116
+
117
+ def request(event)
118
+ self.class.runtime += event.duration
119
+ return unless logger.debug?
120
+
121
+ payload = event.payload
122
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
123
+
124
+ debug " #{color(name, YELLOW, true)} #{payload.except(:name).to_json}"
125
+ end
126
+
127
+ def multi_search(event)
128
+ self.class.runtime += event.duration
129
+ return unless logger.debug?
130
+
131
+ payload = event.payload
132
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
133
+
134
+ # no easy way to tell which host the client will use
135
+ host = Searchkick.client.transport.hosts.first
136
+ debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/_msearch?pretty -d '#{payload[:body]}'"
137
+ end
138
+ end
139
+
140
+ # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/railties/controller_runtime.rb
141
+ module ControllerRuntime
142
+ extend ActiveSupport::Concern
143
+
144
+ protected
145
+
146
+ attr_internal :searchkick_runtime
147
+
148
+ def process_action(action, *args)
149
+ # We also need to reset the runtime before each action
150
+ # because of queries in middleware or in cases we are streaming
151
+ # and it won't be cleaned up by the method below.
152
+ Searchkick::LogSubscriber.reset_runtime
153
+ super
154
+ end
155
+
156
+ def cleanup_view_runtime
157
+ searchkick_rt_before_render = Searchkick::LogSubscriber.reset_runtime
158
+ runtime = super
159
+ searchkick_rt_after_render = Searchkick::LogSubscriber.reset_runtime
160
+ self.searchkick_runtime = searchkick_rt_before_render + searchkick_rt_after_render
161
+ runtime - searchkick_rt_after_render
162
+ end
163
+
164
+ def append_info_to_payload(payload)
165
+ super
166
+ payload[:searchkick_runtime] = (searchkick_runtime || 0) + Searchkick::LogSubscriber.reset_runtime
167
+ end
168
+
169
+ module ClassMethods
170
+ def log_process_action(payload)
171
+ messages = super
172
+ runtime = payload[:searchkick_runtime]
173
+ messages << ("Searchkick: %.1fms" % runtime.to_f) if runtime.to_f > 0
174
+ messages
175
+ end
176
+ end
177
+ end
178
+ end
179
+ Searchkick::Query.send(:prepend, Searchkick::QueryWithInstrumentation)
180
+ Searchkick::Index.send(:prepend, Searchkick::IndexWithInstrumentation)
181
+ Searchkick.singleton_class.send(:prepend, Searchkick::SearchkickWithInstrumentation)
182
+ Searchkick::LogSubscriber.attach_to :searchkick
183
+ ActiveSupport.on_load(:action_controller) do
184
+ include Searchkick::ControllerRuntime
185
+ end
@@ -0,0 +1,12 @@
1
+ require "faraday/middleware"
2
+
3
+ module Searchkick
4
+ class Middleware < Faraday::Middleware
5
+ def call(env)
6
+ if env[:method] == :get && env[:url].path.to_s.end_with?("/_search")
7
+ env[:request][:timeout] = Searchkick.search_timeout
8
+ end
9
+ @app.call(env)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,105 @@
1
+ module Searchkick
2
+ module Reindex; end # legacy for Searchjoy
3
+
4
+ module Model
5
+ def searchkick(options = {})
6
+ raise "Only call searchkick once per model" if respond_to?(:searchkick_index)
7
+
8
+ Searchkick.models << self
9
+
10
+ class_eval do
11
+ cattr_reader :searchkick_options, :searchkick_klass
12
+
13
+ callbacks = options.key?(:callbacks) ? options[:callbacks] : true
14
+
15
+ class_variable_set :@@searchkick_options, options.dup
16
+ class_variable_set :@@searchkick_klass, self
17
+ class_variable_set :@@searchkick_callbacks, callbacks
18
+ class_variable_set :@@searchkick_index, options[:index_name] ||
19
+ (options[:index_prefix].respond_to?(:call) && proc { [options[:index_prefix].call, model_name.plural, Searchkick.env].compact.join("_") }) ||
20
+ [options[:index_prefix], model_name.plural, Searchkick.env].compact.join("_")
21
+
22
+ class << self
23
+ def searchkick_search(term = nil, options = {}, &block)
24
+ searchkick_index.search_model(self, term, options, &block)
25
+ end
26
+ alias_method Searchkick.search_method_name, :searchkick_search if Searchkick.search_method_name
27
+
28
+ def searchkick_index
29
+ index = class_variable_get :@@searchkick_index
30
+ index = index.call if index.respond_to? :call
31
+ Searchkick::Index.new(index, searchkick_options)
32
+ end
33
+
34
+ def enable_search_callbacks
35
+ class_variable_set :@@searchkick_callbacks, true
36
+ end
37
+
38
+ def disable_search_callbacks
39
+ class_variable_set :@@searchkick_callbacks, false
40
+ end
41
+
42
+ def search_callbacks?
43
+ class_variable_get(:@@searchkick_callbacks) && Searchkick.callbacks?
44
+ end
45
+
46
+ def searchkick_reindex(options = {})
47
+ unless options[:accept_danger]
48
+ if (respond_to?(:current_scope) && respond_to?(:default_scoped) && current_scope && current_scope.to_sql != default_scoped.to_sql) ||
49
+ (respond_to?(:queryable) && queryable != unscoped.with_default_scope)
50
+ raise Searchkick::DangerousOperation, "Only call reindex on models, not relations. Pass `accept_danger: true` if this is your intention."
51
+ end
52
+ end
53
+ searchkick_index.reindex_scope(searchkick_klass, options)
54
+ end
55
+ alias_method :reindex, :searchkick_reindex unless method_defined?(:reindex)
56
+
57
+ def clean_indices
58
+ searchkick_index.clean_indices
59
+ end
60
+
61
+ def searchkick_import(options = {})
62
+ (options[:index] || searchkick_index).import_scope(searchkick_klass)
63
+ end
64
+
65
+ def searchkick_create_index
66
+ searchkick_index.create_index
67
+ end
68
+
69
+ def searchkick_index_options
70
+ searchkick_index.index_options
71
+ end
72
+ end
73
+ extend Searchkick::Reindex # legacy for Searchjoy
74
+
75
+ callback_name = callbacks == :async ? :reindex_async : :reindex
76
+ if respond_to?(:after_commit)
77
+ after_commit callback_name, if: proc { self.class.search_callbacks? }
78
+ elsif respond_to?(:after_save)
79
+ after_save callback_name, if: proc { self.class.search_callbacks? }
80
+ after_destroy callback_name, if: proc { self.class.search_callbacks? }
81
+ end
82
+
83
+ def reindex
84
+ self.class.searchkick_index.reindex_record(self)
85
+ end unless method_defined?(:reindex)
86
+
87
+ def reindex_async
88
+ self.class.searchkick_index.reindex_record_async(self)
89
+ end unless method_defined?(:reindex_async)
90
+
91
+ def similar(options = {})
92
+ self.class.searchkick_index.similar_record(self, options)
93
+ end unless method_defined?(:similar)
94
+
95
+ def search_data
96
+ respond_to?(:to_hash) ? to_hash : serializable_hash
97
+ end unless method_defined?(:search_data)
98
+
99
+ def should_index?
100
+ true
101
+ end unless method_defined?(:should_index?)
102
+ end
103
+ end
104
+ end
105
+ end