pg_search 0.7.8 → 0.7.9

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: 8d79889dbc874d312da8be0d1770875df5048975
4
- data.tar.gz: d29022a5e3c1bcfd6668637e7f1c436d65140b63
3
+ metadata.gz: e2af7110ee0301accace6b1a5143605fdddaf59b
4
+ data.tar.gz: ae0a9e2e5da80e29afd0449057685d64f20cb08c
5
5
  SHA512:
6
- metadata.gz: 64adadba491dc22693215cb70ff4ee1d006336d4c1619f554755a50bd8a53f81601c7accd29d84fc1d787a291920f71f11bd450af6ded9ec341269325af3c248
7
- data.tar.gz: 51a38eecd6feef873c613d1bf57cf2de36beeeca59e899f1d2ddb1919fec5c8de24c337445f012af118289f57d1a28eca3244fda45a7979e51599edcb89e7527
6
+ metadata.gz: 6d5b4e6084872eac490fa2e7e923a3c9e53bd5acf77596a6a2a42547ecf90cee8f75a9c1d9a5819bcf4bc3546fc2dcc8f1ba46848dd5561c62172235d8ffff6b
7
+ data.tar.gz: baf8e2bcf8fd110c8db6f1bd68acdb62af2510ec5eab40bfd607494f83c202282238db599b95c30e44b64d7a13338b894b2dd074d7104779512fd0efac7b5ad0
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ Style/StringLiterals:
4
+ Enabled: false
5
+
6
+ # # Offense count: 2
7
+ # Lint/HandleExceptions:
8
+ # Enabled: false
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,273 @@
1
+ # This configuration was generated by `rubocop --auto-gen-config`
2
+ # on 2014-12-12 15:10:11 -0600 using RuboCop version 0.28.0.
3
+ # The point is for the user to remove these configuration records
4
+ # one by one as the offenses are removed from the code base.
5
+ # Note that changes in the inspected code, or installation of new
6
+ # versions of RuboCop, may require this file to be generated again.
7
+
8
+ # Offense count: 11
9
+ # Cop supports --auto-correct.
10
+ Lint/UnusedBlockArgument:
11
+ Enabled: false
12
+
13
+ # Offense count: 12
14
+ Lint/UselessAssignment:
15
+ Enabled: false
16
+
17
+ # Offense count: 6
18
+ Metrics/AbcSize:
19
+ Max: 27
20
+
21
+ # Offense count: 347
22
+ # Configuration parameters: AllowURI, URISchemes.
23
+ Metrics/LineLength:
24
+ Max: 304
25
+
26
+ # Offense count: 9
27
+ # Configuration parameters: CountComments.
28
+ Metrics/MethodLength:
29
+ Max: 21
30
+
31
+ # Offense count: 1
32
+ # Cop supports --auto-correct.
33
+ Style/Alias:
34
+ Enabled: false
35
+
36
+ # Offense count: 3
37
+ # Cop supports --auto-correct.
38
+ # Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle, SupportedLastArgumentHashStyles.
39
+ Style/AlignHash:
40
+ Enabled: false
41
+
42
+ # Offense count: 42
43
+ # Cop supports --auto-correct.
44
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
45
+ Style/AlignParameters:
46
+ Enabled: false
47
+
48
+ # Offense count: 14
49
+ # Cop supports --auto-correct.
50
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
51
+ Style/BarePercentLiterals:
52
+ Enabled: false
53
+
54
+ # Offense count: 1
55
+ # Cop supports --auto-correct.
56
+ Style/BlockEndNewline:
57
+ Enabled: false
58
+
59
+ # Offense count: 14
60
+ # Cop supports --auto-correct.
61
+ Style/Blocks:
62
+ Enabled: false
63
+
64
+ # Offense count: 1
65
+ # Cop supports --auto-correct.
66
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
67
+ Style/BracesAroundHashParameters:
68
+ Enabled: false
69
+
70
+ # Offense count: 27
71
+ Style/Documentation:
72
+ Enabled: false
73
+
74
+ # Offense count: 5
75
+ # Cop supports --auto-correct.
76
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
77
+ Style/DotPosition:
78
+ Enabled: false
79
+
80
+ # Offense count: 1
81
+ # Cop supports --auto-correct.
82
+ Style/ElseAlignment:
83
+ Enabled: false
84
+
85
+ # Offense count: 1
86
+ # Cop supports --auto-correct.
87
+ Style/EmptyLines:
88
+ Enabled: false
89
+
90
+ # Offense count: 6
91
+ # Cop supports --auto-correct.
92
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
93
+ Style/EmptyLinesAroundBlockBody:
94
+ Enabled: false
95
+
96
+ # Offense count: 2
97
+ # Cop supports --auto-correct.
98
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
99
+ Style/EmptyLinesAroundClassBody:
100
+ Enabled: false
101
+
102
+ # Offense count: 1
103
+ # Cop supports --auto-correct.
104
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
105
+ Style/EmptyLinesAroundModuleBody:
106
+ Enabled: false
107
+
108
+ # Offense count: 2
109
+ # Configuration parameters: MinBodyLength.
110
+ Style/GuardClause:
111
+ Enabled: false
112
+
113
+ # Offense count: 521
114
+ # Cop supports --auto-correct.
115
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
116
+ Style/HashSyntax:
117
+ Enabled: false
118
+
119
+ # Offense count: 1
120
+ # Configuration parameters: MaxLineLength.
121
+ Style/IfUnlessModifier:
122
+ Enabled: false
123
+
124
+ # Offense count: 23
125
+ # Cop supports --auto-correct.
126
+ Style/IndentArray:
127
+ Enabled: false
128
+
129
+ # Offense count: 1
130
+ # Cop supports --auto-correct.
131
+ Style/IndentationConsistency:
132
+ Enabled: false
133
+
134
+ # Offense count: 3
135
+ # Cop supports --auto-correct.
136
+ # Configuration parameters: Width.
137
+ Style/IndentationWidth:
138
+ Enabled: false
139
+
140
+ # Offense count: 10
141
+ Style/Lambda:
142
+ Enabled: false
143
+
144
+ # Offense count: 1
145
+ # Cop supports --auto-correct.
146
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
147
+ Style/MethodDefParentheses:
148
+ Enabled: false
149
+
150
+ # Offense count: 2
151
+ Style/MultilineBlockChain:
152
+ Enabled: false
153
+
154
+ # Offense count: 1
155
+ # Cop supports --auto-correct.
156
+ Style/MultilineBlockLayout:
157
+ Enabled: false
158
+
159
+ # Offense count: 16
160
+ # Cop supports --auto-correct.
161
+ Style/NumericLiterals:
162
+ MinDigits: 6
163
+
164
+ # Offense count: 12
165
+ # Cop supports --auto-correct.
166
+ # Configuration parameters: PreferredDelimiters.
167
+ Style/PercentLiteralDelimiters:
168
+ Enabled: false
169
+
170
+ # Offense count: 2
171
+ # Cop supports --auto-correct.
172
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
173
+ Style/PercentQLiterals:
174
+ Enabled: false
175
+
176
+ # Offense count: 1
177
+ # Cop supports --auto-correct.
178
+ Style/PerlBackrefs:
179
+ Enabled: false
180
+
181
+ # Offense count: 1
182
+ # Cop supports --auto-correct.
183
+ Style/Proc:
184
+ Enabled: false
185
+
186
+ # Offense count: 4
187
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
188
+ Style/RaiseArgs:
189
+ Enabled: false
190
+
191
+ # Offense count: 1
192
+ # Cop supports --auto-correct.
193
+ # Configuration parameters: AllowMultipleReturnValues.
194
+ Style/RedundantReturn:
195
+ Enabled: false
196
+
197
+ # Offense count: 2
198
+ Style/RegexpLiteral:
199
+ MaxSlashes: 0
200
+
201
+ # Offense count: 10
202
+ # Cop supports --auto-correct.
203
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
204
+ Style/SignalException:
205
+ Enabled: false
206
+
207
+ # Offense count: 2
208
+ # Cop supports --auto-correct.
209
+ Style/SpaceAfterComma:
210
+ Enabled: false
211
+
212
+ # Offense count: 1
213
+ # Cop supports --auto-correct.
214
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
215
+ Style/SpaceAroundEqualsInParameterDefault:
216
+ Enabled: false
217
+
218
+ # Offense count: 1
219
+ # Cop supports --auto-correct.
220
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
221
+ Style/SpaceBeforeBlockBraces:
222
+ Enabled: false
223
+
224
+ # Offense count: 2
225
+ # Cop supports --auto-correct.
226
+ # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
227
+ Style/SpaceInsideBlockBraces:
228
+ Enabled: false
229
+
230
+ # Offense count: 76
231
+ # Cop supports --auto-correct.
232
+ # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles.
233
+ Style/SpaceInsideHashLiteralBraces:
234
+ Enabled: false
235
+
236
+ # Offense count: 2
237
+ # Cop supports --auto-correct.
238
+ Style/SpaceInsideParens:
239
+ Enabled: false
240
+
241
+ # Offense count: 3
242
+ # Cop supports --auto-correct.
243
+ Style/SpecialGlobalVars:
244
+ Enabled: false
245
+
246
+ # Offense count: 2
247
+ # Cop supports --auto-correct.
248
+ # Configuration parameters: IgnoredMethods.
249
+ Style/SymbolProc:
250
+ Enabled: false
251
+
252
+ # Offense count: 4
253
+ # Cop supports --auto-correct.
254
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
255
+ Style/TrailingBlankLines:
256
+ Enabled: false
257
+
258
+ # Offense count: 9
259
+ # Cop supports --auto-correct.
260
+ # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
261
+ Style/TrailingComma:
262
+ Enabled: false
263
+
264
+ # Offense count: 6
265
+ # Cop supports --auto-correct.
266
+ Style/UnneededPercentQ:
267
+ Enabled: false
268
+
269
+ # Offense count: 3
270
+ # Cop supports --auto-correct.
271
+ # Configuration parameters: WordRegex.
272
+ Style/WordArray:
273
+ MinSize: 2
data/.travis.yml CHANGED
@@ -1,16 +1,19 @@
1
1
  language: ruby
2
+ sudo: false
2
3
 
3
4
  rvm:
4
- - 1.9.3
5
- - 2.0.0
6
- - 2.1.2
5
+ - "1.9"
6
+ - "2.0"
7
+ - "2.1"
8
+ - "2.2"
7
9
  - jruby-19mode
8
10
 
9
11
  env:
10
12
  - ACTIVE_RECORD_BRANCH="master"
13
+ - ACTIVE_RECORD_BRANCH="4-2-stable"
11
14
  - ACTIVE_RECORD_BRANCH="4-1-stable"
12
15
  - ACTIVE_RECORD_BRANCH="4-0-stable"
13
- - ACTIVE_RECORD_VERSION="~> 4.2.0.beta1"
16
+ - ACTIVE_RECORD_VERSION="~> 4.2.0"
14
17
  - ACTIVE_RECORD_VERSION="~> 4.1.0"
15
18
  - ACTIVE_RECORD_VERSION="~> 4.0.0"
16
19
  - ACTIVE_RECORD_VERSION="~> 3.2.0"
@@ -20,11 +23,20 @@ matrix:
20
23
  allow_failures:
21
24
  - rvm: jruby-19mode
22
25
  - env: ACTIVE_RECORD_BRANCH="master"
26
+ - env: ACTIVE_RECORD_BRANCH="4-2-stable"
23
27
  - env: ACTIVE_RECORD_BRANCH="4-1-stable"
24
28
  - env: ACTIVE_RECORD_BRANCH="4-0-stable"
25
- - env: ACTIVE_RECORD_VERSION="~> 4.2.0.beta1"
29
+ exclude:
30
+ - rvm: "1.9"
31
+ env: ACTIVE_RECORD_BRANCH="master"
32
+ - rvm: "2.0"
33
+ env: ACTIVE_RECORD_BRANCH="master"
34
+ - rvm: "2.1"
35
+ env: ACTIVE_RECORD_BRANCH="master"
36
+ - rvm: jruby-19mode
37
+ env: ACTIVE_RECORD_BRANCH="master"
26
38
 
27
39
  before_script:
28
40
  - "psql -c 'create database pg_search_test;' -U postgres >/dev/null"
29
41
 
30
- script: "bundle exec rspec spec"
42
+ script: "bin/rake"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # pg_search changelog
2
2
 
3
+ ## 0.7.9
4
+
5
+ * Improve support for single table inheritance (STI) models (Ewan McDougall)
6
+
3
7
  ## 0.7.8
4
8
 
5
9
  * Stop inadvertently including binstubs for guard and rspec.
data/Gemfile CHANGED
@@ -5,11 +5,13 @@ gemspec
5
5
  gem 'pg', :platform => :ruby
6
6
  gem "activerecord-jdbcpostgresql-adapter", ">= 1.3.1", :platform => :jruby
7
7
 
8
- gem "activerecord", ENV["ACTIVE_RECORD_VERSION"] if ENV["ACTIVE_RECORD_VERSION"]
9
- gem "activerecord", :github => "rails", :branch => ENV["ACTIVE_RECORD_BRANCH"] if ENV["ACTIVE_RECORD_BRANCH"]
8
+ if ENV['ACTIVE_RECORD_BRANCH']
9
+ gem 'activerecord', :git => 'https://github.com/rails/rails.git', :branch => ENV['ACTIVE_RECORD_BRANCH']
10
+ gem 'arel', :git => 'https://github.com/rails/arel.git' if ENV['ACTIVE_RECORD_BRANCH'] == 'master'
11
+ end
10
12
 
11
- if ENV["TRAVIS"]
12
- gem 'coveralls', :require => false, :platform => :mri_20
13
+ if ENV['ACTIVE_RECORD_VERSION']
14
+ gem 'activerecord', ENV['ACTIVE_RECORD_VERSION']
13
15
  end
14
16
 
15
17
  group :development do
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # [pg_search](http://github.com/Casecommons/pg_search/)
2
2
 
3
3
  [![Build Status](https://secure.travis-ci.org/Casecommons/pg_search.svg?branch=master)](https://travis-ci.org/Casecommons/pg_search)
4
- [![Code Climate](https://img.shields.io/codeclimate/github/Casecommons/pg_search.svg)](https://codeclimate.com/github/Casecommons/pg_search)
5
- [![Coverage Status](https://img.shields.io/coveralls/Casecommons/pg_search/master.svg)](https://coveralls.io/r/Casecommons/pg_search)
6
- [![Gem Version](https://badge.fury.io/rb/pg_search.svg)](https://rubygems.org/gems/pg_search)
7
- [![Dependency Status](https://gemnasium.com/Casecommons/pg_search.svg)](https://gemnasium.com/Casecommons/pg_search)
4
+ [![Code Climate](https://img.shields.io/codeclimate/github/Casecommons/pg_search.svg?style=flat)](https://codeclimate.com/github/Casecommons/pg_search)
5
+ [![Gem Version](https://img.shields.io/gem/v/pg_search.svg?style=flat)](https://rubygems.org/gems/pg_search)
6
+ [![Dependency Status](https://img.shields.io/gemnasium/Casecommons/pg_search.svg?style=flat)](https://gemnasium.com/Casecommons/pg_search)
7
+ [![Inline docs](http://inch-ci.org/github/Casecommons/pg_search.svg?branch=master&style=flat)](http://inch-ci.org/github/Casecommons/pg_search)
8
8
 
9
9
  ## DESCRIPTION
10
10
 
@@ -562,6 +562,36 @@ robin = Superhero.create :name => 'Robin'
562
562
 
563
563
  Superhero.whose_name_starts_with("Bat") # => [batman, batgirl]
564
564
  ```
565
+ ##### :negation
566
+
567
+ PostgreSQL's full text search matches all search terms by default. If you want
568
+ to exclude certain words, you can set :negation to true. Then any term that begins with
569
+ an exclamation point `!` will be excluded from the results. Since this
570
+ is a :tsearch-specific option, you should pass it to :tsearch directly, as
571
+ shown in the following example.
572
+
573
+ Note that combining this with other search features can have unexpected results. For
574
+ example, :trigram searches don't have a concept of excluded terms, and thus if you
575
+ use both :tsearch and :trigram in tandem, you may still find results that contain the
576
+ term that you were trying to exclude.
577
+
578
+ ```ruby
579
+ class Animal < ActiveRecord::Base
580
+ include PgSearch
581
+ pg_search_scope :with_name_matching,
582
+ :against => :name,
583
+ :using => {
584
+ :tsearch => {:negation => true}
585
+ }
586
+ end
587
+
588
+ one_fish = Animal.create(:name => "one fish")
589
+ two_fish = Animal.create(:name => "two fish")
590
+ red_fish = Animal.create(:name => "red fish")
591
+ blue_fish = Animal.create(:name => "blue fish")
592
+
593
+ Animal.with_name_matching("fish !red !blue") # => [one_fish, two_fish]
594
+ ```
565
595
 
566
596
  ##### :dictionary
567
597
 
@@ -879,19 +909,20 @@ To use this functionality you'll need to do a few things:
879
909
  function uses.
880
910
  * Add the option to pg_search_scope, e.g:
881
911
 
882
- pg_search_scope :fast_content_search,
883
- :against => :content,
884
- :using => {
885
- dmetaphone: {
886
- tsvector_column: 'tsvector_content_dmetaphone'
887
- },
888
- tsearch: {
889
- dictionary: 'english',
890
- tsvector_column: 'tsvector_content_tsearch'
891
- }
892
- trigram: {} # trigram does not use tsvectors
893
- }
894
-
912
+ ```ruby
913
+ pg_search_scope :fast_content_search,
914
+ :against => :content,
915
+ :using => {
916
+ dmetaphone: {
917
+ tsvector_column: 'tsvector_content_dmetaphone'
918
+ },
919
+ tsearch: {
920
+ dictionary: 'english',
921
+ tsvector_column: 'tsvector_content_tsearch'
922
+ }
923
+ trigram: {} # trigram does not use tsvectors
924
+ }
925
+ ```
895
926
  * You cannot dump a `tsvector` column to `schema.rb`. Instead, you need to switch to using the native PostgreSQL SQL format schema dump.
896
927
  In your `config/application.rb` you should set
897
928
 
@@ -903,6 +934,36 @@ To use this functionality you'll need to do a few things:
903
934
  Please note that the :against column is only used when the tsvector_column is
904
935
  not present for the search type.
905
936
 
937
+ #### Combining multiple tsvectors
938
+
939
+ It's possible to search against more than one tsvector at a time. This could be useful if you want to maintain multiple search scopes but do not want to maintain separate tsvectors for each scope. For example:
940
+
941
+ ```ruby
942
+ pg_search_scope :search_title,
943
+ :against => :title,
944
+ :using => {
945
+ :tsearch => {
946
+ :tsvector_column => "title_tsvector"
947
+ }
948
+ }
949
+
950
+ pg_search_scope :search_body,
951
+ :against => :body,
952
+ :using => {
953
+ :tsearch => {
954
+ :tsvector_column => "body_tsvector"
955
+ }
956
+ }
957
+
958
+ pg_search_scope :search_title_and_body,
959
+ :against => [:title, :body],
960
+ :using => {
961
+ :tsearch => {
962
+ :tsvector_column => ["title_tsvector", "body_tsvector"]
963
+ }
964
+ }
965
+ ```
966
+
906
967
  ### Configuring ranking and ordering
907
968
 
908
969
  #### :ranked_by (Choosing a ranking algorithm)
data/Rakefile CHANGED
@@ -1,13 +1,10 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
3
 
4
- task :default => :spec
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
5
6
 
6
- def bundle_exec(command)
7
- sh %Q{bundle update && bundle exec #{command}}
8
- end
7
+ require "rubocop/rake_task"
8
+ RuboCop::RakeTask.new
9
9
 
10
- desc "Run all specs"
11
- task "spec" do
12
- bundle_exec("rspec spec")
13
- end
10
+ task :default => %w[ spec rubocop ]
data/bin/rake ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rake', 'rake')
@@ -18,10 +18,10 @@ module PgSearch
18
18
 
19
19
  pg_search_scope :search, lambda { |*args|
20
20
  options = if PgSearch.multisearch_options.respond_to?(:call)
21
- PgSearch.multisearch_options.call(*args)
22
- else
23
- {:query => args.first}.merge(PgSearch.multisearch_options)
24
- end
21
+ PgSearch.multisearch_options.call(*args)
22
+ else
23
+ {:query => args.first}.merge(PgSearch.multisearch_options)
24
+ end
25
25
 
26
26
  {:against => :content}.merge(options)
27
27
  }
@@ -29,13 +29,20 @@ module PgSearch
29
29
  DISALLOWED_TSQUERY_CHARACTERS = /['?\\:]/
30
30
 
31
31
  def tsquery_for_term(unsanitized_term)
32
+ if options[:negation] && unsanitized_term.start_with?("!")
33
+ unsanitized_term[0] = ''
34
+ negated = true
35
+ end
36
+
32
37
  sanitized_term = unsanitized_term.gsub(DISALLOWED_TSQUERY_CHARACTERS, " ")
33
38
 
34
39
  term_sql = Arel.sql(normalize(connection.quote(sanitized_term)))
35
40
 
36
41
  # After this, the SQL expression evaluates to a string containing the term surrounded by single-quotes.
37
- # If :prefix is true, then the term will also have :* appended to the end.
42
+ # If :prefix is true, then the term will have :* appended to the end.
43
+ # If :negated is true, then the term will have ! prepended to the front.
38
44
  terms = [
45
+ (Compatibility.build_quoted('!') if negated),
39
46
  Compatibility.build_quoted("' "),
40
47
  term_sql,
41
48
  Compatibility.build_quoted(" '"),
@@ -65,8 +72,13 @@ module PgSearch
65
72
  end
66
73
 
67
74
  if options[:tsvector_column]
68
- column_name = connection.quote_column_name(options[:tsvector_column])
69
- tsdocument_terms << "#{quoted_table_name}.#{column_name}"
75
+ tsvector_columns = Array.wrap(options[:tsvector_column])
76
+
77
+ tsdocument_terms << tsvector_columns.map do |tsvector_column|
78
+ column_name = connection.quote_column_name(tsvector_column)
79
+
80
+ "#{quoted_table_name}.#{column_name}"
81
+ end
70
82
  end
71
83
 
72
84
  tsdocument_terms.join(' || ')
@@ -6,7 +6,7 @@ module PgSearch
6
6
  class << self
7
7
  def rebuild(model, clean_up=true)
8
8
  model.transaction do
9
- PgSearch::Document.where(:searchable_type => model.name).delete_all if clean_up
9
+ PgSearch::Document.where(:searchable_type => model.base_class.name).delete_all if clean_up
10
10
  Rebuilder.new(model).rebuild
11
11
  end
12
12
  end
@@ -35,14 +35,14 @@ module PgSearch
35
35
  def rebuild_sql_template
36
36
  <<-SQL.strip_heredoc
37
37
  INSERT INTO :documents_table (searchable_type, searchable_id, content, created_at, updated_at)
38
- SELECT :model_name AS searchable_type,
38
+ SELECT :base_model_name AS searchable_type,
39
39
  :model_table.#{primary_key} AS searchable_id,
40
40
  (
41
41
  :content_expressions
42
42
  ) AS content,
43
43
  :current_time AS created_at,
44
44
  :current_time AS updated_at
45
- FROM :model_table
45
+ FROM :model_table :sti_clause
46
46
  SQL
47
47
  end
48
48
 
@@ -52,8 +52,20 @@ module PgSearch
52
52
  end
53
53
  end
54
54
 
55
+ def sti_clause
56
+ clause = ""
57
+ if model.column_names.include? 'type'
58
+ clause = "WHERE"
59
+ if model.base_class == model
60
+ clause = "#{clause} type IS NULL OR"
61
+ end
62
+ clause = "#{clause} type = #{model_name}"
63
+ end
64
+ clause
65
+ end
66
+
55
67
  def replacements
56
- %w[content_expressions model_name model_table documents_table current_time]
68
+ %w[content_expressions base_model_name model_name model_table documents_table current_time sti_clause]
57
69
  end
58
70
 
59
71
  def content_expressions
@@ -70,6 +82,10 @@ module PgSearch
70
82
  connection.quote(model.name)
71
83
  end
72
84
 
85
+ def base_model_name
86
+ connection.quote(model.base_class.name)
87
+ end
88
+
73
89
  def model_table
74
90
  model.quoted_table_name
75
91
  end
@@ -23,10 +23,7 @@ module PgSearch
23
23
  unless_conditions.all? { |condition| !condition.to_proc.call(self) }
24
24
 
25
25
  if should_have_document
26
- unless pg_search_document.present?
27
- build_pg_search_document.searchable_type = self.class.name
28
- end
29
- pg_search_document.save
26
+ pg_search_document ? pg_search_document.save : create_pg_search_document
30
27
  else
31
28
  pg_search_document.destroy if pg_search_document
32
29
  end
@@ -1,3 +1,3 @@
1
1
  module PgSearch
2
- VERSION = "0.7.8".freeze
2
+ VERSION = "0.7.9".freeze
3
3
  end
data/pg_search.gemspec CHANGED
@@ -17,14 +17,15 @@ Gem::Specification.new do |s|
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.require_paths = ["lib"]
19
19
 
20
- s.add_dependency 'activerecord', '>=3.1'
21
- s.add_dependency 'activesupport', '>=3.1'
20
+ s.add_dependency 'activerecord', '>= 3.1'
21
+ s.add_dependency 'activesupport', '>= 3.1'
22
22
  s.add_dependency 'arel'
23
23
 
24
24
  s.add_development_dependency 'rake'
25
25
  s.add_development_dependency 'pry'
26
26
  s.add_development_dependency 'rspec', '~> 3.0'
27
- s.add_development_dependency 'with_model'
27
+ s.add_development_dependency 'with_model', '>= 1.2'
28
+ s.add_development_dependency 'rubocop'
28
29
 
29
30
  s.required_ruby_version = ">= 1.9.2"
30
31
  end
@@ -536,6 +536,58 @@ describe "an Active Record model which includes PgSearch" do
536
536
  expect(results.map(&:title)).to eq([])
537
537
  end
538
538
  end
539
+
540
+ context "with :negation" do
541
+ before do
542
+ ModelWithPgSearch.pg_search_scope :search_with_negation,
543
+ :against => :title,
544
+ :using => {
545
+ :tsearch => {:negation => true}
546
+ }
547
+ end
548
+
549
+ it "doesn't return results that contain terms prepended with '!'" do
550
+ included = [
551
+ ModelWithPgSearch.create!(:title => "one fish"),
552
+ ModelWithPgSearch.create!(:title => "two fish")
553
+ ]
554
+
555
+ excluded = [
556
+ ModelWithPgSearch.create!(:title => "red fish"),
557
+ ModelWithPgSearch.create!(:title => "blue fish")
558
+ ]
559
+
560
+ results = ModelWithPgSearch.search_with_negation("fish !red !blue")
561
+
562
+ expect(results).to include(*included)
563
+ expect(results).not_to include(*excluded)
564
+ end
565
+ end
566
+
567
+ context "without :negation" do
568
+ before do
569
+ ModelWithPgSearch.pg_search_scope :search_without_negation,
570
+ :against => :title,
571
+ :using => {
572
+ :tsearch => {}
573
+ }
574
+ end
575
+
576
+ it "return results that contain terms prepended with '!'" do
577
+ included = [
578
+ ModelWithPgSearch.create!(:title => "!bang")
579
+ ]
580
+
581
+ excluded = [
582
+ ModelWithPgSearch.create!(:title => "?question")
583
+ ]
584
+
585
+ results = ModelWithPgSearch.search_without_negation("!bang")
586
+
587
+ expect(results).to include(*included)
588
+ expect(results).not_to include(*excluded)
589
+ end
590
+ end
539
591
  end
540
592
 
541
593
  context "using dmetaphone" do
@@ -735,6 +787,30 @@ describe "an Active Record model which includes PgSearch" do
735
787
  end
736
788
  end
737
789
 
790
+ context 'using multiple tsvector columns' do
791
+ with_model :ModelWithTsvector do
792
+ model do
793
+ include PgSearch
794
+
795
+ pg_search_scope :search_by_multiple_tsvector_columns,
796
+ :against => ['content', 'message'],
797
+ :using => {
798
+ :tsearch => {
799
+ :tsvector_column => ['content_tsvector', 'message_tsvector'],
800
+ :dictionary => 'english'
801
+ }
802
+ }
803
+ end
804
+ end
805
+
806
+ it 'concats tsvector columns' do
807
+ expected = "#{ModelWithTsvector.quoted_table_name}.\"content_tsvector\" || "\
808
+ "#{ModelWithTsvector.quoted_table_name}.\"message_tsvector\""
809
+
810
+ expect(ModelWithTsvector.search_by_multiple_tsvector_columns("something").to_sql).to include(expected)
811
+ end
812
+ end
813
+
738
814
  context "using a tsvector column with" do
739
815
  with_model :ModelWithTsvector do
740
816
  table do |t|
@@ -937,29 +1013,29 @@ describe "an Active Record model which includes PgSearch" do
937
1013
  before do
938
1014
  SuperclassModel.pg_search_scope :search_content, :against => :content
939
1015
 
940
- class SubclassModel < SuperclassModel
1016
+ class SearchableSubclassModel < SuperclassModel
941
1017
  end
942
1018
 
943
- class AnotherSubclassModel < SuperclassModel
1019
+ class AnotherSearchableSubclassModel < SuperclassModel
944
1020
  end
945
1021
  end
946
1022
 
947
1023
  it "returns only results for that subclass" do
948
1024
  included = [
949
- SubclassModel.create!(:content => "foo bar")
1025
+ SearchableSubclassModel.create!(:content => "foo bar")
950
1026
  ]
951
1027
  excluded = [
952
- SubclassModel.create!(:content => "baz"),
1028
+ SearchableSubclassModel.create!(:content => "baz"),
953
1029
  SuperclassModel.create!(:content => "foo bar"),
954
1030
  SuperclassModel.create!(:content => "baz"),
955
- AnotherSubclassModel.create!(:content => "foo bar"),
956
- AnotherSubclassModel.create!(:content => "baz")
1031
+ AnotherSearchableSubclassModel.create!(:content => "foo bar"),
1032
+ AnotherSearchableSubclassModel.create!(:content => "baz")
957
1033
  ]
958
1034
 
959
1035
  expect(SuperclassModel.count).to eq(6)
960
- expect(SubclassModel.count).to eq(2)
1036
+ expect(SearchableSubclassModel.count).to eq(2)
961
1037
 
962
- results = SubclassModel.search_content("foo bar")
1038
+ results = SearchableSubclassModel.search_content("foo bar")
963
1039
 
964
1040
  expect(results).to include(*included)
965
1041
  expect(results).not_to include(*excluded)
@@ -1074,7 +1150,7 @@ describe "an Active Record model which includes PgSearch" do
1074
1150
  end
1075
1151
 
1076
1152
  context "on an STI subclass" do
1077
- with_model :ASuperclassModel do
1153
+ with_model :SuperclassModel do
1078
1154
  table do |t|
1079
1155
  t.text 'content'
1080
1156
  t.string 'type'
@@ -1082,14 +1158,13 @@ describe "an Active Record model which includes PgSearch" do
1082
1158
  end
1083
1159
 
1084
1160
  before do
1085
-
1086
- class SearchableSubclassModel < ASuperclassModel
1161
+ searchable_subclass_model = Class.new(SuperclassModel) do
1087
1162
  include PgSearch
1088
1163
  multisearchable :against => :content
1089
1164
  end
1090
-
1091
- class NonSearchableSubclassModel < ASuperclassModel
1092
- end
1165
+ stub_const("SearchableSubclassModel", searchable_subclass_model)
1166
+ stub_const("AnotherSearchableSubclassModel", searchable_subclass_model)
1167
+ stub_const("NonSearchableSubclassModel", Class.new(SuperclassModel))
1093
1168
  end
1094
1169
 
1095
1170
  it "returns only results for that subclass" do
@@ -1098,21 +1173,68 @@ describe "an Active Record model which includes PgSearch" do
1098
1173
  ]
1099
1174
  excluded = [
1100
1175
  SearchableSubclassModel.create!(:content => "baz"),
1101
- ASuperclassModel.create!(:content => "foo bar"),
1102
- ASuperclassModel.create!(:content => "baz"),
1176
+ SuperclassModel.create!(:content => "foo bar"),
1177
+ SuperclassModel.create!(:content => "baz"),
1103
1178
  NonSearchableSubclassModel.create!(:content => "foo bar"),
1104
1179
  NonSearchableSubclassModel.create!(:content => "baz")
1105
1180
  ]
1106
1181
 
1107
- expect(ASuperclassModel.count).to be 6
1182
+ expect(SuperclassModel.count).to be 6
1108
1183
  expect(SearchableSubclassModel.count).to be 2
1109
1184
 
1185
+ expect(PgSearch::Document.count).to be 2
1186
+
1110
1187
  results = PgSearch.multisearch("foo bar")
1111
1188
 
1112
- expect(results.map(&:searchable_id)).to include(*included.map(&:id))
1113
- expect(results.map(&:searchable_id)).not_to include(*excluded.map(&:id))
1114
- expect(results.map(&:searchable_type)).to include(*%w[SearchableSubclassModel])
1115
- expect(results.map(&:searchable_type)).not_to include(*%w[ASuperclassModel NonSearchableSubclassModel])
1189
+ expect(results.length).to be 1
1190
+ expect(results.first.searchable.class).to be SearchableSubclassModel
1191
+ expect(results.first.searchable).to eq included.first
1192
+ end
1193
+
1194
+ it "updates an existing STI model does not create a new pg_search document" do
1195
+ model = SearchableSubclassModel.create!(:content => "foo bar")
1196
+ expect(SearchableSubclassModel.count).to eq(1)
1197
+ # We fetch the model from the database again otherwise
1198
+ # the pg_search_document from the cache is used.
1199
+ model = SearchableSubclassModel.find(model.id)
1200
+ model.content = "foo"
1201
+ model.save!
1202
+ results = PgSearch.multisearch("foo")
1203
+ expect(results.size).to eq(SearchableSubclassModel.count)
1204
+ end
1205
+
1206
+ it "reindexing works" do
1207
+ NonSearchableSubclassModel.create!(:content => "foo bar")
1208
+ NonSearchableSubclassModel.create!(:content => "baz")
1209
+ expected = SearchableSubclassModel.create!(:content => "baz")
1210
+ SuperclassModel.create!(:content => "foo bar")
1211
+ SuperclassModel.create!(:content => "baz")
1212
+ SuperclassModel.create!(:content => "baz2")
1213
+
1214
+ expect(SuperclassModel.count).to be 6
1215
+ expect(NonSearchableSubclassModel.count).to be 2
1216
+ expect(SearchableSubclassModel.count).to be 1
1217
+
1218
+ expect(PgSearch::Document.count).to be 1
1219
+
1220
+ PgSearch::Multisearch.rebuild(SearchableSubclassModel)
1221
+
1222
+ expect(PgSearch::Document.count).to be 1
1223
+ expect(PgSearch::Document.first.searchable.class).to be SearchableSubclassModel
1224
+ expect(PgSearch::Document.first.searchable).to eq expected
1225
+ end
1226
+
1227
+ it "reindexing searchable STI doesn't clobber other related STI models" do
1228
+ searchable_s = SearchableSubclassModel.create!(:content => "baz")
1229
+ searchable_a = AnotherSearchableSubclassModel.create!(:content => "baz")
1230
+
1231
+ expect(PgSearch::Document.count).to be 2
1232
+ PgSearch::Multisearch.rebuild(SearchableSubclassModel)
1233
+ expect(PgSearch::Document.count).to be 2
1234
+
1235
+ classes = PgSearch::Document.all.collect {|d| d.searchable.class }
1236
+ expect(classes).to include SearchableSubclassModel
1237
+ expect(classes).to include AnotherSearchableSubclassModel
1116
1238
  end
1117
1239
  end
1118
1240
  end
@@ -1137,7 +1259,7 @@ describe "an Active Record model which includes PgSearch" do
1137
1259
  @multisearch_enabled_inside = PgSearch.multisearch_enabled?
1138
1260
  raise
1139
1261
  end
1140
- rescue
1262
+ rescue # rubocop:disable Lint/HandleExceptions
1141
1263
  end
1142
1264
 
1143
1265
  @multisearch_enabled_after = PgSearch.multisearch_enabled?
@@ -49,5 +49,77 @@ describe PgSearch::Features::TSearch do
49
49
  %Q{((to_tsvector('simple', coalesce(#{Model.quoted_table_name}."name"::text, '')) || to_tsvector('simple', coalesce(#{Model.quoted_table_name}."content"::text, ''))) @@ (to_tsquery('simple', ''' ' || 'query' || ' ''')))}
50
50
  )
51
51
  end
52
+
53
+ context "when options[:negation] is true" do
54
+ it "returns a negated expression when a query is prepended with !" do
55
+ query = "!query"
56
+ columns = [
57
+ PgSearch::Configuration::Column.new(:name, nil, Model),
58
+ PgSearch::Configuration::Column.new(:content, nil, Model),
59
+ ]
60
+ options = {:negation => true}
61
+ config = double(:config, :ignore => [])
62
+ normalizer = PgSearch::Normalizer.new(config)
63
+
64
+ feature = described_class.new(query, options, columns, Model, normalizer)
65
+ expect(feature.conditions.to_sql).to eq(
66
+ %Q{((to_tsvector('simple', coalesce(#{Model.quoted_table_name}."name"::text, '')) || to_tsvector('simple', coalesce(#{Model.quoted_table_name}."content"::text, ''))) @@ (to_tsquery('simple', '!' || ''' ' || 'query' || ' ''')))}
67
+ )
68
+ end
69
+ end
70
+
71
+ context "when options[:negation] is false" do
72
+ it "does not return a negated expression when a query is prepended with !" do
73
+ query = "!query"
74
+ columns = [
75
+ PgSearch::Configuration::Column.new(:name, nil, Model),
76
+ PgSearch::Configuration::Column.new(:content, nil, Model),
77
+ ]
78
+ options = {:negation => false}
79
+ config = double(:config, :ignore => [])
80
+ normalizer = PgSearch::Normalizer.new(config)
81
+
82
+ feature = described_class.new(query, options, columns, Model, normalizer)
83
+ expect(feature.conditions.to_sql).to eq(
84
+ %Q{((to_tsvector('simple', coalesce(#{Model.quoted_table_name}."name"::text, '')) || to_tsvector('simple', coalesce(#{Model.quoted_table_name}."content"::text, ''))) @@ (to_tsquery('simple', ''' ' || '!query' || ' ''')))}
85
+ )
86
+ end
87
+ end
88
+
89
+ context "when options[:tsvector_column] is a string" do
90
+ it 'uses the tsvector column' do
91
+ query = "query"
92
+ columns = [
93
+ PgSearch::Configuration::Column.new(:name, nil, Model),
94
+ PgSearch::Configuration::Column.new(:content, nil, Model),
95
+ ]
96
+ options = { tsvector_column: "my_tsvector" }
97
+ config = double(:config, :ignore => [])
98
+ normalizer = PgSearch::Normalizer.new(config)
99
+
100
+ feature = described_class.new(query, options, columns, Model, normalizer)
101
+ expect(feature.conditions.to_sql).to eq(
102
+ %Q{((#{Model.quoted_table_name}.\"my_tsvector\") @@ (to_tsquery('simple', ''' ' || 'query' || ' ''')))}
103
+ )
104
+ end
105
+ end
106
+
107
+ context "when options[:tsvector_column] is an array of strings" do
108
+ it 'uses the tsvector column' do
109
+ query = "query"
110
+ columns = [
111
+ PgSearch::Configuration::Column.new(:name, nil, Model),
112
+ PgSearch::Configuration::Column.new(:content, nil, Model),
113
+ ]
114
+ options = { tsvector_column: ["tsvector1", "tsvector2"] }
115
+ config = double(:config, :ignore => [])
116
+ normalizer = PgSearch::Normalizer.new(config)
117
+
118
+ feature = described_class.new(query, options, columns, Model, normalizer)
119
+ expect(feature.conditions.to_sql).to eq(
120
+ %Q{((#{Model.quoted_table_name}.\"tsvector1\" || #{Model.quoted_table_name}.\"tsvector2\") @@ (to_tsquery('simple', ''' ' || 'query' || ' ''')))}
121
+ )
122
+ end
123
+ end
52
124
  end
53
125
  end
@@ -121,7 +121,7 @@ describe PgSearch::Multisearch::Rebuilder do
121
121
  ActiveSupport::Notifications.unsubscribe(notifier)
122
122
 
123
123
  expect(executed_sql.length).to eq(1)
124
- expect(executed_sql.first).to eq(expected_sql)
124
+ expect(executed_sql.first.strip).to eq(expected_sql.strip)
125
125
  end
126
126
 
127
127
  context "for a model with a non-standard primary key" do
@@ -177,7 +177,7 @@ describe PgSearch::Multisearch::Rebuilder do
177
177
  ActiveSupport::Notifications.unsubscribe(notifier)
178
178
 
179
179
  expect(executed_sql.length).to eq(1)
180
- expect(executed_sql.first).to eq(expected_sql)
180
+ expect(executed_sql.first.strip).to eq(expected_sql.strip)
181
181
  end
182
182
  end
183
183
  end
@@ -129,11 +129,11 @@ describe PgSearch::Multisearch do
129
129
  SQL
130
130
 
131
131
  statements = []
132
- allow(connection).to receive(:execute) { |sql| statements << sql }
132
+ allow(connection).to receive(:execute) { |sql| statements << sql.strip }
133
133
 
134
134
  PgSearch::Multisearch.rebuild(model)
135
135
 
136
- expect(statements).to include(expected_sql)
136
+ expect(statements).to include(expected_sql.strip)
137
137
  end
138
138
  end
139
139
 
@@ -156,11 +156,11 @@ describe PgSearch::Multisearch do
156
156
  SQL
157
157
 
158
158
  statements = []
159
- allow(connection).to receive(:execute) { |sql| statements << sql }
159
+ allow(connection).to receive(:execute) { |sql| statements << sql.strip }
160
160
 
161
161
  PgSearch::Multisearch.rebuild(model)
162
162
 
163
- expect(statements).to include(expected_sql)
163
+ expect(statements).to include(expected_sql.strip)
164
164
  end
165
165
  end
166
166
  end
data/spec/spec_helper.rb CHANGED
@@ -11,7 +11,6 @@ RSpec.configure do |config|
11
11
  end
12
12
  end
13
13
 
14
- require 'support/coveralls'
15
14
  require 'support/database'
16
15
  require 'support/with_model'
17
16
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.8
4
+ version: 0.7.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grant Hutchins
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-09-06 00:00:00.000000000 Z
12
+ date: 2015-02-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -97,6 +97,20 @@ dependencies:
97
97
  version: '3.0'
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: with_model
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '1.2'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '1.2'
112
+ - !ruby/object:Gem::Dependency
113
+ name: rubocop
100
114
  requirement: !ruby/object:Gem::Requirement
101
115
  requirements:
102
116
  - - ">="
@@ -121,6 +135,8 @@ files:
121
135
  - ".autotest"
122
136
  - ".gitignore"
123
137
  - ".rspec"
138
+ - ".rubocop.yml"
139
+ - ".rubocop_todo.yml"
124
140
  - ".travis.yml"
125
141
  - CHANGELOG.md
126
142
  - CONTRIBUTING.md
@@ -130,6 +146,7 @@ files:
130
146
  - README.md
131
147
  - Rakefile
132
148
  - bin/guard
149
+ - bin/rake
133
150
  - bin/rspec
134
151
  - lib/pg_search.rb
135
152
  - lib/pg_search/compatibility.rb
@@ -175,7 +192,6 @@ files:
175
192
  - spec/lib/pg_search/multisearchable_spec.rb
176
193
  - spec/lib/pg_search/normalizer_spec.rb
177
194
  - spec/spec_helper.rb
178
- - spec/support/coveralls.rb
179
195
  - spec/support/database.rb
180
196
  - spec/support/with_model.rb
181
197
  - sql/array_agg.sql
@@ -204,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
220
  version: '0'
205
221
  requirements: []
206
222
  rubyforge_project:
207
- rubygems_version: 2.2.2
223
+ rubygems_version: 2.4.5
208
224
  signing_key:
209
225
  specification_version: 4
210
226
  summary: PgSearch builds Active Record named scopes that take advantage of PostgreSQL's
@@ -225,6 +241,5 @@ test_files:
225
241
  - spec/lib/pg_search/multisearchable_spec.rb
226
242
  - spec/lib/pg_search/normalizer_spec.rb
227
243
  - spec/spec_helper.rb
228
- - spec/support/coveralls.rb
229
244
  - spec/support/database.rb
230
245
  - spec/support/with_model.rb
@@ -1,7 +0,0 @@
1
- if ENV["TRAVIS"]
2
- begin
3
- require 'coveralls'
4
- Coveralls.wear!
5
- rescue LoadError
6
- end
7
- end