searchkick-sinneduy 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.travis.yml +28 -0
  4. data/CHANGELOG.md +272 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +1109 -0
  8. data/Rakefile +8 -0
  9. data/ci/before_install.sh +14 -0
  10. data/gemfiles/activerecord31.gemfile +7 -0
  11. data/gemfiles/activerecord32.gemfile +7 -0
  12. data/gemfiles/activerecord40.gemfile +8 -0
  13. data/gemfiles/activerecord41.gemfile +8 -0
  14. data/gemfiles/mongoid2.gemfile +7 -0
  15. data/gemfiles/mongoid3.gemfile +6 -0
  16. data/gemfiles/mongoid4.gemfile +7 -0
  17. data/gemfiles/nobrainer.gemfile +6 -0
  18. data/lib/searchkick.rb +72 -0
  19. data/lib/searchkick/index.rb +550 -0
  20. data/lib/searchkick/logging.rb +136 -0
  21. data/lib/searchkick/model.rb +102 -0
  22. data/lib/searchkick/query.rb +567 -0
  23. data/lib/searchkick/reindex_job.rb +28 -0
  24. data/lib/searchkick/reindex_v2_job.rb +24 -0
  25. data/lib/searchkick/results.rb +158 -0
  26. data/lib/searchkick/tasks.rb +35 -0
  27. data/lib/searchkick/version.rb +3 -0
  28. data/searchkick.gemspec +28 -0
  29. data/test/autocomplete_test.rb +67 -0
  30. data/test/boost_test.rb +126 -0
  31. data/test/facets_test.rb +91 -0
  32. data/test/highlight_test.rb +58 -0
  33. data/test/index_test.rb +119 -0
  34. data/test/inheritance_test.rb +80 -0
  35. data/test/match_test.rb +163 -0
  36. data/test/model_test.rb +38 -0
  37. data/test/query_test.rb +14 -0
  38. data/test/reindex_job_test.rb +33 -0
  39. data/test/reindex_v2_job_test.rb +34 -0
  40. data/test/routing_test.rb +14 -0
  41. data/test/should_index_test.rb +34 -0
  42. data/test/similar_test.rb +20 -0
  43. data/test/sql_test.rb +327 -0
  44. data/test/suggest_test.rb +82 -0
  45. data/test/synonyms_test.rb +50 -0
  46. data/test/test_helper.rb +276 -0
  47. metadata +194 -0
@@ -0,0 +1,82 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestSuggest < Minitest::Test
4
+
5
+ def test_basic
6
+ store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
7
+ assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [:name]
8
+ end
9
+
10
+ def test_perfect
11
+ store_names ["Tiger Shark", "Great White Shark"]
12
+ assert_suggest "Tiger Shark", nil, fields: [:name] # no correction
13
+ end
14
+
15
+ def test_phrase
16
+ store_names ["Big Tiger Shark", "Tiger Sharp Teeth", "Tiger Sharp Mind"]
17
+ assert_suggest "How to catch a big tiger shar", "how to catch a big tiger shark", fields: [:name]
18
+ end
19
+
20
+ def test_without_option
21
+ store_names ["hi"] # needed to prevent ElasticsearchException - seed 668
22
+ assert_raises(RuntimeError) { Product.search("hi").suggestions }
23
+ end
24
+
25
+ def test_multiple_fields
26
+ store [
27
+ {name: "Shark", color: "Sharp"},
28
+ {name: "Shark", color: "Sharp"}
29
+ ]
30
+ assert_suggest_all "shar", ["shark", "sharp"]
31
+ end
32
+
33
+ def test_multiple_fields_highest_score_first
34
+ store [
35
+ {name: "Tiger Shark", color: "Sharp"}
36
+ ]
37
+ assert_suggest "tiger shar", "tiger shark"
38
+ end
39
+
40
+ def test_multiple_fields_same_value
41
+ store [
42
+ {name: "Shark", color: "Shark"}
43
+ ]
44
+ assert_suggest_all "shar", ["shark"]
45
+ end
46
+
47
+ def test_fields_option
48
+ store [
49
+ {name: "Shark", color: "Sharp"}
50
+ ]
51
+ assert_suggest_all "shar", ["shark"], fields: [:name]
52
+ end
53
+
54
+ def test_fields_option_multiple
55
+ store [
56
+ {name: "Shark"}
57
+ ]
58
+ assert_suggest "shar", "shark", fields: [:name, :unknown]
59
+ end
60
+
61
+ def test_fields_partial_match
62
+ store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
63
+ assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [{name: :word_start}]
64
+ end
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
+
71
+ protected
72
+
73
+ def assert_suggest(term, expected, options = {})
74
+ assert_equal expected, Product.search(term, options.merge(suggest: true)).suggestions.first
75
+ end
76
+
77
+ # any order
78
+ def assert_suggest_all(term, expected, options = {})
79
+ assert_equal expected.sort, Product.search(term, options.merge(suggest: true)).suggestions.sort
80
+ end
81
+
82
+ end
@@ -0,0 +1,50 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestSynonyms < Minitest::Test
4
+
5
+ def test_bleach
6
+ store_names ["Clorox Bleach", "Kroger Bleach"]
7
+ assert_search "clorox", ["Clorox Bleach", "Kroger Bleach"]
8
+ end
9
+
10
+ def test_saran_wrap
11
+ store_names ["Saran Wrap", "Kroger Plastic Wrap"]
12
+ assert_search "saran wrap", ["Saran Wrap", "Kroger Plastic Wrap"]
13
+ end
14
+
15
+ def test_burger_buns
16
+ store_names ["Hamburger Buns"]
17
+ assert_search "burger buns", ["Hamburger Buns"]
18
+ end
19
+
20
+ def test_bandaids
21
+ store_names ["Band-Aid", "Kroger 12-Pack Bandages"]
22
+ assert_search "bandaids", ["Band-Aid", "Kroger 12-Pack Bandages"]
23
+ end
24
+
25
+ def test_qtips
26
+ store_names ["Q Tips", "Kroger Cotton Swabs"]
27
+ assert_search "q tips", ["Q Tips", "Kroger Cotton Swabs"]
28
+ end
29
+
30
+ def test_reverse
31
+ store_names ["Scallions"]
32
+ assert_search "green onions", ["Scallions"]
33
+ end
34
+
35
+ def test_exact
36
+ store_names ["Green Onions", "Yellow Onions"]
37
+ assert_search "scallion", ["Green Onions"]
38
+ end
39
+
40
+ def test_stemmed
41
+ store_names ["Green Onions", "Yellow Onions"]
42
+ assert_search "scallions", ["Green Onions"]
43
+ end
44
+
45
+ # def test_wordnet
46
+ # store_names ["Creature", "Beast", "Dragon"], Animal
47
+ # assert_search "animal", ["Creature", "Beast"], {}, Animal
48
+ # end
49
+
50
+ end
@@ -0,0 +1,276 @@
1
+ require "bundler/setup"
2
+ Bundler.require(:default)
3
+ require "minitest/autorun"
4
+ require "minitest/pride"
5
+ require "logger"
6
+
7
+ ENV["RACK_ENV"] = "test"
8
+
9
+ Minitest::Test = Minitest::Unit::TestCase unless defined?(Minitest::Test)
10
+
11
+ File.delete("elasticsearch.log") if File.exist?("elasticsearch.log")
12
+ Searchkick.client.transport.logger = Logger.new("elasticsearch.log")
13
+
14
+ puts "Running against Elasticsearch #{Searchkick.server_version}"
15
+
16
+ I18n.config.enforce_available_locales = true
17
+
18
+ ActiveJob::Base.logger = nil if defined?(ActiveJob)
19
+
20
+ if defined?(Mongoid)
21
+
22
+ def mongoid2?
23
+ Mongoid::VERSION.starts_with?("2.")
24
+ end
25
+
26
+ if mongoid2?
27
+ # enable comparison of BSON::ObjectIds
28
+ module BSON
29
+ class ObjectId
30
+ def <=>(other)
31
+ data <=> other.data
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ Mongoid.configure do |config|
38
+ if mongoid2?
39
+ config.master = Mongo::Connection.new.db("searchkick_test")
40
+ else
41
+ config.connect_to "searchkick_test"
42
+ end
43
+ end
44
+
45
+ class Product
46
+ include Mongoid::Document
47
+ include Mongoid::Timestamps
48
+
49
+ field :name
50
+ field :store_id, type: Integer
51
+ field :in_stock, type: Boolean
52
+ field :backordered, type: Boolean
53
+ field :orders_count, type: Integer
54
+ field :price, type: Integer
55
+ field :color
56
+ field :latitude, type: BigDecimal
57
+ field :longitude, type: BigDecimal
58
+ field :description
59
+ end
60
+
61
+ class Store
62
+ include Mongoid::Document
63
+
64
+ field :name
65
+ end
66
+
67
+ class Animal
68
+ include Mongoid::Document
69
+
70
+ field :name
71
+ end
72
+
73
+ class Dog < Animal
74
+ end
75
+
76
+ class Cat < Animal
77
+ end
78
+ elsif defined?(NoBrainer)
79
+ NoBrainer.configure do |config|
80
+ config.app_name = :searchkick
81
+ config.environment = :test
82
+ end
83
+
84
+ class Product
85
+ include NoBrainer::Document
86
+ include NoBrainer::Document::Timestamps
87
+
88
+ field :name, type: String
89
+ field :store_id, type: Integer
90
+ field :in_stock, type: Boolean
91
+ field :backordered, type: Boolean
92
+ field :orders_count, type: Integer
93
+ field :price, type: Integer
94
+ field :color, type: String
95
+ field :latitude
96
+ field :longitude
97
+ field :description, type: String
98
+ end
99
+
100
+ class Store
101
+ include NoBrainer::Document
102
+
103
+ field :name, type: String
104
+ end
105
+
106
+ class Animal
107
+ include NoBrainer::Document
108
+
109
+ field :name, type: String
110
+ end
111
+
112
+ class Dog < Animal
113
+ end
114
+
115
+ class Cat < Animal
116
+ end
117
+ else
118
+ require "active_record"
119
+
120
+ # for debugging
121
+ # ActiveRecord::Base.logger = Logger.new(STDOUT)
122
+
123
+ # rails does this in activerecord/lib/active_record/railtie.rb
124
+ ActiveRecord::Base.default_timezone = :utc
125
+ ActiveRecord::Base.time_zone_aware_attributes = true
126
+
127
+ # migrations
128
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
129
+
130
+ ActiveRecord::Base.raise_in_transactional_callbacks = true if ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks=)
131
+
132
+ ActiveRecord::Migration.create_table :products do |t|
133
+ t.string :name
134
+ t.integer :store_id
135
+ t.boolean :in_stock
136
+ t.boolean :backordered
137
+ t.integer :orders_count
138
+ t.integer :price
139
+ t.string :color
140
+ t.decimal :latitude, precision: 10, scale: 7
141
+ t.decimal :longitude, precision: 10, scale: 7
142
+ t.text :description
143
+ t.timestamps null: true
144
+ end
145
+
146
+ ActiveRecord::Migration.create_table :stores do |t|
147
+ t.string :name
148
+ end
149
+
150
+ ActiveRecord::Migration.create_table :animals do |t|
151
+ t.string :name
152
+ t.string :type
153
+ end
154
+
155
+ class Product < ActiveRecord::Base
156
+ end
157
+
158
+ class Store < ActiveRecord::Base
159
+ end
160
+
161
+ class Animal < ActiveRecord::Base
162
+ end
163
+
164
+ class Dog < Animal
165
+ end
166
+
167
+ class Cat < Animal
168
+ end
169
+ end
170
+
171
+ class Product
172
+ belongs_to :store
173
+
174
+ searchkick \
175
+ synonyms: [
176
+ ["clorox", "bleach"],
177
+ ["scallion", "greenonion"],
178
+ ["saranwrap", "plasticwrap"],
179
+ ["qtip", "cottonswab"],
180
+ ["burger", "hamburger"],
181
+ ["bandaid", "bandag"]
182
+ ],
183
+ autocomplete: [:name],
184
+ suggest: [:name, :color],
185
+ conversions: "conversions",
186
+ personalize: "user_ids",
187
+ locations: ["location", "multiple_locations"],
188
+ text_start: [:name],
189
+ text_middle: [:name],
190
+ text_end: [:name],
191
+ word_start: [:name],
192
+ word_middle: [:name],
193
+ word_end: [:name],
194
+ highlight: [:name],
195
+ unsearchable: [:description]
196
+
197
+ attr_accessor :conversions, :user_ids, :aisle
198
+
199
+ def search_data
200
+ serializable_hash.except("id").merge(
201
+ conversions: conversions,
202
+ user_ids: user_ids,
203
+ location: [latitude, longitude],
204
+ multiple_locations: [[latitude, longitude], [0, 0]],
205
+ aisle: aisle
206
+ )
207
+ end
208
+
209
+ def should_index?
210
+ name != "DO NOT INDEX"
211
+ end
212
+ end
213
+
214
+ class Store
215
+ searchkick \
216
+ routing: :name,
217
+ merge_mappings: true,
218
+ mappings: {
219
+ store: {
220
+ properties: {
221
+ name: {type: "string", analyzer: "keyword"},
222
+ }
223
+ }
224
+ }
225
+ end
226
+
227
+ class Animal
228
+ searchkick \
229
+ autocomplete: [:name],
230
+ suggest: [:name],
231
+ index_name: -> { "#{name.tableize}-#{Date.today.year}" }
232
+ # wordnet: true
233
+ end
234
+
235
+ Product.searchkick_index.delete if Product.searchkick_index.exists?
236
+ Product.reindex
237
+ Product.reindex # run twice for both index paths
238
+
239
+ Store.reindex
240
+ Animal.reindex
241
+
242
+ class Minitest::Test
243
+
244
+ def setup
245
+ Product.destroy_all
246
+ Store.destroy_all
247
+ Animal.destroy_all
248
+ end
249
+
250
+ protected
251
+
252
+ def store(documents, klass = Product)
253
+ documents.shuffle.each do |document|
254
+ klass.create!(document)
255
+ end
256
+ klass.searchkick_index.refresh
257
+ end
258
+
259
+ def store_names(names, klass = Product)
260
+ store names.map { |name| {name: name} }, klass
261
+ end
262
+
263
+ # no order
264
+ def assert_search(term, expected, options = {}, klass = Product)
265
+ assert_equal expected.sort, klass.search(term, options).map(&:name).sort
266
+ end
267
+
268
+ def assert_order(term, expected, options = {}, klass = Product)
269
+ assert_equal expected, klass.search(term, options).map(&:name)
270
+ end
271
+
272
+ def assert_first(term, expected, options = {}, klass = Product)
273
+ assert_equal expected, klass.search(term, options).map(&:name).first
274
+ end
275
+
276
+ end
metadata ADDED
@@ -0,0 +1,194 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: searchkick-sinneduy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: elasticsearch-sinneduy
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.13
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.13
41
+ - !ruby/object:Gem::Dependency
42
+ name: hashie
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Intelligent search made easy
98
+ email:
99
+ - andrew@chartkick.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - .travis.yml
106
+ - CHANGELOG.md
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - ci/before_install.sh
112
+ - gemfiles/activerecord31.gemfile
113
+ - gemfiles/activerecord32.gemfile
114
+ - gemfiles/activerecord40.gemfile
115
+ - gemfiles/activerecord41.gemfile
116
+ - gemfiles/mongoid2.gemfile
117
+ - gemfiles/mongoid3.gemfile
118
+ - gemfiles/mongoid4.gemfile
119
+ - gemfiles/nobrainer.gemfile
120
+ - lib/searchkick.rb
121
+ - lib/searchkick/index.rb
122
+ - lib/searchkick/logging.rb
123
+ - lib/searchkick/model.rb
124
+ - lib/searchkick/query.rb
125
+ - lib/searchkick/reindex_job.rb
126
+ - lib/searchkick/reindex_v2_job.rb
127
+ - lib/searchkick/results.rb
128
+ - lib/searchkick/tasks.rb
129
+ - lib/searchkick/version.rb
130
+ - searchkick.gemspec
131
+ - test/autocomplete_test.rb
132
+ - test/boost_test.rb
133
+ - test/facets_test.rb
134
+ - test/highlight_test.rb
135
+ - test/index_test.rb
136
+ - test/inheritance_test.rb
137
+ - test/match_test.rb
138
+ - test/model_test.rb
139
+ - test/query_test.rb
140
+ - test/reindex_job_test.rb
141
+ - test/reindex_v2_job_test.rb
142
+ - test/routing_test.rb
143
+ - test/should_index_test.rb
144
+ - test/similar_test.rb
145
+ - test/sql_test.rb
146
+ - test/suggest_test.rb
147
+ - test/synonyms_test.rb
148
+ - test/test_helper.rb
149
+ homepage: https://github.com/ankane/searchkick
150
+ licenses:
151
+ - MIT
152
+ metadata: {}
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - '>='
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubyforge_project:
169
+ rubygems_version: 2.0.14
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Searchkick learns what your users are looking for. As more people search,
173
+ it gets smarter and the results get better. It’s friendly for developers - and magical
174
+ for your users.
175
+ test_files:
176
+ - test/autocomplete_test.rb
177
+ - test/boost_test.rb
178
+ - test/facets_test.rb
179
+ - test/highlight_test.rb
180
+ - test/index_test.rb
181
+ - test/inheritance_test.rb
182
+ - test/match_test.rb
183
+ - test/model_test.rb
184
+ - test/query_test.rb
185
+ - test/reindex_job_test.rb
186
+ - test/reindex_v2_job_test.rb
187
+ - test/routing_test.rb
188
+ - test/should_index_test.rb
189
+ - test/similar_test.rb
190
+ - test/sql_test.rb
191
+ - test/suggest_test.rb
192
+ - test/synonyms_test.rb
193
+ - test/test_helper.rb
194
+ has_rdoc: