mincer 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/Gemfile +2 -1
  4. data/README.md +111 -64
  5. data/lib/mincer/action_view/sort_helper.rb +7 -7
  6. data/lib/mincer/base.rb +1 -1
  7. data/lib/mincer/config.rb +29 -0
  8. data/lib/mincer/core_ext/string.rb +5 -0
  9. data/lib/mincer/processors/cache_digest/processor.rb +54 -0
  10. data/lib/mincer/processors/helpers.rb +15 -0
  11. data/lib/mincer/processors/pagination/processor.rb +63 -0
  12. data/lib/mincer/processors/pg_json_dumper/processor.rb +69 -0
  13. data/lib/mincer/processors/pg_search/processor.rb +148 -0
  14. data/lib/mincer/processors/pg_search/sanitizer.rb +61 -0
  15. data/lib/mincer/processors/pg_search/search_engines/array.rb +41 -0
  16. data/lib/mincer/processors/pg_search/search_engines/base.rb +56 -0
  17. data/lib/mincer/processors/pg_search/search_engines/fulltext.rb +58 -0
  18. data/lib/mincer/processors/pg_search/search_engines/trigram.rb +41 -0
  19. data/lib/mincer/processors/pg_search/search_statement.rb +31 -0
  20. data/lib/mincer/processors/sorting/processor.rb +113 -0
  21. data/lib/mincer/version.rb +1 -1
  22. data/lib/mincer.rb +31 -31
  23. data/mincer.gemspec +0 -2
  24. data/spec/lib/mincer/action_view/sort_helper_spec.rb +11 -0
  25. data/spec/lib/mincer/base_spec.rb +15 -0
  26. data/spec/lib/mincer/config_spec.rb +7 -0
  27. data/spec/lib/{processors/cache_digest_spec.rb → mincer/processors/cache_digest/processor_spec.rb} +2 -9
  28. data/spec/lib/{processors/paginate_spec.rb → mincer/processors/pagination/processor_spec.rb} +43 -17
  29. data/spec/lib/{processors/pg_json_dumper_spec.rb → mincer/processors/pg_json_dumper/processor_spec.rb} +2 -6
  30. data/spec/lib/mincer/processors/pg_search/processor_spec.rb +268 -0
  31. data/spec/lib/mincer/processors/pg_search/sanitizer_spec.rb +38 -0
  32. data/spec/lib/mincer/processors/pg_search/search_engines/array_spec.rb +83 -0
  33. data/spec/lib/mincer/processors/pg_search/search_engines/fulltext_spec.rb +101 -0
  34. data/spec/lib/mincer/processors/pg_search/search_engines/trigram_spec.rb +91 -0
  35. data/spec/lib/mincer/processors/sorting/processor_spec.rb +181 -0
  36. data/spec/mincer_config.rb +38 -0
  37. data/spec/spec_helper.rb +40 -4
  38. data/spec/support/postgres_adapter.rb +12 -3
  39. data/spec/support/sqlite3_adapter.rb +3 -0
  40. metadata +42 -45
  41. data/lib/mincer/processors/cache_digest.rb +0 -50
  42. data/lib/mincer/processors/paginate.rb +0 -41
  43. data/lib/mincer/processors/pg_json_dumper.rb +0 -51
  44. data/lib/mincer/processors/search.rb +0 -34
  45. data/lib/mincer/processors/sort.rb +0 -59
  46. data/spec/lib/processors/search_spec.rb +0 -77
  47. data/spec/lib/processors/sort_spec.rb +0 -77
@@ -1,11 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe ::Mincer::Processors::CacheDigest do
3
+ describe ::Mincer::Processors::CacheDigest::Processor do
4
4
  context 'when postgres used' do
5
5
  before do
6
- setup_basic_postgres_table
7
- class ActiveRecordModel < ActiveRecord::Base
8
- end
6
+ setup_postgres_table
9
7
  ActiveRecordModel.create!(text: 'Test1')
10
8
  ActiveRecordModel.create!(text: 'Test2')
11
9
  end
@@ -26,9 +24,4 @@ describe ::Mincer::Processors::CacheDigest do
26
24
  end
27
25
  end
28
26
 
29
-
30
- context 'when postgres used' do
31
-
32
- end
33
-
34
27
  end
@@ -1,10 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe ::Mincer::Processors::Paginate do
3
+ describe ::Mincer::Processors::Pagination::Processor do
4
4
  before do
5
5
  setup_basic_sqlite3_table
6
- class ActiveRecordModel < ActiveRecord::Base
7
- end
8
6
  30.times { |i| ActiveRecordModel.create(text: i) }
9
7
  end
10
8
 
@@ -39,10 +37,7 @@ describe ::Mincer::Processors::Paginate do
39
37
 
40
38
  context 'when WillPaginate is used for pagination' do
41
39
  before do
42
- pending 'Need to fix test, need to unload Kaminari to test this'
43
- #::Mincer::Processors::Paginator.any_instance.stub(:kaminari?).and_return(false)
44
- #require 'will_paginate'
45
- #require 'will_paginate/active_record'
40
+ ::Mincer::Processors::Pagination::Processor.stub(:kaminari?).and_return(false)
46
41
  end
47
42
 
48
43
  describe 'paginating with basic model without any Mincer::Base configuration' do
@@ -51,17 +46,11 @@ describe ::Mincer::Processors::Paginate do
51
46
  end
52
47
 
53
48
  it 'paginates by with provided page and per_page in args' do
54
- query = subject.new(ActiveRecordModel, { 'page' => '2', 'per_page' => '20' })
55
- query.to_a.count.should eq(10)
56
- end
57
-
58
- it 'paginates by default page(1) and per_page(10) when nothing passed to args' do
59
- query = subject.new(ActiveRecordModel)
60
- query.to_a.count.should eq(25)
49
+ ActiveRecord::Relation.any_instance.should_receive(:paginate).with(page: '2', per_page: '20')
50
+ subject.new(ActiveRecordModel, { 'page' => '2', 'per_page' => '20' })
61
51
  end
62
52
  end
63
53
 
64
-
65
54
  describe 'paginating when basic model has disabled pagination' do
66
55
  it 'returns all items' do
67
56
  subject = Class.new(Mincer::Base) do
@@ -76,8 +65,8 @@ describe ::Mincer::Processors::Paginate do
76
65
  context 'when there is no gem for pagination in loaded' do
77
66
  it 'returns all items' do
78
67
  subject = Class.new(Mincer::Base)
79
- ::Mincer::Processors::Paginate.any_instance.stub(:kaminari?).and_return(false)
80
- ::Mincer::Processors::Paginate.any_instance.stub(:will_paginate?).and_return(false)
68
+ ::Mincer::Processors::Pagination::Processor.stub(:kaminari?).and_return(false)
69
+ ::Mincer::Processors::Pagination::Processor.stub(:will_paginate?).and_return(false)
81
70
 
82
71
  query = subject.new(ActiveRecordModel)
83
72
  query.to_a.count.should eq(30)
@@ -85,4 +74,41 @@ describe ::Mincer::Processors::Paginate do
85
74
 
86
75
  end
87
76
 
77
+
78
+ describe 'configuration of pg_search' do
79
+ before do
80
+ Mincer.config.instance_variable_set('@pagination', nil)
81
+ end
82
+
83
+ describe 'param_name' do
84
+ it 'uses "page" as default value for page_param_name' do
85
+ Mincer.config.pagination.page_param_name.should == :page
86
+ end
87
+
88
+ it 'sets param_name string' do
89
+ Mincer.configure do |config|
90
+ config.pagination do |search|
91
+ search.page_param_name = 's'
92
+ end
93
+ end
94
+ Mincer.config.pagination.page_param_name.should == 's'
95
+ end
96
+ end
97
+
98
+ describe 'param_name' do
99
+ it 'uses :per_page as default value for per_page_param_name' do
100
+ Mincer.config.pagination.per_page_param_name.should == :per_page
101
+ end
102
+
103
+ it 'sets param_name string' do
104
+ Mincer.configure do |config|
105
+ config.pagination do |search|
106
+ search.per_page_param_name = 's'
107
+ end
108
+ end
109
+ Mincer.config.pagination.per_page_param_name.should == 's'
110
+ end
111
+ end
112
+ end
113
+
88
114
  end
@@ -1,11 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe ::Mincer::Processors::PgJsonDumper do
3
+ describe ::Mincer::Processors::PgJsonDumper::Processor do
4
4
  context 'when postgres is used' do
5
5
  before do
6
- setup_basic_postgres_table
7
- class ActiveRecordModel < ActiveRecord::Base
8
- end
6
+ setup_postgres_table
9
7
  ActiveRecordModel.create!(text: 'Test1')
10
8
  ActiveRecordModel.create!(text: 'Test2')
11
9
  end
@@ -48,8 +46,6 @@ describe ::Mincer::Processors::PgJsonDumper do
48
46
  context 'when postgres is NOT used' do
49
47
  before do
50
48
  setup_basic_sqlite3_table
51
- class ActiveRecordModel < ActiveRecord::Base
52
- end
53
49
  ActiveRecordModel.create!(id: 1, text: 'Test1')
54
50
  ActiveRecordModel.create!(id: 2, text: 'Test2')
55
51
  end
@@ -0,0 +1,268 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Mincer::Processors::PgSearch::Processor do
4
+ context 'when postgres used' do
5
+ before do
6
+ setup_postgres_table
7
+ ActiveRecordModel.create!(text: 'Test')
8
+ ActiveRecordModel.create!(text: 'Bingo')
9
+ ActiveRecordModel.create!(text: 'Bongo')
10
+ end
11
+
12
+ describe 'config' do
13
+ it 'defines method with pg_options' do
14
+ subject = Class.new(Mincer::Base) do
15
+ pg_search [{ columns: %w{"active_record_models"."tags" }, engines: [:array] }]
16
+ end
17
+ query = subject.new(ActiveRecordModel)
18
+ query.send(:pg_search_params).should == [{ columns: %w{"active_record_models"."tags" }, engines: [:array] }]
19
+ end
20
+ end
21
+
22
+ describe 'searching' do
23
+ describe 'searches with basic model without any Mincer::Base configuration' do
24
+ subject(:model) do
25
+ Class.new(Mincer::Base)
26
+ end
27
+
28
+ describe 'searching with fulltext search' do
29
+ it 'searches by pattern in args' do
30
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'Bingo' })
31
+ query.to_a.count.should eq(1)
32
+ end
33
+
34
+ it 'avoids search when pattern is an empty string or spaces' do
35
+ query = subject.new(ActiveRecordModel, { 'pattern' => ' ' })
36
+
37
+ query.to_a.count.should eq(3)
38
+ end
39
+
40
+ context 'when another search_column exists with nil value on a found item' do
41
+ before do
42
+ setup_postgres_table([['id', 'SERIAL PRIMARY KEY'], ['text', 'TEXT'], ['text2', 'TEXT']])
43
+ ActiveRecordModel.create!(text: 'Test')
44
+ ActiveRecordModel.create!(text: 'Bingo')
45
+ end
46
+
47
+ it 'still includes found item in results' do
48
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'Bingo' })
49
+ query.to_a.count.should eq(1)
50
+ end
51
+ end
52
+
53
+ describe 'searching with 2 statements' do
54
+ before do
55
+ setup_postgres_table([['id', 'SERIAL PRIMARY KEY'], ['text', 'TEXT'], ['tags', 'TEXT[]']])
56
+ ActiveRecordModel.create!(text: 'Test', tags: ['a', 'b'])
57
+ ActiveRecordModel.create!(text: 'Bingo', tags: ['b', 'c'])
58
+ end
59
+
60
+ it 'searches using 2 statements' do
61
+ subject = Class.new(Mincer::Base) do
62
+ pg_search [
63
+ { :columns => %w{"active_record_models"."tags" }, engines: [:array] },
64
+ { :columns => %w{"active_record_models"."text" }, engines: [:fulltext] }
65
+ ]
66
+ end
67
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'c' })
68
+ query.to_a.count.should eq(1)
69
+ query.to_a.first.text.should == 'Bingo'
70
+
71
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'Test' })
72
+ query.to_a.count.should eq(1)
73
+ query.to_a.first.text.should == 'Test'
74
+ end
75
+
76
+ it 'searches using 2 statements with aggregator set to :and' do
77
+ subject = Class.new(Mincer::Base) do
78
+ pg_search [
79
+ { :columns => %w{"active_record_models"."tags" }, engines: [:array] },
80
+ { :columns => %w{"active_record_models"."text" }, engines: [:fulltext] }
81
+ ], join_with: :and
82
+ end
83
+
84
+ ActiveRecordModel.create!(text: 'O', tags: ['O'])
85
+
86
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'O' })
87
+ query.to_a.count.should eq(1)
88
+ query.to_a.first.text.should == 'O'
89
+ end
90
+ end
91
+
92
+
93
+ describe 'searching with array' do
94
+ before do
95
+ setup_postgres_table([['id', 'SERIAL PRIMARY KEY'], ['text', 'TEXT'], ['tags', 'TEXT[]']])
96
+ ActiveRecordModel.create!(text: 'Test', tags: ['a', 'b'])
97
+ ActiveRecordModel.create!(text: 'Bingo', tags: ['b', 'c'])
98
+ end
99
+
100
+ it 'includes 2 items when both items include pattern' do
101
+ subject = Class.new(Mincer::Base) do
102
+ pg_search [{ :columns => %w{"active_record_models"."tags" }, engines: [:array] }]
103
+ end
104
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'b' })
105
+ query.to_a.count.should eq(2)
106
+ end
107
+
108
+ it 'includes 1 item when match was found on one item' do
109
+ subject = Class.new(Mincer::Base) do
110
+ pg_search [{ :columns => %w{"active_record_models"."tags" }, engines: [:array] }]
111
+ end
112
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'c' })
113
+ query.to_a.count.should eq(1)
114
+ end
115
+
116
+ it 'includes both when matched with array overlap and option "any_word" set to true' do
117
+ subject = Class.new(Mincer::Base) do
118
+ pg_search [{ :columns => %w{"active_record_models"."tags" }, engines: [:array], any_word: true }]
119
+ end
120
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'a c d' })
121
+ query.to_a.count.should eq(2)
122
+ end
123
+
124
+ it 'includes both when matched with array overlap and option "any_word" set to true(separated with ",")' do
125
+ subject = Class.new(Mincer::Base) do
126
+ def pg_search_params
127
+ [{ :columns => %w{"active_record_models"."tags"}, engines: [:array], any_word: true }]
128
+ end
129
+ end
130
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'a, c,d' })
131
+ query.to_a.count.should eq(2)
132
+ end
133
+
134
+ it 'includes no items when nothing matched pattern' do
135
+ subject = Class.new(Mincer::Base) do
136
+ def pg_search_params
137
+ [{ :columns => %w{"active_record_models"."tags" }, engines: [:array] }]
138
+ end
139
+ end
140
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'd e' })
141
+ query.to_a.count.should eq(0)
142
+ end
143
+
144
+ end
145
+ end
146
+ end
147
+
148
+
149
+ describe 'searching when basic model has disabled search' do
150
+ it 'does not modifies relation' do
151
+ subject = Class.new(Mincer::Base) do
152
+ skip_search!
153
+ end
154
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'Bingo' })
155
+ query.to_a.count.should eq(3)
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'when postgres is NOT used' do
162
+ before do
163
+ setup_basic_sqlite3_table
164
+ ActiveRecordModel.create!(text: 'Test')
165
+ ActiveRecordModel.create!(text: 'Bingo')
166
+ ActiveRecordModel.create!(text: 'Bongo')
167
+ end
168
+
169
+ subject(:model) do
170
+ Class.new(Mincer::Base)
171
+ end
172
+
173
+ it 'returns all records' do
174
+ query = subject.new(ActiveRecordModel, { 'pattern' => 'Bingo' })
175
+ query.to_a.count.should eq(3)
176
+ end
177
+ end
178
+
179
+
180
+ describe 'configuration of pg_search' do
181
+ before do
182
+ Mincer.config.instance_variable_set('@pg_search', nil)
183
+ end
184
+
185
+ describe 'param_name' do
186
+ it 'uses "pattern" as default value for param_name' do
187
+ Mincer.config.pg_search.param_name.should == 'pattern'
188
+ end
189
+
190
+ it 'sets param_name string' do
191
+ Mincer.configure do |config|
192
+ config.pg_search do |search|
193
+ search.param_name = 's'
194
+ end
195
+ end
196
+ Mincer.config.pg_search.param_name.should == 's'
197
+ end
198
+ end
199
+
200
+ describe 'fulltext_engine' do
201
+ it 'sets "ignore_accent" to true as default value' do
202
+ Mincer.config.pg_search.fulltext_engine[:ignore_accent].should be_true
203
+ end
204
+
205
+ it 'sets "any_word" to false as default value' do
206
+ Mincer.config.pg_search.fulltext_engine[:any_word].should be_false
207
+ end
208
+
209
+ it 'sets "dictionary" to "simple" as default value' do
210
+ Mincer.config.pg_search.fulltext_engine[:dictionary].should == :simple
211
+ end
212
+
213
+ it 'sets "ignore_case" to "false" as default value' do
214
+ Mincer.config.pg_search.fulltext_engine[:ignore_case].should be_false
215
+ end
216
+
217
+ it 'sets fulltext_engine options while merging with defaults' do
218
+ Mincer.configure do |config|
219
+ config.pg_search do |search|
220
+ search.fulltext_engine = search.fulltext_engine.merge(ignore_accent: false)
221
+ end
222
+ end
223
+ Mincer.config.pg_search.fulltext_engine.should == { ignore_accent: false, any_word: false, dictionary: :simple, ignore_case: false }
224
+ end
225
+ end
226
+
227
+ describe 'trigram_engine' do
228
+ it 'sets "ignore_accent" to true as default value' do
229
+ Mincer.config.pg_search.trigram_engine[:ignore_accent].should be_true
230
+ end
231
+
232
+ it 'sets "threshold" to 0.3 as default value' do
233
+ Mincer.config.pg_search.trigram_engine[:threshold].should == 0.3
234
+ end
235
+
236
+ it 'sets trigram_engine options while merging with defaults' do
237
+ Mincer.configure do |config|
238
+ config.pg_search do |search|
239
+ search.trigram_engine = search.trigram_engine.merge(threshold: 0.5)
240
+ end
241
+ end
242
+ Mincer.config.pg_search.trigram_engine.should == { ignore_accent: true, threshold: 0.5 }
243
+ end
244
+ end
245
+
246
+ describe 'array_engine' do
247
+ it 'sets "ignore_accent" to true as default value' do
248
+ Mincer.config.pg_search.array_engine[:ignore_accent].should be_true
249
+ end
250
+
251
+ it 'sets "any_word" to false as default value' do
252
+ Mincer.config.pg_search.array_engine[:any_word].should be_true
253
+ end
254
+
255
+ it 'sets array_engine options while merging with defaults' do
256
+ Mincer.configure do |config|
257
+ config.pg_search do |search|
258
+ search.array_engine = search.array_engine.merge(any_word: false)
259
+ end
260
+ end
261
+ Mincer.config.pg_search.array_engine.should == { ignore_accent: true, any_word: false }
262
+ end
263
+ end
264
+
265
+ end
266
+
267
+
268
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Mincer::Processors::PgSearch::Sanitizer do
4
+ before do
5
+ setup_postgres_table
6
+ end
7
+
8
+ describe '#sanitize' do
9
+ subject { ::Mincer::Processors::PgSearch::Sanitizer }
10
+ it 'applies "ignore_case" option' do
11
+ subject.sanitize_string('text', :ignore_case).to_sql.should == "lower('text')"
12
+ end
13
+
14
+ it 'applies "ignore_accent" option' do
15
+ subject.sanitize_string('text', :ignore_accent).to_sql.should == "unaccent('text')"
16
+ end
17
+
18
+ describe 'coalesce option' do
19
+ context 'when postgres extension installed' do
20
+ it 'applies "coalesce" option' do
21
+ subject.sanitize_string('text', :coalesce).to_sql.should == "coalesce('text', '')"
22
+ end
23
+ end
24
+ context 'when postgres extension is unavailable' do
25
+ it 'applies "coalesce" option' do
26
+ Mincer.instance_variable_get('@installed_extensions')[:unaccent] = false
27
+ subject.sanitize_string('text', :coalesce).to_sql.should == 'text'
28
+ Mincer.instance_variable_get('@installed_extensions')[:unaccent] = true
29
+ end
30
+ end
31
+ end
32
+
33
+ it 'applies multiple sanitizers' do
34
+ subject.sanitize_string('text', :ignore_case, :ignore_accent, :coalesce).to_sql.should == "unaccent(lower(coalesce('text', '')))"
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Mincer::PgSearch::SearchEngines::Array do
4
+ before do
5
+ setup_postgres_table
6
+ end
7
+ subject(:search_engine_class) { ::Mincer::PgSearch::SearchEngines::Array }
8
+ let(:search_statement_class) { ::Mincer::Processors::PgSearch::SearchStatement }
9
+
10
+ describe '.search_engine_statements' do
11
+ context 'when 2 columns' do
12
+ it 'stores them in instance variable hash under :or key' do
13
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:array])
14
+ search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:array])
15
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
16
+ search_engine.send(:search_engine_statements).should include(search_statement1)
17
+ search_engine.send(:search_engine_statements).should include(search_statement2)
18
+ end
19
+ end
20
+
21
+ it 'ignores other engines' do
22
+ search_statement1 = search_statement_class.new(['"records"."text"'])
23
+ search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:trigram])
24
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
25
+ search_engine.send(:search_engine_statements).should == []
26
+ search_engine.send(:search_engine_statements).should == []
27
+ end
28
+ end
29
+
30
+ describe '.conditions' do
31
+ it 'generates search condition with one column, one term and no options' do
32
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:array])
33
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
34
+ search_engine.conditions.to_sql.should == %{((("records"."text"::text[]) @> ARRAY['search']))}
35
+ end
36
+
37
+ it 'generates search condition with two columns, one term and no options' do
38
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:array])
39
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
40
+ search_engine.conditions.to_sql.should == %{((("records"."text"::text[] || "records"."text2"::text[]) @> ARRAY['search']))}
41
+ end
42
+
43
+ it 'generates search condition with two columns, two terms and no options' do
44
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:array])
45
+ search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
46
+ search_engine.conditions.to_sql.should == %{((("records"."text"::text[] || "records"."text2"::text[]) @> ARRAY['search','word']))}
47
+ end
48
+
49
+ it 'generates search condition with two columns, two terms and option "ignore_accent" set to true ' do
50
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:array], ignore_accent: true)
51
+ search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
52
+ search_engine.conditions.to_sql.should == %{((("records"."text"::text[] || "records"."text2"::text[]) @> ARRAY[unaccent('search'),unaccent('word')]))}
53
+ end
54
+
55
+ #TODO: sanitizer can not be set on array columns since we ned to unpack an reconstruct those arrays. Find a solution
56
+ it 'generates search condition with two columns, two terms and option "any_word" set to true ' do
57
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:array], any_word: true)
58
+ search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
59
+ search_engine.conditions.to_sql.should == %{((("records"."text"::text[] || "records"."text2"::text[]) && ARRAY['search','word']))}
60
+ end
61
+
62
+ it 'generates search condition with two columns, two terms and option "ignore_accent" and "ignore_case" set to true ' do
63
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:array], ignore_accent: true, ignore_case: true)
64
+ search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
65
+ search_engine.conditions.to_sql.should == %{((("records"."text"::text[] || "records"."text2"::text[]) @> ARRAY[unaccent(lower('search')),unaccent(lower('word'))]))}
66
+ end
67
+
68
+ it 'generates search condition with one column, one term, two statements and no options' do
69
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:array])
70
+ search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:array], any_word: true)
71
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
72
+ search_engine.conditions.to_sql.should == %{((("records"."text"::text[]) @> ARRAY['search']) OR (("records"."text2"::text[]) && ARRAY['search']))}
73
+ end
74
+
75
+ it 'generates search condition with one column, one term, two statements and no "param_name" options set to "s"' do
76
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:array])
77
+ search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:array], any_word: true, param_name: 's')
78
+ search_engine = search_engine_class.new({ pattern: 'search', s: 'word' }, [search_statement1, search_statement2])
79
+ search_engine.conditions.to_sql.should == %{((("records"."text"::text[]) @> ARRAY['search']) OR (("records"."text2"::text[]) && ARRAY['word']))}
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Mincer::PgSearch::SearchEngines::Fulltext do
4
+ before do
5
+ setup_postgres_table
6
+ end
7
+ subject(:search_engine_class) { ::Mincer::PgSearch::SearchEngines::Fulltext }
8
+ let(:search_statement_class) { ::Mincer::Processors::PgSearch::SearchStatement }
9
+
10
+ describe '.search_engine_statements' do
11
+ context 'when 2 columns' do
12
+ it 'stores them in instance variable hash under :or key' do
13
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext])
14
+ search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:fulltext])
15
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
16
+ search_engine.send(:search_engine_statements).should include(search_statement1)
17
+ search_engine.send(:search_engine_statements).should include(search_statement2)
18
+ end
19
+ end
20
+
21
+ it 'ignores other engines' do
22
+ search_statement1 = search_statement_class.new(['"records"."text"'])
23
+ search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:array])
24
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
25
+ search_engine.send(:search_engine_statements).should == []
26
+ search_engine.send(:search_engine_statements).should == []
27
+ end
28
+ end
29
+
30
+ describe '.conditions' do
31
+ it 'generates search condition with one column, one term and no options with columns wrapped with coalesce' do
32
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext])
33
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
34
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', coalesce("records"."text", ''))) @@ (to_tsquery('simple', 'search'))))}
35
+ end
36
+
37
+ it 'generates search condition with two columns, one term and no options' do
38
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext])
39
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
40
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', coalesce("records"."text", '')) || to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'search'))))}
41
+ end
42
+
43
+ it 'generates search condition with two columns, two terms and no options' do
44
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext])
45
+ search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
46
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', coalesce("records"."text", '')) || to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'search' || ' & ' || 'word'))))}
47
+ end
48
+
49
+ it 'generates search condition with two columns, two terms and option "any_word" set to true ' do
50
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext], any_word: true)
51
+ search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
52
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', coalesce("records"."text", '')) || to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'search' || ' | ' || 'word'))))}
53
+ end
54
+
55
+ it 'generates search condition with two columns, two terms and option "any_word" set to true while escaping special characters' do
56
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext], any_word: true)
57
+ search_engine = search_engine_class.new({ pattern: 'search word!(:&|) !' }, [search_statement1])
58
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', coalesce("records"."text", '')) || to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'search' || ' | ' || 'word'))))}
59
+ end
60
+
61
+ it 'generates search condition with two columns, two terms and option "ignore_accent" set to true ' do
62
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext], ignore_accent: true)
63
+ search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
64
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', unaccent(coalesce("records"."text", ''))) || to_tsvector('simple', unaccent(coalesce("records"."text2", '')))) @@ (to_tsquery('simple', unaccent('search') || ' & ' || unaccent('word')))))}
65
+ end
66
+
67
+ it 'generates search condition with two columns, two terms and option "ignore_accent" and "ignore_case" set to true ' do
68
+ search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext], ignore_accent: true, ignore_case: true)
69
+ search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
70
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', unaccent(lower(coalesce("records"."text", '')))) || to_tsvector('simple', unaccent(lower(coalesce("records"."text2", ''))))) @@ (to_tsquery('simple', unaccent(lower('search')) || ' & ' || unaccent(lower('word'))))))}
71
+ end
72
+
73
+ it 'generates search condition with one column, one term and option "dictionary" set to :english' do
74
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext], dictionary: :english)
75
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
76
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('english', coalesce("records"."text", ''))) @@ (to_tsquery('english', 'search'))))}
77
+ end
78
+
79
+ it 'generates search condition with two search statements one column, one term and no options' do
80
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext])
81
+ search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:fulltext])
82
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
83
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', coalesce("records"."text", ''))) @@ (to_tsquery('simple', 'search'))) OR ((to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'search'))))}
84
+ end
85
+
86
+ it 'generates search condition with one column, one term, two statements and no options' do
87
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext])
88
+ search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:fulltext])
89
+ search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
90
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', coalesce("records"."text", ''))) @@ (to_tsquery('simple', 'search'))) OR ((to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'search'))))}
91
+ end
92
+
93
+ it 'generates search condition with one column, one term, two statements and "param_name" option set to "s"' do
94
+ search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext])
95
+ search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:fulltext], param_name: 's')
96
+ search_engine = search_engine_class.new({ pattern: 'search', s: 'word' }, [search_statement1, search_statement2])
97
+ search_engine.conditions.to_sql.should == %{(((to_tsvector('simple', coalesce("records"."text", ''))) @@ (to_tsquery('simple', 'search'))) OR ((to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'word'))))}
98
+ end
99
+ end
100
+
101
+ end