searchkick 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +18 -1
- data/lib/searchkick.rb +10 -0
- data/lib/searchkick/index.rb +14 -7
- data/lib/searchkick/logging.rb +13 -15
- data/lib/searchkick/middleware.rb +2 -2
- data/lib/searchkick/query.rb +23 -7
- data/lib/searchkick/version.rb +1 -1
- data/test/highlight_test.rb +5 -0
- data/test/index_test.rb +7 -0
- data/test/model_test.rb +6 -0
- data/test/similar_test.rb +10 -0
- data/test/test_helper.rb +4 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49a9ad19b612e5079b3c97ec4ff756e62b8e2333
|
4
|
+
data.tar.gz: 2e9133f49f0287cabf5bc88fe2fd28e6594ffa27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e15ce0859a66945f0675226bb5492d67db7612f23e480d5cec930903db151c2622380d30192e1eace1feb146b455f56b5cb3f55c07129580ac8117da224f42e
|
7
|
+
data.tar.gz: 87c387df771fcf4dcec72b46f779169515acef02bd29ff12565c1c52374fc99a13d54a7d5a38ff0ef57cb7beb58412b0c354abf2beb0860342f2ff545605130a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 1.2.0
|
2
|
+
|
3
|
+
- Fixed deprecation warnings with `alias_method_chain`
|
4
|
+
- Added `analyzed_only` option for large text fields
|
5
|
+
- Added `encoder` option to highlight
|
6
|
+
- Fixed issue in `similar` method with `per_page` option
|
7
|
+
- Added basic support for multiple models
|
8
|
+
|
1
9
|
## 1.1.2
|
2
10
|
|
3
11
|
- Added bulk updates with `callbacks` method
|
data/README.md
CHANGED
@@ -35,6 +35,9 @@ Plus:
|
|
35
35
|
|
36
36
|
```sh
|
37
37
|
brew install elasticsearch
|
38
|
+
|
39
|
+
# start the server
|
40
|
+
elasticsearch
|
38
41
|
```
|
39
42
|
|
40
43
|
Add this line to your application’s Gemfile:
|
@@ -394,7 +397,7 @@ There are three strategies for keeping the index synced with your database.
|
|
394
397
|
end
|
395
398
|
```
|
396
399
|
|
397
|
-
And [install Active Job](https://github.com/ankane/activejob_backport) for Rails 4.1 and below
|
400
|
+
And [install Active Job](https://github.com/ankane/activejob_backport) for Rails 4.1 and below. Jobs are added to a queue named `searchkick`.
|
398
401
|
|
399
402
|
3. Manual
|
400
403
|
|
@@ -1121,6 +1124,14 @@ Remove old indices
|
|
1121
1124
|
Product.clean_indices
|
1122
1125
|
```
|
1123
1126
|
|
1127
|
+
Use custom settings
|
1128
|
+
|
1129
|
+
```ruby
|
1130
|
+
class Product < ActiveRecord::Base
|
1131
|
+
searchkick settings: {number_of_shards: 3}
|
1132
|
+
end
|
1133
|
+
```
|
1134
|
+
|
1124
1135
|
Use a different index name
|
1125
1136
|
|
1126
1137
|
```ruby
|
@@ -1235,6 +1246,12 @@ class Product < ActiveRecord::Base
|
|
1235
1246
|
end
|
1236
1247
|
```
|
1237
1248
|
|
1249
|
+
Search multiple models [master]
|
1250
|
+
|
1251
|
+
```ruby
|
1252
|
+
Searchkick.search "milk", index_name: [Product, Category]
|
1253
|
+
```
|
1254
|
+
|
1238
1255
|
Reindex all models - Rails only
|
1239
1256
|
|
1240
1257
|
```sh
|
data/lib/searchkick.rb
CHANGED
@@ -128,6 +128,16 @@ module Searchkick
|
|
128
128
|
def self.callbacks_value=(value)
|
129
129
|
Thread.current[:searchkick_callbacks_enabled] = value
|
130
130
|
end
|
131
|
+
|
132
|
+
def self.search(term = nil, options = {}, &block)
|
133
|
+
query = Searchkick::Query.new(nil, term, options)
|
134
|
+
block.call(query.body) if block
|
135
|
+
if options[:execute] == false
|
136
|
+
query
|
137
|
+
else
|
138
|
+
query.execute
|
139
|
+
end
|
140
|
+
end
|
131
141
|
end
|
132
142
|
|
133
143
|
# TODO find better ActiveModel hook
|
data/lib/searchkick/index.rb
CHANGED
@@ -102,7 +102,7 @@ module Searchkick
|
|
102
102
|
options[:where] ||= {}
|
103
103
|
options[:where][:_id] ||= {}
|
104
104
|
options[:where][:_id][:not] = record.id.to_s
|
105
|
-
options[:
|
105
|
+
options[:per_page] ||= 10
|
106
106
|
options[:similar] = true
|
107
107
|
|
108
108
|
# TODO use index class instead of record class
|
@@ -132,7 +132,12 @@ module Searchkick
|
|
132
132
|
|
133
133
|
# remove old indices that start w/ index_name
|
134
134
|
def clean_indices
|
135
|
-
all_indices =
|
135
|
+
all_indices =
|
136
|
+
begin
|
137
|
+
client.indices.get_aliases
|
138
|
+
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
139
|
+
[]
|
140
|
+
end
|
136
141
|
indices = all_indices.select { |k, v| (v.empty? || v["aliases"].empty?) && k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
|
137
142
|
indices.each do |index|
|
138
143
|
Searchkick::Index.new(index).delete
|
@@ -408,7 +413,7 @@ module Searchkick
|
|
408
413
|
end
|
409
414
|
|
410
415
|
mapping_options = Hash[
|
411
|
-
[:autocomplete, :suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable]
|
416
|
+
[:autocomplete, :suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable, :only_analyzed]
|
412
417
|
.map { |type| [type, (options[type] || []).map(&:to_s)] }
|
413
418
|
]
|
414
419
|
|
@@ -417,11 +422,13 @@ module Searchkick
|
|
417
422
|
mapping_options.values.flatten.uniq.each do |field|
|
418
423
|
field_mapping = {
|
419
424
|
type: "multi_field",
|
420
|
-
fields: {
|
421
|
-
field => {type: "string", index: "not_analyzed"}
|
422
|
-
}
|
425
|
+
fields: {}
|
423
426
|
}
|
424
427
|
|
428
|
+
unless mapping_options[:only_analyzed].include?(field)
|
429
|
+
field_mapping[:fields][field] = {type: "string", index: "not_analyzed"}
|
430
|
+
end
|
431
|
+
|
425
432
|
if !options[:searchable] || mapping_options[:searchable].include?(field)
|
426
433
|
if word
|
427
434
|
field_mapping[:fields]["analyzed"] = {type: "string", index: "analyzed"}
|
@@ -431,7 +438,7 @@ module Searchkick
|
|
431
438
|
end
|
432
439
|
end
|
433
440
|
|
434
|
-
mapping_options.except(:highlight, :searchable).each do |type, fields|
|
441
|
+
mapping_options.except(:highlight, :searchable, :only_analyzed).each do |type, fields|
|
435
442
|
if options[:match] == type || fields.include?(field)
|
436
443
|
field_mapping[:fields][type] = {type: "string", index: "analyzed", analyzer: "searchkick_#{type}_index"}
|
437
444
|
end
|
data/lib/searchkick/logging.rb
CHANGED
@@ -1,54 +1,51 @@
|
|
1
1
|
# based on https://gist.github.com/mnutt/566725
|
2
2
|
|
3
3
|
module Searchkick
|
4
|
-
|
5
|
-
def
|
4
|
+
module QueryWithInstrumentation
|
5
|
+
def execute_search
|
6
6
|
event = {
|
7
7
|
name: "#{searchkick_klass.name} Search",
|
8
8
|
query: params
|
9
9
|
}
|
10
10
|
ActiveSupport::Notifications.instrument("search.searchkick", event) do
|
11
|
-
|
11
|
+
super
|
12
12
|
end
|
13
13
|
end
|
14
|
-
alias_method_chain :execute_search, :instrumentation
|
15
14
|
end
|
16
15
|
|
17
|
-
|
18
|
-
def
|
16
|
+
module IndexWithInstrumentation
|
17
|
+
def store(record)
|
19
18
|
event = {
|
20
19
|
name: "#{record.searchkick_klass.name} Store",
|
21
20
|
id: search_id(record)
|
22
21
|
}
|
23
22
|
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
24
|
-
|
23
|
+
super(record)
|
25
24
|
end
|
26
25
|
end
|
27
|
-
alias_method_chain :store, :instrumentation
|
28
26
|
|
29
|
-
|
27
|
+
|
28
|
+
def remove(record)
|
30
29
|
event = {
|
31
30
|
name: "#{record.searchkick_klass.name} Remove",
|
32
31
|
id: search_id(record)
|
33
32
|
}
|
34
33
|
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
35
|
-
|
34
|
+
super(record)
|
36
35
|
end
|
37
36
|
end
|
38
|
-
alias_method_chain :remove, :instrumentation
|
39
37
|
|
40
|
-
def
|
38
|
+
def import(records)
|
41
39
|
if records.any?
|
42
40
|
event = {
|
43
41
|
name: "#{records.first.searchkick_klass.name} Import",
|
44
42
|
count: records.size
|
45
43
|
}
|
46
44
|
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
47
|
-
|
45
|
+
super(records)
|
48
46
|
end
|
49
47
|
end
|
50
48
|
end
|
51
|
-
alias_method_chain :import, :instrumentation
|
52
49
|
end
|
53
50
|
|
54
51
|
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/log_subscriber.rb
|
@@ -131,7 +128,8 @@ module Searchkick
|
|
131
128
|
end
|
132
129
|
end
|
133
130
|
end
|
134
|
-
|
131
|
+
Searchkick::Query.send(:prepend, Searchkick::QueryWithInstrumentation)
|
132
|
+
Searchkick::Index.send(:prepend, Searchkick::IndexWithInstrumentation)
|
135
133
|
Searchkick::LogSubscriber.attach_to :searchkick
|
136
134
|
ActiveSupport.on_load(:action_controller) do
|
137
135
|
include Searchkick::ControllerRuntime
|
@@ -3,8 +3,8 @@ require "faraday/middleware"
|
|
3
3
|
module Searchkick
|
4
4
|
class Middleware < Faraday::Middleware
|
5
5
|
def call(env)
|
6
|
-
if env
|
7
|
-
env
|
6
|
+
if env[:method] == :get && env[:url].path.to_s.end_with?("/_search")
|
7
|
+
env[:request][:timeout] = Searchkick.search_timeout
|
8
8
|
end
|
9
9
|
@app.call(env)
|
10
10
|
end
|
data/lib/searchkick/query.rb
CHANGED
@@ -28,20 +28,29 @@ module Searchkick
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def searchkick_index
|
31
|
-
klass.searchkick_index
|
31
|
+
klass ? klass.searchkick_index : nil
|
32
32
|
end
|
33
33
|
|
34
34
|
def searchkick_options
|
35
|
-
klass.searchkick_options
|
35
|
+
klass ? klass.searchkick_options : {}
|
36
36
|
end
|
37
37
|
|
38
38
|
def searchkick_klass
|
39
|
-
klass.searchkick_klass
|
39
|
+
klass ? klass.searchkick_klass : nil
|
40
40
|
end
|
41
41
|
|
42
42
|
def params
|
43
|
+
index =
|
44
|
+
if options[:index_name]
|
45
|
+
Array(options[:index_name]).map { |v| v.respond_to?(:searchkick_index) ? v.searchkick_index.name : v }.join(",")
|
46
|
+
elsif searchkick_index
|
47
|
+
searchkick_index.name
|
48
|
+
else
|
49
|
+
"_all"
|
50
|
+
end
|
51
|
+
|
43
52
|
params = {
|
44
|
-
index:
|
53
|
+
index: index,
|
45
54
|
body: body
|
46
55
|
}
|
47
56
|
params.merge!(type: @type) if @type
|
@@ -60,7 +69,7 @@ module Searchkick
|
|
60
69
|
rescue => e # TODO rescue type
|
61
70
|
status_code = e.message[1..3].to_i
|
62
71
|
if status_code == 404
|
63
|
-
raise MissingIndexError, "Index missing - run #{
|
72
|
+
raise MissingIndexError, "Index missing - run #{reindex_command}"
|
64
73
|
elsif status_code == 500 && (
|
65
74
|
e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") ||
|
66
75
|
e.message.include?("No query registered for [multi_match]") ||
|
@@ -71,7 +80,7 @@ module Searchkick
|
|
71
80
|
raise UnsupportedVersionError, "This version of Searchkick requires Elasticsearch 1.0 or greater"
|
72
81
|
elsif status_code == 400
|
73
82
|
if e.message.include?("[multi_match] analyzer [searchkick_search] not found")
|
74
|
-
raise InvalidQueryError, "Bad mapping - run #{
|
83
|
+
raise InvalidQueryError, "Bad mapping - run #{reindex_command}"
|
75
84
|
else
|
76
85
|
raise InvalidQueryError, e.message
|
77
86
|
end
|
@@ -116,6 +125,10 @@ module Searchkick
|
|
116
125
|
|
117
126
|
private
|
118
127
|
|
128
|
+
def reindex_command
|
129
|
+
searchkick_klass ? "#{searchkick_klass.name}.reindex" : "reindex"
|
130
|
+
end
|
131
|
+
|
119
132
|
def execute_search
|
120
133
|
Searchkick.client.search(params)
|
121
134
|
end
|
@@ -536,6 +549,9 @@ module Searchkick
|
|
536
549
|
if (fragment_size = options[:highlight][:fragment_size])
|
537
550
|
payload[:highlight][:fragment_size] = fragment_size
|
538
551
|
end
|
552
|
+
if (encoder = options[:highlight][:encoder])
|
553
|
+
payload[:highlight][:encoder] = encoder
|
554
|
+
end
|
539
555
|
|
540
556
|
highlight_fields = options[:highlight][:fields]
|
541
557
|
if highlight_fields
|
@@ -558,7 +574,7 @@ module Searchkick
|
|
558
574
|
payload[:fields] = []
|
559
575
|
end
|
560
576
|
|
561
|
-
if options[:type] || klass != searchkick_klass
|
577
|
+
if options[:type] || (klass != searchkick_klass && searchkick_index)
|
562
578
|
@type = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v) }
|
563
579
|
end
|
564
580
|
|
data/lib/searchkick/version.rb
CHANGED
data/test/highlight_test.rb
CHANGED
@@ -36,6 +36,11 @@ class HighlightTest < Minitest::Test
|
|
36
36
|
assert_equal "<em>Hello</em> World <em>Hello</em>", Product.search("hello", fields: [:name], highlight: true).with_details.first[1][:highlight][:name]
|
37
37
|
end
|
38
38
|
|
39
|
+
def test_encoder
|
40
|
+
store_names ["<b>Hello</b>"]
|
41
|
+
assert_equal "<b><em>Hello</em></b>", Product.search("hello", fields: [:name], highlight: {encoder: "html"}, misspellings: false).with_details.first[1][:highlight][:name]
|
42
|
+
end
|
43
|
+
|
39
44
|
def test_json
|
40
45
|
skip if ENV["MATCH"] == "word_start"
|
41
46
|
store_names ["Two Door Cinema Club"]
|
data/test/index_test.rb
CHANGED
@@ -110,4 +110,11 @@ class IndexTest < Minitest::Test
|
|
110
110
|
end
|
111
111
|
assert_search "product", []
|
112
112
|
end
|
113
|
+
|
114
|
+
def test_analyzed_only
|
115
|
+
skip if nobrainer?
|
116
|
+
large_value = 10000.times.map { "hello" }.join(" ")
|
117
|
+
store [{name: "Product A", alt_description: large_value}]
|
118
|
+
assert_search "product", ["Product A"]
|
119
|
+
end
|
113
120
|
end
|
data/test/model_test.rb
CHANGED
@@ -33,4 +33,10 @@ class ModelTest < Minitest::Test
|
|
33
33
|
|
34
34
|
assert_search "product", ["product a", "product b"]
|
35
35
|
end
|
36
|
+
|
37
|
+
def test_multiple_models
|
38
|
+
store_names ["Product A"]
|
39
|
+
store_names ["Product B"], Store
|
40
|
+
assert_equal Product.all + Store.all, Searchkick.search("product", index_name: [Product, Store], order: "name").to_a
|
41
|
+
end
|
36
42
|
end
|
data/test/similar_test.rb
CHANGED
@@ -15,4 +15,14 @@ class SimilarTest < Minitest::Test
|
|
15
15
|
store_names ["Lucerne Milk Chocolate Fat Free", "Clover Fat Free Milk"]
|
16
16
|
assert_order "Lucerne Fat Free Chocolate Milk", ["Lucerne Milk Chocolate Fat Free", "Clover Fat Free Milk"], similar: true
|
17
17
|
end
|
18
|
+
|
19
|
+
def test_limit
|
20
|
+
store_names ["1% Organic Milk", "2% Organic Milk", "Fat Free Organic Milk", "Popcorn"]
|
21
|
+
assert_equal ["2% Organic Milk"], Product.where(name: "1% Organic Milk").first.similar(fields: ["name"], order: ["name"], limit: 1).map(&:name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_per_page
|
25
|
+
store_names ["1% Organic Milk", "2% Organic Milk", "Fat Free Organic Milk", "Popcorn"]
|
26
|
+
assert_equal ["2% Organic Milk"], Product.where(name: "1% Organic Milk").first.similar(fields: ["name"], order: ["name"], per_page: 1).map(&:name)
|
27
|
+
end
|
18
28
|
end
|
data/test/test_helper.rb
CHANGED
@@ -73,6 +73,7 @@ if defined?(Mongoid)
|
|
73
73
|
field :latitude, type: BigDecimal
|
74
74
|
field :longitude, type: BigDecimal
|
75
75
|
field :description
|
76
|
+
field :alt_description
|
76
77
|
end
|
77
78
|
|
78
79
|
class Store
|
@@ -114,6 +115,7 @@ elsif defined?(NoBrainer)
|
|
114
115
|
field :latitude
|
115
116
|
field :longitude
|
116
117
|
field :description, type: String
|
118
|
+
field :alt_description, type: String
|
117
119
|
|
118
120
|
belongs_to :store, validates: false
|
119
121
|
end
|
@@ -164,6 +166,7 @@ else
|
|
164
166
|
t.decimal :latitude, precision: 10, scale: 7
|
165
167
|
t.decimal :longitude, precision: 10, scale: 7
|
166
168
|
t.text :description
|
169
|
+
t.text :alt_description
|
167
170
|
t.timestamps null: true
|
168
171
|
end
|
169
172
|
|
@@ -219,6 +222,7 @@ class Product
|
|
219
222
|
highlight: [:name],
|
220
223
|
# unsearchable: [:description],
|
221
224
|
searchable: [:name, :color],
|
225
|
+
only_analyzed: [:alt_description],
|
222
226
|
match: ENV["MATCH"] ? ENV["MATCH"].to_sym : nil
|
223
227
|
|
224
228
|
attr_accessor :conversions, :user_ids, :aisle
|
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: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -219,3 +219,4 @@ test_files:
|
|
219
219
|
- test/synonyms_test.rb
|
220
220
|
- test/test_helper.rb
|
221
221
|
- test/where_test.rb
|
222
|
+
has_rdoc:
|