searchkick 0.8.5 → 0.8.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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