pg_search 0.7.4 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +30 -13
- data/lib/pg_search/document.rb +4 -2
- data/lib/pg_search/features/tsearch.rb +23 -19
- data/lib/pg_search/migration/associated_against_generator.rb +4 -16
- data/lib/pg_search/migration/dmetaphone_generator.rb +4 -16
- data/lib/pg_search/migration/generator.rb +28 -0
- data/lib/pg_search/migration/multisearch_generator.rb +4 -8
- data/lib/pg_search/migration/templates/add_pg_search_associated_against_support_functions.rb.erb +2 -2
- data/lib/pg_search/migration/templates/{create_pg_search_documents.rb → create_pg_search_documents.rb.erb} +0 -0
- data/lib/pg_search/scope_options.rb +3 -1
- data/lib/pg_search/version.rb +1 -1
- data/pg_search.gemspec +1 -1
- data/spec/integration/associations_spec.rb +25 -25
- data/spec/integration/pagination_spec.rb +4 -4
- data/spec/integration/pg_search_spec.rb +123 -103
- data/spec/lib/pg_search/configuration/column_spec.rb +2 -2
- data/spec/lib/pg_search/document_spec.rb +11 -4
- data/spec/lib/pg_search/features/dmetaphone_spec.rb +4 -2
- data/spec/lib/pg_search/features/tsearch_spec.rb +4 -2
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +16 -16
- data/spec/lib/pg_search/multisearch_spec.rb +15 -18
- data/spec/lib/pg_search/multisearchable_spec.rb +39 -38
- data/spec/lib/pg_search/normalizer_spec.rb +8 -8
- data/spec/spec_helper.rb +8 -94
- data/spec/support/coveralls.rb +7 -0
- data/spec/support/database.rb +83 -0
- data/spec/support/with_model.rb +5 -0
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56abc4100a0dcd75ed558ec0ec631d53088ab5bf
|
4
|
+
data.tar.gz: 33edaaa054f61e59efa18c8e96f0ecd9cc12630c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4e3fb0b2571d4fec4f447f1f6df9108b70e6219c7381e9d79e95d559bb063389a4ded5f50db1c320a1564647a83a9050ba6dfb32f3b87adae9866028c7633b8
|
7
|
+
data.tar.gz: f618f99de2af02e89346f6cde00036e6c56b88292511b434140276d470b6033da2717ebc132b5364e4ba9bdf32afc29ed6811098c532916db6973154b0db0d9e
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,17 +1,10 @@
|
|
1
|
-
# pg_search
|
1
|
+
# [pg_search](http://github.com/Casecommons/pg_search/)
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
[
|
6
|
-
|
7
|
-
[
|
8
|
-
/>](https://gemnasium.com/Casecommons/pg_search)
|
9
|
-
[<img src="https://codeclimate.com/github/Casecommons/pg_search.png"
|
10
|
-
/>](https://codeclimate.com/github/Casecommons/pg_search)
|
11
|
-
[<img src="https://coveralls.io/repos/Casecommons/pg_search/badge.png?branch=master"
|
12
|
-
alt="Coverage Status" />](https://coveralls.io/r/Casecommons/pg_search)
|
13
|
-
[<img src="https://badge.fury.io/rb/pg_search.png" alt="Gem Version"
|
14
|
-
/>](http://badge.fury.io/rb/pg_search)
|
3
|
+
[](https://travis-ci.org/Casecommons/pg_search)
|
4
|
+
[](https://codeclimate.com/github/Casecommons/pg_search)
|
5
|
+
[](https://coveralls.io/r/Casecommons/pg_search)
|
6
|
+
[](https://rubygems.org/gems/pg_search)
|
7
|
+
[](https://gemnasium.com/Casecommons/pg_search)
|
15
8
|
|
16
9
|
## DESCRIPTION
|
17
10
|
|
@@ -670,6 +663,30 @@ Number.search_any_word('one two three') # => [one, two, three]
|
|
670
663
|
Number.search_all_words('one two three') # => []
|
671
664
|
```
|
672
665
|
|
666
|
+
##### :sort_only
|
667
|
+
|
668
|
+
Setting this attribute to true will make this feature available for sorting,
|
669
|
+
but will not include it in the query's WHERE condition.
|
670
|
+
|
671
|
+
```ruby
|
672
|
+
class Person < ActiveRecord::Base
|
673
|
+
include PgSearch
|
674
|
+
pg_search_scope :search,
|
675
|
+
:against => :name,
|
676
|
+
:using => {
|
677
|
+
:tsearch => {:any_word => true}
|
678
|
+
:dmetaphone => {:any_word => true, :sort_only => true}
|
679
|
+
}
|
680
|
+
end
|
681
|
+
|
682
|
+
exact = Person.create!(:name => 'ash hines')
|
683
|
+
one_exact_one_close = Person.create!(:name => 'ash heinz')
|
684
|
+
one_exact = Person.create!(:name => 'ash smith')
|
685
|
+
one_close = Person.create!(:name => 'leigh heinz')
|
686
|
+
|
687
|
+
Person.search('ash hines') # => [exact, one_exact_one_close, one_exact]
|
688
|
+
```
|
689
|
+
|
673
690
|
#### :dmetaphone (Double Metaphone soundalike search)
|
674
691
|
|
675
692
|
[Double Metaphone](http://en.wikipedia.org/wiki/Double_Metaphone) is an
|
data/lib/pg_search/document.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require 'logger'
|
2
2
|
|
3
3
|
module PgSearch
|
4
4
|
class Document < ActiveRecord::Base
|
5
5
|
include PgSearch
|
6
|
+
|
6
7
|
self.table_name = 'pg_search_documents'
|
7
8
|
belongs_to :searchable, :polymorphic => true
|
8
9
|
|
9
|
-
before_validation :update_content
|
10
|
+
before_validation :update_content,
|
11
|
+
:unless => Proc.new { |doc| doc.searchable.nil? }
|
10
12
|
|
11
13
|
# The logger might not have loaded yet.
|
12
14
|
# https://github.com/Casecommons/pg_search/issues/26
|
@@ -54,25 +54,8 @@ module PgSearch
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def tsdocument
|
57
|
-
tsdocument_terms = []
|
58
|
-
|
59
|
-
columns_to_use = options[:tsvector_column] ?
|
60
|
-
columns.select { |c| c.is_a?(PgSearch::Configuration::ForeignColumn) } :
|
61
|
-
columns
|
62
|
-
|
63
|
-
if columns_to_use.present?
|
64
|
-
tsdocument_terms << columns_to_use.map do |search_column|
|
65
|
-
tsvector = Arel::Nodes::NamedFunction.new(
|
66
|
-
"to_tsvector",
|
67
|
-
[dictionary, Arel.sql(normalize(search_column.to_sql))]
|
68
|
-
).to_sql
|
69
|
-
|
70
|
-
if search_column.weight.nil?
|
71
|
-
tsvector
|
72
|
-
else
|
73
|
-
"setweight(#{tsvector}, #{connection.quote(search_column.weight)})"
|
74
|
-
end
|
75
|
-
end.join(" || ")
|
57
|
+
tsdocument_terms = (columns_to_use || []).map do |search_column|
|
58
|
+
column_to_tsvector(search_column)
|
76
59
|
end
|
77
60
|
|
78
61
|
if options[:tsvector_column]
|
@@ -107,6 +90,27 @@ module PgSearch
|
|
107
90
|
def arel_wrap(sql_string)
|
108
91
|
Arel::Nodes::Grouping.new(Arel.sql(sql_string))
|
109
92
|
end
|
93
|
+
|
94
|
+
def columns_to_use
|
95
|
+
if options[:tsvector_column]
|
96
|
+
columns.select { |c| c.is_a?(PgSearch::Configuration::ForeignColumn) }
|
97
|
+
else
|
98
|
+
columns
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def column_to_tsvector(search_column)
|
103
|
+
tsvector = Arel::Nodes::NamedFunction.new(
|
104
|
+
"to_tsvector",
|
105
|
+
[dictionary, Arel.sql(normalize(search_column.to_sql))]
|
106
|
+
).to_sql
|
107
|
+
|
108
|
+
if search_column.weight.nil?
|
109
|
+
tsvector
|
110
|
+
else
|
111
|
+
"setweight(#{tsvector}, #{connection.quote(search_column.weight)})"
|
112
|
+
end
|
113
|
+
end
|
110
114
|
end
|
111
115
|
end
|
112
116
|
end
|
@@ -1,22 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require 'pg_search/migration/generator'
|
2
2
|
|
3
3
|
module PgSearch
|
4
4
|
module Migration
|
5
|
-
class AssociatedAgainstGenerator <
|
6
|
-
|
7
|
-
|
8
|
-
def create_migration
|
9
|
-
now = Time.now.utc
|
10
|
-
filename = "#{now.strftime('%Y%m%d%H%M%S')}_add_pg_search_associated_against_support_functions.rb"
|
11
|
-
template "add_pg_search_associated_against_support_functions.rb.erb", "db/migrate/#{filename}"
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def read_sql_file(filename)
|
17
|
-
sql_directory = File.expand_path("../../../../sql", __FILE__)
|
18
|
-
source_path = File.join(sql_directory, "#{filename}.sql")
|
19
|
-
File.read(source_path)
|
5
|
+
class AssociatedAgainstGenerator < Generator
|
6
|
+
def migration_name
|
7
|
+
'add_pg_search_associated_against_support_functions'.freeze
|
20
8
|
end
|
21
9
|
end
|
22
10
|
end
|
@@ -1,22 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require 'pg_search/migration/generator'
|
2
2
|
|
3
3
|
module PgSearch
|
4
4
|
module Migration
|
5
|
-
class DmetaphoneGenerator <
|
6
|
-
|
7
|
-
|
8
|
-
def create_migration
|
9
|
-
now = Time.now.utc
|
10
|
-
filename = "#{now.strftime('%Y%m%d%H%M%S')}_add_pg_search_dmetaphone_support_functions.rb"
|
11
|
-
template "add_pg_search_dmetaphone_support_functions.rb.erb", "db/migrate/#{filename}"
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def read_sql_file(filename)
|
17
|
-
sql_directory = File.expand_path("../../../../sql", __FILE__)
|
18
|
-
source_path = File.join(sql_directory, "#{filename}.sql")
|
19
|
-
File.read(source_path)
|
5
|
+
class DmetaphoneGenerator < Generator
|
6
|
+
def migration_name
|
7
|
+
'add_pg_search_dmetaphone_support_functions'.freeze
|
20
8
|
end
|
21
9
|
end
|
22
10
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
|
3
|
+
module PgSearch
|
4
|
+
module Migration
|
5
|
+
class Generator < Rails::Generators::Base
|
6
|
+
hide!
|
7
|
+
|
8
|
+
def self.inherited(subclass)
|
9
|
+
super
|
10
|
+
subclass.source_root File.expand_path('../templates', __FILE__)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_migration
|
14
|
+
now = Time.now.utc
|
15
|
+
filename = "#{now.strftime('%Y%m%d%H%M%S')}_#{migration_name}.rb"
|
16
|
+
template "#{migration_name}.rb.erb", "db/migrate/#{filename}"
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def read_sql_file(filename)
|
22
|
+
sql_directory = File.expand_path('../../../../sql', __FILE__)
|
23
|
+
source_path = File.join(sql_directory, "#{filename}.sql")
|
24
|
+
File.read(source_path).strip
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,14 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require 'pg_search/migration/generator'
|
2
2
|
|
3
3
|
module PgSearch
|
4
4
|
module Migration
|
5
|
-
class MultisearchGenerator <
|
6
|
-
|
7
|
-
|
8
|
-
def create_migration
|
9
|
-
now = Time.now.utc
|
10
|
-
filename = "#{now.strftime('%Y%m%d%H%M%S')}_create_pg_search_documents.rb"
|
11
|
-
copy_file "create_pg_search_documents.rb", "db/migrate/#{filename}"
|
5
|
+
class MultisearchGenerator < Generator
|
6
|
+
def migration_name
|
7
|
+
'create_pg_search_documents'
|
12
8
|
end
|
13
9
|
end
|
14
10
|
end
|
data/lib/pg_search/migration/templates/add_pg_search_associated_against_support_functions.rb.erb
CHANGED
@@ -3,7 +3,7 @@ class AddPgSearchAssociatedAgainstSupportFunctions < ActiveRecord::Migration
|
|
3
3
|
say_with_time("Adding support functions for pg_search :associated_against") do
|
4
4
|
if ActiveRecord::Base.connection.send(:postgresql_version) < 80400
|
5
5
|
execute <<-'SQL'
|
6
|
-
|
6
|
+
<%= read_sql_file "array_agg" %>
|
7
7
|
SQL
|
8
8
|
end
|
9
9
|
end
|
@@ -13,7 +13,7 @@ class AddPgSearchAssociatedAgainstSupportFunctions < ActiveRecord::Migration
|
|
13
13
|
say_with_time("Dropping support functions for pg_search :associated_against") do
|
14
14
|
if ActiveRecord::Base.connection.send(:postgresql_version) < 80400
|
15
15
|
execute <<-'SQL'
|
16
|
-
|
16
|
+
<%= read_sql_file "uninstall_array_agg" %>
|
17
17
|
SQL
|
18
18
|
end
|
19
19
|
end
|
File without changes
|
@@ -33,7 +33,9 @@ module PgSearch
|
|
33
33
|
delegate :connection, :quoted_table_name, :to => :@model
|
34
34
|
|
35
35
|
def conditions
|
36
|
-
config.features.
|
36
|
+
config.features.reject do |feature_name, feature_options|
|
37
|
+
feature_options && feature_options[:sort_only]
|
38
|
+
end.map do |feature_name, feature_options|
|
37
39
|
feature_for(feature_name).conditions
|
38
40
|
end.inject do |accumulator, expression|
|
39
41
|
Arel::Nodes::Or.new(accumulator, expression)
|
data/lib/pg_search/version.rb
CHANGED
data/pg_search.gemspec
CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
|
25
25
|
s.add_development_dependency 'rake'
|
26
26
|
s.add_development_dependency 'pry'
|
27
|
-
s.add_development_dependency 'rspec', '
|
27
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
28
28
|
s.add_development_dependency 'with_model'
|
29
29
|
|
30
30
|
s.required_ruby_version = ">= 1.9.2"
|
@@ -34,8 +34,8 @@ describe PgSearch do
|
|
34
34
|
]
|
35
35
|
|
36
36
|
results = ModelWithoutAgainst.with_another('abcdef')
|
37
|
-
results.map(&:title).
|
38
|
-
results.
|
37
|
+
expect(results.map(&:title)).to match_array(included.map(&:title))
|
38
|
+
expect(results).not_to include(excluded)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -70,8 +70,8 @@ describe PgSearch do
|
|
70
70
|
:another_model => AssociatedModel.create!(:title => 'stuvwx'))
|
71
71
|
|
72
72
|
results = ModelWithBelongsTo.with_associated('abcdef')
|
73
|
-
results.map(&:title).
|
74
|
-
results.
|
73
|
+
expect(results.map(&:title)).to match_array(included.map(&:title))
|
74
|
+
expect(results).not_to include(excluded)
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -113,8 +113,8 @@ describe PgSearch do
|
|
113
113
|
])
|
114
114
|
|
115
115
|
results = ModelWithHasMany.with_associated('foo bar')
|
116
|
-
results.map(&:title).
|
117
|
-
results.
|
116
|
+
expect(results.map(&:title)).to match_array(included.map(&:title))
|
117
|
+
expect(results).not_to include(excluded)
|
118
118
|
end
|
119
119
|
|
120
120
|
it "uses an unscoped relation of the assocated model" do
|
@@ -129,8 +129,8 @@ describe PgSearch do
|
|
129
129
|
]
|
130
130
|
|
131
131
|
results = ModelWithHasMany.limit(1).order("id ASC").with_associated('foo bar')
|
132
|
-
results.map(&:title).
|
133
|
-
results.
|
132
|
+
expect(results.map(&:title)).to match_array(included.map(&:title))
|
133
|
+
expect(results).not_to include(excluded)
|
134
134
|
end
|
135
135
|
|
136
136
|
end
|
@@ -192,8 +192,8 @@ describe PgSearch do
|
|
192
192
|
]
|
193
193
|
|
194
194
|
results = ModelWithManyAssociations.with_associated('foo bar')
|
195
|
-
results.map(&:title).
|
196
|
-
excluded.each { |object| results.
|
195
|
+
expect(results.map(&:title)).to match_array(included.map(&:title))
|
196
|
+
excluded.each { |object| expect(results).not_to include(object) }
|
197
197
|
end
|
198
198
|
end
|
199
199
|
|
@@ -247,8 +247,8 @@ describe PgSearch do
|
|
247
247
|
]
|
248
248
|
|
249
249
|
results = ModelWithDoubleAssociation.with_associated('foo bar')
|
250
|
-
results.map(&:title).
|
251
|
-
excluded.each { |object| results.
|
250
|
+
expect(results.map(&:title)).to match_array(included.map(&:title))
|
251
|
+
excluded.each { |object| expect(results).not_to include(object) }
|
252
252
|
end
|
253
253
|
end
|
254
254
|
end
|
@@ -300,9 +300,9 @@ describe PgSearch do
|
|
300
300
|
|
301
301
|
results = ModelWithAssociation.with_associated('foo bar')
|
302
302
|
|
303
|
-
results.to_sql.scan("INNER JOIN").length.
|
304
|
-
included.each { |object| results.
|
305
|
-
excluded.each { |object| results.
|
303
|
+
expect(results.to_sql.scan("INNER JOIN").length).to eq(1)
|
304
|
+
included.each { |object| expect(results).to include(object) }
|
305
|
+
excluded.each { |object| expect(results).not_to include(object) }
|
306
306
|
end
|
307
307
|
|
308
308
|
end
|
@@ -339,8 +339,8 @@ describe PgSearch do
|
|
339
339
|
]
|
340
340
|
|
341
341
|
results = Model.with_associated('123')
|
342
|
-
results.map(&:number).
|
343
|
-
results.
|
342
|
+
expect(results.map(&:number)).to match_array(included.map(&:number))
|
343
|
+
expect(results).not_to include(excluded)
|
344
344
|
end
|
345
345
|
end
|
346
346
|
|
@@ -375,8 +375,8 @@ describe PgSearch do
|
|
375
375
|
results = Parent.search_name('bar.foo').includes(:children)
|
376
376
|
results.to_a
|
377
377
|
|
378
|
-
results.
|
379
|
-
results.
|
378
|
+
expect(results).to include(included)
|
379
|
+
expect(results).not_to include(excluded)
|
380
380
|
end
|
381
381
|
end
|
382
382
|
end
|
@@ -422,8 +422,8 @@ describe PgSearch do
|
|
422
422
|
|
423
423
|
results = ModelWithAssociation.joins(:associated_models).merge(relation)
|
424
424
|
|
425
|
-
results.
|
426
|
-
results.
|
425
|
+
expect(results).to include(*included)
|
426
|
+
expect(results).not_to include(*excluded)
|
427
427
|
end
|
428
428
|
end
|
429
429
|
|
@@ -463,8 +463,8 @@ describe PgSearch do
|
|
463
463
|
|
464
464
|
results = company.positions.search('teller 1')
|
465
465
|
|
466
|
-
results.
|
467
|
-
results.
|
466
|
+
expect(results).to include(*included)
|
467
|
+
expect(results).not_to include(*excluded)
|
468
468
|
end
|
469
469
|
end
|
470
470
|
|
@@ -505,8 +505,8 @@ describe PgSearch do
|
|
505
505
|
|
506
506
|
results = company.positions.search('teller 1')
|
507
507
|
|
508
|
-
results.
|
509
|
-
results.
|
508
|
+
expect(results).to include(*included)
|
509
|
+
expect(results).not_to include(*excluded)
|
510
510
|
end
|
511
511
|
end
|
512
512
|
end
|
@@ -23,8 +23,8 @@ describe "pagination" do
|
|
23
23
|
best = PaginatedModel.create!(:name => "foo foo foo")
|
24
24
|
good = PaginatedModel.create!(:name => "foo bar bar")
|
25
25
|
|
26
|
-
PaginatedModel.page(1).search_name("foo").
|
27
|
-
PaginatedModel.page(2).search_name("foo").
|
26
|
+
expect(PaginatedModel.page(1).search_name("foo")).to eq([best, better])
|
27
|
+
expect(PaginatedModel.page(2).search_name("foo")).to eq([good])
|
28
28
|
end
|
29
29
|
|
30
30
|
it "is chainable after a search scope" do
|
@@ -32,8 +32,8 @@ describe "pagination" do
|
|
32
32
|
best = PaginatedModel.create!(:name => "foo foo foo")
|
33
33
|
good = PaginatedModel.create!(:name => "foo bar bar")
|
34
34
|
|
35
|
-
PaginatedModel.search_name("foo").page(1).
|
36
|
-
PaginatedModel.search_name("foo").page(2).
|
35
|
+
expect(PaginatedModel.search_name("foo").page(1)).to eq([best, better])
|
36
|
+
expect(PaginatedModel.search_name("foo").page(2)).to eq([good])
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|