searchkick 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cffaa2412f04c693b99dbd54f530b22f7ab32c53
4
- data.tar.gz: 2dd98e1341946940a3cb0997400fe88d2687f540
3
+ metadata.gz: e5398d5720ac0d793c3e056ef46c93257da4e79a
4
+ data.tar.gz: 71d3ca9b8a853231cd5a285bb5b300912a7cbf5c
5
5
  SHA512:
6
- metadata.gz: 16f646bd8e81672e98dc619d25814f071037b3522d63d235e64ee2aa7af782c483ab08e139411dd0a7501bb1e73163b475b9fffb69b44483525ad01d8f4b6981
7
- data.tar.gz: 95b73d727ddbe424a8e9167dd2b62c2fead9fefe326a08a6da3c26cf98a33d8d824cc2c2f68921dca25b1167e26baced0cf80a802b1c976f4049a87e4cef1fec
6
+ metadata.gz: 0ccea382ae95b340d7bb9df48cde62f84dbceca61451cfd5dce6ca0212e73b8da464a1d9342a20be26fe9b3ef24fadf9599fbd4b09715790c9ab37c6fd6d48a8
7
+ data.tar.gz: 72f1350835f6a260d7fd051e677f24c3cacee5a469035b8fbc5da80c9b55c123b88e36eab0f41ce1955dd08ca55db6e8762ac0e8b9916b7f38d21a9f4424840a
data/README.md CHANGED
@@ -167,13 +167,11 @@ class Product < ActiveRecord::Base
167
167
  end
168
168
  ```
169
169
 
170
- ### Continually Improve Results
170
+ ### Keep Getting Better
171
171
 
172
- Searchkick uses conversion data to learn what users are looking for.
172
+ Searchkick uses conversion data to learn what users are looking for. If a user searches for “ice cream” and adds Ben & Jerry’s Chunky Monkey to the cart (our conversion metric at Instacart), that item gets a little more weight for similar searches.
173
173
 
174
- First, choose a conversion metric for your application. At Instacart, an item added to the cart is a conversion.
175
-
176
- Next, track the conversions. The database works well for low volume, but feel free to use Redis or another datastore.
174
+ The first step is to define your conversion metric and start tracking conversions. The database works well for low volume, but feel free to use Redis or another datastore.
177
175
 
178
176
  ```ruby
179
177
  class Search < ActiveRecord::Base
@@ -200,7 +198,7 @@ end
200
198
  Reindex and set up a cron job to add new conversions daily.
201
199
 
202
200
  ```ruby
203
- heroku run rake searchkick:reindex CLASS=Product
201
+ rake searchkick:reindex CLASS=Product
204
202
  ```
205
203
 
206
204
  ### Facets
@@ -298,7 +296,9 @@ Product.search "milk", load: false
298
296
  2. Replace tire mapping w/ searchkick method
299
297
 
300
298
  ```ruby
301
- searchkick index_name: "products_v2"
299
+ class Product < ActiveRecord::Base
300
+ searchkick
301
+ end
302
302
  ```
303
303
 
304
304
  3. Deploy and reindex
@@ -323,10 +323,12 @@ end
323
323
 
324
324
  ## Thanks
325
325
 
326
- Thanks to [Karel Minarik](https://github.com/karmi) for Tire and [Jaroslav Kalistsuk](https://github.com/jarosan) for zero downtime reindexing.
326
+ Thanks to Karel Minarik for [Tire](https://github.com/karmi/tire) and Jaroslav Kalistsuk for [zero downtime reindexing](https://gist.github.com/jarosan/3124884).
327
327
 
328
328
  ## TODO
329
329
 
330
+ - Custom results for each user
331
+ - Make Searchkick work with any language
330
332
  - Built-in synonyms from WordNet
331
333
 
332
334
  ## Contributing
@@ -22,19 +22,20 @@ module Searchkick
22
22
  end
23
23
 
24
24
  a.indices.add new_index
25
- a.save
25
+ response = a.save
26
26
 
27
- old_indices.each do |index|
28
- i = Tire::Index.new(index)
29
- i.delete
27
+ if response.success?
28
+ old_indices.each do |index|
29
+ i = Tire::Index.new(index)
30
+ i.delete
31
+ end
30
32
  end
31
33
  else
32
- i = Tire::Index.new(alias_name)
33
- i.delete if i.exists?
34
- Tire::Alias.create(name: alias_name, indices: [new_index])
34
+ tire.index.delete
35
+ response = Tire::Alias.create(name: alias_name, indices: [new_index])
35
36
  end
36
37
 
37
- true
38
+ response.success? || (raise response.to_s)
38
39
  end
39
40
 
40
41
  private
@@ -55,12 +56,12 @@ module Searchkick
55
56
  tokenizer: "standard",
56
57
  # synonym should come last, after stemming and shingle
57
58
  # shingle must come before snowball
58
- filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_index_shingle"]
59
+ filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_index_shingle", "snowball"]
59
60
  },
60
61
  searchkick_search: {
61
62
  type: "custom",
62
63
  tokenizer: "standard",
63
- filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_search_shingle"]
64
+ filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_search_shingle", "snowball"]
64
65
  },
65
66
  searchkick_search2: {
66
67
  type: "custom",
@@ -90,8 +91,12 @@ module Searchkick
90
91
  ignore_case: true,
91
92
  synonyms: synonyms.select{|s| s.size > 1 }.map{|s| "#{s[0..-2].join(",")} => #{s[-1]}" }
92
93
  }
94
+ # choosing a place for the synonym filter when stemming is not easy
95
+ # https://groups.google.com/forum/#!topic/elasticsearch/p7qcQlgHdB8
96
+ # TODO use a snowball stemmer on synonyms when creating the token filter
97
+ settings[:analysis][:analyzer][:default_index][:filter].insert(-4, "searchkick_synonym")
93
98
  settings[:analysis][:analyzer][:default_index][:filter] << "searchkick_synonym"
94
- settings[:analysis][:analyzer][:searchkick_search][:filter].insert(-2, "searchkick_synonym")
99
+ settings[:analysis][:analyzer][:searchkick_search][:filter].insert(-4, "searchkick_synonym")
95
100
  settings[:analysis][:analyzer][:searchkick_search][:filter] << "searchkick_synonym"
96
101
  settings[:analysis][:analyzer][:searchkick_search2][:filter] << "searchkick_synonym"
97
102
  end
@@ -109,7 +114,28 @@ module Searchkick
109
114
 
110
115
  mappings = {
111
116
  document_type.to_sym => {
112
- properties: mapping
117
+ properties: mapping,
118
+ # https://gist.github.com/kimchy/2898285
119
+ dynamic_templates: [
120
+ {
121
+ string_template: {
122
+ match: "*",
123
+ match_mapping_type: "string",
124
+ mapping: {
125
+ # http://www.elasticsearch.org/guide/reference/mapping/multi-field-type/
126
+ type: "multi_field",
127
+ fields: {
128
+ # analyzed field must be the default field for include_in_all
129
+ # http://www.elasticsearch.org/guide/reference/mapping/multi-field-type/
130
+ # however, we can include the not_analyzed field in _all
131
+ # and the _all index analyzer will take care of it
132
+ "{name}" => {type: "string", index: "not_analyzed"},
133
+ "analyzed" => {type: "string", index: "analyzed"}
134
+ }
135
+ }
136
+ }
137
+ }
138
+ ]
113
139
  }
114
140
  }
115
141
 
@@ -6,10 +6,17 @@ module Searchkick
6
6
  fields = options[:fields] || ["_all"]
7
7
  operator = options[:partial] ? "or" : "and"
8
8
  load = options[:load].nil? ? true : options[:load]
9
- load = (options[:include] || true) if load
9
+ load = (options[:include] ? {include: options[:include]} : true) if load
10
+ page = options.has_key?(:page) ? [options[:page].to_i, 1].max : nil
11
+ tire_options = {
12
+ load: load,
13
+ page: page,
14
+ per_page: options[:limit] || options[:per_page] || 100000 # return all
15
+ }
16
+ tire_options[:index] = options[:index_name] if options[:index_name]
10
17
 
11
18
  collection =
12
- tire.search load: load, page: options[:page], per_page: options[:per_page] do
19
+ tire.search tire_options do
13
20
  query do
14
21
  boolean do
15
22
  must do
@@ -24,10 +31,10 @@ module Searchkick
24
31
  match fields, term, boost: 10, operator: operator, analyzer: "searchkick_search2"
25
32
  end
26
33
  query do
27
- match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: operator, analyzer: "searchkick_search"
34
+ match fields, term, use_dis_max: false, fuzziness: 1, max_expansions: 1, operator: operator, analyzer: "searchkick_search"
28
35
  end
29
36
  query do
30
- match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: operator, analyzer: "searchkick_search2"
37
+ match fields, term, use_dis_max: false, fuzziness: 1, max_expansions: 1, operator: operator, analyzer: "searchkick_search2"
31
38
  end
32
39
  end
33
40
  end
@@ -36,7 +43,7 @@ module Searchkick
36
43
  should do
37
44
  nested path: "conversions", score_mode: "total" do
38
45
  query do
39
- custom_score script: "log(doc['count'].value)" do
46
+ custom_score script: "doc['count'].value" do
40
47
  match "query", term
41
48
  end
42
49
  end
@@ -45,14 +52,14 @@ module Searchkick
45
52
  end
46
53
  end
47
54
  end
48
- size options[:limit] || 100000 # return all - like sql query
49
55
  from options[:offset] if options[:offset]
50
56
  explain options[:explain] if options[:explain]
51
57
 
52
58
  # order
53
59
  if options[:order]
60
+ order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
54
61
  sort do
55
- options[:order].each do |k, v|
62
+ order.each do |k, v|
56
63
  by k, v
57
64
  end
58
65
  end
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -0,0 +1,41 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestBoost < Minitest::Unit::TestCase
4
+
5
+ # conversions
6
+
7
+ def test_conversions
8
+ store [
9
+ {name: "Tomato A", conversions: {"tomato" => 1}},
10
+ {name: "Tomato B", conversions: {"tomato" => 2}},
11
+ {name: "Tomato C", conversions: {"tomato" => 3}}
12
+ ]
13
+ assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"]
14
+ end
15
+
16
+ def test_conversions_stemmed
17
+ store [
18
+ {name: "Tomato A", conversions: {"tomato" => 1, "tomatos" => 1, "Tomatoes" => 1}},
19
+ {name: "Tomato B", conversions: {"tomato" => 2}}
20
+ ]
21
+ assert_order "tomato", ["Tomato A", "Tomato B"]
22
+ end
23
+
24
+ # global boost
25
+
26
+ def test_boost
27
+ store [
28
+ {name: "Organic Tomato A"},
29
+ {name: "Tomato B", orders_count: 10}
30
+ ]
31
+ assert_order "tomato", ["Tomato B", "Organic Tomato A"], boost: "orders_count"
32
+ end
33
+
34
+ def test_boost_zero
35
+ store [
36
+ {name: "Zero Boost", orders_count: 0}
37
+ ]
38
+ assert_order "zero", ["Zero Boost"], boost: "orders_count"
39
+ end
40
+
41
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestFacets < Minitest::Unit::TestCase
4
+
5
+ def test_facets
6
+ store [
7
+ {name: "Product Show", store_id: 1, in_stock: true, color: "blue"},
8
+ {name: "Product Hide", store_id: 2, in_stock: false, color: "green"},
9
+ {name: "Product B", store_id: 2, in_stock: false, color: "red"}
10
+ ]
11
+ assert_equal 2, Product.search("Product", facets: [:store_id]).facets["store_id"]["terms"].size
12
+ assert_equal 1, Product.search("Product", facets: {store_id: {where: {in_stock: true}}}).facets["store_id"]["terms"].size
13
+ assert_equal 1, Product.search("Product", facets: {store_id: {where: {in_stock: true, color: "blue"}}}).facets["store_id"]["terms"].size
14
+ end
15
+
16
+ end
@@ -0,0 +1,113 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestMatch < Minitest::Unit::TestCase
4
+
5
+ # exact
6
+
7
+ def test_match
8
+ store_names ["Whole Milk", "Fat Free Milk", "Milk"]
9
+ assert_search "milk", ["Milk", "Whole Milk", "Fat Free Milk"]
10
+ end
11
+
12
+ def test_case
13
+ store_names ["Whole Milk", "Fat Free Milk", "Milk"]
14
+ assert_search "MILK", ["Milk", "Whole Milk", "Fat Free Milk"]
15
+ end
16
+
17
+ def test_cheese_space_in_index
18
+ store_names ["Pepper Jack Cheese Skewers"]
19
+ assert_search "pepperjack cheese skewers", ["Pepper Jack Cheese Skewers"]
20
+ end
21
+
22
+ def test_cheese_space_in_query
23
+ store_names ["Pepperjack Cheese Skewers"]
24
+ assert_search "pepper jack cheese skewers", ["Pepperjack Cheese Skewers"]
25
+ end
26
+
27
+ def test_middle_token
28
+ store_names ["Dish Washer Amazing Organic Soap"]
29
+ assert_search "dish soap", ["Dish Washer Amazing Organic Soap"]
30
+ end
31
+
32
+ def test_percent
33
+ store_names ["1% Milk", "2% Milk", "Whole Milk"]
34
+ assert_search "1%", ["1% Milk"]
35
+ end
36
+
37
+ # ascii
38
+
39
+ def test_jalapenos
40
+ store_names ["Jalapeño"]
41
+ assert_search "jalapeno", ["Jalapeño"]
42
+ end
43
+
44
+ # stemming
45
+
46
+ def test_stemming
47
+ store_names ["Whole Milk", "Fat Free Milk", "Milk"]
48
+ assert_search "milks", ["Milk", "Whole Milk", "Fat Free Milk"]
49
+ end
50
+
51
+ # fuzzy
52
+
53
+ def test_misspelling_sriracha
54
+ store_names ["Sriracha"]
55
+ assert_search "siracha", ["Sriracha"]
56
+ end
57
+
58
+ def test_short_word
59
+ store_names ["Finn"]
60
+ assert_search "fin", ["Finn"]
61
+ end
62
+
63
+ def test_edit_distance
64
+ store_names ["Bingo"]
65
+ assert_search "bin", []
66
+ assert_search "bing", ["Bingo"]
67
+ assert_search "bingoo", ["Bingo"]
68
+ assert_search "bingooo", []
69
+ assert_search "ringo", ["Bingo"]
70
+ assert_search "mango", []
71
+ store_names ["thisisareallylongword"]
72
+ assert_search "thisisareallylongwor", ["thisisareallylongword"] # missing letter
73
+ assert_search "thisisareelylongword", [] # edit distance = 2
74
+ end
75
+
76
+ def test_misspelling_tabasco
77
+ store_names ["Tabasco"]
78
+ assert_search "tobasco", ["Tabasco"]
79
+ end
80
+
81
+ def test_misspelling_zucchini
82
+ store_names ["Zucchini"]
83
+ assert_search "zuchini", ["Zucchini"]
84
+ end
85
+
86
+ def test_misspelling_ziploc
87
+ store_names ["Ziploc"]
88
+ assert_search "zip lock", ["Ziploc"]
89
+ end
90
+
91
+ # spaces
92
+
93
+ def test_spaces_in_field
94
+ store_names ["Red Bull"]
95
+ assert_search "redbull", ["Red Bull"]
96
+ end
97
+
98
+ def test_spaces_in_query
99
+ store_names ["Dishwasher"]
100
+ assert_search "dish washer", ["Dishwasher"]
101
+ end
102
+
103
+ def test_spaces_three_words
104
+ store_names ["Dish Washer Soap", "Dish Washer"]
105
+ assert_search "dish washer soap", ["Dish Washer Soap"]
106
+ end
107
+
108
+ def test_spaces_stemming
109
+ store_names ["Almond Milk"]
110
+ assert_search "almondmilks", ["Almond Milk"]
111
+ end
112
+
113
+ end
data/test/sql_test.rb ADDED
@@ -0,0 +1,106 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestSql < Minitest::Unit::TestCase
4
+
5
+ def test_limit
6
+ store_names ["Product A", "Product B", "Product C", "Product D"]
7
+ assert_search "product", ["Product A", "Product B"], order: {name: :asc}, limit: 2
8
+ end
9
+
10
+ def test_offset
11
+ store_names ["Product A", "Product B", "Product C", "Product D"]
12
+ assert_search "product", ["Product C", "Product D"], order: {name: :asc}, offset: 2
13
+ end
14
+
15
+ def test_pagination
16
+ store_names ["Product A", "Product B", "Product C", "Product D", "Product E"]
17
+ assert_search "product", ["Product C", "Product D"], order: {name: :asc}, page: 2, per_page: 2
18
+ end
19
+
20
+ def test_pagination_nil_page
21
+ store_names ["Product A", "Product B", "Product C", "Product D", "Product E"]
22
+ assert_search "product", ["Product A", "Product B"], order: {name: :asc}, page: nil, per_page: 2
23
+ end
24
+
25
+ def test_where
26
+ now = Time.now
27
+ store [
28
+ {name: "Product A", store_id: 1, in_stock: true, backordered: true, created_at: now, orders_count: 4},
29
+ {name: "Product B", store_id: 2, in_stock: true, backordered: false, created_at: now - 1, orders_count: 3},
30
+ {name: "Product C", store_id: 3, in_stock: false, backordered: true, created_at: now - 2, orders_count: 2},
31
+ {name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1},
32
+ ]
33
+ assert_search "product", ["Product A", "Product B"], where: {in_stock: true}
34
+ # date
35
+ assert_search "product", ["Product A"], where: {created_at: {gt: now - 1}}
36
+ assert_search "product", ["Product A", "Product B"], where: {created_at: {gte: now - 1}}
37
+ assert_search "product", ["Product D"], where: {created_at: {lt: now - 2}}
38
+ assert_search "product", ["Product C", "Product D"], where: {created_at: {lte: now - 2}}
39
+ # integer
40
+ assert_search "product", ["Product A"], where: {store_id: {lt: 2}}
41
+ assert_search "product", ["Product A", "Product B"], where: {store_id: {lte: 2}}
42
+ assert_search "product", ["Product D"], where: {store_id: {gt: 3}}
43
+ assert_search "product", ["Product C", "Product D"], where: {store_id: {gte: 3}}
44
+ # range
45
+ assert_search "product", ["Product A", "Product B"], where: {store_id: 1..2}
46
+ assert_search "product", ["Product A"], where: {store_id: 1...2}
47
+ assert_search "product", ["Product A", "Product B"], where: {store_id: [1, 2]}
48
+ assert_search "product", ["Product B", "Product C", "Product D"], where: {store_id: {not: 1}}
49
+ assert_search "product", ["Product C", "Product D"], where: {store_id: {not: [1, 2]}}
50
+ assert_search "product", ["Product A", "Product B", "Product C"], where: {or: [[{in_stock: true}, {store_id: 3}]]}
51
+ end
52
+
53
+ def test_where_string
54
+ store [
55
+ {name: "Product A", color: "RED"}
56
+ ]
57
+ assert_search "product", ["Product A"], where: {color: ["RED"]}
58
+ end
59
+
60
+ def test_order_hash
61
+ store_names ["Product A", "Product B", "Product C", "Product D"]
62
+ assert_search "product", ["Product D", "Product C", "Product B", "Product A"], order: {name: :desc}
63
+ end
64
+
65
+ def test_order_string
66
+ store_names ["Product A", "Product B", "Product C", "Product D"]
67
+ assert_search "product", ["Product A", "Product B", "Product C", "Product D"], order: "name"
68
+ end
69
+
70
+ def test_partial
71
+ store_names ["Honey"]
72
+ assert_search "fresh honey", []
73
+ assert_search "fresh honey", ["Honey"], partial: true
74
+ end
75
+
76
+ def test_fields
77
+ store [
78
+ {name: "red", color: "blue"},
79
+ {name: "blue", color: "red"}
80
+ ]
81
+ assert_search "blue", ["red"], fields: ["color"]
82
+ end
83
+
84
+ # load
85
+
86
+ def test_load_default
87
+ store_names ["Product A"]
88
+ assert_kind_of Product, Product.search("product").first
89
+ end
90
+
91
+ def test_load_false
92
+ store_names ["Product A"]
93
+ assert_kind_of Tire::Results::Item, Product.search("product", load: false).first
94
+ end
95
+
96
+ def test_load_false_with_include
97
+ store_names ["Product A"]
98
+ assert_kind_of Tire::Results::Item, Product.search("product", load: false, include: [:store]).first
99
+ end
100
+
101
+ def test_include
102
+ store_names ["Product A"]
103
+ assert Product.search("product", include: [:store]).first.association(:store).loaded?
104
+ end
105
+
106
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestSynonyms < Minitest::Unit::TestCase
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
+ end
data/test/test_helper.rb CHANGED
@@ -24,12 +24,69 @@ ActiveRecord::Migration.create_table :products, :force => true do |t|
24
24
  t.timestamps
25
25
  end
26
26
 
27
- ActiveRecord::Migration.create_table :searches, :force => true do |t|
28
- t.string :query
29
- t.timestamp :searched_at
30
- t.timestamp :converted_at
31
- t.references :product
27
+ ActiveRecord::Migration.create_table :store, :force => true do |t|
32
28
  end
33
29
 
34
30
  File.delete("elasticsearch.log") if File.exists?("elasticsearch.log")
35
- Tire.configure { logger "elasticsearch.log", :level => "debug" }
31
+ Tire.configure do
32
+ logger "elasticsearch.log", :level => "debug"
33
+ pretty true
34
+ end
35
+
36
+ class Product < ActiveRecord::Base
37
+ belongs_to :store
38
+
39
+ searchkick \
40
+ settings: {
41
+ number_of_shards: 1
42
+ },
43
+ synonyms: [
44
+ ["clorox", "bleach"],
45
+ ["scallion", "greenonion"],
46
+ ["saranwrap", "plasticwrap"],
47
+ ["qtip", "cotton swab"],
48
+ ["burger", "hamburger"],
49
+ ["bandaid", "bandag"]
50
+ ]
51
+
52
+ attr_accessor :conversions
53
+
54
+ def search_data
55
+ as_json.merge conversions: conversions
56
+ end
57
+ end
58
+
59
+ class Store < ActiveRecord::Base
60
+ end
61
+
62
+ Product.reindex
63
+
64
+ class MiniTest::Unit::TestCase
65
+
66
+ def setup
67
+ Product.destroy_all
68
+ end
69
+
70
+ protected
71
+
72
+ def store(documents)
73
+ documents.each do |document|
74
+ Product.create!(document)
75
+ end
76
+ Product.index.refresh
77
+ end
78
+
79
+ def store_names(names)
80
+ store names.map{|name| {name: name} }
81
+ end
82
+
83
+ # no order
84
+ def assert_search(term, expected, options = {})
85
+ assert_equal expected.sort, Product.search(term, options).map(&:name).sort
86
+ end
87
+
88
+ def assert_order(term, expected, options = {})
89
+ assert_equal expected, Product.search(term, options).map(&:name)
90
+ end
91
+
92
+ end
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.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-07-30 00:00:00.000000000 Z
11
+ date: 2013-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tire
@@ -113,7 +113,11 @@ files:
113
113
  - lib/searchkick/tasks.rb
114
114
  - lib/searchkick/version.rb
115
115
  - searchkick.gemspec
116
- - test/searchkick_test.rb
116
+ - test/boost_test.rb
117
+ - test/facets_test.rb
118
+ - test/match_test.rb
119
+ - test/sql_test.rb
120
+ - test/synonyms_test.rb
117
121
  - test/test_helper.rb
118
122
  homepage: https://github.com/ankane/searchkick
119
123
  licenses:
@@ -140,5 +144,9 @@ signing_key:
140
144
  specification_version: 4
141
145
  summary: Search made easy
142
146
  test_files:
143
- - test/searchkick_test.rb
147
+ - test/boost_test.rb
148
+ - test/facets_test.rb
149
+ - test/match_test.rb
150
+ - test/sql_test.rb
151
+ - test/synonyms_test.rb
144
152
  - test/test_helper.rb
@@ -1,286 +0,0 @@
1
- require "test_helper"
2
-
3
- class Product < ActiveRecord::Base
4
- has_many :searches
5
-
6
- searchkick \
7
- synonyms: [
8
- ["clorox", "bleach"],
9
- ["scallion", "greenonion"],
10
- ["saranwrap", "plasticwrap"],
11
- ["qtip", "cotton swab"],
12
- ["burger", "hamburger"],
13
- ["bandaid", "bandag"]
14
- ],
15
- settings: {
16
- number_of_shards: 1
17
- }
18
-
19
- def search_data
20
- as_json.merge conversions: searches.group("query").count
21
- end
22
- end
23
-
24
- class Search < ActiveRecord::Base
25
- belongs_to :product
26
- end
27
-
28
- Product.reindex
29
-
30
- class TestSearchkick < Minitest::Unit::TestCase
31
-
32
- def setup
33
- Search.delete_all
34
- Product.destroy_all
35
- end
36
-
37
- # exact
38
-
39
- def test_match
40
- store_names ["Whole Milk", "Fat Free Milk", "Milk"]
41
- assert_search "milk", ["Milk", "Whole Milk", "Fat Free Milk"]
42
- end
43
-
44
- def test_case
45
- store_names ["Whole Milk", "Fat Free Milk", "Milk"]
46
- assert_search "MILK", ["Milk", "Whole Milk", "Fat Free Milk"]
47
- end
48
-
49
- def test_cheese_space_in_index
50
- store_names ["Pepper Jack Cheese Skewers"]
51
- assert_search "pepperjack cheese skewers", ["Pepper Jack Cheese Skewers"]
52
- end
53
-
54
- def test_cheese_space_in_query
55
- store_names ["Pepperjack Cheese Skewers"]
56
- assert_search "pepper jack cheese skewers", ["Pepperjack Cheese Skewers"]
57
- end
58
-
59
- def test_middle_token
60
- store_names ["Dish Washer Amazing Organic Soap"]
61
- assert_search "dish soap", ["Dish Washer Amazing Organic Soap"]
62
- end
63
-
64
- def test_percent
65
- store_names ["1% Milk", "2% Milk", "Whole Milk"]
66
- assert_search "1%", ["1% Milk"]
67
- end
68
-
69
- # ascii
70
-
71
- def test_jalapenos
72
- store_names ["Jalapeño"]
73
- assert_search "jalapeno", ["Jalapeño"]
74
- end
75
-
76
- # stemming
77
-
78
- def test_stemming
79
- store_names ["Whole Milk", "Fat Free Milk", "Milk"]
80
- assert_search "milks", ["Milk", "Whole Milk", "Fat Free Milk"]
81
- end
82
-
83
- # fuzzy
84
-
85
- def test_misspelling_sriracha
86
- store_names ["Sriracha"]
87
- assert_search "siracha", ["Sriracha"]
88
- end
89
-
90
- def test_misspelling_tabasco
91
- store_names ["Tabasco"]
92
- assert_search "tobasco", ["Tabasco"]
93
- end
94
-
95
- def test_misspelling_zucchini
96
- store_names ["Zucchini"]
97
- assert_search "zuchini", ["Zucchini"]
98
- end
99
-
100
- def test_misspelling_ziploc
101
- store_names ["Ziploc"]
102
- assert_search "zip lock", ["Ziploc"]
103
- end
104
-
105
- # conversions
106
-
107
- def test_conversions
108
- store_conversions [
109
- {name: "Tomato Sauce", conversions: [{query: "tomato sauce", count: 5}, {query: "tomato", count: 20}]},
110
- {name: "Tomato Paste", conversions: []},
111
- {name: "Tomatoes", conversions: [{query: "tomato", count: 10}, {query: "tomato sauce", count: 2}]}
112
- ]
113
- assert_search "tomato", ["Tomato Sauce", "Tomatoes", "Tomato Paste"]
114
- end
115
-
116
- def test_conversions_stemmed
117
- store_conversions [
118
- {name: "Tomato A", conversions: [{query: "tomato", count: 2}, {query: "tomatos", count: 2}, {query: "Tomatoes", count: 2}]},
119
- {name: "Tomato B", conversions: [{query: "tomato", count: 4}]}
120
- ]
121
- assert_search "tomato", ["Tomato A", "Tomato B"]
122
- end
123
-
124
- # spaces
125
-
126
- def test_spaces_in_field
127
- store_names ["Red Bull"]
128
- assert_search "redbull", ["Red Bull"]
129
- end
130
-
131
- def test_spaces_in_query
132
- store_names ["Dishwasher Soap"]
133
- assert_search "dish washer", ["Dishwasher Soap"]
134
- end
135
-
136
- def test_spaces_three_words
137
- store_names ["Dish Washer Soap", "Dish Washer"]
138
- assert_search "dish washer soap", ["Dish Washer Soap"]
139
- end
140
-
141
- def test_spaces_stemming
142
- store_names ["Almond Milk"]
143
- assert_search "almondmilks", ["Almond Milk"]
144
- end
145
-
146
- # keywords
147
-
148
- def test_keywords
149
- store_names ["Clorox Bleach", "Kroger Bleach", "Saran Wrap", "Kroger Plastic Wrap", "Hamburger Buns", "Band-Aid", "Kroger 12-Pack Bandages"]
150
- assert_search "clorox", ["Clorox Bleach", "Kroger Bleach"]
151
- assert_search "saran wrap", ["Saran Wrap", "Kroger Plastic Wrap"]
152
- assert_search "burger buns", ["Hamburger Buns"]
153
- assert_search "bandaids", ["Band-Aid", "Kroger 12-Pack Bandages"]
154
- end
155
-
156
- def test_keywords_qtips
157
- store_names ["Q Tips", "Kroger Cotton Swabs"]
158
- assert_search "q tips", ["Q Tips", "Kroger Cotton Swabs"]
159
- end
160
-
161
- def test_keywords_reverse
162
- store_names ["Scallions"]
163
- assert_search "green onions", ["Scallions"]
164
- end
165
-
166
- def test_keywords_exact
167
- store_names ["Green Onions", "Yellow Onions"]
168
- assert_search "scallion", ["Green Onions"]
169
- end
170
-
171
- def test_keywords_stemmed
172
- store_names ["Green Onions", "Yellow Onions"]
173
- assert_search "scallions", ["Green Onions"]
174
- end
175
-
176
- # global boost
177
-
178
- def test_boost
179
- store [
180
- {name: "Organic Tomato A"},
181
- {name: "Tomato B", orders_count: 10}
182
- ]
183
- assert_search "tomato", ["Tomato B", "Organic Tomato A"], boost: "orders_count"
184
- end
185
-
186
- def test_boost_zero
187
- store [
188
- {name: "Zero Boost", orders_count: 0}
189
- ]
190
- assert_search "zero", ["Zero Boost"], boost: "orders_count"
191
- end
192
-
193
- # search method
194
-
195
- def test_limit
196
- store_names ["Product A", "Product B"]
197
- assert_equal 1, Product.search("Product", limit: 1).size
198
- end
199
-
200
- def test_offset
201
- store_names ["Product A", "Product B"]
202
- assert_equal 1, Product.search("Product", offset: 1).size
203
- end
204
-
205
- def test_where
206
- now = Time.now
207
- store [
208
- {name: "Product A", store_id: 1, in_stock: true, backordered: true, created_at: now, orders_count: 4},
209
- {name: "Product B", store_id: 2, in_stock: true, backordered: false, created_at: now - 1, orders_count: 3},
210
- {name: "Product C", store_id: 3, in_stock: false, backordered: true, created_at: now - 2, orders_count: 2},
211
- {name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1},
212
- ]
213
- assert_search "product", ["Product A", "Product B"], where: {in_stock: true}
214
- # date
215
- assert_search "product", ["Product A"], where: {created_at: {gt: now - 1}}
216
- assert_search "product", ["Product A", "Product B"], where: {created_at: {gte: now - 1}}
217
- assert_search "product", ["Product D"], where: {created_at: {lt: now - 2}}
218
- assert_search "product", ["Product C", "Product D"], where: {created_at: {lte: now - 2}}
219
- # integer
220
- assert_search "product", ["Product A"], where: {store_id: {lt: 2}}
221
- assert_search "product", ["Product A", "Product B"], where: {store_id: {lte: 2}}
222
- assert_search "product", ["Product D"], where: {store_id: {gt: 3}}
223
- assert_search "product", ["Product C", "Product D"], where: {store_id: {gte: 3}}
224
- # range
225
- assert_search "product", ["Product A", "Product B"], where: {store_id: 1..2}
226
- assert_search "product", ["Product A"], where: {store_id: 1...2}
227
- assert_search "product", ["Product A", "Product B"], where: {store_id: [1, 2]}
228
- assert_search "product", ["Product B", "Product C", "Product D"], where: {store_id: {not: 1}}
229
- assert_search "product", ["Product C", "Product D"], where: {store_id: {not: [1, 2]}}
230
- assert_search "product", ["Product A", "Product B", "Product C"], where: {or: [[{in_stock: true}, {store_id: 3}]]}
231
- end
232
-
233
- def test_order
234
- store_names ["Product A", "Product B", "Product C", "Product D"]
235
- assert_search "product", ["Product D", "Product C", "Product B", "Product A"], order: {name: :desc}
236
- end
237
-
238
- def test_facets
239
- store [
240
- {name: "Product Show", store_id: 1, in_stock: true, color: "blue"},
241
- {name: "Product Hide", store_id: 2, in_stock: false, color: "green"},
242
- {name: "Product B", store_id: 2, in_stock: false, color: "red"}
243
- ]
244
- assert_equal 2, Product.search("Product", facets: [:store_id]).facets["store_id"]["terms"].size
245
- assert_equal 1, Product.search("Product", facets: {store_id: {where: {in_stock: true}}}).facets["store_id"]["terms"].size
246
- assert_equal 1, Product.search("Product", facets: {store_id: {where: {in_stock: true, color: "blue"}}}).facets["store_id"]["terms"].size
247
- end
248
-
249
- def test_partial
250
- store_names ["Honey"]
251
- assert_search "fresh honey", []
252
- assert_search "fresh honey", ["Honey"], partial: true
253
- end
254
-
255
- protected
256
-
257
- def store(documents)
258
- documents.each do |document|
259
- Product.create!(document)
260
- end
261
- Product.index.refresh
262
- end
263
-
264
- def store_names(names)
265
- store names.map{|name| {name: name} }
266
- end
267
-
268
- def store_conversions(documents)
269
- documents.each do |document|
270
- conversions = document.delete(:conversions)
271
- product = Product.create!(document)
272
- conversions.each do |c|
273
- c[:count].times do
274
- product.searches.create!(query: c[:query])
275
- end
276
- end
277
- end
278
- Product.reindex
279
- Product.index.refresh
280
- end
281
-
282
- def assert_search(term, expected, options = {})
283
- assert_equal expected, Product.search(term, options.merge(fields: [:name])).map(&:name)
284
- end
285
-
286
- end