searchkick 0.8.5 → 0.8.6

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.
@@ -1,9 +1,13 @@
1
1
  module Searchkick
2
+ module Reindex; end # legacy for Searchjoy
3
+
2
4
  module Model
3
5
 
4
6
  def searchkick(options = {})
5
7
  raise "Only call searchkick once per model" if respond_to?(:searchkick_index)
6
8
 
9
+ Searchkick.models << self
10
+
7
11
  class_eval do
8
12
  cattr_reader :searchkick_options, :searchkick_klass
9
13
 
@@ -14,32 +18,51 @@ module Searchkick
14
18
  class_variable_set :@@searchkick_callbacks, callbacks
15
19
  class_variable_set :@@searchkick_index, options[:index_name] || [options[:index_prefix], model_name.plural, Searchkick.env].compact.join("_")
16
20
 
17
- def self.searchkick_index
18
- index = class_variable_get :@@searchkick_index
19
- index = index.call if index.respond_to? :call
20
- Searchkick::Index.new(index)
21
+ define_singleton_method(Searchkick.search_method_name) do |term = nil, options={}, &block|
22
+ searchkick_index.search_model(self, term, options, &block)
21
23
  end
24
+ extend Searchkick::Reindex # legacy for Searchjoy
22
25
 
23
- define_singleton_method(Searchkick.search_method_name) do |term = nil, options={}, &block|
24
- query = Searchkick::Query.new(self, term, options)
25
- if block
26
- block.call(query.body)
26
+ class << self
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)
27
32
  end
28
- if options[:execute] == false
29
- query
30
- else
31
- query.execute
33
+
34
+ def enable_search_callbacks
35
+ class_variable_set :@@searchkick_callbacks, true
32
36
  end
33
- end
34
- extend Searchkick::Reindex
35
- include Searchkick::Similar
36
37
 
37
- def reindex_async
38
- if defined?(Searchkick::ReindexV2Job)
39
- Searchkick::ReindexV2Job.perform_later(self.class.name, id.to_s)
40
- else
41
- Delayed::Job.enqueue Searchkick::ReindexJob.new(self.class.name, id.to_s)
38
+ def disable_search_callbacks
39
+ class_variable_set :@@searchkick_callbacks, false
42
40
  end
41
+
42
+ def search_callbacks?
43
+ class_variable_get(:@@searchkick_callbacks) && Searchkick.callbacks?
44
+ end
45
+
46
+ def reindex(options = {})
47
+ searchkick_index.reindex_scope(searchkick_klass, options)
48
+ end
49
+
50
+ def clean_indices
51
+ searchkick_index.clean_indices
52
+ end
53
+
54
+ def searchkick_import(options = {})
55
+ (options[:index] || searchkick_index).import_scope(searchkick_klass)
56
+ end
57
+
58
+ def searchkick_create_index
59
+ searchkick_index.create_index
60
+ end
61
+
62
+ def searchkick_index_options
63
+ searchkick_index.index_options
64
+ end
65
+
43
66
  end
44
67
 
45
68
  if callbacks
@@ -52,38 +75,25 @@ module Searchkick
52
75
  end
53
76
  end
54
77
 
55
- def self.enable_search_callbacks
56
- class_variable_set :@@searchkick_callbacks, true
57
- end
58
-
59
- def self.disable_search_callbacks
60
- class_variable_set :@@searchkick_callbacks, false
61
- end
62
-
63
- def self.search_callbacks?
64
- class_variable_get(:@@searchkick_callbacks) && Searchkick.callbacks?
65
- end
78
+ def reindex
79
+ self.class.searchkick_index.reindex_record(self)
80
+ end unless method_defined?(:reindex)
66
81
 
67
- def should_index?
68
- true
69
- end
82
+ def reindex_async
83
+ self.class.searchkick_index.reindex_record_async(self)
84
+ end unless method_defined?(:reindex_async)
70
85
 
71
- def reindex
72
- index = self.class.searchkick_index
73
- if destroyed? or !should_index?
74
- begin
75
- index.remove self
76
- rescue Elasticsearch::Transport::Transport::Errors::NotFound
77
- # do nothing
78
- end
79
- else
80
- index.store self
81
- end
82
- end
86
+ def similar(options = {})
87
+ self.class.searchkick_index.similar_record(self, options)
88
+ end unless method_defined?(:similar)
83
89
 
84
90
  def search_data
85
91
  respond_to?(:to_hash) ? to_hash : serializable_hash
86
- end
92
+ end unless method_defined?(:search_data)
93
+
94
+ def should_index?
95
+ true
96
+ end unless method_defined?(:should_index?)
87
97
 
88
98
  end
89
99
  end
@@ -28,7 +28,7 @@ module Searchkick
28
28
  k, v = value.is_a?(Hash) ? value.to_a.first : [value, :word]
29
29
  k2, boost = k.to_s.split("^", 2)
30
30
  field = "#{k2}.#{v == :word ? "analyzed" : v}"
31
- boost_fields[field] = boost.to_i if boost
31
+ boost_fields[field] = boost.to_f if boost
32
32
  field
33
33
  end
34
34
  end
@@ -195,20 +195,21 @@ module Searchkick
195
195
  boost_where[personalize_field] = options[:user_id]
196
196
  end
197
197
  if options[:personalize]
198
- boost_where.merge!(options[:personalize])
198
+ boost_where = boost_where.merge(options[:personalize])
199
199
  end
200
200
  boost_where.each do |field, value|
201
- if value.is_a?(Hash)
201
+ if value.is_a?(Array) and value.first.is_a?(Hash)
202
+ value.each do |value_factor|
203
+ value, factor = value_factor[:value], value_factor[:factor]
204
+ custom_filters << custom_filter(field, value, factor)
205
+ end
206
+ elsif value.is_a?(Hash)
202
207
  value, factor = value[:value], value[:factor]
208
+ custom_filters << custom_filter(field, value, factor)
203
209
  else
204
210
  factor = 1000
211
+ custom_filters << custom_filter(field, value, factor)
205
212
  end
206
- custom_filters << {
207
- filter: {
208
- term: {field => value}
209
- },
210
- boost_factor: factor
211
- }
212
213
  end
213
214
 
214
215
  boost_by_distance = options[:boost_by_distance]
@@ -317,7 +318,7 @@ module Searchkick
317
318
 
318
319
  # intersection
319
320
  if options[:fields]
320
- suggest_fields = suggest_fields & options[:fields].map{|v| (v.is_a?(Hash) ? v.keys.first : v).to_s }
321
+ suggest_fields = suggest_fields & options[:fields].map{|v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
321
322
  end
322
323
 
323
324
  if suggest_fields.any?
@@ -535,5 +536,12 @@ module Searchkick
535
536
  end
536
537
  end
537
538
 
539
+ def custom_filter(field, value, factor)
540
+ {
541
+ filter: term_filters(field, value),
542
+ boost_factor: factor
543
+ }
544
+ end
545
+
538
546
  end
539
547
  end
@@ -24,7 +24,11 @@ module Searchkick
24
24
  hits.group_by{|hit, i| hit["_type"] }.each do |type, grouped_hits|
25
25
  records = type.camelize.constantize
26
26
  if options[:includes]
27
- records = records.includes(options[:includes])
27
+ if defined?(NoBrainer::Document) and records < NoBrainer::Document
28
+ records = records.preload(options[:includes])
29
+ else
30
+ records = records.includes(options[:includes])
31
+ end
28
32
  end
29
33
  results[type] = results_query(records, grouped_hits)
30
34
  end
@@ -143,6 +147,9 @@ module Searchkick
143
147
  elsif records.respond_to?(:queryable)
144
148
  # Mongoid 3+
145
149
  records.queryable.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
150
+ elsif records.respond_to?(:unscoped) and records.all.respond_to?(:preload)
151
+ # Nobrainer
152
+ records.unscoped.where(:id.in => grouped_hits.map{|hit| hit["_id"] }).to_a
146
153
  else
147
154
  raise "Not sure how to load records"
148
155
  end
@@ -22,7 +22,7 @@ namespace :searchkick do
22
22
  desc "reindex all models"
23
23
  task :all => :environment do
24
24
  Rails.application.eager_load!
25
- (Searchkick::Reindex.instance_variable_get(:@descendents) || []).each do |model|
25
+ Searchkick.models.each do |model|
26
26
  puts "Reindexing #{model.name}..."
27
27
  model.reindex
28
28
  end
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "0.8.5"
2
+ VERSION = "0.8.6"
3
3
  end
@@ -75,6 +75,14 @@ class TestBoost < Minitest::Test
75
75
  assert_order "red", ["Red", "White"], fields: ["name^10", "color"]
76
76
  end
77
77
 
78
+ def test_boost_fields_decimal
79
+ store [
80
+ {name: "Red", color: "White"},
81
+ {name: "White", color: "Red Red Red"}
82
+ ]
83
+ assert_order "red", ["Red", "White"], fields: ["name^10.5", "color"]
84
+ end
85
+
78
86
  def test_boost_fields_word_start
79
87
  store [
80
88
  {name: "Red", color: "White"},
@@ -96,12 +104,14 @@ class TestBoost < Minitest::Test
96
104
  def test_boost_where
97
105
  store [
98
106
  {name: "Tomato A"},
99
- {name: "Tomato B", user_ids: [1, 2, 3]},
100
- {name: "Tomato C"},
101
- {name: "Tomato D"}
107
+ {name: "Tomato B", user_ids: [1, 2]},
108
+ {name: "Tomato C", user_ids: [3]}
102
109
  ]
103
110
  assert_first "tomato", "Tomato B", boost_where: {user_ids: 2}
111
+ assert_first "tomato", "Tomato B", boost_where: {user_ids: [1, 4]}
104
112
  assert_first "tomato", "Tomato B", boost_where: {user_ids: {value: 2, factor: 10}}
113
+ assert_first "tomato", "Tomato B", boost_where: {user_ids: {value: [1, 4], factor: 10}}
114
+ assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], boost_where: {user_ids: [{value: 1, factor: 10}, {value: 3, factor: 20}]}
105
115
  end
106
116
 
107
117
  def test_boost_by_distance
@@ -1,4 +1,5 @@
1
1
  require_relative "test_helper"
2
+ require "active_support/core_ext"
2
3
 
3
4
  class TestFacets < Minitest::Test
4
5
 
@@ -312,7 +312,7 @@ class TestSql < Minitest::Test
312
312
  end
313
313
 
314
314
  # TODO see if Mongoid is loaded
315
- if !defined?(Mongoid)
315
+ unless defined?(Mongoid) or defined?(NoBrainer)
316
316
  def test_include
317
317
  store_names ["Product A"]
318
318
  assert Product.search("product", include: [:store]).first.association(:store).loaded?
@@ -18,6 +18,7 @@ class TestSuggest < Minitest::Test
18
18
  end
19
19
 
20
20
  def test_without_option
21
+ store_names ["hi"] # needed to prevent ElasticsearchException - seed 668
21
22
  assert_raises(RuntimeError){ Product.search("hi").suggestions }
22
23
  end
23
24
 
@@ -57,11 +58,16 @@ class TestSuggest < Minitest::Test
57
58
  assert_suggest "shar", "shark", fields: [:name, :unknown]
58
59
  end
59
60
 
60
- def test_fields_word_start
61
+ def test_fields_partial_match
61
62
  store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
62
63
  assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [{name: :word_start}]
63
64
  end
64
65
 
66
+ def test_fields_partial_match_boost
67
+ store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
68
+ assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [{"name^2" => :word_start}]
69
+ end
70
+
65
71
  protected
66
72
 
67
73
  def assert_suggest(term, expected, options = {})
@@ -71,6 +71,45 @@ if defined?(Mongoid)
71
71
  class Dog < Animal
72
72
  end
73
73
 
74
+ class Cat < Animal
75
+ end
76
+ elsif defined?(NoBrainer)
77
+ NoBrainer.configure do |config|
78
+ config.app_name = :searchkick
79
+ config.environment = :test
80
+ end
81
+
82
+ class Product
83
+ include NoBrainer::Document
84
+ include NoBrainer::Document::Timestamps
85
+
86
+ field :name, type: String
87
+ field :store_id, type: Integer
88
+ field :in_stock, type: Boolean
89
+ field :backordered, type: Boolean
90
+ field :orders_count, type: Integer
91
+ field :price, type: Integer
92
+ field :color, type: String
93
+ field :latitude
94
+ field :longitude
95
+ field :description, type: String
96
+ end
97
+
98
+ class Store
99
+ include NoBrainer::Document
100
+
101
+ field :name, type: String
102
+ end
103
+
104
+ class Animal
105
+ include NoBrainer::Document
106
+
107
+ field :name, type: String
108
+ end
109
+
110
+ class Dog < Animal
111
+ end
112
+
74
113
  class Cat < Animal
75
114
  end
76
115
  else
@@ -86,6 +125,8 @@ else
86
125
  # migrations
87
126
  ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
88
127
 
128
+ ActiveRecord::Base.raise_in_transactional_callbacks = true if ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks=)
129
+
89
130
  ActiveRecord::Migration.create_table :products do |t|
90
131
  t.string :name
91
132
  t.integer :store_id
@@ -97,7 +138,7 @@ else
97
138
  t.decimal :latitude, precision: 10, scale: 7
98
139
  t.decimal :longitude, precision: 10, scale: 7
99
140
  t.text :description
100
- t.timestamps
141
+ t.timestamps null: true
101
142
  end
102
143
 
103
144
  ActiveRecord::Migration.create_table :stores do |t|
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.8.5
4
+ version: 0.8.6
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-11-11 00:00:00.000000000 Z
11
+ date: 2015-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -108,22 +108,23 @@ files:
108
108
  - LICENSE.txt
109
109
  - README.md
110
110
  - Rakefile
111
+ - ci/before_install.sh
111
112
  - gemfiles/activerecord31.gemfile
112
113
  - gemfiles/activerecord32.gemfile
113
114
  - gemfiles/activerecord40.gemfile
115
+ - gemfiles/activerecord41.gemfile
114
116
  - gemfiles/mongoid2.gemfile
115
117
  - gemfiles/mongoid3.gemfile
116
118
  - gemfiles/mongoid4.gemfile
119
+ - gemfiles/nobrainer.gemfile
117
120
  - lib/searchkick.rb
118
121
  - lib/searchkick/index.rb
119
122
  - lib/searchkick/logging.rb
120
123
  - lib/searchkick/model.rb
121
124
  - lib/searchkick/query.rb
122
- - lib/searchkick/reindex.rb
123
125
  - lib/searchkick/reindex_job.rb
124
126
  - lib/searchkick/reindex_v2_job.rb
125
127
  - lib/searchkick/results.rb
126
- - lib/searchkick/similar.rb
127
128
  - lib/searchkick/tasks.rb
128
129
  - lib/searchkick/version.rb
129
130
  - searchkick.gemspec
@@ -164,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
165
  version: '0'
165
166
  requirements: []
166
167
  rubyforge_project:
167
- rubygems_version: 2.2.2
168
+ rubygems_version: 2.4.5
168
169
  signing_key:
169
170
  specification_version: 4
170
171
  summary: Searchkick learns what your users are looking for. As more people search,
@@ -1,339 +0,0 @@
1
- module Searchkick
2
- module Reindex
3
-
4
- # https://gist.github.com/jarosan/3124884
5
- # http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/
6
- def reindex(options = {})
7
- skip_import = options[:import] == false
8
-
9
- alias_name = searchkick_index.name
10
- new_name = "#{alias_name}_#{Time.now.strftime('%Y%m%d%H%M%S%L')}"
11
- index = Searchkick::Index.new(new_name)
12
-
13
- clean_indices
14
-
15
- index.create searchkick_index_options
16
-
17
- # check if alias exists
18
- if Searchkick.client.indices.exists_alias(name: alias_name)
19
- # import before swap
20
- searchkick_import(index: index) unless skip_import
21
-
22
- # get existing indices to remove
23
- old_indices = Searchkick.client.indices.get_alias(name: alias_name).keys
24
- actions = old_indices.map{|name| {remove: {index: name, alias: alias_name}} } + [{add: {index: new_name, alias: alias_name}}]
25
- Searchkick.client.indices.update_aliases body: {actions: actions}
26
- clean_indices
27
- else
28
- searchkick_index.delete if searchkick_index.exists?
29
- Searchkick.client.indices.update_aliases body: {actions: [{add: {index: new_name, alias: alias_name}}]}
30
-
31
- # import after swap
32
- searchkick_import(index: index) unless skip_import
33
- end
34
-
35
- index.refresh
36
-
37
- true
38
- end
39
-
40
- # remove old indices that start w/ index_name
41
- def clean_indices
42
- all_indices = Searchkick.client.indices.get_aliases
43
- indices = all_indices.select{|k, v| (v.empty? || v["aliases"].empty?) && k =~ /\A#{Regexp.escape(searchkick_index.name)}_\d{14,17}\z/ }.keys
44
- indices.each do |index|
45
- Searchkick::Index.new(index).delete
46
- end
47
- indices
48
- end
49
-
50
- def self.extended(klass)
51
- @descendents ||= []
52
- @descendents << klass unless @descendents.include?(klass)
53
- end
54
-
55
- def searchkick_import(options = {})
56
- index = options[:index] || searchkick_index
57
- batch_size = searchkick_options[:batch_size] || 1000
58
-
59
- # use scope for import
60
- scope = searchkick_klass
61
- scope = scope.search_import if scope.respond_to?(:search_import)
62
- if scope.respond_to?(:find_in_batches)
63
- scope.find_in_batches batch_size: batch_size do |batch|
64
- index.import batch.select{|item| item.should_index? }
65
- end
66
- else
67
- # https://github.com/karmi/tire/blob/master/lib/tire/model/import.rb
68
- # use cursor for Mongoid
69
- items = []
70
- scope.all.each do |item|
71
- items << item if item.should_index?
72
- if items.length == batch_size
73
- index.import items
74
- items = []
75
- end
76
- end
77
- index.import items
78
- end
79
- end
80
-
81
- def searchkick_index_options
82
- options = searchkick_options
83
-
84
- if options[:mappings] and !options[:merge_mappings]
85
- settings = options[:settings] || {}
86
- mappings = options[:mappings]
87
- else
88
- settings = {
89
- analysis: {
90
- analyzer: {
91
- searchkick_keyword: {
92
- type: "custom",
93
- tokenizer: "keyword",
94
- filter: ["lowercase", "searchkick_stemmer"]
95
- },
96
- default_index: {
97
- type: "custom",
98
- tokenizer: "standard",
99
- # synonym should come last, after stemming and shingle
100
- # shingle must come before searchkick_stemmer
101
- filter: ["standard", "lowercase", "asciifolding", "searchkick_index_shingle", "searchkick_stemmer"]
102
- },
103
- searchkick_search: {
104
- type: "custom",
105
- tokenizer: "standard",
106
- filter: ["standard", "lowercase", "asciifolding", "searchkick_search_shingle", "searchkick_stemmer"]
107
- },
108
- searchkick_search2: {
109
- type: "custom",
110
- tokenizer: "standard",
111
- filter: ["standard", "lowercase", "asciifolding", "searchkick_stemmer"]
112
- },
113
- # https://github.com/leschenko/elasticsearch_autocomplete/blob/master/lib/elasticsearch_autocomplete/analyzers.rb
114
- searchkick_autocomplete_index: {
115
- type: "custom",
116
- tokenizer: "searchkick_autocomplete_ngram",
117
- filter: ["lowercase", "asciifolding"]
118
- },
119
- searchkick_autocomplete_search: {
120
- type: "custom",
121
- tokenizer: "keyword",
122
- filter: ["lowercase", "asciifolding"]
123
- },
124
- searchkick_word_search: {
125
- type: "custom",
126
- tokenizer: "standard",
127
- filter: ["lowercase", "asciifolding"]
128
- },
129
- searchkick_suggest_index: {
130
- type: "custom",
131
- tokenizer: "standard",
132
- filter: ["lowercase", "asciifolding", "searchkick_suggest_shingle"]
133
- },
134
- searchkick_text_start_index: {
135
- type: "custom",
136
- tokenizer: "keyword",
137
- filter: ["lowercase", "asciifolding", "searchkick_edge_ngram"]
138
- },
139
- searchkick_text_middle_index: {
140
- type: "custom",
141
- tokenizer: "keyword",
142
- filter: ["lowercase", "asciifolding", "searchkick_ngram"]
143
- },
144
- searchkick_text_end_index: {
145
- type: "custom",
146
- tokenizer: "keyword",
147
- filter: ["lowercase", "asciifolding", "reverse", "searchkick_edge_ngram", "reverse"]
148
- },
149
- searchkick_word_start_index: {
150
- type: "custom",
151
- tokenizer: "standard",
152
- filter: ["lowercase", "asciifolding", "searchkick_edge_ngram"]
153
- },
154
- searchkick_word_middle_index: {
155
- type: "custom",
156
- tokenizer: "standard",
157
- filter: ["lowercase", "asciifolding", "searchkick_ngram"]
158
- },
159
- searchkick_word_end_index: {
160
- type: "custom",
161
- tokenizer: "standard",
162
- filter: ["lowercase", "asciifolding", "reverse", "searchkick_edge_ngram", "reverse"]
163
- }
164
- },
165
- filter: {
166
- searchkick_index_shingle: {
167
- type: "shingle",
168
- token_separator: ""
169
- },
170
- # lucky find http://web.archiveorange.com/archive/v/AAfXfQ17f57FcRINsof7
171
- searchkick_search_shingle: {
172
- type: "shingle",
173
- token_separator: "",
174
- output_unigrams: false,
175
- output_unigrams_if_no_shingles: true
176
- },
177
- searchkick_suggest_shingle: {
178
- type: "shingle",
179
- max_shingle_size: 5
180
- },
181
- searchkick_edge_ngram: {
182
- type: "edgeNGram",
183
- min_gram: 1,
184
- max_gram: 50
185
- },
186
- searchkick_ngram: {
187
- type: "nGram",
188
- min_gram: 1,
189
- max_gram: 50
190
- },
191
- searchkick_stemmer: {
192
- type: "snowball",
193
- language: options[:language] || "English"
194
- }
195
- },
196
- tokenizer: {
197
- searchkick_autocomplete_ngram: {
198
- type: "edgeNGram",
199
- min_gram: 1,
200
- max_gram: 50
201
- }
202
- }
203
- }
204
- }
205
-
206
- if Searchkick.env == "test"
207
- settings.merge!(number_of_shards: 1, number_of_replicas: 0)
208
- end
209
-
210
- settings.deep_merge!(options[:settings] || {})
211
-
212
- # synonyms
213
- synonyms = options[:synonyms] || []
214
- if synonyms.any?
215
- settings[:analysis][:filter][:searchkick_synonym] = {
216
- type: "synonym",
217
- synonyms: synonyms.select{|s| s.size > 1 }.map{|s| s.join(",") }
218
- }
219
- # choosing a place for the synonym filter when stemming is not easy
220
- # https://groups.google.com/forum/#!topic/elasticsearch/p7qcQlgHdB8
221
- # TODO use a snowball stemmer on synonyms when creating the token filter
222
-
223
- # http://elasticsearch-users.115913.n3.nabble.com/synonym-multi-words-search-td4030811.html
224
- # I find the following approach effective if you are doing multi-word synonyms (synonym phrases):
225
- # - Only apply the synonym expansion at index time
226
- # - Don't have the synonym filter applied search
227
- # - Use directional synonyms where appropriate. You want to make sure that you're not injecting terms that are too general.
228
- settings[:analysis][:analyzer][:default_index][:filter].insert(4, "searchkick_synonym")
229
- settings[:analysis][:analyzer][:default_index][:filter] << "searchkick_synonym"
230
- end
231
-
232
- if options[:wordnet]
233
- settings[:analysis][:filter][:searchkick_wordnet] = {
234
- type: "synonym",
235
- format: "wordnet",
236
- synonyms_path: Searchkick.wordnet_path
237
- }
238
-
239
- settings[:analysis][:analyzer][:default_index][:filter].insert(4, "searchkick_wordnet")
240
- settings[:analysis][:analyzer][:default_index][:filter] << "searchkick_wordnet"
241
- end
242
-
243
- if options[:special_characters] == false
244
- settings[:analysis][:analyzer].each do |analyzer, analyzer_settings|
245
- analyzer_settings[:filter].reject!{|f| f == "asciifolding" }
246
- end
247
- end
248
-
249
- mapping = {}
250
-
251
- # conversions
252
- if options[:conversions]
253
- mapping[:conversions] = {
254
- type: "nested",
255
- properties: {
256
- query: {type: "string", analyzer: "searchkick_keyword"},
257
- count: {type: "integer"}
258
- }
259
- }
260
- end
261
-
262
- mapping_options = Hash[
263
- [:autocomplete, :suggest, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight]
264
- .map{|type| [type, (options[type] || []).map(&:to_s)] }
265
- ]
266
-
267
- mapping_options.values.flatten.uniq.each do |field|
268
- field_mapping = {
269
- type: "multi_field",
270
- fields: {
271
- field => {type: "string", index: "not_analyzed"},
272
- "analyzed" => {type: "string", index: "analyzed"}
273
- # term_vector: "with_positions_offsets" for fast / correct highlighting
274
- # http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html#_fast_vector_highlighter
275
- }
276
- }
277
-
278
- mapping_options.except(:highlight).each do |type, fields|
279
- if fields.include?(field)
280
- field_mapping[:fields][type] = {type: "string", index: "analyzed", analyzer: "searchkick_#{type}_index"}
281
- end
282
- end
283
-
284
- if mapping_options[:highlight].include?(field)
285
- field_mapping[:fields]["analyzed"][:term_vector] = "with_positions_offsets"
286
- end
287
-
288
- mapping[field] = field_mapping
289
- end
290
-
291
- (options[:locations] || []).map(&:to_s).each do |field|
292
- mapping[field] = {
293
- type: "geo_point"
294
- }
295
- end
296
-
297
- (options[:unsearchable] || []).map(&:to_s).each do |field|
298
- mapping[field] = {
299
- type: "string",
300
- index: "no"
301
- }
302
- end
303
-
304
- mappings = {
305
- _default_: {
306
- properties: mapping,
307
- # https://gist.github.com/kimchy/2898285
308
- dynamic_templates: [
309
- {
310
- string_template: {
311
- match: "*",
312
- match_mapping_type: "string",
313
- mapping: {
314
- # http://www.elasticsearch.org/guide/reference/mapping/multi-field-type/
315
- type: "multi_field",
316
- fields: {
317
- # analyzed field must be the default field for include_in_all
318
- # http://www.elasticsearch.org/guide/reference/mapping/multi-field-type/
319
- # however, we can include the not_analyzed field in _all
320
- # and the _all index analyzer will take care of it
321
- "{name}" => {type: "string", index: "not_analyzed"},
322
- "analyzed" => {type: "string", index: "analyzed"}
323
- }
324
- }
325
- }
326
- }
327
- ]
328
- }
329
- }.deep_merge(options[:mappings] || {})
330
- end
331
-
332
- {
333
- settings: settings,
334
- mappings: mappings
335
- }
336
- end
337
-
338
- end
339
- end