searchkick-hooopo 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +35 -0
  4. data/CHANGELOG.md +491 -0
  5. data/Gemfile +12 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +1908 -0
  8. data/Rakefile +20 -0
  9. data/benchmark/Gemfile +23 -0
  10. data/benchmark/benchmark.rb +97 -0
  11. data/lib/searchkick/bulk_reindex_job.rb +17 -0
  12. data/lib/searchkick/index.rb +500 -0
  13. data/lib/searchkick/index_options.rb +333 -0
  14. data/lib/searchkick/indexer.rb +28 -0
  15. data/lib/searchkick/logging.rb +242 -0
  16. data/lib/searchkick/middleware.rb +12 -0
  17. data/lib/searchkick/model.rb +156 -0
  18. data/lib/searchkick/process_batch_job.rb +23 -0
  19. data/lib/searchkick/process_queue_job.rb +23 -0
  20. data/lib/searchkick/query.rb +901 -0
  21. data/lib/searchkick/reindex_queue.rb +38 -0
  22. data/lib/searchkick/reindex_v2_job.rb +39 -0
  23. data/lib/searchkick/results.rb +216 -0
  24. data/lib/searchkick/tasks.rb +33 -0
  25. data/lib/searchkick/version.rb +3 -0
  26. data/lib/searchkick.rb +215 -0
  27. data/searchkick.gemspec +28 -0
  28. data/test/aggs_test.rb +197 -0
  29. data/test/autocomplete_test.rb +75 -0
  30. data/test/boost_test.rb +175 -0
  31. data/test/callbacks_test.rb +59 -0
  32. data/test/ci/before_install.sh +17 -0
  33. data/test/errors_test.rb +19 -0
  34. data/test/gemfiles/activerecord31.gemfile +7 -0
  35. data/test/gemfiles/activerecord32.gemfile +7 -0
  36. data/test/gemfiles/activerecord40.gemfile +8 -0
  37. data/test/gemfiles/activerecord41.gemfile +8 -0
  38. data/test/gemfiles/activerecord42.gemfile +7 -0
  39. data/test/gemfiles/activerecord50.gemfile +7 -0
  40. data/test/gemfiles/apartment.gemfile +8 -0
  41. data/test/gemfiles/cequel.gemfile +8 -0
  42. data/test/gemfiles/mongoid2.gemfile +7 -0
  43. data/test/gemfiles/mongoid3.gemfile +6 -0
  44. data/test/gemfiles/mongoid4.gemfile +7 -0
  45. data/test/gemfiles/mongoid5.gemfile +7 -0
  46. data/test/gemfiles/mongoid6.gemfile +8 -0
  47. data/test/gemfiles/nobrainer.gemfile +8 -0
  48. data/test/gemfiles/parallel_tests.gemfile +8 -0
  49. data/test/geo_shape_test.rb +172 -0
  50. data/test/highlight_test.rb +78 -0
  51. data/test/index_test.rb +153 -0
  52. data/test/inheritance_test.rb +83 -0
  53. data/test/marshal_test.rb +8 -0
  54. data/test/match_test.rb +276 -0
  55. data/test/misspellings_test.rb +56 -0
  56. data/test/model_test.rb +42 -0
  57. data/test/multi_search_test.rb +22 -0
  58. data/test/multi_tenancy_test.rb +22 -0
  59. data/test/order_test.rb +46 -0
  60. data/test/pagination_test.rb +53 -0
  61. data/test/partial_reindex_test.rb +58 -0
  62. data/test/query_test.rb +35 -0
  63. data/test/records_test.rb +10 -0
  64. data/test/reindex_test.rb +52 -0
  65. data/test/reindex_v2_job_test.rb +32 -0
  66. data/test/routing_test.rb +23 -0
  67. data/test/should_index_test.rb +32 -0
  68. data/test/similar_test.rb +28 -0
  69. data/test/sql_test.rb +198 -0
  70. data/test/suggest_test.rb +85 -0
  71. data/test/synonyms_test.rb +67 -0
  72. data/test/test_helper.rb +527 -0
  73. data/test/where_test.rb +223 -0
  74. metadata +250 -0
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in searchkick.gemspec
4
+ gemspec path: "../../"
5
+
6
+ gem "sqlite3"
7
+ gem "activerecord", "~> 5.0.0"
8
+ gem "parallel_tests"
@@ -0,0 +1,172 @@
1
+ require_relative "test_helper"
2
+
3
+ class GeoShapeTest < Minitest::Test
4
+ def setup
5
+ Region.destroy_all
6
+ store [
7
+ {
8
+ name: "Region A",
9
+ text: "The witch had a cat",
10
+ territory: {
11
+ type: "polygon",
12
+ coordinates: [[[30, 40], [35, 45], [40, 40], [40, 30], [30, 30], [30, 40]]]
13
+ }
14
+ },
15
+ {
16
+ name: "Region B",
17
+ text: "and a very tall hat",
18
+ territory: {
19
+ type: "polygon",
20
+ coordinates: [[[50, 60], [55, 65], [60, 60], [60, 50], [50, 50], [50, 60]]]
21
+ }
22
+ },
23
+ {
24
+ name: "Region C",
25
+ text: "and long ginger hair which she wore in a plait",
26
+ territory: {
27
+ type: "polygon",
28
+ coordinates: [[[10, 20], [15, 25], [20, 20], [20, 10], [10, 10], [10, 20]]]
29
+ }
30
+ }
31
+ ], Region
32
+ end
33
+
34
+ def test_circle
35
+ assert_search "*", ["Region A"], {
36
+ where: {
37
+ territory: {
38
+ geo_shape: {
39
+ type: "circle",
40
+ coordinates: {lat: 28.0, lon: 38.0},
41
+ radius: "444000m"
42
+ }
43
+ }
44
+ }
45
+ }, Region
46
+ end
47
+
48
+ def test_envelope
49
+ assert_search "*", ["Region A"], {
50
+ where: {
51
+ territory: {
52
+ geo_shape: {
53
+ type: "envelope",
54
+ coordinates: [[28, 42], [32, 38]]
55
+ }
56
+ }
57
+ }
58
+ }, Region
59
+ end
60
+
61
+ def test_polygon
62
+ assert_search "*", ["Region A"], {
63
+ where: {
64
+ territory: {
65
+ geo_shape: {
66
+ type: "polygon",
67
+ coordinates: [[[38, 42], [42, 42], [42, 38], [38, 38], [38, 42]]]
68
+ }
69
+ }
70
+ }
71
+ }, Region
72
+ end
73
+
74
+ def test_multipolygon
75
+ assert_search "*", ["Region A", "Region B"], {
76
+ where: {
77
+ territory: {
78
+ geo_shape: {
79
+ type: "multipolygon",
80
+ coordinates: [
81
+ [[[38, 42], [42, 42], [42, 38], [38, 38], [38, 42]]],
82
+ [[[58, 62], [62, 62], [62, 58], [58, 58], [58, 62]]]
83
+ ]
84
+ }
85
+ }
86
+ }
87
+ }, Region
88
+ end
89
+
90
+ def test_disjoint
91
+ assert_search "*", ["Region B", "Region C"], {
92
+ where: {
93
+ territory: {
94
+ geo_shape: {
95
+ type: "envelope",
96
+ relation: "disjoint",
97
+ coordinates: [[28, 42], [32, 38]]
98
+ }
99
+ }
100
+ }
101
+ }, Region
102
+ end
103
+
104
+ def test_within
105
+ assert_search "*", ["Region A"], {
106
+ where: {
107
+ territory: {
108
+ geo_shape: {
109
+ type: "envelope",
110
+ relation: "within",
111
+ coordinates: [[20,50], [50,20]]
112
+ }
113
+ }
114
+ }
115
+ }, Region
116
+ end
117
+
118
+ def test_search_math
119
+ assert_search "witch", ["Region A"], {
120
+ where: {
121
+ territory: {
122
+ geo_shape: {
123
+ type: "envelope",
124
+ coordinates: [[28, 42], [32, 38]]
125
+ }
126
+ }
127
+ }
128
+ }, Region
129
+ end
130
+
131
+ def test_search_no_match
132
+ assert_search "ginger hair", [], {
133
+ where: {
134
+ territory: {
135
+ geo_shape: {
136
+ type: "envelope",
137
+ coordinates: [[28, 42], [32, 38]]
138
+ }
139
+ }
140
+ }
141
+ }, Region
142
+ end
143
+
144
+ def test_contains
145
+ skip if elasticsearch_below22?
146
+ assert_search "*", ["Region C"], {
147
+ where: {
148
+ territory: {
149
+ geo_shape: {
150
+ type: "envelope",
151
+ relation: "contains",
152
+ coordinates: [[12, 13], [13, 12]]
153
+ }
154
+ }
155
+ }
156
+ }, Region
157
+ end
158
+
159
+ def test_latlon
160
+ assert_search "*", ["Region A"], {
161
+ where: {
162
+ territory: {
163
+ geo_shape: {
164
+ type: "envelope",
165
+ coordinates: [{lat: 42, lon: 28}, {lat: 38, lon: 32}]
166
+ }
167
+ }
168
+ }
169
+ }, Region
170
+ end
171
+
172
+ end
@@ -0,0 +1,78 @@
1
+ require_relative "test_helper"
2
+
3
+ class HighlightTest < Minitest::Test
4
+ def test_basic
5
+ store_names ["Two Door Cinema Club"]
6
+ assert_equal "Two Door <em>Cinema</em> Club", Product.search("cinema", fields: [:name], highlight: true).first.search_highlights[:name]
7
+ end
8
+
9
+ def test_tag
10
+ store_names ["Two Door Cinema Club"]
11
+ assert_equal "Two Door <strong>Cinema</strong> Club", Product.search("cinema", fields: [:name], highlight: {tag: "<strong>"}).first.search_highlights[:name]
12
+ end
13
+
14
+ def test_tag_class
15
+ store_names ["Two Door Cinema Club"]
16
+ assert_equal "Two Door <strong class='classy'>Cinema</strong> Club", Product.search("cinema", fields: [:name], highlight: {tag: "<strong class='classy'>"}).first.search_highlights[:name]
17
+ end
18
+
19
+ def test_multiple_fields
20
+ store [{name: "Two Door Cinema Club", color: "Cinema Orange"}]
21
+ highlights = Product.search("cinema", fields: [:name, :color], highlight: true).first.search_highlights
22
+ assert_equal "Two Door <em>Cinema</em> Club", highlights[:name]
23
+ assert_equal "<em>Cinema</em> Orange", highlights[:color]
24
+ end
25
+
26
+ def test_fields
27
+ store [{name: "Two Door Cinema Club", color: "Cinema Orange"}]
28
+ highlights = Product.search("cinema", fields: [:name, :color], highlight: {fields: [:name]}).first.search_highlights
29
+ assert_equal "Two Door <em>Cinema</em> Club", highlights[:name]
30
+ assert_nil highlights[:color]
31
+ end
32
+
33
+ def test_field_options
34
+ store_names ["Two Door Cinema Club are a Northern Irish indie rock band"]
35
+ fragment_size = ENV["MATCH"] == "word_start" ? 26 : 20
36
+ assert_equal "Two Door <em>Cinema</em> Club are", Product.search("cinema", fields: [:name], highlight: {fields: {name: {fragment_size: fragment_size}}}).first.search_highlights[:name]
37
+ end
38
+
39
+ def test_multiple_words
40
+ store_names ["Hello World Hello"]
41
+ assert_equal "<em>Hello</em> World <em>Hello</em>", Product.search("hello", fields: [:name], highlight: true).first.search_highlights[:name]
42
+ end
43
+
44
+ def test_encoder
45
+ store_names ["<b>Hello</b>"]
46
+ assert_equal "&lt;b&gt;<em>Hello</em>&lt;&#x2F;b&gt;", Product.search("hello", fields: [:name], highlight: {encoder: "html"}, misspellings: false).first.search_highlights[:name]
47
+ end
48
+
49
+ def test_word_middle
50
+ store_names ["Two Door Cinema Club"]
51
+ assert_equal "Two Door <em>Cinema</em> Club", Product.search("ine", fields: [:name], match: :word_middle, highlight: true).first.search_highlights[:name]
52
+ end
53
+
54
+ def test_body
55
+ skip if ENV["MATCH"] == "word_start"
56
+ store_names ["Two Door Cinema Club"]
57
+ body = {
58
+ query: {
59
+ match: {
60
+ "name.analyzed" => "cinema"
61
+ }
62
+ },
63
+ highlight: {
64
+ pre_tags: ["<strong>"],
65
+ post_tags: ["</strong>"],
66
+ fields: {
67
+ "name.analyzed" => {}
68
+ }
69
+ }
70
+ }
71
+ assert_equal "Two Door <strong>Cinema</strong> Club", Product.search(body: body).first.search_highlights[:"name.analyzed"]
72
+ end
73
+
74
+ def test_legacy
75
+ store_names ["Two Door Cinema Club"]
76
+ assert_equal "Two Door <em>Cinema</em> Club", Product.search("cinema", fields: [:name], highlight: true).with_details.first[1][:highlight][:name]
77
+ end
78
+ end
@@ -0,0 +1,153 @@
1
+ require_relative "test_helper"
2
+
3
+ class IndexTest < Minitest::Test
4
+ def setup
5
+ super
6
+ Region.destroy_all
7
+ end
8
+
9
+ def test_clean_indices
10
+ suffix = Searchkick.index_suffix ? "_#{Searchkick.index_suffix}" : ""
11
+ old_index = Searchkick::Index.new("products_test#{suffix}_20130801000000000")
12
+ different_index = Searchkick::Index.new("items_test#{suffix}_20130801000000000")
13
+
14
+ old_index.delete if old_index.exists?
15
+ different_index.delete if different_index.exists?
16
+
17
+ # create indexes
18
+ old_index.create
19
+ different_index.create
20
+
21
+ Product.searchkick_index.clean_indices
22
+
23
+ assert Product.searchkick_index.exists?
24
+ assert different_index.exists?
25
+ assert !old_index.exists?
26
+ end
27
+
28
+ def test_clean_indices_old_format
29
+ suffix = Searchkick.index_suffix ? "_#{Searchkick.index_suffix}" : ""
30
+ old_index = Searchkick::Index.new("products_test#{suffix}_20130801000000")
31
+ old_index.create
32
+
33
+ Product.searchkick_index.clean_indices
34
+
35
+ assert !old_index.exists?
36
+ end
37
+
38
+ def test_retain
39
+ Product.reindex
40
+ assert_equal 1, Product.searchkick_index.all_indices.size
41
+ Product.reindex(retain: true)
42
+ assert_equal 2, Product.searchkick_index.all_indices.size
43
+ end
44
+
45
+ def test_total_docs
46
+ store_names ["Product A"]
47
+ assert_equal 1, Product.searchkick_index.total_docs
48
+ end
49
+
50
+ def test_mapping
51
+ store_names ["Dollar Tree"], Store
52
+ assert_equal [], Store.search(body: {query: {match: {name: "dollar"}}}).map(&:name)
53
+ assert_equal ["Dollar Tree"], Store.search(body: {query: {match: {name: "Dollar Tree"}}}).map(&:name)
54
+ end
55
+
56
+ def test_body
57
+ store_names ["Dollar Tree"], Store
58
+ assert_equal [], Store.search(body: {query: {match: {name: "dollar"}}}).map(&:name)
59
+ assert_equal ["Dollar Tree"], Store.search(body: {query: {match: {name: "Dollar Tree"}}}, load: false).map(&:name)
60
+ end
61
+
62
+ def test_block
63
+ store_names ["Dollar Tree"]
64
+ products =
65
+ Product.search "boom" do |body|
66
+ body[:query] = {match_all: {}}
67
+ end
68
+ assert_equal ["Dollar Tree"], products.map(&:name)
69
+ end
70
+
71
+ def test_tokens
72
+ assert_equal ["dollar", "dollartre", "tree"], Product.searchkick_index.tokens("Dollar Tree", analyzer: "searchkick_index")
73
+ end
74
+
75
+ def test_tokens_analyzer
76
+ assert_equal ["dollar", "tree"], Product.searchkick_index.tokens("Dollar Tree", analyzer: "searchkick_search2")
77
+ end
78
+
79
+ def test_record_not_found
80
+ store_names ["Product A", "Product B"]
81
+ Product.where(name: "Product A").delete_all
82
+ assert_search "product", ["Product B"]
83
+ ensure
84
+ Product.reindex
85
+ end
86
+
87
+ def test_bad_mapping
88
+ Product.searchkick_index.delete
89
+ store_names ["Product A"]
90
+ assert_raises(Searchkick::InvalidQueryError) { Product.search "test" }
91
+ ensure
92
+ Product.reindex
93
+ end
94
+
95
+ def test_remove_blank_id
96
+ store_names ["Product A"]
97
+ Product.searchkick_index.remove(Product.new)
98
+ assert_search "product", ["Product A"]
99
+ ensure
100
+ Product.reindex
101
+ end
102
+
103
+ def test_missing_index
104
+ assert_raises(Searchkick::MissingIndexError) { Product.search("test", index_name: "not_found") }
105
+ end
106
+
107
+ def test_unsupported_version
108
+ raises_exception = ->(_) { raise Elasticsearch::Transport::Transport::Error, "[500] No query registered for [multi_match]" }
109
+ Searchkick.client.stub :search, raises_exception do
110
+ assert_raises(Searchkick::UnsupportedVersionError) { Product.search("test") }
111
+ end
112
+ end
113
+
114
+ def test_invalid_body
115
+ assert_raises(Searchkick::InvalidQueryError) { Product.search(body: {boom: true}) }
116
+ end
117
+
118
+ def test_transaction
119
+ skip unless defined?(ActiveRecord)
120
+ Product.transaction do
121
+ store_names ["Product A"]
122
+ raise ActiveRecord::Rollback
123
+ end
124
+ assert_search "*", []
125
+ end
126
+
127
+ def test_filterable
128
+ # skip for 5.0 since it throws
129
+ # Cannot search on field [alt_description] since it is not indexed.
130
+ skip unless elasticsearch_below50?
131
+ store [{name: "Product A", alt_description: "Hello"}]
132
+ assert_search "*", [], where: {alt_description: "Hello"}
133
+ end
134
+
135
+ def test_large_value
136
+ skip if nobrainer?
137
+ large_value = 1000.times.map { "hello" }.join(" ")
138
+ store [{name: "Product A", text: large_value}], Region
139
+ assert_search "product", ["Product A"], {}, Region
140
+ assert_search "hello", ["Product A"], {fields: [:name, :text]}, Region
141
+ assert_search "hello", ["Product A"], {}, Region
142
+ end
143
+
144
+ def test_very_large_value
145
+ skip if nobrainer? || elasticsearch_below22?
146
+ large_value = 10000.times.map { "hello" }.join(" ")
147
+ store [{name: "Product A", text: large_value}], Region
148
+ assert_search "product", ["Product A"], {}, Region
149
+ assert_search "hello", ["Product A"], {fields: [:name, :text]}, Region
150
+ # values that exceed ignore_above are not included in _all field :(
151
+ # assert_search "hello", ["Product A"], {}, Region
152
+ end
153
+ end
@@ -0,0 +1,83 @@
1
+ require_relative "test_helper"
2
+
3
+ class InheritanceTest < Minitest::Test
4
+ def setup
5
+ skip if defined?(Cequel)
6
+ super
7
+ end
8
+
9
+ def test_child_reindex
10
+ store_names ["Max"], Cat
11
+ assert Dog.reindex
12
+ Animal.searchkick_index.refresh
13
+ assert_equal 1, Animal.search("*").size
14
+ end
15
+
16
+ def test_child_index_name
17
+ assert_equal "animals-#{Date.today.year}", Dog.searchkick_index.name
18
+ end
19
+
20
+ def test_child_search
21
+ store_names ["Bear"], Dog
22
+ store_names ["Bear"], Cat
23
+ assert_equal 1, Dog.search("bear").size
24
+ end
25
+
26
+ def test_parent_search
27
+ store_names ["Bear"], Dog
28
+ store_names ["Bear"], Cat
29
+ assert_equal 2, Animal.search("bear").size
30
+ end
31
+
32
+ def test_force_one_type
33
+ store_names ["Green Bear"], Dog
34
+ store_names ["Blue Bear"], Cat
35
+ assert_equal ["Blue Bear"], Animal.search("bear", type: [Cat]).map(&:name)
36
+ end
37
+
38
+ def test_force_multiple_types
39
+ store_names ["Green Bear"], Dog
40
+ store_names ["Blue Bear"], Cat
41
+ store_names ["Red Bear"], Animal
42
+ assert_equal ["Green Bear", "Blue Bear"], Animal.search("bear", type: [Dog, Cat]).map(&:name)
43
+ end
44
+
45
+ def test_child_autocomplete
46
+ store_names ["Max"], Cat
47
+ store_names ["Mark"], Dog
48
+ assert_equal ["Max"], Cat.search("ma", fields: [:name], match: :text_start).map(&:name)
49
+ end
50
+
51
+ def test_parent_autocomplete
52
+ store_names ["Max"], Cat
53
+ store_names ["Bear"], Dog
54
+ assert_equal ["Bear"], Animal.search("bea", fields: [:name], match: :text_start).map(&:name).sort
55
+ end
56
+
57
+ # def test_child_suggest
58
+ # store_names ["Shark"], Cat
59
+ # store_names ["Sharp"], Dog
60
+ # assert_equal ["shark"], Cat.search("shar", fields: [:name], suggest: true).suggestions
61
+ # end
62
+
63
+ def test_parent_suggest
64
+ store_names ["Shark"], Cat
65
+ store_names ["Tiger"], Dog
66
+ assert_equal ["tiger"], Animal.search("tige", fields: [:name], suggest: true).suggestions.sort
67
+ end
68
+
69
+ def test_reindex
70
+ store_names ["Bear A"], Cat
71
+ store_names ["Bear B"], Dog
72
+ Animal.reindex
73
+ assert_equal 1, Dog.search("bear").size
74
+ end
75
+
76
+ # TODO move somewhere better
77
+
78
+ def test_multiple_indices
79
+ store_names ["Product A"]
80
+ store_names ["Product B"], Animal
81
+ assert_search "product", ["Product A", "Product B"], index_name: [Product.searchkick_index.name, Animal.searchkick_index.name], conversions: false
82
+ end
83
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "test_helper"
2
+
3
+ class MarshalTest < Minitest::Test
4
+ def test_marshal
5
+ store_names ["Product A"]
6
+ assert Marshal.dump(Product.search("*", load: {dumpable: true}).results)
7
+ end
8
+ end