mincer 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0d97a97f1c5f14b8411b406709621b4fe9f2c14b
4
- data.tar.gz: bf7f943474b0fc73e112bb5d78ce0edf5f018dea
3
+ metadata.gz: 977d1742d488e2ec263b062b0ebd6ff27335a377
4
+ data.tar.gz: 436e3e27dc8664fd945af1ba045b58bfff108f57
5
5
  SHA512:
6
- metadata.gz: d01b5f077f72bb1e9b70b7f88bbe64a5afb92409d476a04a8200f15d02e95491cd4a0c6db067266d95c3beb377488fb58be3a77f34fdc47d80933c750f1238d0
7
- data.tar.gz: 9bd281ba05fcd19e8f8ff96eb2da31eba134a9e163c925431e5b43f3564ad59aaaa567c882db7cb6128659fac58c1c12e666d267b058f4c1b67265854cf4267c
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
- Lets create class EmployeesListQuery class that will inherit from Mincer::Base, and instantiate it
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
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ class <%= class_name %>Query < Mincer::Base
4
+ def build_query(relation, args)
5
+ # Apply your conditions, custom selects, etc. to relation
6
+ relation
7
+ end
8
+ 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
- conditions = prepared_search_statements.map do |search_statement|
11
- arel_group(Arel::Nodes::InfixOperation.new('@@', document_for(search_statement), query_for(search_statement)))
12
- end
13
- join_expressions(conditions, :or)
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
- Arel::Nodes::NamedFunction.new('ts_rank', [document_for(search_statement), query_for(search_statement)]).to_sql
22
- end
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 document_for(search_statement)
38
- arel_group do
39
- search_statement.columns.map do |search_column|
40
- sanitized_term = sanitize_column(search_column, search_statement.sanitizers + [:coalesce])
41
- Arel::Nodes::NamedFunction.new('to_tsvector', [search_statement.dictionary, sanitized_term]).to_sql
42
- end.join(' || ')
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 query_for(search_statement)
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
- arel_group do
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
- similarity = Arel::Nodes::NamedFunction.new('similarity', [sanitize_column(search_column, search_statement.sanitizers), sanitize_string(search_statement.pattern, search_statement.sanitizers)])
26
- arel_group(similarity.gteq(search_statement.threshold))
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
- Arel::Nodes::NamedFunction.new('similarity', [sanitize_column(search_column, search_statement.sanitizers), sanitize_string(search_statement.pattern, search_statement.sanitizers)])
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 ||= Sanitizer::AVAILABLE_SANITIZERS.select { |sanitizer| options[sanitizer] }
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
@@ -1,7 +1,7 @@
1
1
  module Mincer
2
2
 
3
3
  def self.version
4
- Gem::Version.new '0.2.2'
4
+ Gem::Version.new '0.2.3'
5
5
  end
6
6
 
7
7
  module VERSION #:nodoc:
data/mincer.gemspec CHANGED
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'sqlite3'
27
27
  spec.add_development_dependency 'kaminari'
28
28
  spec.add_development_dependency 'will_paginate'
29
+ spec.add_development_dependency 'generator_spec'
29
30
  end
@@ -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((to_tsvector('simple', unaccent(coalesce(active_record_models.text, '')))), (to_tsquery('simple', unaccent('Bingo')))))")
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
- 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'))))}
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
- 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
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
- 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
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
- 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
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
- 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
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
- 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'))))))}
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 == %{(((to_tsvector('english', coalesce("records"."text", ''))) @@ (to_tsquery('english', 'search'))))}
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 == %{(((to_tsvector('simple', coalesce("records"."text", ''))) @@ (to_tsquery('simple', 'search'))) OR ((to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'search'))))}
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 == %{(((to_tsvector('simple', coalesce("records"."text", ''))) @@ (to_tsquery('simple', 'search'))) OR ((to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'search'))))}
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 == %{(((to_tsvector('simple', coalesce("records"."text", ''))) @@ (to_tsquery('simple', 'search'))) OR ((to_tsvector('simple', coalesce("records"."text2", ''))) @@ (to_tsquery('simple', 'word'))))}
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 == %{((similarity("records"."text", 'search') >= 0.3))}
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 == %{((similarity("records"."text", 'search') >= 0.3) OR (similarity("records"."text2", 'search') >= 0.3))}
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 == %{((similarity("records"."text", 'search word') >= 0.3) OR (similarity("records"."text2", 'search word') >= 0.3))}
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 == %{((similarity(unaccent("records"."text"), unaccent('search word')) >= 0.3) OR (similarity(unaccent("records"."text2"), unaccent('search word')) >= 0.3))}
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 == %{((similarity("records"."text", 'search') >= 0.3) OR (similarity("records"."text2", 'search') >= 0.3))}
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 == %{((similarity("records"."text", 'search') >= 0.3) OR (similarity("records"."text2", 'word') >= 0.3))}
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.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-08-18 00:00:00.000000000 Z
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.3
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