mincer 0.2.2 → 0.2.3
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.
- checksums.yaml +4 -4
- data/README.md +21 -1
- data/lib/generators/mincer/query_generator.rb +18 -0
- data/lib/generators/mincer/templates/query.rb +8 -0
- data/lib/mincer/processors/pg_search/search_engines/array.rb +1 -1
- data/lib/mincer/processors/pg_search/search_engines/fulltext.rb +23 -18
- data/lib/mincer/processors/pg_search/search_engines/trigram.rb +23 -3
- data/lib/mincer/processors/pg_search/search_statement.rb +5 -2
- data/lib/mincer/processors/sorting/processor.rb +3 -3
- data/lib/mincer/version.rb +1 -1
- data/mincer.gemspec +1 -0
- data/spec/generators/query/query_generator_spec.rb +15 -0
- data/spec/lib/mincer/processors/pg_search/processor_spec.rb +1 -1
- data/spec/lib/mincer/processors/pg_search/search_engines/fulltext_spec.rb +50 -37
- data/spec/lib/mincer/processors/pg_search/search_engines/trigram_spec.rb +6 -6
- data/spec/lib/mincer/processors/pg_search/search_statement_spec.rb +55 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/postgres_adapter.rb +1 -0
- metadata +41 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 977d1742d488e2ec263b062b0ebd6ff27335a377
|
4
|
+
data.tar.gz: 436e3e27dc8664fd945af1ba045b58bfff108f57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f984595c4b855119c56707fff4e997f3d1790d4a4d46f4f53b8480d4b4527e69c17ba0d30aef198d79d623155fd9769dc7c47553149bbf57e2a33c183a4139b0
|
7
|
+
data.tar.gz: e3bc8da6242291d4786574a4b5654c86148b96e3012f6583d7ff256b11751d2db54364af4193edf9203843a15c1a3cba2cb61431d6f74f54b5e4ab06b7c862d6
|
data/README.md
CHANGED
@@ -38,7 +38,20 @@ Lets assume we have 2 models
|
|
38
38
|
has_many :employees
|
39
39
|
end
|
40
40
|
|
41
|
-
|
41
|
+
### Generating custom query
|
42
|
+
|
43
|
+
rails generate mincer:query EmployeesList
|
44
|
+
|
45
|
+
Will generate employees_list_query.rb file in /app/queries/ directory
|
46
|
+
|
47
|
+
class EmployeesListQuery < Mincer::Base
|
48
|
+
def build_query(relation, args)
|
49
|
+
# Apply your conditions, custom selects, etc. to relation
|
50
|
+
relation
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
It inherits from Mincer::Base. Lets instantiate it
|
42
55
|
|
43
56
|
class EmployeesListQuery < Mincer::Base
|
44
57
|
# method should always return relation
|
@@ -205,6 +218,12 @@ If you set `ignore_case` attribute to true - search will ignore case.
|
|
205
218
|
|
206
219
|
pg_search [{ :columns => %w{employees.full_name companies.name} }, :ignore_case => true ]
|
207
220
|
|
221
|
+
Options like `unaccent`, `any_word`, `ignore_case` can be set to be used only on query or document. In Example if you use specific column that already has unaccented and lowercased text with GIN/GIST index and do not want to additionally use `unaccent` or `ignore_case` functions on that column(because this will cause index not to work) -you can disable those options. Ex.
|
222
|
+
|
223
|
+
pg_search [{ :columns => %w{employees.full_name} }, :ignore_case => {query: true} ]
|
224
|
+
|
225
|
+
This way `ignore_case` function will be used only on pattern that you are searching for and not on columns.
|
226
|
+
|
208
227
|
If you set `param_name` attribute to any other string - this string will be used to extract search term from params(Default param_name = 'patern').
|
209
228
|
|
210
229
|
pg_search [{ :columns => %w{employees.full_name companies.name} }, :param_name => 's']
|
@@ -299,6 +318,7 @@ or run `CREATE EXTENSION IF NOT EXISTS pgcrypto;`
|
|
299
318
|
2. Change default arguments(sort, order, pattern, page, per_page..)
|
300
319
|
3. Disable some processors for all Mincer objects
|
301
320
|
2. Create rails generators.
|
321
|
+
3. Add coalescing as option for document
|
302
322
|
|
303
323
|
|
304
324
|
## Contributing
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Mincer
|
4
|
+
module Generators
|
5
|
+
class QueryGenerator < ::Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
desc <<DESC
|
9
|
+
Description:
|
10
|
+
Creates query file for selected model
|
11
|
+
DESC
|
12
|
+
|
13
|
+
def create_query_file
|
14
|
+
template 'query.rb', "app/queries/#{file_name}_query.rb"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -29,7 +29,7 @@ module Mincer
|
|
29
29
|
|
30
30
|
def query_for(search_statement)
|
31
31
|
normalized_pattern = search_statement.pattern.split(%r{\s|,}).uniq.reject(&:empty?).map do |item|
|
32
|
-
sanitize_string_quoted(item, search_statement.sanitizers).to_sql
|
32
|
+
sanitize_string_quoted(item, search_statement.sanitizers(:query)).to_sql
|
33
33
|
end.join(',')
|
34
34
|
Arel.sql("ARRAY[#{normalized_pattern}]")
|
35
35
|
end
|
@@ -7,10 +7,14 @@ module Mincer
|
|
7
7
|
def conditions
|
8
8
|
return nil unless prepared_search_statements.any?
|
9
9
|
arel_group do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
documents = prepared_search_statements.map do |search_statement|
|
11
|
+
ts_query = ts_query_for(search_statement)
|
12
|
+
ts_vectors_for(search_statement).map do |ts_vector|
|
13
|
+
arel_group(Arel::Nodes::InfixOperation.new('@@', ts_vector, ts_query))
|
14
|
+
end
|
15
|
+
end.flatten
|
16
|
+
|
17
|
+
join_expressions(documents, :or)
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
@@ -18,8 +22,12 @@ module Mincer
|
|
18
22
|
return nil unless prepared_search_statements.any?
|
19
23
|
arel_group do
|
20
24
|
ranks = prepared_search_statements.map do |search_statement|
|
21
|
-
|
22
|
-
|
25
|
+
ts_query = ts_query_for(search_statement)
|
26
|
+
ts_vectors_for(search_statement).map do |ts_vector|
|
27
|
+
Arel::Nodes::NamedFunction.new('ts_rank', [ts_vector, ts_query])
|
28
|
+
end
|
29
|
+
end.flatten
|
30
|
+
|
23
31
|
join_expressions(ranks, '+')
|
24
32
|
end
|
25
33
|
end
|
@@ -34,23 +42,20 @@ module Mincer
|
|
34
42
|
end.compact
|
35
43
|
end
|
36
44
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
def ts_vectors_for(search_statement)
|
46
|
+
sanitizers = search_statement.sanitizers(:document)
|
47
|
+
# sanitizers += [:coalesce] if (search_statement.columns.size > 1)
|
48
|
+
documents = search_statement.columns.map do |search_column|
|
49
|
+
sanitized_term = sanitize_column(search_column, sanitizers)
|
50
|
+
ts_vector = Arel::Nodes::NamedFunction.new('to_tsvector', [search_statement.dictionary, sanitized_term])
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
46
|
-
def
|
54
|
+
def ts_query_for(search_statement)
|
47
55
|
terms_delimiter = search_statement.options[:any_word] ? '|' : '&'
|
48
|
-
tsquery_sql = Arel.sql(search_statement.terms.map { |term| sanitize_string_quoted(term, search_statement.sanitizers).to_sql }.join(" || ' #{terms_delimiter} ' || "))
|
49
|
-
|
50
|
-
Arel::Nodes::NamedFunction.new('to_tsquery', [search_statement.dictionary, tsquery_sql])
|
51
|
-
end
|
56
|
+
tsquery_sql = Arel.sql(search_statement.terms.map { |term| sanitize_string_quoted(term, search_statement.sanitizers(:query)).to_sql }.join(" || ' #{terms_delimiter} ' || "))
|
57
|
+
Arel::Nodes::NamedFunction.new('to_tsquery', [search_statement.dictionary, tsquery_sql])
|
52
58
|
end
|
53
|
-
|
54
59
|
end
|
55
60
|
|
56
61
|
end
|
@@ -2,6 +2,7 @@ module Mincer
|
|
2
2
|
module PgSearch
|
3
3
|
module SearchEngines
|
4
4
|
class Trigram < Base
|
5
|
+
@@default_threshold = nil
|
5
6
|
|
6
7
|
def conditions
|
7
8
|
return nil unless prepared_search_statements.any?
|
@@ -22,19 +23,38 @@ module Mincer
|
|
22
23
|
|
23
24
|
def document_for(search_statement)
|
24
25
|
documents = search_statement.columns.map do |search_column|
|
25
|
-
|
26
|
-
|
26
|
+
if self.class.default_threshold == search_statement.threshold
|
27
|
+
arel_group(join_expressions([sanitize_column(search_column, search_statement.sanitizers(:document)), sanitize_string(search_statement.pattern, search_statement.sanitizers(:query))], '%'))
|
28
|
+
else
|
29
|
+
arel_group(similarity_function(search_column, search_statement).gteq(search_statement.threshold))
|
30
|
+
end
|
27
31
|
end
|
28
32
|
join_expressions(documents, :or)
|
29
33
|
end
|
30
34
|
|
31
35
|
def rank_for(search_statement)
|
32
36
|
ranks = search_statement.columns.map do |search_column|
|
33
|
-
|
37
|
+
similarity_function(search_column, search_statement)
|
34
38
|
end
|
35
39
|
join_expressions(ranks, :+)
|
36
40
|
end
|
37
41
|
|
42
|
+
def similarity_function(search_column, search_statement)
|
43
|
+
Arel::Nodes::NamedFunction.new('similarity', [sanitize_column(search_column, search_statement.sanitizers(:document)), sanitize_string(search_statement.pattern, search_statement.sanitizers(:query))])
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.default_threshold
|
47
|
+
if @@default_threshold.nil?
|
48
|
+
grab_default_threshold
|
49
|
+
else
|
50
|
+
@@default_threshold
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.grab_default_threshold
|
55
|
+
@@default_threshold = ::Mincer.connection.execute('SELECT show_limit();').first['show_limit'].to_r
|
56
|
+
end
|
57
|
+
|
38
58
|
end
|
39
59
|
end
|
40
60
|
end
|
@@ -9,8 +9,11 @@ module Mincer
|
|
9
9
|
@columns, @options = columns, ::ActiveSupport::HashWithIndifferentAccess.new(options)
|
10
10
|
end
|
11
11
|
|
12
|
-
def sanitizers
|
13
|
-
@sanitizers ||=
|
12
|
+
def sanitizers(type = :all)
|
13
|
+
@sanitizers ||= {}
|
14
|
+
@sanitizers[type] ||= Sanitizer::AVAILABLE_SANITIZERS.select do |sanitizer|
|
15
|
+
options[sanitizer].is_a?(Hash) && [:query, :document].include?(type) ? options[sanitizer][type] : options[sanitizer]
|
16
|
+
end
|
14
17
|
end
|
15
18
|
|
16
19
|
def dictionary
|
@@ -8,7 +8,7 @@ module Mincer
|
|
8
8
|
|
9
9
|
def apply
|
10
10
|
relation = @relation.order(sort_string)
|
11
|
-
@mincer.sort_attribute, @mincer.sort_order = sort_attr, order_attr
|
11
|
+
@mincer.sort_attribute, @mincer.sort_order = sort_attr.to_s, order_attr.to_s
|
12
12
|
relation
|
13
13
|
end
|
14
14
|
|
@@ -21,11 +21,11 @@ module Mincer
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def sort_attr
|
24
|
-
@mincer.send(:allowed_sort_attributes).include?(sort) && sort
|
24
|
+
(@mincer.send(:allowed_sort_attributes).include?(sort) && sort) || default_sort
|
25
25
|
end
|
26
26
|
|
27
27
|
def order_attr
|
28
|
-
%w{asc desc}.include?(order.try(:downcase)) && order
|
28
|
+
(%w{asc desc}.include?(order.try(:downcase)) && order) || default_order
|
29
29
|
end
|
30
30
|
|
31
31
|
def sort
|
data/lib/mincer/version.rb
CHANGED
data/mincer.gemspec
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'generators/mincer/query_generator'
|
3
|
+
|
4
|
+
describe ::Mincer::Generators::QueryGenerator, :type => :generator do
|
5
|
+
destination File.expand_path("../../tmp", __FILE__)
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
prepare_destination
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should properly create query file' do
|
12
|
+
run_generator %w(User)
|
13
|
+
assert_file 'app/queries/user_query.rb', /class UserQuery < Mincer::Base/
|
14
|
+
end
|
15
|
+
end
|
@@ -33,7 +33,7 @@ describe ::Mincer::Processors::PgSearch::Processor do
|
|
33
33
|
|
34
34
|
it 'order by rank' do
|
35
35
|
query = subject.new(ActiveRecordModel, { 'pattern' => 'Bingo' })
|
36
|
-
query.to_sql.should include("(ts_rank(
|
36
|
+
query.to_sql.should include("(ts_rank(to_tsvector('simple', unaccent(active_record_models.text)), to_tsquery('simple', unaccent('Bingo'))))")
|
37
37
|
query.to_a.count.should eq(1)
|
38
38
|
end
|
39
39
|
|
@@ -28,73 +28,86 @@ describe ::Mincer::PgSearch::SearchEngines::Fulltext do
|
|
28
28
|
end
|
29
29
|
|
30
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
31
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
32
|
+
context 'when only one column is searchable' do
|
33
|
+
it 'generates search condition with one column, one term and no options without columns wrapped with coalesce' do
|
34
|
+
search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext])
|
35
|
+
search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
|
36
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', "records"."text") @@ to_tsquery('simple', 'search')))}
|
37
|
+
end
|
41
38
|
end
|
42
39
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
40
|
+
context 'when 2 columns is searchable' do
|
41
|
+
it 'generates search condition with two columns, one term and no options' do
|
42
|
+
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext])
|
43
|
+
search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
|
44
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', \"records\".\"text\") @@ to_tsquery('simple', 'search')) OR (to_tsvector('simple', \"records\".\"text2\") @@ to_tsquery('simple', 'search')))}
|
45
|
+
end
|
48
46
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
it 'generates search condition with two columns, two terms and no options' do
|
48
|
+
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext])
|
49
|
+
search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
|
50
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', \"records\".\"text\") @@ to_tsquery('simple', 'search' || ' & ' || 'word')) OR (to_tsvector('simple', \"records\".\"text2\") @@ to_tsquery('simple', 'search' || ' & ' || 'word')))}
|
51
|
+
end
|
54
52
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
53
|
+
it 'generates search condition with two columns, two terms and option "any_word" set to true ' do
|
54
|
+
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext], any_word: true)
|
55
|
+
search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
|
56
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', \"records\".\"text\") @@ to_tsquery('simple', 'search' || ' | ' || 'word')) OR (to_tsvector('simple', \"records\".\"text2\") @@ to_tsquery('simple', 'search' || ' | ' || 'word')))}
|
57
|
+
end
|
60
58
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
it 'generates search condition with two columns, two terms and option "any_word" set to true while escaping special characters' do
|
60
|
+
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext], any_word: true)
|
61
|
+
search_engine = search_engine_class.new({ pattern: 'search word!(:&|) !' }, [search_statement1])
|
62
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', \"records\".\"text\") @@ to_tsquery('simple', 'search' || ' | ' || 'word')) OR (to_tsvector('simple', \"records\".\"text2\") @@ to_tsquery('simple', 'search' || ' | ' || 'word')))}
|
63
|
+
end
|
66
64
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
65
|
+
it 'generates search condition with two columns, two terms and option "ignore_accent" set to true ' do
|
66
|
+
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext], ignore_accent: true)
|
67
|
+
search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
|
68
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', unaccent(\"records\".\"text\")) @@ to_tsquery('simple', unaccent('search') || ' & ' || unaccent('word'))) OR (to_tsvector('simple', unaccent(\"records\".\"text2\")) @@ to_tsquery('simple', unaccent('search') || ' & ' || unaccent('word'))))}
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when unaccent set only on query' do
|
72
|
+
it 'generates search condition with two columns, two terms and option "ignore_accent" set to be used on query ' do
|
73
|
+
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext], ignore_accent: {query: true})
|
74
|
+
search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
|
75
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', \"records\".\"text\") @@ to_tsquery('simple', unaccent('search') || ' & ' || unaccent('word'))) OR (to_tsvector('simple', \"records\".\"text2\") @@ to_tsquery('simple', unaccent('search') || ' & ' || unaccent('word'))))}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'generates search condition with two columns, two terms and option "ignore_accent" and "ignore_case" set to true ' do
|
80
|
+
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:fulltext], ignore_accent: true, ignore_case: true)
|
81
|
+
search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
|
82
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', unaccent(lower(\"records\".\"text\"))) @@ to_tsquery('simple', unaccent(lower('search')) || ' & ' || unaccent(lower('word')))) OR (to_tsvector('simple', unaccent(lower(\"records\".\"text2\"))) @@ to_tsquery('simple', unaccent(lower('search')) || ' & ' || unaccent(lower('word')))))}
|
83
|
+
end
|
71
84
|
end
|
72
85
|
|
73
86
|
it 'generates search condition with one column, one term and option "dictionary" set to :english' do
|
74
87
|
search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext], dictionary: :english)
|
75
88
|
search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
|
76
|
-
search_engine.conditions.to_sql.should == %{((
|
89
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('english', "records"."text") @@ to_tsquery('english', 'search')))}
|
77
90
|
end
|
78
91
|
|
79
92
|
it 'generates search condition with two search statements one column, one term and no options' do
|
80
93
|
search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext])
|
81
94
|
search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:fulltext])
|
82
95
|
search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
|
83
|
-
search_engine.conditions.to_sql.should == %{((
|
96
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', "records"."text") @@ to_tsquery('simple', 'search')) OR (to_tsvector('simple', "records"."text2") @@ to_tsquery('simple', 'search')))}
|
84
97
|
end
|
85
98
|
|
86
99
|
it 'generates search condition with one column, one term, two statements and no options' do
|
87
100
|
search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext])
|
88
101
|
search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:fulltext])
|
89
102
|
search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
|
90
|
-
search_engine.conditions.to_sql.should == %{((
|
103
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', \"records\".\"text\") @@ to_tsquery('simple', 'search')) OR (to_tsvector('simple', \"records\".\"text2\") @@ to_tsquery('simple', 'search')))}
|
91
104
|
end
|
92
105
|
|
93
106
|
it 'generates search condition with one column, one term, two statements and "param_name" option set to "s"' do
|
94
107
|
search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:fulltext])
|
95
108
|
search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:fulltext], param_name: 's')
|
96
109
|
search_engine = search_engine_class.new({ pattern: 'search', s: 'word' }, [search_statement1, search_statement2])
|
97
|
-
search_engine.conditions.to_sql.should == %{((
|
110
|
+
search_engine.conditions.to_sql.should == %{((to_tsvector('simple', \"records\".\"text\") @@ to_tsquery('simple', 'search')) OR (to_tsvector('simple', \"records\".\"text2\") @@ to_tsquery('simple', 'word')))}
|
98
111
|
end
|
99
112
|
end
|
100
113
|
|
@@ -31,7 +31,7 @@ describe ::Mincer::PgSearch::SearchEngines::Trigram do
|
|
31
31
|
it 'generates search condition with one column, one term and no options' do
|
32
32
|
search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:trigram])
|
33
33
|
search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
|
34
|
-
search_engine.conditions.to_sql.should == %{((
|
34
|
+
search_engine.conditions.to_sql.should == %{(("records"."text" % 'search'))}
|
35
35
|
end
|
36
36
|
|
37
37
|
it 'generates search condition with one column, one term and "threshold" option set to 0.5' do
|
@@ -43,33 +43,33 @@ describe ::Mincer::PgSearch::SearchEngines::Trigram do
|
|
43
43
|
it 'generates search condition with two columns, one term and no options' do
|
44
44
|
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:trigram])
|
45
45
|
search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1])
|
46
|
-
search_engine.conditions.to_sql.should == %{((
|
46
|
+
search_engine.conditions.to_sql.should == %{(("records"."text" % 'search') OR ("records"."text2" % 'search'))}
|
47
47
|
end
|
48
48
|
|
49
49
|
it 'generates search condition with two columns, two terms and no options' do
|
50
50
|
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:trigram])
|
51
51
|
search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
|
52
|
-
search_engine.conditions.to_sql.should == %{((
|
52
|
+
search_engine.conditions.to_sql.should == %{(("records"."text" % 'search word') OR ("records"."text2" % 'search word'))}
|
53
53
|
end
|
54
54
|
|
55
55
|
it 'generates search condition with two columns, two terms and option "ignore_accent" set to true ' do
|
56
56
|
search_statement1 = search_statement_class.new(['"records"."text"', '"records"."text2"'], engines: [:trigram], ignore_accent: true)
|
57
57
|
search_engine = search_engine_class.new({ pattern: 'search word' }, [search_statement1])
|
58
|
-
search_engine.conditions.to_sql.should == %{((
|
58
|
+
search_engine.conditions.to_sql.should == %{((unaccent("records"."text") % unaccent('search word')) OR (unaccent("records"."text2") % unaccent('search word')))}
|
59
59
|
end
|
60
60
|
|
61
61
|
it 'generates search condition with one column, one term, two statements and no options' do
|
62
62
|
search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:trigram])
|
63
63
|
search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:trigram])
|
64
64
|
search_engine = search_engine_class.new({ pattern: 'search' }, [search_statement1, search_statement2])
|
65
|
-
search_engine.conditions.to_sql.should == %{((
|
65
|
+
search_engine.conditions.to_sql.should == %{(("records"."text" % 'search') OR ("records"."text2" % 'search'))}
|
66
66
|
end
|
67
67
|
|
68
68
|
it 'generates search condition with one column, one term, two statements and "param_name" option set to "s"' do
|
69
69
|
search_statement1 = search_statement_class.new(['"records"."text"'], engines: [:trigram])
|
70
70
|
search_statement2 = search_statement_class.new(['"records"."text2"'], engines: [:trigram], param_name: 's')
|
71
71
|
search_engine = search_engine_class.new({ pattern: 'search', s: 'word' }, [search_statement1, search_statement2])
|
72
|
-
search_engine.conditions.to_sql.should == %{((
|
72
|
+
search_engine.conditions.to_sql.should == %{(("records"."text" % 'search') OR ("records"."text2" % 'word'))}
|
73
73
|
end
|
74
74
|
|
75
75
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Mincer::Processors::PgSearch::SearchStatement do
|
4
|
+
describe '.sanitizers' do
|
5
|
+
let(:search_statement_class) { Mincer::Processors::PgSearch::SearchStatement }
|
6
|
+
|
7
|
+
it 'returns sanitizers that has true in options' do
|
8
|
+
search_statement = search_statement_class.new('text', {"ignore_accent"=>true, "any_word"=>false, "dictionary"=>:simple, "ignore_case"=>false, "engines"=>[:fulltext]} )
|
9
|
+
search_statement.sanitizers.should == [:ignore_accent]
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when key "document" is given' do
|
13
|
+
it 'returns only sanitizers with true value in options' do
|
14
|
+
search_statement = search_statement_class.new('text', {"ignore_accent"=>true, "any_word"=>false, "dictionary"=>:simple, "ignore_case"=>false, "engines"=>[:fulltext]} )
|
15
|
+
search_statement.sanitizers(:document).should == [:ignore_accent]
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns sanitizers when sanitizer set as hash with type set to true in options' do
|
19
|
+
search_statement = search_statement_class.new('text', {"ignore_accent"=>true, "any_word"=>false, "dictionary"=>:simple, "ignore_case"=> { :document => true}, "engines"=>[:fulltext]} )
|
20
|
+
search_statement.sanitizers(:document).size.should == 2
|
21
|
+
search_statement.sanitizers(:document).should include(:ignore_accent)
|
22
|
+
search_statement.sanitizers(:document).should include(:ignore_case)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns sanitizers when sanitizer set as hash with type set to false in options' do
|
26
|
+
search_statement = search_statement_class.new('text', {"ignore_accent"=>true, "any_word"=>false, "dictionary"=>:simple, "ignore_case"=> { :query => true}, "engines"=>[:fulltext]} )
|
27
|
+
search_statement.sanitizers(:document).size.should == 1
|
28
|
+
search_statement.sanitizers(:document).should == [:ignore_accent]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when key "query" is given' do
|
33
|
+
it 'returns only sanitizers with true value in options' do
|
34
|
+
search_statement = search_statement_class.new('text', {"ignore_accent"=>true, "any_word"=>false, "dictionary"=>:simple, "ignore_case"=>false, "engines"=>[:fulltext]} )
|
35
|
+
search_statement.sanitizers(:query).should == [:ignore_accent]
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns sanitizers when sanitizer set as hash with type set to true in options' do
|
39
|
+
search_statement = search_statement_class.new('text', {"ignore_accent"=>true, "any_word"=>false, "dictionary"=>:simple, "ignore_case"=> { :query => true}, "engines"=>[:fulltext]} )
|
40
|
+
search_statement.sanitizers(:query).size.should == 2
|
41
|
+
search_statement.sanitizers(:query).should include(:ignore_accent)
|
42
|
+
search_statement.sanitizers(:query).should include(:ignore_case)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns sanitizers when sanitizer set as hash with type set to false in options' do
|
46
|
+
search_statement = search_statement_class.new('text', {"ignore_accent"=>true, "any_word"=>false, "dictionary"=>:simple, "ignore_case"=> { :document => true}, "engines"=>[:fulltext]} )
|
47
|
+
search_statement.sanitizers(:query).size.should == 1
|
48
|
+
search_statement.sanitizers(:query).should == [:ignore_accent]
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -37,6 +37,8 @@ require 'mincer'
|
|
37
37
|
require 'support/postgres_adapter'
|
38
38
|
require 'support/sqlite3_adapter'
|
39
39
|
|
40
|
+
require 'generator_spec'
|
41
|
+
|
40
42
|
RSpec.configure do |config|
|
41
43
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|
42
44
|
config.run_all_when_everything_filtered = true
|
@@ -13,6 +13,7 @@ def setup_postgres_table(columns = [['id', 'SERIAL PRIMARY KEY'], ['text', 'TEXT
|
|
13
13
|
columns_sql = columns.map {|column| column.join(' ') }.join(',')
|
14
14
|
ActiveRecord::Base.connection.execute("CREATE TABLE IF NOT EXISTS active_record_models (#{columns_sql})")
|
15
15
|
ActiveRecord::Base.connection.execute('CREATE EXTENSION IF NOT EXISTS unaccent')
|
16
|
+
ActiveRecord::Base.connection.execute('CREATE EXTENSION IF NOT EXISTS pg_trgm')
|
16
17
|
ActiveRecord::Base.connection.execute('CREATE EXTENSION IF NOT EXISTS pgcrypto')
|
17
18
|
ActiveRecordModel.reset_column_information
|
18
19
|
end
|
metadata
CHANGED
@@ -1,111 +1,125 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mincer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Krasinsky
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '4.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.3'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: pg
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: sqlite3
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: kaminari
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: will_paginate
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: generator_spec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
111
125
|
description: Add pagination, cache_digest, order, json generation with postgres, simple
|
@@ -116,12 +130,14 @@ executables: []
|
|
116
130
|
extensions: []
|
117
131
|
extra_rdoc_files: []
|
118
132
|
files:
|
119
|
-
- .gitignore
|
120
|
-
- .travis.yml
|
133
|
+
- ".gitignore"
|
134
|
+
- ".travis.yml"
|
121
135
|
- Gemfile
|
122
136
|
- LICENSE.txt
|
123
137
|
- README.md
|
124
138
|
- Rakefile
|
139
|
+
- lib/generators/mincer/query_generator.rb
|
140
|
+
- lib/generators/mincer/templates/query.rb
|
125
141
|
- lib/mincer.rb
|
126
142
|
- lib/mincer/action_view/sort_helper.rb
|
127
143
|
- lib/mincer/base.rb
|
@@ -142,6 +158,7 @@ files:
|
|
142
158
|
- lib/mincer/version.rb
|
143
159
|
- mincer.gemspec
|
144
160
|
- spec/database.yml.example
|
161
|
+
- spec/generators/query/query_generator_spec.rb
|
145
162
|
- spec/lib/mincer/action_view/sort_helper_spec.rb
|
146
163
|
- spec/lib/mincer/base_spec.rb
|
147
164
|
- spec/lib/mincer/config_spec.rb
|
@@ -153,6 +170,7 @@ files:
|
|
153
170
|
- spec/lib/mincer/processors/pg_search/search_engines/array_spec.rb
|
154
171
|
- spec/lib/mincer/processors/pg_search/search_engines/fulltext_spec.rb
|
155
172
|
- spec/lib/mincer/processors/pg_search/search_engines/trigram_spec.rb
|
173
|
+
- spec/lib/mincer/processors/pg_search/search_statement_spec.rb
|
156
174
|
- spec/lib/mincer/processors/sorting/processor_spec.rb
|
157
175
|
- spec/mincer_config.rb
|
158
176
|
- spec/spec_helper.rb
|
@@ -168,23 +186,24 @@ require_paths:
|
|
168
186
|
- lib
|
169
187
|
required_ruby_version: !ruby/object:Gem::Requirement
|
170
188
|
requirements:
|
171
|
-
- -
|
189
|
+
- - ">="
|
172
190
|
- !ruby/object:Gem::Version
|
173
191
|
version: '0'
|
174
192
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
193
|
requirements:
|
176
|
-
- -
|
194
|
+
- - ">="
|
177
195
|
- !ruby/object:Gem::Version
|
178
196
|
version: '0'
|
179
197
|
requirements: []
|
180
198
|
rubyforge_project:
|
181
|
-
rubygems_version: 2.0
|
199
|
+
rubygems_version: 2.2.0
|
182
200
|
signing_key:
|
183
201
|
specification_version: 4
|
184
202
|
summary: ActiveRecord::Relation wrapper for pagination, order, json, search, cache_digest
|
185
203
|
support
|
186
204
|
test_files:
|
187
205
|
- spec/database.yml.example
|
206
|
+
- spec/generators/query/query_generator_spec.rb
|
188
207
|
- spec/lib/mincer/action_view/sort_helper_spec.rb
|
189
208
|
- spec/lib/mincer/base_spec.rb
|
190
209
|
- spec/lib/mincer/config_spec.rb
|
@@ -196,6 +215,7 @@ test_files:
|
|
196
215
|
- spec/lib/mincer/processors/pg_search/search_engines/array_spec.rb
|
197
216
|
- spec/lib/mincer/processors/pg_search/search_engines/fulltext_spec.rb
|
198
217
|
- spec/lib/mincer/processors/pg_search/search_engines/trigram_spec.rb
|
218
|
+
- spec/lib/mincer/processors/pg_search/search_statement_spec.rb
|
199
219
|
- spec/lib/mincer/processors/sorting/processor_spec.rb
|
200
220
|
- spec/mincer_config.rb
|
201
221
|
- spec/spec_helper.rb
|