searchkick 1.1.2 → 1.2.0

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: 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: