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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01f20120d276745b8434ffaa0529c328f8870080
4
- data.tar.gz: 18756878052ac0bd566d025e7f87569992f903aa
3
+ metadata.gz: 49a9ad19b612e5079b3c97ec4ff756e62b8e2333
4
+ data.tar.gz: 2e9133f49f0287cabf5bc88fe2fd28e6594ffa27
5
5
  SHA512:
6
- metadata.gz: 51e077ad2d600a823fea927c02a9c76299423a923286bf483ac9d0caffd32632de2592b9116fb5e34bd7558216e171d8ce8cff78a43bf2900a394830580cbf6c
7
- data.tar.gz: 106cca0902d1a59b1de01380b6da09972c626075520e54a29e879740b90ff0ecf42f69d8fe16467308ed8c013526b51295a9e1e848ac1e0cec945aa524b309c0
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
@@ -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[:limit] ||= 10
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 = client.indices.get_aliases
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
@@ -1,54 +1,51 @@
1
1
  # based on https://gist.github.com/mnutt/566725
2
2
 
3
3
  module Searchkick
4
- class Query
5
- def execute_search_with_instrumentation
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
- execute_search_without_instrumentation
11
+ super
12
12
  end
13
13
  end
14
- alias_method_chain :execute_search, :instrumentation
15
14
  end
16
15
 
17
- class Index
18
- def store_with_instrumentation(record)
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
- store_without_instrumentation(record)
23
+ super(record)
25
24
  end
26
25
  end
27
- alias_method_chain :store, :instrumentation
28
26
 
29
- def remove_with_instrumentation(record)
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
- remove_without_instrumentation(record)
34
+ super(record)
36
35
  end
37
36
  end
38
- alias_method_chain :remove, :instrumentation
39
37
 
40
- def import_with_instrumentation(records)
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
- import_without_instrumentation(records)
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.method == :get && env.url.path.to_s.end_with?("/_search")
7
- env.request.timeout = Searchkick.search_timeout
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
@@ -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: options[:index_name] || searchkick_index.name,
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 #{searchkick_klass.name}.reindex"
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 #{searchkick_klass.name}.reindex"
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
 
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "1.1.2"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -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 "&lt;b&gt;<em>Hello</em>&lt;&#x2F;b&gt;", 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.1.2
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: 2015-12-18 00:00:00.000000000 Z
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: