searchkick-sinneduy 0.9.0

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.
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,91 @@
1
+ require_relative "test_helper"
2
+ require "active_support/core_ext"
3
+
4
+ class TestFacets < Minitest::Test
5
+
6
+ def setup
7
+ super
8
+ store [
9
+ {name: "Product Show", latitude: 37.7833, longitude: 12.4167, store_id: 1, in_stock: true, color: "blue", price: 21, created_at: 2.days.ago},
10
+ {name: "Product Hide", latitude: 29.4167, longitude: -98.5000, store_id: 2, in_stock: false, color: "green", price: 25, created_at: 2.days.from_now},
11
+ {name: "Product B", latitude: 43.9333, longitude: -122.4667, store_id: 2, in_stock: false, color: "red", price: 5},
12
+ {name: "Foo", latitude: 43.9333, longitude: 12.4667, store_id: 3, in_stock: false, color: "yellow", price: 15}
13
+ ]
14
+ end
15
+
16
+ def test_basic
17
+ assert_equal ({1 => 1, 2 => 2}), store_facet(facets: [:store_id])
18
+ end
19
+
20
+ def test_where
21
+ assert_equal ({1 => 1}), store_facet(facets: {store_id: {where: {in_stock: true}}})
22
+ end
23
+
24
+ def test_field
25
+ assert_equal ({1 => 1, 2 => 2}), store_facet(facets: {store_id: {}})
26
+ assert_equal ({1 => 1, 2 => 2}), store_facet(facets: {store_id: {field: "store_id"}})
27
+ assert_equal ({1 => 1, 2 => 2}), store_facet({facets: {store_id_new: {field: "store_id"}}}, "store_id_new")
28
+ end
29
+
30
+ def test_limit
31
+ facet = Product.search("Product", facets: {store_id: {limit: 1}}).facets["store_id"]
32
+ assert_equal 1, facet["terms"].size
33
+ assert_equal 3, facet["total"]
34
+ assert_equal 1, facet["other"]
35
+ end
36
+
37
+ def test_ranges
38
+ price_ranges = [{to: 10}, {from: 10, to: 20}, {from: 20}]
39
+ facet = Product.search("Product", facets: {price: {ranges: price_ranges}}).facets["price"]
40
+
41
+ assert_equal 3, facet["ranges"].size
42
+ assert_equal 10.0, facet["ranges"][0]["to"]
43
+ assert_equal 20.0, facet["ranges"][2]["from"]
44
+ assert_equal 1, facet["ranges"][0]["count"]
45
+ assert_equal 0, facet["ranges"][1]["count"]
46
+ assert_equal 2, facet["ranges"][2]["count"]
47
+ end
48
+
49
+ def test_ranges_dates
50
+ ranges = [{to: 1.day.ago}, {from: 1.day.ago, to: 1.day.from_now}, {from: 1.day.from_now}]
51
+ facet = Product.search("Product", facets: {created_at: {ranges: ranges}}).facets["created_at"]
52
+
53
+ assert_equal 1, facet["ranges"][0]["count"]
54
+ assert_equal 1, facet["ranges"][1]["count"]
55
+ assert_equal 1, facet["ranges"][2]["count"]
56
+ end
57
+
58
+ def test_where_no_smart_facets
59
+ assert_equal ({2 => 2}), store_facet(where: {color: "red"}, facets: {store_id: {where: {in_stock: false}}})
60
+ end
61
+
62
+ def test_smart_facets
63
+ assert_equal ({1 => 1}), store_facet(where: {in_stock: true}, facets: [:store_id], smart_facets: true)
64
+ end
65
+
66
+ def test_smart_facets_where
67
+ assert_equal ({2 => 1}), store_facet(where: {color: "red"}, facets: {store_id: {where: {in_stock: false}}}, smart_facets: true)
68
+ end
69
+
70
+ def test_smart_facets_skip_facet
71
+ assert_equal ({1 => 1, 2 => 2}), store_facet(where: {store_id: 2}, facets: [:store_id], smart_facets: true)
72
+ end
73
+
74
+ def test_smart_facets_skip_facet_complex
75
+ assert_equal ({1 => 1, 2 => 1}), store_facet(where: {store_id: 2, price: {gt: 5}}, facets: [:store_id], smart_facets: true)
76
+ end
77
+
78
+ def test_stats_facets
79
+ options = {where: {store_id: 2}, facets: {store_id: {stats: true}}}
80
+ facets = Product.search("Product", options).facets["store_id"]["terms"]
81
+ expected_facets_keys = %w[term count total_count min max total mean]
82
+ assert_equal expected_facets_keys, facets.first.keys
83
+ end
84
+
85
+ protected
86
+
87
+ def store_facet(options, facet_key="store_id")
88
+ Hash[Product.search("Product", options).facets[facet_key]["terms"].map { |v| [v["term"], v["count"]] }]
89
+ end
90
+
91
+ end
@@ -0,0 +1,58 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestHighlight < Minitest::Test
4
+
5
+ def test_basic
6
+ store_names ["Two Door Cinema Club"]
7
+ assert_equal "Two Door <em>Cinema</em> Club", Product.search("cinema", fields: [:name], highlight: true).with_details.first[1][:highlight][:name]
8
+ end
9
+
10
+ def test_tag
11
+ store_names ["Two Door Cinema Club"]
12
+ assert_equal "Two Door <strong>Cinema</strong> Club", Product.search("cinema", fields: [:name], highlight: {tag: "<strong>"}).with_details.first[1][:highlight][:name]
13
+ end
14
+
15
+ def test_multiple_fields
16
+ store [{name: "Two Door Cinema Club", color: "Cinema Orange"}]
17
+ highlight = Product.search("cinema", fields: [:name, :color], highlight: true).with_details.first[1][:highlight]
18
+ assert_equal "Two Door <em>Cinema</em> Club", highlight[:name]
19
+ assert_equal "<em>Cinema</em> Orange", highlight[:color]
20
+ end
21
+
22
+ def test_fields
23
+ store [{name: "Two Door Cinema Club", color: "Cinema Orange"}]
24
+ highlight = Product.search("cinema", fields: [:name, :color], highlight: {fields: [:name]}).with_details.first[1][:highlight]
25
+ assert_equal "Two Door <em>Cinema</em> Club", highlight[:name]
26
+ assert_equal nil, highlight[:color]
27
+ end
28
+
29
+ def test_field_options
30
+ store_names ["Two Door Cinema Club are a Northern Irish indie rock band"]
31
+ assert_equal "Two Door <em>Cinema</em> Club are", Product.search("cinema", fields: [:name], highlight: {fields: {name: {fragment_size: 20}}}).with_details.first[1][:highlight][:name]
32
+ end
33
+
34
+ def test_multiple_words
35
+ store_names ["Hello World Hello"]
36
+ assert_equal "<em>Hello</em> World <em>Hello</em>", Product.search("hello", fields: [:name], highlight: true).with_details.first[1][:highlight][:name]
37
+ end
38
+
39
+ def test_json
40
+ store_names ["Two Door Cinema Club"]
41
+ json = {
42
+ query: {
43
+ match: {
44
+ _all: "cinema"
45
+ }
46
+ },
47
+ highlight: {
48
+ pre_tags: ["<strong>"],
49
+ post_tags: ["</strong>"],
50
+ fields: {
51
+ "name.analyzed" => {}
52
+ }
53
+ }
54
+ }
55
+ assert_equal "Two Door <strong>Cinema</strong> Club", Product.search(json: json).with_details.first[1][:highlight][:"name.analyzed"]
56
+ end
57
+
58
+ end
@@ -0,0 +1,119 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestIndex < Minitest::Test
4
+
5
+ def test_clean_indices
6
+ old_index = Searchkick::Index.new("products_test_20130801000000000")
7
+ different_index = Searchkick::Index.new("items_test_20130801000000000")
8
+
9
+ old_index.delete if old_index.exists?
10
+ different_index.delete if different_index.exists?
11
+
12
+ # create indexes
13
+ old_index.create
14
+ different_index.create
15
+
16
+ Product.clean_indices
17
+
18
+ assert Product.searchkick_index.exists?
19
+ assert different_index.exists?
20
+ assert !old_index.exists?
21
+ end
22
+
23
+ def test_clean_indices_old_format
24
+ old_index = Searchkick::Index.new("products_test_20130801000000")
25
+ old_index.create
26
+
27
+ Product.clean_indices
28
+
29
+ assert !old_index.exists?
30
+ end
31
+
32
+ def test_mapping
33
+ store_names ["Dollar Tree"], Store
34
+ assert_equal [], Store.search(query: {match: {name: "dollar"}}).map(&:name)
35
+ assert_equal ["Dollar Tree"], Store.search(query: {match: {name: "Dollar Tree"}}).map(&:name)
36
+ end
37
+
38
+ def test_json
39
+ store_names ["Dollar Tree"], Store
40
+ assert_equal [], Store.search(query: {match: {name: "dollar"}}).map(&:name)
41
+ assert_equal ["Dollar Tree"], Store.search(json: {query: {match: {name: "Dollar Tree"}}}, load: false).map(&:name)
42
+ end
43
+
44
+ def test_body
45
+ store_names ["Dollar Tree"], Store
46
+ assert_equal [], Store.search(query: {match: {name: "dollar"}}).map(&:name)
47
+ assert_equal ["Dollar Tree"], Store.search(body: {query: {match: {name: "Dollar Tree"}}}, load: false).map(&:name)
48
+ end
49
+
50
+ def test_block
51
+ store_names ["Dollar Tree"]
52
+ products =
53
+ Product.search "boom" do |body|
54
+ body[:query] = {match_all: {}}
55
+ end
56
+ assert_equal ["Dollar Tree"], products.map(&:name)
57
+ end
58
+
59
+ def test_tokens
60
+ assert_equal ["dollar", "dollartre", "tree"], Product.searchkick_index.tokens("Dollar Tree")
61
+ end
62
+
63
+ def test_tokens_analyzer
64
+ assert_equal ["dollar", "tree"], Product.searchkick_index.tokens("Dollar Tree", analyzer: "searchkick_search2")
65
+ end
66
+
67
+ def test_record_not_found
68
+ store_names ["Product A", "Product B"]
69
+ Product.where(name: "Product A").delete_all
70
+ assert_search "product", ["Product B"]
71
+ ensure
72
+ Product.reindex
73
+ end
74
+
75
+ def test_bad_mapping
76
+ Product.searchkick_index.delete
77
+ store_names ["Product A"]
78
+ assert_raises(Searchkick::InvalidQueryError) { Product.search "test" }
79
+ ensure
80
+ Product.reindex
81
+ end
82
+
83
+ def test_remove_blank_id
84
+ store_names ["Product A"]
85
+ Product.searchkick_index.remove(OpenStruct.new)
86
+ assert_search "product", ["Product A"]
87
+ ensure
88
+ Product.reindex
89
+ end
90
+
91
+ def test_missing_index
92
+ assert_raises(Searchkick::MissingIndexError) { Product.search "test", index_name: "not_found" }
93
+ end
94
+
95
+ def test_unsupported_version
96
+ raises_exception = ->(s) { raise Elasticsearch::Transport::Transport::Error.new("[500] No query registered for [multi_match]") }
97
+ Searchkick.client.stub :search, raises_exception do
98
+ assert_raises(Searchkick::UnsupportedVersionError) { Product.search("test") }
99
+ end
100
+ end
101
+
102
+ def test_invalid_query
103
+ assert_raises(Searchkick::InvalidQueryError) { Product.search(query: {}) }
104
+ end
105
+
106
+ if defined?(ActiveRecord)
107
+
108
+ def test_transaction
109
+ Product.transaction do
110
+ store_names ["Product A"]
111
+ raise ActiveRecord::Rollback
112
+ end
113
+
114
+ assert_search "product", []
115
+ end
116
+
117
+ end
118
+
119
+ end
@@ -0,0 +1,80 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestInheritance < Minitest::Test
4
+
5
+ def test_child_reindex
6
+ store_names ["Max"], Cat
7
+ assert Dog.reindex
8
+ Animal.searchkick_index.refresh
9
+ assert_equal 1, Animal.search("*").size
10
+ end
11
+
12
+ def test_child_index_name
13
+ assert_equal "animals-#{Date.today.year}", Dog.searchkick_index.name
14
+ end
15
+
16
+ def test_child_search
17
+ store_names ["Bear"], Dog
18
+ store_names ["Bear"], Cat
19
+ assert_equal 1, Dog.search("bear").size
20
+ end
21
+
22
+ def test_parent_search
23
+ store_names ["Bear"], Dog
24
+ store_names ["Bear"], Cat
25
+ assert_equal 2, Animal.search("bear").size
26
+ end
27
+
28
+ def test_force_one_type
29
+ store_names ["Green Bear"], Dog
30
+ store_names ["Blue Bear"], Cat
31
+ assert_equal ["Blue Bear"], Animal.search("bear", type: [Cat]).map(&:name)
32
+ end
33
+
34
+ def test_force_multiple_types
35
+ store_names ["Green Bear"], Dog
36
+ store_names ["Blue Bear"], Cat
37
+ store_names ["Red Bear"], Animal
38
+ assert_equal ["Green Bear", "Blue Bear"], Animal.search("bear", type: [Dog, Cat]).map(&:name)
39
+ end
40
+
41
+ def test_child_autocomplete
42
+ store_names ["Max"], Cat
43
+ store_names ["Mark"], Dog
44
+ assert_equal ["Max"], Cat.search("ma", fields: [:name], autocomplete: true).map(&:name)
45
+ end
46
+
47
+ def test_parent_autocomplete
48
+ store_names ["Max"], Cat
49
+ store_names ["Bear"], Dog
50
+ assert_equal ["Bear"], Animal.search("bea", fields: [:name], autocomplete: true).map(&:name).sort
51
+ end
52
+
53
+ # def test_child_suggest
54
+ # store_names ["Shark"], Cat
55
+ # store_names ["Sharp"], Dog
56
+ # assert_equal ["shark"], Cat.search("shar", fields: [:name], suggest: true).suggestions
57
+ # end
58
+
59
+ def test_parent_suggest
60
+ store_names ["Shark"], Cat
61
+ store_names ["Tiger"], Dog
62
+ assert_equal ["tiger"], Animal.search("tige", fields: [:name], suggest: true).suggestions.sort
63
+ end
64
+
65
+ def test_reindex
66
+ store_names ["Bear A"], Cat
67
+ store_names ["Bear B"], Dog
68
+ Animal.reindex
69
+ assert_equal 1, Dog.search("bear").size
70
+ end
71
+
72
+ # TODO move somewhere better
73
+
74
+ def test_multiple_indices
75
+ store_names ["Product A"]
76
+ store_names ["Product B"], Animal
77
+ assert_search "product", ["Product A", "Product B"], index_name: [Product.searchkick_index.name, Animal.searchkick_index.name], conversions: false
78
+ end
79
+
80
+ end
@@ -0,0 +1,163 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative "test_helper"
4
+
5
+ class TestMatch < Minitest::Test
6
+
7
+ # exact
8
+
9
+ def test_match
10
+ store_names ["Whole Milk", "Fat Free Milk", "Milk"]
11
+ assert_search "milk", ["Milk", "Whole Milk", "Fat Free Milk"]
12
+ end
13
+
14
+ def test_case
15
+ store_names ["Whole Milk", "Fat Free Milk", "Milk"]
16
+ assert_search "MILK", ["Milk", "Whole Milk", "Fat Free Milk"]
17
+ end
18
+
19
+ def test_cheese_space_in_index
20
+ store_names ["Pepper Jack Cheese Skewers"]
21
+ assert_search "pepperjack cheese skewers", ["Pepper Jack Cheese Skewers"]
22
+ end
23
+
24
+ # def test_cheese_space_in_query
25
+ # store_names ["Pepperjack Cheese Skewers"]
26
+ # assert_search "pepper jack cheese skewers", ["Pepperjack Cheese Skewers"]
27
+ # end
28
+
29
+ def test_middle_token
30
+ store_names ["Dish Washer Amazing Organic Soap"]
31
+ assert_search "dish soap", ["Dish Washer Amazing Organic Soap"]
32
+ end
33
+
34
+ def test_middle_token_wine
35
+ store_names ["Beringer Wine Founders Estate Chardonnay"]
36
+ assert_search "beringer chardonnay", ["Beringer Wine Founders Estate Chardonnay"]
37
+ end
38
+
39
+ def test_percent
40
+ store_names ["1% Milk", "2% Milk", "Whole Milk"]
41
+ assert_search "1%", ["1% Milk"]
42
+ end
43
+
44
+ # ascii
45
+
46
+ def test_jalapenos
47
+ store_names ["Jalapeño"]
48
+ assert_search "jalapeno", ["Jalapeño"]
49
+ end
50
+
51
+ def test_swedish
52
+ store_names ["ÅÄÖ"]
53
+ assert_search "aao", ["ÅÄÖ"]
54
+ end
55
+
56
+ # stemming
57
+
58
+ def test_stemming
59
+ store_names ["Whole Milk", "Fat Free Milk", "Milk"]
60
+ assert_search "milks", ["Milk", "Whole Milk", "Fat Free Milk"]
61
+ end
62
+
63
+ # fuzzy
64
+
65
+ def test_misspelling_sriracha
66
+ store_names ["Sriracha"]
67
+ assert_search "siracha", ["Sriracha"]
68
+ end
69
+
70
+ def test_misspelling_multiple
71
+ store_names ["Greek Yogurt", "Green Onions"]
72
+ assert_search "greed", ["Greek Yogurt", "Green Onions"]
73
+ end
74
+
75
+ def test_short_word
76
+ store_names ["Finn"]
77
+ assert_search "fin", ["Finn"]
78
+ end
79
+
80
+ def test_edit_distance_two
81
+ store_names ["Bingo"]
82
+ assert_search "bin", []
83
+ assert_search "bingooo", []
84
+ assert_search "mango", []
85
+ end
86
+
87
+ def test_edit_distance_one
88
+ store_names ["Bingo"]
89
+ assert_search "bing", ["Bingo"]
90
+ assert_search "bingoo", ["Bingo"]
91
+ assert_search "ringo", ["Bingo"]
92
+ end
93
+
94
+ def test_edit_distance_long_word
95
+ store_names ["thisisareallylongword"]
96
+ assert_search "thisisareallylongwor", ["thisisareallylongword"] # missing letter
97
+ assert_search "thisisareelylongword", [] # edit distance = 2
98
+ end
99
+
100
+ def test_misspelling_tabasco
101
+ store_names ["Tabasco"]
102
+ assert_search "tobasco", ["Tabasco"]
103
+ end
104
+
105
+ def test_misspelling_zucchini
106
+ store_names ["Zucchini"]
107
+ assert_search "zuchini", ["Zucchini"]
108
+ end
109
+
110
+ def test_misspelling_ziploc
111
+ store_names ["Ziploc"]
112
+ assert_search "zip lock", ["Ziploc"]
113
+ end
114
+
115
+ # spaces
116
+
117
+ def test_spaces_in_field
118
+ store_names ["Red Bull"]
119
+ assert_search "redbull", ["Red Bull"]
120
+ end
121
+
122
+ def test_spaces_in_query
123
+ store_names ["Dishwasher"]
124
+ assert_search "dish washer", ["Dishwasher"]
125
+ end
126
+
127
+ def test_spaces_three_words
128
+ store_names ["Dish Washer Soap", "Dish Washer"]
129
+ assert_search "dish washer soap", ["Dish Washer Soap"]
130
+ end
131
+
132
+ def test_spaces_stemming
133
+ store_names ["Almond Milk"]
134
+ assert_search "almondmilks", ["Almond Milk"]
135
+ end
136
+
137
+ def test_all
138
+ store_names ["Product A", "Product B"]
139
+ assert_search "*", ["Product A", "Product B"]
140
+ end
141
+
142
+ def test_no_arguments
143
+ assert_equal [], Product.search.to_a
144
+ end
145
+
146
+ def test_no_term
147
+ store_names ["Product A"]
148
+ assert_equal ["Product A"], Product.search(where: {name: "Product A"}).map(&:name)
149
+ end
150
+
151
+ def test_to_be_or_not_to_be
152
+ store_names ["to be or not to be"]
153
+ assert_search "to be", ["to be or not to be"]
154
+ end
155
+
156
+ def test_unsearchable
157
+ store [
158
+ {name: "Unsearchable", description: "Almond"}
159
+ ]
160
+ assert_search "almond", []
161
+ end
162
+
163
+ end