searchkick 0.6.1 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c81923ad42ba7a15c923bf2b2320ed03894b6a14
4
- data.tar.gz: 5655aa933464e320c0473b41b9b6a7d644c46dbb
3
+ metadata.gz: 9b2be48320f7dd756e116946199f07a52642b3e7
4
+ data.tar.gz: dc9c8e1fc6c82afa143f55556157786f95fb1aeb
5
5
  SHA512:
6
- metadata.gz: fb8db1d17809462eb65aacb6662b9157637b68cc99ac417e2b570a3ff589a547b0bd72d66d50cb1b1b1e3a7d469d5787e653976916b9438ec887c44a3a975373
7
- data.tar.gz: ce0f005d8c74c490007d39a9c65d07455a81ab8d2a5ca0377b9f93d3cf6b7d799f45ce6324ebc5bee3060dea1f6d75a8762105dd9e308289e2c130eb5f1400e8
6
+ metadata.gz: 8d88370f0e37d4da8c5c9bb57cdfda98b242b830cf156b099e7d69b5be338b0924b52a37b5a1c38a57cdd7e75b8984c00f83504bbda6870df4fe8e68cf75b295
7
+ data.tar.gz: 134b2bd7d3fd80cd5f2ff18288304edd543f0250e344303baf14bff18b1674e97468de3c5d7e93d99e5e531d2d2440658e51d75983295c43ed8a2fbd2a718aed
@@ -1,3 +1,9 @@
1
+ ## 0.6.2
2
+
3
+ - Added logging
4
+ - Fixed index_name option
5
+ - Added ability to use proc as the index name
6
+
1
7
  ## 0.6.1
2
8
 
3
9
  - Fixed huge issue w/ zero-downtime reindexing on 0.90 and elasticsearch-ruby 1.0
data/README.md CHANGED
@@ -744,6 +744,10 @@ rake searchkick:reindex:all
744
744
 
745
745
  4. Once it finishes, replace search calls w/ searchkick calls
746
746
 
747
+ ## Note about 0.6.0
748
+
749
+ If running Searchkick `0.6.0` and Elasticsearch `0.90`, we recommend upgrading to Searchkick `0.6.1` to fix an issue that causes downtime when reindexing.
750
+
747
751
  ## Elasticsearch Gotchas
748
752
 
749
753
  ### Inconsistent Scores
@@ -11,8 +11,7 @@ require "searchkick/search"
11
11
  require "searchkick/similar"
12
12
  require "searchkick/model"
13
13
  require "searchkick/tasks"
14
- # TODO add logger
15
- # require "searchkick/logger" if defined?(Rails)
14
+ require "searchkick/logging" if defined?(Rails)
16
15
 
17
16
  module Searchkick
18
17
 
@@ -25,16 +25,16 @@ module Searchkick
25
25
  def store(record)
26
26
  client.index(
27
27
  index: name,
28
- type: record.document_type,
28
+ type: document_type(record),
29
29
  id: record.id,
30
- body: record.as_indexed_json
30
+ body: search_data(record)
31
31
  )
32
32
  end
33
33
 
34
34
  def remove(record)
35
35
  client.delete(
36
36
  index: name,
37
- type: record.document_type,
37
+ type: document_type(record),
38
38
  id: record.id
39
39
  )
40
40
  end
@@ -43,25 +43,95 @@ module Searchkick
43
43
  if records.any?
44
44
  client.bulk(
45
45
  index: name,
46
- type: records.first.document_type,
47
- body: records.map{|r| data = r.as_indexed_json; {index: {_id: data["_id"] || data["id"] || r.id, data: data}} }
46
+ type: document_type(records.first),
47
+ body: records.map{|r| data = search_data(r); {index: {_id: data["_id"] || data["id"] || r.id, data: data}} }
48
48
  )
49
49
  end
50
50
  end
51
51
 
52
- def retrieve(document_type, id)
52
+ def retrieve(record)
53
53
  client.get(
54
54
  index: name,
55
- type: document_type,
56
- id: id
55
+ type: document_type(record),
56
+ id: record.id
57
57
  )["_source"]
58
58
  end
59
59
 
60
+ def klass_document_type(klass)
61
+ klass.model_name.to_s.underscore
62
+ end
63
+
60
64
  protected
61
65
 
62
66
  def client
63
67
  Searchkick.client
64
68
  end
65
69
 
70
+ def document_type(record)
71
+ klass_document_type(record.class)
72
+ end
73
+
74
+ def search_data(record)
75
+ source = record.search_data
76
+
77
+ # stringify fields
78
+ source = source.inject({}){|memo,(k,v)| memo[k.to_s] = v; memo}
79
+
80
+ # Mongoid 4 hack
81
+ if defined?(BSON::ObjectId) and source["_id"].is_a?(BSON::ObjectId)
82
+ source["_id"] = source["_id"].to_s
83
+ end
84
+
85
+ options = record.class.searchkick_options
86
+
87
+ # conversions
88
+ conversions_field = options[:conversions]
89
+ if conversions_field and source[conversions_field]
90
+ source[conversions_field] = source[conversions_field].map{|k, v| {query: k, count: v} }
91
+ end
92
+
93
+ # hack to prevent generator field doesn't exist error
94
+ (options[:suggest] || []).map(&:to_s).each do |field|
95
+ source[field] = nil if !source[field]
96
+ end
97
+
98
+ # locations
99
+ (options[:locations] || []).map(&:to_s).each do |field|
100
+ if source[field]
101
+ if source[field].first.is_a?(Array) # array of arrays
102
+ source[field] = source[field].map{|a| a.map(&:to_f).reverse }
103
+ else
104
+ source[field] = source[field].map(&:to_f).reverse
105
+ end
106
+ end
107
+ end
108
+
109
+ cast_big_decimal(source)
110
+
111
+ # p search_data
112
+
113
+ source.as_json
114
+ end
115
+
116
+ # change all BigDecimal values to floats due to
117
+ # https://github.com/rails/rails/issues/6033
118
+ # possible loss of precision :/
119
+ def cast_big_decimal(obj)
120
+ case obj
121
+ when BigDecimal
122
+ obj.to_f
123
+ when Hash
124
+ obj.each do |k, v|
125
+ obj[k] = cast_big_decimal(v)
126
+ end
127
+ when Enumerable
128
+ obj.map do |v|
129
+ cast_big_decimal(v)
130
+ end
131
+ else
132
+ obj
133
+ end
134
+ end
135
+
66
136
  end
67
137
  end
@@ -0,0 +1,89 @@
1
+ # based on https://gist.github.com/mnutt/566725
2
+
3
+ module Searchkick
4
+ class Query
5
+ def execute_with_instrumentation
6
+ event = {
7
+ name: "#{searchkick_klass.name} Search",
8
+ query: params
9
+ }
10
+ ActiveSupport::Notifications.instrument("search.searchkick", event) do
11
+ execute_without_instrumentation
12
+ end
13
+ end
14
+
15
+ alias_method_chain :execute, :instrumentation
16
+ end
17
+
18
+ # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/log_subscriber.rb
19
+ class LogSubscriber < ActiveSupport::LogSubscriber
20
+ def self.runtime=(value)
21
+ Thread.current[:searchkick_runtime] = value
22
+ end
23
+
24
+ def self.runtime
25
+ Thread.current[:searchkick_runtime] ||= 0
26
+ end
27
+
28
+ def self.reset_runtime
29
+ rt, self.runtime = runtime, 0
30
+ rt
31
+ end
32
+
33
+ def search(event)
34
+ self.class.runtime += event.duration
35
+ return unless logger.debug?
36
+
37
+ payload = event.payload
38
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
39
+ type = payload[:query][:type]
40
+
41
+ # no easy way to tell which host the client will use
42
+ host = Searchkick.client.transport.hosts.first
43
+ debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/#{CGI.escape(payload[:query][:index])}#{type ? "/#{type.map{|t| CGI.escape(t) }.join(",")}" : ""}/_search?pretty -d '#{payload[:query][:body].to_json}'"
44
+ end
45
+ end
46
+
47
+ # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/railties/controller_runtime.rb
48
+ module ControllerRuntime
49
+ extend ActiveSupport::Concern
50
+
51
+ protected
52
+
53
+ attr_internal :searchkick_runtime
54
+
55
+ def process_action(action, *args)
56
+ # We also need to reset the runtime before each action
57
+ # because of queries in middleware or in cases we are streaming
58
+ # and it won't be cleaned up by the method below.
59
+ Searchkick::LogSubscriber.reset_runtime
60
+ super
61
+ end
62
+
63
+ def cleanup_view_runtime
64
+ searchkick_rt_before_render = Searchkick::LogSubscriber.reset_runtime
65
+ runtime = super
66
+ searchkick_rt_after_render = Searchkick::LogSubscriber.reset_runtime
67
+ self.searchkick_runtime = searchkick_rt_before_render + searchkick_rt_after_render
68
+ runtime - searchkick_rt_after_render
69
+ end
70
+
71
+ def append_info_to_payload(payload)
72
+ super
73
+ payload[:searchkick_runtime] = (searchkick_runtime || 0) + Searchkick::LogSubscriber.reset_runtime
74
+ end
75
+
76
+ module ClassMethods
77
+ def log_process_action(payload)
78
+ messages, runtime = super, payload[:searchkick_runtime]
79
+ messages << ("Searchkick: %.1fms" % runtime.to_f) if runtime.to_f > 0
80
+ messages
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ Searchkick::LogSubscriber.attach_to :searchkick
87
+ ActiveSupport.on_load(:action_controller) do
88
+ include Searchkick::ControllerRuntime
89
+ end
@@ -3,17 +3,19 @@ module Searchkick
3
3
 
4
4
  def searchkick(options = {})
5
5
  class_eval do
6
- cattr_reader :searchkick_options, :searchkick_env, :searchkick_klass, :searchkick_index
6
+ cattr_reader :searchkick_options, :searchkick_env, :searchkick_klass
7
7
 
8
8
  class_variable_set :@@searchkick_options, options.dup
9
9
  class_variable_set :@@searchkick_env, ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
10
10
  class_variable_set :@@searchkick_klass, self
11
11
  class_variable_set :@@searchkick_callbacks, options[:callbacks] != false
12
+ class_variable_set :@@searchkick_index, options[:index_name] || [options[:index_prefix], model_name.plural, searchkick_env].compact.join("_")
12
13
 
13
- # set index name
14
- # TODO support proc
15
- index_name = options[:index_name] || [options[:index_prefix], model_name.plural, searchkick_env].compact.join("_")
16
- class_variable_set :@@searchkick_index, Searchkick::Index.new(index_name)
14
+ def self.searchkick_index
15
+ index = class_variable_get :@@searchkick_index
16
+ index = index.call if index.respond_to? :call
17
+ Searchkick::Index.new(index)
18
+ end
17
19
 
18
20
  extend Searchkick::Search
19
21
  extend Searchkick::Reindex
@@ -59,79 +61,6 @@ module Searchkick
59
61
  respond_to?(:to_hash) ? to_hash : serializable_hash
60
62
  end
61
63
 
62
- def as_indexed_json
63
- source = search_data
64
-
65
- # stringify fields
66
- source = source.inject({}){|memo,(k,v)| memo[k.to_s] = v; memo}
67
-
68
- # Mongoid 4 hack
69
- if defined?(BSON::ObjectId) and source["_id"].is_a?(BSON::ObjectId)
70
- source["_id"] = source["_id"].to_s
71
- end
72
-
73
- options = self.class.searchkick_options
74
-
75
- # conversions
76
- conversions_field = options[:conversions]
77
- if conversions_field and source[conversions_field]
78
- source[conversions_field] = source[conversions_field].map{|k, v| {query: k, count: v} }
79
- end
80
-
81
- # hack to prevent generator field doesn't exist error
82
- (options[:suggest] || []).map(&:to_s).each do |field|
83
- source[field] = nil if !source[field]
84
- end
85
-
86
- # locations
87
- (options[:locations] || []).map(&:to_s).each do |field|
88
- if source[field]
89
- if source[field].first.is_a?(Array) # array of arrays
90
- source[field] = source[field].map{|a| a.map(&:to_f).reverse }
91
- else
92
- source[field] = source[field].map(&:to_f).reverse
93
- end
94
- end
95
- end
96
-
97
- # change all BigDecimal values to floats due to
98
- # https://github.com/rails/rails/issues/6033
99
- # possible loss of precision :/
100
- cast_big_decimal =
101
- proc do |obj|
102
- case obj
103
- when BigDecimal
104
- obj.to_f
105
- when Hash
106
- obj.each do |k, v|
107
- obj[k] = cast_big_decimal.call(v)
108
- end
109
- when Enumerable
110
- obj.map! do |v|
111
- cast_big_decimal.call(v)
112
- end
113
- else
114
- obj
115
- end
116
- end
117
-
118
- cast_big_decimal.call(source)
119
-
120
- # p search_data
121
-
122
- source.as_json
123
- end
124
-
125
- # TODO remove
126
-
127
- def self.document_type
128
- model_name.to_s.underscore
129
- end
130
-
131
- def document_type
132
- self.class.document_type
133
- end
134
-
135
64
  end
136
65
  end
137
66
 
@@ -39,7 +39,6 @@ module Searchkick
39
39
  page = [options[:page].to_i, 1].max
40
40
  per_page = (options[:limit] || options[:per_page] || 100000).to_i
41
41
  offset = options[:offset] || (page - 1) * per_page
42
- index_name = options[:index_name] || searchkick_index.name
43
42
 
44
43
  conversions_field = searchkick_options[:conversions]
45
44
  personalize_field = searchkick_options[:personalize]
@@ -282,7 +281,7 @@ module Searchkick
282
281
  payload[:fields] = [] if load
283
282
 
284
283
  if options[:type] or klass != searchkick_klass
285
- @type = [options[:type] || klass].flatten.map(&:document_type)
284
+ @type = [options[:type] || klass].flatten.map{|v| searchkick_index.klass_document_type(v) }
286
285
  end
287
286
 
288
287
  @body = payload
@@ -304,16 +303,16 @@ module Searchkick
304
303
  klass.searchkick_klass
305
304
  end
306
305
 
307
- def document_type
308
- klass.document_type
309
- end
310
-
311
- def execute
306
+ def params
312
307
  params = {
313
- index: searchkick_index.name,
308
+ index: options[:index_name] || searchkick_index.name,
314
309
  body: body
315
310
  }
316
311
  params.merge!(type: @type) if @type
312
+ params
313
+ end
314
+
315
+ def execute
317
316
  begin
318
317
  response = Searchkick.client.search(params)
319
318
  rescue => e # TODO rescue type
@@ -2,7 +2,7 @@ module Searchkick
2
2
  module Similar
3
3
 
4
4
  def similar(options = {})
5
- like_text = self.class.searchkick_index.retrieve(document_type, id).to_hash
5
+ like_text = self.class.searchkick_index.retrieve(self).to_hash
6
6
  .keep_if{|k,v| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
7
7
  .values.compact.join(" ")
8
8
 
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "0.6.1"
2
+ VERSION = "0.6.2"
3
3
  end
@@ -10,7 +10,7 @@ class TestInheritance < Minitest::Unit::TestCase
10
10
  end
11
11
 
12
12
  def test_child_index_name
13
- assert_equal "animals_test", Dog.searchkick_index.name
13
+ assert_equal "animals-#{Date.today.year}", Dog.searchkick_index.name
14
14
  end
15
15
 
16
16
  def test_child_search
@@ -143,7 +143,7 @@ class Store
143
143
  end
144
144
 
145
145
  class Animal
146
- searchkick autocomplete: [:name], suggest: [:name]
146
+ searchkick autocomplete: [:name], suggest: [:name], index_name: -> { "#{self.name.tableize}-#{Date.today.year}" }
147
147
  end
148
148
 
149
149
  Product.searchkick_index.delete if Product.searchkick_index.exists?
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.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-25 00:00:00.000000000 Z
11
+ date: 2014-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -126,6 +126,7 @@ files:
126
126
  - gemfiles/mongoid4.gemfile
127
127
  - lib/searchkick.rb
128
128
  - lib/searchkick/index.rb
129
+ - lib/searchkick/logging.rb
129
130
  - lib/searchkick/model.rb
130
131
  - lib/searchkick/query.rb
131
132
  - lib/searchkick/reindex.rb