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 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