searchkick_bharthur 0.0.1
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 +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +44 -0
- data/CHANGELOG.md +360 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +1443 -0
- data/Rakefile +8 -0
- data/lib/searchkick/index.rb +662 -0
- data/lib/searchkick/logging.rb +185 -0
- data/lib/searchkick/middleware.rb +12 -0
- data/lib/searchkick/model.rb +105 -0
- data/lib/searchkick/query.rb +845 -0
- data/lib/searchkick/reindex_job.rb +26 -0
- data/lib/searchkick/reindex_v2_job.rb +23 -0
- data/lib/searchkick/results.rb +211 -0
- data/lib/searchkick/tasks.rb +33 -0
- data/lib/searchkick/version.rb +3 -0
- data/lib/searchkick.rb +159 -0
- data/searchkick.gemspec +28 -0
- data/test/aggs_test.rb +115 -0
- data/test/autocomplete_test.rb +65 -0
- data/test/boost_test.rb +144 -0
- data/test/callbacks_test.rb +27 -0
- data/test/ci/before_install.sh +21 -0
- data/test/dangerous_reindex_test.rb +27 -0
- data/test/facets_test.rb +90 -0
- data/test/gemfiles/activerecord31.gemfile +7 -0
- data/test/gemfiles/activerecord32.gemfile +7 -0
- data/test/gemfiles/activerecord40.gemfile +8 -0
- data/test/gemfiles/activerecord41.gemfile +8 -0
- data/test/gemfiles/activerecord50.gemfile +7 -0
- data/test/gemfiles/apartment.gemfile +8 -0
- data/test/gemfiles/mongoid2.gemfile +7 -0
- data/test/gemfiles/mongoid3.gemfile +6 -0
- data/test/gemfiles/mongoid4.gemfile +7 -0
- data/test/gemfiles/mongoid5.gemfile +7 -0
- data/test/gemfiles/nobrainer.gemfile +6 -0
- data/test/highlight_test.rb +63 -0
- data/test/index_test.rb +120 -0
- data/test/inheritance_test.rb +78 -0
- data/test/match_test.rb +227 -0
- data/test/misspellings_test.rb +46 -0
- data/test/model_test.rb +42 -0
- data/test/multi_search_test.rb +22 -0
- data/test/multi_tenancy_test.rb +22 -0
- data/test/order_test.rb +44 -0
- data/test/pagination_test.rb +53 -0
- data/test/query_test.rb +13 -0
- data/test/records_test.rb +8 -0
- data/test/reindex_job_test.rb +31 -0
- data/test/reindex_v2_job_test.rb +32 -0
- data/test/routing_test.rb +13 -0
- data/test/should_index_test.rb +32 -0
- data/test/similar_test.rb +28 -0
- data/test/sql_test.rb +196 -0
- data/test/suggest_test.rb +80 -0
- data/test/synonyms_test.rb +54 -0
- data/test/test_helper.rb +361 -0
- data/test/where_test.rb +171 -0
- metadata +231 -0
data/test/sql_test.rb
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class SqlTest < Minitest::Test
|
4
|
+
def test_partial
|
5
|
+
store_names ["Honey"]
|
6
|
+
assert_search "fresh honey", []
|
7
|
+
assert_search "fresh honey", ["Honey"], partial: true
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_operator
|
11
|
+
store_names ["Honey"]
|
12
|
+
assert_search "fresh honey", []
|
13
|
+
assert_search "fresh honey", ["Honey"], operator: "or"
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_fields_operator
|
17
|
+
store [
|
18
|
+
{name: "red", color: "red"},
|
19
|
+
{name: "blue", color: "blue"},
|
20
|
+
{name: "cyan", color: "blue green"},
|
21
|
+
{name: "magenta", color: "red blue"},
|
22
|
+
{name: "green", color: "green"}
|
23
|
+
]
|
24
|
+
assert_search "red blue", ["red", "blue", "cyan", "magenta"], operator: "or", fields: ["color"]
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_fields
|
28
|
+
store [
|
29
|
+
{name: "red", color: "light blue"},
|
30
|
+
{name: "blue", color: "red fish"}
|
31
|
+
]
|
32
|
+
assert_search "blue", ["red"], fields: ["color"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_non_existent_field
|
36
|
+
store_names ["Milk"]
|
37
|
+
assert_search "milk", [], fields: ["not_here"]
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_fields_both_match
|
41
|
+
store [
|
42
|
+
{name: "Blue A", color: "red"},
|
43
|
+
{name: "Blue B", color: "light blue"}
|
44
|
+
]
|
45
|
+
assert_first "blue", "Blue B", fields: [:name, :color]
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_big_decimal
|
49
|
+
store [
|
50
|
+
{name: "Product", latitude: 80.0}
|
51
|
+
]
|
52
|
+
assert_search "product", ["Product"], where: {latitude: {gt: 79}}
|
53
|
+
end
|
54
|
+
|
55
|
+
# load
|
56
|
+
|
57
|
+
def test_load_default
|
58
|
+
store_names ["Product A"]
|
59
|
+
assert_kind_of Product, Product.search("product").first
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_load_false
|
63
|
+
store_names ["Product A"]
|
64
|
+
assert_kind_of Hash, Product.search("product", load: false).first
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_load_false_methods
|
68
|
+
store_names ["Product A"]
|
69
|
+
assert_equal "Product A", Product.search("product", load: false).first.name
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_load_false_with_include
|
73
|
+
store_names ["Product A"]
|
74
|
+
assert_kind_of Hash, Product.search("product", load: false, include: [:store]).first
|
75
|
+
end
|
76
|
+
|
77
|
+
# select
|
78
|
+
|
79
|
+
def test_select
|
80
|
+
skip unless elasticsearch_below50?
|
81
|
+
store [{name: "Product A", store_id: 1}]
|
82
|
+
result = Product.search("product", load: false, select: [:name, :store_id]).first
|
83
|
+
assert_equal %w(id name store_id), result.keys.reject { |k| k.start_with?("_") }.sort
|
84
|
+
assert_equal ["Product A"], result.name # this is not great
|
85
|
+
assert_equal [1], result.store_id
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_select_array
|
89
|
+
skip unless elasticsearch_below50?
|
90
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
91
|
+
result = Product.search("product", load: false, select: [:user_ids]).first
|
92
|
+
assert_equal [1, 2], result.user_ids
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_select_single_field
|
96
|
+
skip unless elasticsearch_below50?
|
97
|
+
store [{name: "Product A", store_id: 1}]
|
98
|
+
result = Product.search("product", load: false, select: :name).first
|
99
|
+
assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
|
100
|
+
assert_equal ["Product A"], result.name
|
101
|
+
assert_nil result.store_id
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_select_all
|
105
|
+
skip unless elasticsearch_below50?
|
106
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
107
|
+
hit = Product.search("product", select: true).hits.first
|
108
|
+
assert_equal hit["_source"]["name"], "Product A"
|
109
|
+
assert_equal hit["_source"]["user_ids"], [1, 2]
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_select_none
|
113
|
+
skip unless elasticsearch_below50?
|
114
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
115
|
+
hit = Product.search("product", select: []).hits.first
|
116
|
+
assert_nil hit["_source"]
|
117
|
+
end
|
118
|
+
|
119
|
+
# select_v2
|
120
|
+
|
121
|
+
def test_select_v2
|
122
|
+
store [{name: "Product A", store_id: 1}]
|
123
|
+
result = Product.search("product", load: false, select_v2: [:name, :store_id]).first
|
124
|
+
assert_equal %w(id name store_id), result.keys.reject { |k| k.start_with?("_") }.sort
|
125
|
+
assert_equal "Product A", result.name
|
126
|
+
assert_equal 1, result.store_id
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_select_v2_array
|
130
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
131
|
+
result = Product.search("product", load: false, select_v2: [:user_ids]).first
|
132
|
+
assert_equal [1, 2], result.user_ids
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_select_v2_single_field
|
136
|
+
store [{name: "Product A", store_id: 1}]
|
137
|
+
result = Product.search("product", load: false, select_v2: :name).first
|
138
|
+
assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
|
139
|
+
assert_equal "Product A", result.name
|
140
|
+
assert_nil result.store_id
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_select_v2_all
|
144
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
145
|
+
hit = Product.search("product", select_v2: true).hits.first
|
146
|
+
assert_equal hit["_source"]["name"], "Product A"
|
147
|
+
assert_equal hit["_source"]["user_ids"], [1, 2]
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_select_v2_none
|
151
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
152
|
+
hit = Product.search("product", select_v2: []).hits.first
|
153
|
+
assert_nil hit["_source"]
|
154
|
+
hit = Product.search("product", select_v2: false).hits.first
|
155
|
+
assert_nil hit["_source"]
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_select_v2_include
|
159
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
160
|
+
result = Product.search("product", load: false, select_v2: {include: [:name]}).first
|
161
|
+
assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
|
162
|
+
assert_equal "Product A", result.name
|
163
|
+
assert_nil result.store_id
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_select_v2_exclude
|
167
|
+
store [{name: "Product A", user_ids: [1, 2], store_id: 1}]
|
168
|
+
result = Product.search("product", load: false, select_v2: {exclude: [:name]}).first
|
169
|
+
assert_nil result.name
|
170
|
+
assert_equal [1, 2], result.user_ids
|
171
|
+
assert_equal 1, result.store_id
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_select_v2_include_and_exclude
|
175
|
+
# let's take this to the next level
|
176
|
+
store [{name: "Product A", user_ids: [1, 2], store_id: 1}]
|
177
|
+
result = Product.search("product", load: false, select_v2: {include: [:store_id], exclude: [:name]}).first
|
178
|
+
assert_equal 1, result.store_id
|
179
|
+
assert_nil result.name
|
180
|
+
assert_nil result.user_ids
|
181
|
+
end
|
182
|
+
|
183
|
+
# other tests
|
184
|
+
|
185
|
+
def test_nested_object
|
186
|
+
aisle = {"id" => 1, "name" => "Frozen"}
|
187
|
+
store [{name: "Product A", aisle: aisle}]
|
188
|
+
assert_equal aisle, Product.search("product", load: false).first.aisle.to_hash
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_include
|
192
|
+
skip unless defined?(ActiveRecord)
|
193
|
+
store_names ["Product A"]
|
194
|
+
assert Product.search("product", include: [:store]).first.association(:store).loaded?
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class SuggestTest < Minitest::Test
|
4
|
+
def test_basic
|
5
|
+
store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
|
6
|
+
assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [:name]
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_perfect
|
10
|
+
store_names ["Tiger Shark", "Great White Shark"]
|
11
|
+
assert_suggest "Tiger Shark", nil, fields: [:name] # no correction
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_phrase
|
15
|
+
store_names ["Big Tiger Shark", "Tiger Sharp Teeth", "Tiger Sharp Mind"]
|
16
|
+
assert_suggest "How to catch a big tiger shar", "how to catch a big tiger shark", fields: [:name]
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_without_option
|
20
|
+
store_names ["hi"] # needed to prevent ElasticsearchException - seed 668
|
21
|
+
assert_raises(RuntimeError) { Product.search("hi").suggestions }
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_multiple_fields
|
25
|
+
store [
|
26
|
+
{name: "Shark", color: "Sharp"},
|
27
|
+
{name: "Shark", color: "Sharp"}
|
28
|
+
]
|
29
|
+
assert_suggest_all "shar", ["shark", "sharp"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_multiple_fields_highest_score_first
|
33
|
+
store [
|
34
|
+
{name: "Tiger Shark", color: "Sharp"}
|
35
|
+
]
|
36
|
+
assert_suggest "tiger shar", "tiger shark"
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_multiple_fields_same_value
|
40
|
+
store [
|
41
|
+
{name: "Shark", color: "Shark"}
|
42
|
+
]
|
43
|
+
assert_suggest_all "shar", ["shark"]
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_fields_option
|
47
|
+
store [
|
48
|
+
{name: "Shark", color: "Sharp"}
|
49
|
+
]
|
50
|
+
assert_suggest_all "shar", ["shark"], fields: [:name]
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_fields_option_multiple
|
54
|
+
store [
|
55
|
+
{name: "Shark"}
|
56
|
+
]
|
57
|
+
assert_suggest "shar", "shark", fields: [:name, :unknown]
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_fields_partial_match
|
61
|
+
store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
|
62
|
+
assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [{name: :word_start}]
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_fields_partial_match_boost
|
66
|
+
store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
|
67
|
+
assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [{"name^2" => :word_start}]
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def assert_suggest(term, expected, options = {})
|
73
|
+
assert_equal expected, Product.search(term, options.merge(suggest: true)).suggestions.first
|
74
|
+
end
|
75
|
+
|
76
|
+
# any order
|
77
|
+
def assert_suggest_all(term, expected, options = {})
|
78
|
+
assert_equal expected.sort, Product.search(term, options.merge(suggest: true)).suggestions.sort
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class SynonymsTest < Minitest::Test
|
4
|
+
def test_bleach
|
5
|
+
store_names ["Clorox Bleach", "Kroger Bleach"]
|
6
|
+
assert_search "clorox", ["Clorox Bleach", "Kroger Bleach"]
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_saran_wrap
|
10
|
+
store_names ["Saran Wrap", "Kroger Plastic Wrap"]
|
11
|
+
assert_search "saran wrap", ["Saran Wrap", "Kroger Plastic Wrap"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_burger_buns
|
15
|
+
store_names ["Hamburger Buns"]
|
16
|
+
assert_search "burger buns", ["Hamburger Buns"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_bandaids
|
20
|
+
store_names ["Band-Aid", "Kroger 12-Pack Bandages"]
|
21
|
+
assert_search "bandaids", ["Band-Aid", "Kroger 12-Pack Bandages"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_qtips
|
25
|
+
store_names ["Q Tips", "Kroger Cotton Swabs"]
|
26
|
+
assert_search "q tips", ["Q Tips", "Kroger Cotton Swabs"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_reverse
|
30
|
+
store_names ["Scallions"]
|
31
|
+
assert_search "green onions", ["Scallions"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_exact
|
35
|
+
store_names ["Green Onions", "Yellow Onions"]
|
36
|
+
assert_search "scallion", ["Green Onions"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_stemmed
|
40
|
+
store_names ["Green Onions", "Yellow Onions"]
|
41
|
+
assert_search "scallions", ["Green Onions"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_word_start
|
45
|
+
store_names ["Clorox Bleach", "Kroger Bleach"]
|
46
|
+
assert_search "clorox", ["Clorox Bleach", "Kroger Bleach"], fields: [{name: :word_start}]
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_wordnet
|
50
|
+
skip unless ENV["TEST_WORDNET"]
|
51
|
+
store_names ["Creature", "Beast", "Dragon"], Animal
|
52
|
+
assert_search "animal", ["Creature", "Beast"], {}, Animal
|
53
|
+
end
|
54
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,361 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
Bundler.require(:default)
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "minitest/pride"
|
5
|
+
require "logger"
|
6
|
+
require "active_support/core_ext" if defined?(NoBrainer)
|
7
|
+
require "active_support/notifications"
|
8
|
+
|
9
|
+
ENV["RACK_ENV"] = "test"
|
10
|
+
|
11
|
+
Minitest::Test = Minitest::Unit::TestCase unless defined?(Minitest::Test)
|
12
|
+
|
13
|
+
File.delete("elasticsearch.log") if File.exist?("elasticsearch.log")
|
14
|
+
Searchkick.client.transport.logger = Logger.new("elasticsearch.log")
|
15
|
+
Searchkick.search_timeout = 5
|
16
|
+
|
17
|
+
puts "Running against Elasticsearch #{Searchkick.server_version}"
|
18
|
+
|
19
|
+
I18n.config.enforce_available_locales = true
|
20
|
+
|
21
|
+
ActiveJob::Base.logger = nil if defined?(ActiveJob)
|
22
|
+
ActiveSupport::LogSubscriber.logger = Logger.new(STDOUT) if ENV["NOTIFICATIONS"]
|
23
|
+
|
24
|
+
def elasticsearch_below50?
|
25
|
+
Searchkick.server_below?("5.0.0-alpha1")
|
26
|
+
end
|
27
|
+
|
28
|
+
def elasticsearch_below20?
|
29
|
+
Searchkick.server_below?("2.0.0")
|
30
|
+
end
|
31
|
+
|
32
|
+
def elasticsearch_below14?
|
33
|
+
Searchkick.server_below?("1.4.0")
|
34
|
+
end
|
35
|
+
|
36
|
+
def mongoid2?
|
37
|
+
defined?(Mongoid) && Mongoid::VERSION.starts_with?("2.")
|
38
|
+
end
|
39
|
+
|
40
|
+
def nobrainer?
|
41
|
+
defined?(NoBrainer)
|
42
|
+
end
|
43
|
+
|
44
|
+
def activerecord_below41?
|
45
|
+
defined?(ActiveRecord) && Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("4.1.0")
|
46
|
+
end
|
47
|
+
|
48
|
+
if defined?(Mongoid)
|
49
|
+
Mongoid.logger.level = Logger::INFO
|
50
|
+
Mongo::Logger.logger.level = Logger::INFO if defined?(Mongo::Logger)
|
51
|
+
|
52
|
+
if mongoid2?
|
53
|
+
# enable comparison of BSON::ObjectIds
|
54
|
+
module BSON
|
55
|
+
class ObjectId
|
56
|
+
def <=>(other)
|
57
|
+
data <=> other.data
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
Mongoid.configure do |config|
|
64
|
+
if mongoid2?
|
65
|
+
config.master = Mongo::Connection.new.db("searchkick_test")
|
66
|
+
else
|
67
|
+
config.connect_to "searchkick_test"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Product
|
72
|
+
include Mongoid::Document
|
73
|
+
include Mongoid::Timestamps
|
74
|
+
|
75
|
+
field :name
|
76
|
+
field :store_id, type: Integer
|
77
|
+
field :in_stock, type: Boolean
|
78
|
+
field :backordered, type: Boolean
|
79
|
+
field :orders_count, type: Integer
|
80
|
+
field :found_rate, type: BigDecimal
|
81
|
+
field :price, type: Integer
|
82
|
+
field :color
|
83
|
+
field :latitude, type: BigDecimal
|
84
|
+
field :longitude, type: BigDecimal
|
85
|
+
field :description
|
86
|
+
field :alt_description
|
87
|
+
end
|
88
|
+
|
89
|
+
class Store
|
90
|
+
include Mongoid::Document
|
91
|
+
has_many :products
|
92
|
+
|
93
|
+
field :name
|
94
|
+
end
|
95
|
+
|
96
|
+
class Animal
|
97
|
+
include Mongoid::Document
|
98
|
+
|
99
|
+
field :name
|
100
|
+
end
|
101
|
+
|
102
|
+
class Dog < Animal
|
103
|
+
end
|
104
|
+
|
105
|
+
class Cat < Animal
|
106
|
+
end
|
107
|
+
elsif defined?(NoBrainer)
|
108
|
+
NoBrainer.configure do |config|
|
109
|
+
config.app_name = :searchkick
|
110
|
+
config.environment = :test
|
111
|
+
end
|
112
|
+
|
113
|
+
class Product
|
114
|
+
include NoBrainer::Document
|
115
|
+
include NoBrainer::Document::Timestamps
|
116
|
+
|
117
|
+
field :id, type: Object
|
118
|
+
field :name, type: String
|
119
|
+
field :in_stock, type: Boolean
|
120
|
+
field :backordered, type: Boolean
|
121
|
+
field :orders_count, type: Integer
|
122
|
+
field :found_rate
|
123
|
+
field :price, type: Integer
|
124
|
+
field :color, type: String
|
125
|
+
field :latitude
|
126
|
+
field :longitude
|
127
|
+
field :description, type: String
|
128
|
+
field :alt_description, type: String
|
129
|
+
|
130
|
+
belongs_to :store, validates: false
|
131
|
+
end
|
132
|
+
|
133
|
+
class Store
|
134
|
+
include NoBrainer::Document
|
135
|
+
|
136
|
+
field :id, type: Object
|
137
|
+
field :name, type: String
|
138
|
+
end
|
139
|
+
|
140
|
+
class Animal
|
141
|
+
include NoBrainer::Document
|
142
|
+
|
143
|
+
field :id, type: Object
|
144
|
+
field :name, type: String
|
145
|
+
end
|
146
|
+
|
147
|
+
class Dog < Animal
|
148
|
+
end
|
149
|
+
|
150
|
+
class Cat < Animal
|
151
|
+
end
|
152
|
+
else
|
153
|
+
require "active_record"
|
154
|
+
|
155
|
+
# for debugging
|
156
|
+
# ActiveRecord::Base.logger = Logger.new(STDOUT)
|
157
|
+
|
158
|
+
# rails does this in activerecord/lib/active_record/railtie.rb
|
159
|
+
ActiveRecord::Base.default_timezone = :utc
|
160
|
+
ActiveRecord::Base.time_zone_aware_attributes = true
|
161
|
+
|
162
|
+
# migrations
|
163
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
164
|
+
|
165
|
+
ActiveRecord::Base.raise_in_transactional_callbacks = true if ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks=)
|
166
|
+
|
167
|
+
if defined?(Apartment)
|
168
|
+
class Rails
|
169
|
+
def self.env
|
170
|
+
ENV["RACK_ENV"]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
tenants = ["tenant1", "tenant2"]
|
175
|
+
Apartment.configure do |config|
|
176
|
+
config.tenant_names = tenants
|
177
|
+
config.database_schema_file = false
|
178
|
+
config.excluded_models = ["Product", "Store", "Animal", "Dog", "Cat"]
|
179
|
+
end
|
180
|
+
|
181
|
+
class Tenant < ActiveRecord::Base
|
182
|
+
searchkick index_prefix: -> { Apartment::Tenant.current }
|
183
|
+
end
|
184
|
+
|
185
|
+
tenants.each do |tenant|
|
186
|
+
begin
|
187
|
+
Apartment::Tenant.create(tenant)
|
188
|
+
rescue Apartment::TenantExists
|
189
|
+
# do nothing
|
190
|
+
end
|
191
|
+
Apartment::Tenant.switch!(tenant)
|
192
|
+
|
193
|
+
ActiveRecord::Migration.create_table :tenants, force: true do |t|
|
194
|
+
t.string :name
|
195
|
+
t.timestamps null: true
|
196
|
+
end
|
197
|
+
|
198
|
+
Tenant.reindex
|
199
|
+
end
|
200
|
+
|
201
|
+
Apartment::Tenant.reset
|
202
|
+
end
|
203
|
+
|
204
|
+
ActiveRecord::Migration.create_table :products do |t|
|
205
|
+
t.string :name
|
206
|
+
t.integer :store_id
|
207
|
+
t.boolean :in_stock
|
208
|
+
t.boolean :backordered
|
209
|
+
t.integer :orders_count
|
210
|
+
t.decimal :found_rate
|
211
|
+
t.integer :price
|
212
|
+
t.string :color
|
213
|
+
t.decimal :latitude, precision: 10, scale: 7
|
214
|
+
t.decimal :longitude, precision: 10, scale: 7
|
215
|
+
t.text :description
|
216
|
+
t.text :alt_description
|
217
|
+
t.timestamps null: true
|
218
|
+
end
|
219
|
+
|
220
|
+
ActiveRecord::Migration.create_table :stores do |t|
|
221
|
+
t.string :name
|
222
|
+
end
|
223
|
+
|
224
|
+
ActiveRecord::Migration.create_table :animals do |t|
|
225
|
+
t.string :name
|
226
|
+
t.string :type
|
227
|
+
end
|
228
|
+
|
229
|
+
class Product < ActiveRecord::Base
|
230
|
+
end
|
231
|
+
|
232
|
+
class Store < ActiveRecord::Base
|
233
|
+
has_many :products
|
234
|
+
end
|
235
|
+
|
236
|
+
class Animal < ActiveRecord::Base
|
237
|
+
end
|
238
|
+
|
239
|
+
class Dog < Animal
|
240
|
+
end
|
241
|
+
|
242
|
+
class Cat < Animal
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
class Product
|
247
|
+
belongs_to :store
|
248
|
+
|
249
|
+
searchkick \
|
250
|
+
synonyms: [
|
251
|
+
["clorox", "bleach"],
|
252
|
+
["scallion", "greenonion"],
|
253
|
+
["saranwrap", "plasticwrap"],
|
254
|
+
["qtip", "cottonswab"],
|
255
|
+
["burger", "hamburger"],
|
256
|
+
["bandaid", "bandag"]
|
257
|
+
],
|
258
|
+
autocomplete: [:name],
|
259
|
+
suggest: [:name, :color],
|
260
|
+
conversions: "conversions",
|
261
|
+
personalize: "user_ids",
|
262
|
+
locations: ["location", "multiple_locations"],
|
263
|
+
text_start: [:name],
|
264
|
+
text_middle: [:name],
|
265
|
+
text_end: [:name],
|
266
|
+
word_start: [:name],
|
267
|
+
word_middle: [:name],
|
268
|
+
word_end: [:name],
|
269
|
+
highlight: [:name],
|
270
|
+
# unsearchable: [:description],
|
271
|
+
searchable: [:name, :color],
|
272
|
+
only_analyzed: [:alt_description],
|
273
|
+
match: ENV["MATCH"] ? ENV["MATCH"].to_sym : nil
|
274
|
+
|
275
|
+
attr_accessor :conversions, :user_ids, :aisle
|
276
|
+
|
277
|
+
def search_data
|
278
|
+
serializable_hash.except("id").merge(
|
279
|
+
conversions: conversions,
|
280
|
+
user_ids: user_ids,
|
281
|
+
location: {lat: latitude, lon: longitude},
|
282
|
+
multiple_locations: [{lat: latitude, lon: longitude}, {lat: 0, lon: 0}],
|
283
|
+
aisle: aisle
|
284
|
+
)
|
285
|
+
end
|
286
|
+
|
287
|
+
def should_index?
|
288
|
+
name != "DO NOT INDEX"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
class Store
|
293
|
+
searchkick \
|
294
|
+
routing: true,
|
295
|
+
merge_mappings: true,
|
296
|
+
mappings: {
|
297
|
+
store: {
|
298
|
+
properties: {
|
299
|
+
name: elasticsearch_below50? ? {type: "string", analyzer: "keyword"} : {type: "keyword"}
|
300
|
+
}
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
def search_document_id
|
305
|
+
id
|
306
|
+
end
|
307
|
+
|
308
|
+
def search_routing
|
309
|
+
name
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class Animal
|
314
|
+
searchkick \
|
315
|
+
autocomplete: [:name],
|
316
|
+
suggest: [:name],
|
317
|
+
index_name: -> { "#{name.tableize}-#{Date.today.year}" }
|
318
|
+
# wordnet: true
|
319
|
+
end
|
320
|
+
|
321
|
+
Product.searchkick_index.delete if Product.searchkick_index.exists?
|
322
|
+
Product.reindex
|
323
|
+
Product.reindex # run twice for both index paths
|
324
|
+
Product.create!(name: "Set mapping")
|
325
|
+
|
326
|
+
Store.reindex
|
327
|
+
Animal.reindex
|
328
|
+
|
329
|
+
class Minitest::Test
|
330
|
+
def setup
|
331
|
+
Product.destroy_all
|
332
|
+
Store.destroy_all
|
333
|
+
Animal.destroy_all
|
334
|
+
end
|
335
|
+
|
336
|
+
protected
|
337
|
+
|
338
|
+
def store(documents, klass = Product)
|
339
|
+
documents.shuffle.each do |document|
|
340
|
+
klass.create!(document)
|
341
|
+
end
|
342
|
+
klass.searchkick_index.refresh
|
343
|
+
end
|
344
|
+
|
345
|
+
def store_names(names, klass = Product)
|
346
|
+
store names.map { |name| {name: name} }, klass
|
347
|
+
end
|
348
|
+
|
349
|
+
# no order
|
350
|
+
def assert_search(term, expected, options = {}, klass = Product)
|
351
|
+
assert_equal expected.sort, klass.search(term, options).map(&:name).sort
|
352
|
+
end
|
353
|
+
|
354
|
+
def assert_order(term, expected, options = {}, klass = Product)
|
355
|
+
assert_equal expected, klass.search(term, options).map(&:name)
|
356
|
+
end
|
357
|
+
|
358
|
+
def assert_first(term, expected, options = {}, klass = Product)
|
359
|
+
assert_equal expected, klass.search(term, options).map(&:name).first
|
360
|
+
end
|
361
|
+
end
|