mincer 0.1.2 → 0.2.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 +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