pg_search 0.4.1 → 0.4.2
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.
- data/CHANGELOG.rdoc +12 -0
- data/Gemfile +2 -3
- data/Rakefile +0 -5
- data/lib/pg_search.rb +2 -2
- data/lib/pg_search/configuration/association.rb +1 -1
- data/lib/pg_search/multisearch.rb +22 -4
- data/lib/pg_search/tasks.rb +1 -1
- data/lib/pg_search/version.rb +1 -1
- data/spec/associations_spec.rb +247 -212
- data/spec/pg_search/document_spec.rb +1 -1
- data/spec/pg_search/multisearch_spec.rb +15 -5
- data/spec/pg_search/multisearchable_spec.rb +1 -1
- data/spec/pg_search_spec.rb +24 -12
- data/spec/spec_helper.rb +1 -2
- metadata +17 -7
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
= PgSearch
|
2
2
|
|
3
|
+
== 0.4.2
|
4
|
+
|
5
|
+
* Fill in timestamps correctly when rebuilding multisearch documents. (Barton McGuire)
|
6
|
+
|
7
|
+
* Fix various issues with rebuilding multisearch documents. (Eugen Neagoe)
|
8
|
+
|
9
|
+
* Fix syntax error in pg_search_dmetaphone() migration (Casey Foster)
|
10
|
+
|
11
|
+
* Rename PgSearch#rank to PgSearch#pg_search_rank and always return a Float.
|
12
|
+
|
13
|
+
* Fix issue with :associated_against and non-text columns.
|
14
|
+
|
3
15
|
== 0.4.1
|
4
16
|
|
5
17
|
* Fix Active Record 3.2 deprecation warnings. (Steven Harman)
|
data/Gemfile
CHANGED
@@ -5,8 +5,7 @@ gemspec
|
|
5
5
|
gem "rake"
|
6
6
|
gem "rdoc"
|
7
7
|
gem "pg"
|
8
|
-
gem "rspec"
|
9
|
-
gem "autotest"
|
8
|
+
gem "rspec"
|
10
9
|
gem "with_model"
|
11
10
|
|
12
|
-
gem "activerecord", "~> #{ENV["ACTIVE_RECORD_VERSION"]}.0" if ENV["ACTIVE_RECORD_VERSION"]
|
11
|
+
gem "activerecord", "~> #{ENV["ACTIVE_RECORD_VERSION"]}.0" if ENV["ACTIVE_RECORD_VERSION"]
|
data/Rakefile
CHANGED
data/lib/pg_search.rb
CHANGED
@@ -23,7 +23,7 @@ module PgSearch
|
|
23
23
|
when 0..90000
|
24
24
|
"array_to_string(array_agg(#{column.full_name}), ' ') AS #{column.alias}"
|
25
25
|
else
|
26
|
-
"string_agg(#{column.full_name}, ' ') AS #{column.alias}"
|
26
|
+
"string_agg(#{column.full_name}::text, ' ') AS #{column.alias}"
|
27
27
|
end
|
28
28
|
end.join(", ")
|
29
29
|
relation = @model.joins(@name).select("#{primary_key} AS id, #{selects}").group(primary_key)
|
@@ -1,12 +1,14 @@
|
|
1
1
|
module PgSearch
|
2
2
|
module Multisearch
|
3
3
|
REBUILD_SQL_TEMPLATE = <<-SQL
|
4
|
-
INSERT INTO :documents_table (searchable_type, searchable_id, content)
|
4
|
+
INSERT INTO :documents_table (searchable_type, searchable_id, content, created_at, updated_at)
|
5
5
|
SELECT :model_name AS searchable_type,
|
6
6
|
:model_table.id AS searchable_id,
|
7
7
|
(
|
8
8
|
:content_expressions
|
9
|
-
) AS content
|
9
|
+
) AS content,
|
10
|
+
:current_time AS created_at,
|
11
|
+
:current_time AS updated_at
|
10
12
|
FROM :model_table
|
11
13
|
SQL
|
12
14
|
|
@@ -21,13 +23,17 @@ SQL
|
|
21
23
|
def rebuild_sql(model)
|
22
24
|
connection = model.connection
|
23
25
|
|
26
|
+
unless model.respond_to?(:pg_search_multisearchable_options)
|
27
|
+
raise ModelNotMultisearchable.new(model)
|
28
|
+
end
|
29
|
+
|
24
30
|
columns = Array.wrap(
|
25
31
|
model.pg_search_multisearchable_options[:against]
|
26
32
|
)
|
27
33
|
|
28
|
-
content_expressions = columns.map
|
34
|
+
content_expressions = columns.map { |column|
|
29
35
|
%Q{coalesce(:model_table.#{column}, '')}
|
30
|
-
|
36
|
+
}.join(" || ' ' || ")
|
31
37
|
|
32
38
|
REBUILD_SQL_TEMPLATE.gsub(
|
33
39
|
":content_expressions", content_expressions
|
@@ -37,9 +43,21 @@ SQL
|
|
37
43
|
":model_table", model.quoted_table_name
|
38
44
|
).gsub(
|
39
45
|
":documents_table", PgSearch::Document.quoted_table_name
|
46
|
+
).gsub(
|
47
|
+
":current_time", connection.quote(connection.quoted_date(Time.now))
|
40
48
|
)
|
41
49
|
end
|
42
50
|
end
|
51
|
+
|
52
|
+
class ModelNotMultisearchable < StandardError
|
53
|
+
def initialize(model_class)
|
54
|
+
@model_class = model_class
|
55
|
+
end
|
56
|
+
|
57
|
+
def message
|
58
|
+
"#{@model_class.name} is not multisearchable. See PgSearch::ClassMethods#multisearchable"
|
59
|
+
end
|
60
|
+
end
|
43
61
|
end
|
44
62
|
end
|
45
63
|
|
data/lib/pg_search/tasks.rb
CHANGED
data/lib/pg_search/version.rb
CHANGED
data/spec/associations_spec.rb
CHANGED
@@ -1,294 +1,329 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe PgSearch do
|
4
4
|
context "joining to another table" do
|
5
|
-
context "
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
t.string "title"
|
10
|
-
end
|
5
|
+
context "without an :against" do
|
6
|
+
with_model :AssociatedModel do
|
7
|
+
table do |t|
|
8
|
+
t.string "title"
|
11
9
|
end
|
10
|
+
end
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
with_model :ModelWithoutAgainst do
|
13
|
+
table do |t|
|
14
|
+
t.string "title"
|
15
|
+
t.belongs_to :another_model
|
16
|
+
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
model do
|
19
|
+
include PgSearch
|
20
|
+
belongs_to :another_model, :class_name => 'AssociatedModel'
|
22
21
|
|
23
|
-
|
24
|
-
end
|
22
|
+
pg_search_scope :with_another, :associated_against => {:another_model => :title}
|
25
23
|
end
|
24
|
+
end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
it "returns rows that match the query in the columns of the associated model only" do
|
27
|
+
associated = AssociatedModel.create!(:title => 'abcdef')
|
28
|
+
included = [
|
29
|
+
ModelWithoutAgainst.create!(:title => 'abcdef', :another_model => associated),
|
30
|
+
ModelWithoutAgainst.create!(:title => 'ghijkl', :another_model => associated)
|
31
|
+
]
|
32
|
+
excluded = [
|
33
|
+
ModelWithoutAgainst.create!(:title => 'abcdef')
|
34
|
+
]
|
35
|
+
|
36
|
+
results = ModelWithoutAgainst.with_another('abcdef')
|
37
|
+
results.map(&:title).should =~ included.map(&:title)
|
38
|
+
results.should_not include(excluded)
|
39
|
+
end
|
40
|
+
end
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
42
|
+
context "through a belongs_to association" do
|
43
|
+
with_model :AssociatedModel do
|
44
|
+
table do |t|
|
45
|
+
t.string 'title'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
with_model :ModelWithBelongsTo do
|
50
|
+
table do |t|
|
51
|
+
t.string 'title'
|
52
|
+
t.belongs_to 'another_model'
|
53
|
+
end
|
54
|
+
|
55
|
+
model do
|
56
|
+
include PgSearch
|
57
|
+
belongs_to :another_model, :class_name => 'AssociatedModel'
|
58
|
+
|
59
|
+
pg_search_scope :with_associated, :against => :title, :associated_against => {:another_model => :title}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns rows that match the query in either its own columns or the columns of the associated model" do
|
64
|
+
associated = AssociatedModel.create!(:title => 'abcdef')
|
65
|
+
included = [
|
66
|
+
ModelWithBelongsTo.create!(:title => 'ghijkl', :another_model => associated),
|
67
|
+
ModelWithBelongsTo.create!(:title => 'abcdef')
|
68
|
+
]
|
69
|
+
excluded = ModelWithBelongsTo.create!(:title => 'mnopqr',
|
70
|
+
:another_model => AssociatedModel.create!(:title => 'stuvwx'))
|
71
|
+
|
72
|
+
results = ModelWithBelongsTo.with_associated('abcdef')
|
73
|
+
results.map(&:title).should =~ included.map(&:title)
|
74
|
+
results.should_not include(excluded)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "through a has_many association" do
|
79
|
+
with_model :AssociatedModelWithHasMany do
|
80
|
+
table do |t|
|
81
|
+
t.string 'title'
|
82
|
+
t.belongs_to 'ModelWithHasMany'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
with_model :ModelWithHasMany do
|
87
|
+
table do |t|
|
88
|
+
t.string 'title'
|
89
|
+
end
|
90
|
+
|
91
|
+
model do
|
92
|
+
include PgSearch
|
93
|
+
has_many :other_models, :class_name => 'AssociatedModelWithHasMany', :foreign_key => 'ModelWithHasMany_id'
|
94
|
+
|
95
|
+
pg_search_scope :with_associated, :against => [:title], :associated_against => {:other_models => :title}
|
40
96
|
end
|
41
97
|
end
|
42
98
|
|
43
|
-
|
44
|
-
|
99
|
+
it "returns rows that match the query in either its own columns or the columns of the associated model" do
|
100
|
+
included = [
|
101
|
+
ModelWithHasMany.create!(:title => 'abcdef', :other_models => [
|
102
|
+
AssociatedModelWithHasMany.create!(:title => 'foo'),
|
103
|
+
AssociatedModelWithHasMany.create!(:title => 'bar')
|
104
|
+
]),
|
105
|
+
ModelWithHasMany.create!(:title => 'ghijkl', :other_models => [
|
106
|
+
AssociatedModelWithHasMany.create!(:title => 'foo bar'),
|
107
|
+
AssociatedModelWithHasMany.create!(:title => 'mnopqr')
|
108
|
+
]),
|
109
|
+
ModelWithHasMany.create!(:title => 'foo bar')
|
110
|
+
]
|
111
|
+
excluded = ModelWithHasMany.create!(:title => 'stuvwx', :other_models => [
|
112
|
+
AssociatedModelWithHasMany.create!(:title => 'abcdef')
|
113
|
+
])
|
114
|
+
|
115
|
+
results = ModelWithHasMany.with_associated('foo bar')
|
116
|
+
results.map(&:title).should =~ included.map(&:title)
|
117
|
+
results.should_not include(excluded)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "across multiple associations" do
|
122
|
+
context "on different tables" do
|
123
|
+
with_model :FirstAssociatedModel do
|
124
|
+
table do |t|
|
125
|
+
t.string 'title'
|
126
|
+
t.belongs_to 'ModelWithManyAssociations'
|
127
|
+
end
|
128
|
+
model {}
|
129
|
+
end
|
130
|
+
|
131
|
+
with_model :SecondAssociatedModel do
|
45
132
|
table do |t|
|
46
133
|
t.string 'title'
|
47
134
|
end
|
135
|
+
model {}
|
48
136
|
end
|
49
137
|
|
50
|
-
with_model :
|
138
|
+
with_model :ModelWithManyAssociations do
|
51
139
|
table do |t|
|
52
140
|
t.string 'title'
|
53
|
-
t.belongs_to '
|
141
|
+
t.belongs_to 'model_of_second_type'
|
54
142
|
end
|
55
143
|
|
56
144
|
model do
|
57
145
|
include PgSearch
|
58
|
-
|
146
|
+
has_many :models_of_first_type, :class_name => 'FirstAssociatedModel', :foreign_key => 'ModelWithManyAssociations_id'
|
147
|
+
belongs_to :model_of_second_type, :class_name => 'SecondAssociatedModel'
|
59
148
|
|
60
|
-
pg_search_scope :with_associated, :against => :title,
|
149
|
+
pg_search_scope :with_associated, :against => :title,
|
150
|
+
:associated_against => {:models_of_first_type => :title, :model_of_second_type => :title}
|
61
151
|
end
|
62
152
|
end
|
63
153
|
|
64
154
|
it "returns rows that match the query in either its own columns or the columns of the associated model" do
|
65
|
-
|
155
|
+
matching_second = SecondAssociatedModel.create!(:title => "foo bar")
|
156
|
+
unmatching_second = SecondAssociatedModel.create!(:title => "uiop")
|
157
|
+
|
66
158
|
included = [
|
67
|
-
|
68
|
-
|
159
|
+
ModelWithManyAssociations.create!(:title => 'abcdef', :models_of_first_type => [
|
160
|
+
FirstAssociatedModel.create!(:title => 'foo'),
|
161
|
+
FirstAssociatedModel.create!(:title => 'bar')
|
162
|
+
]),
|
163
|
+
ModelWithManyAssociations.create!(:title => 'ghijkl', :models_of_first_type => [
|
164
|
+
FirstAssociatedModel.create!(:title => 'foo bar'),
|
165
|
+
FirstAssociatedModel.create!(:title => 'mnopqr')
|
166
|
+
]),
|
167
|
+
ModelWithManyAssociations.create!(:title => 'foo bar'),
|
168
|
+
ModelWithManyAssociations.create!(:title => 'qwerty', :model_of_second_type => matching_second)
|
169
|
+
]
|
170
|
+
excluded = [
|
171
|
+
ModelWithManyAssociations.create!(:title => 'stuvwx', :models_of_first_type => [
|
172
|
+
FirstAssociatedModel.create!(:title => 'abcdef')
|
173
|
+
]),
|
174
|
+
ModelWithManyAssociations.create!(:title => 'qwerty', :model_of_second_type => unmatching_second)
|
69
175
|
]
|
70
|
-
excluded = ModelWithBelongsTo.create!(:title => 'mnopqr',
|
71
|
-
:another_model => AssociatedModel.create!(:title => 'stuvwx'))
|
72
176
|
|
73
|
-
results =
|
177
|
+
results = ModelWithManyAssociations.with_associated('foo bar')
|
74
178
|
results.map(&:title).should =~ included.map(&:title)
|
75
|
-
results.should_not include(
|
179
|
+
excluded.each { |object| results.should_not include(object) }
|
76
180
|
end
|
77
181
|
end
|
78
182
|
|
79
|
-
context "
|
80
|
-
with_model :
|
183
|
+
context "on the same table" do
|
184
|
+
with_model :DoublyAssociatedModel do
|
81
185
|
table do |t|
|
82
186
|
t.string 'title'
|
83
|
-
t.belongs_to '
|
187
|
+
t.belongs_to 'ModelWithDoubleAssociation'
|
188
|
+
t.belongs_to 'ModelWithDoubleAssociation_again'
|
84
189
|
end
|
190
|
+
model {}
|
85
191
|
end
|
86
192
|
|
87
|
-
with_model :
|
193
|
+
with_model :ModelWithDoubleAssociation do
|
88
194
|
table do |t|
|
89
195
|
t.string 'title'
|
90
196
|
end
|
91
197
|
|
92
198
|
model do
|
93
199
|
include PgSearch
|
94
|
-
has_many :
|
200
|
+
has_many :things, :class_name => 'DoublyAssociatedModel', :foreign_key => 'ModelWithDoubleAssociation_id'
|
201
|
+
has_many :thingamabobs, :class_name => 'DoublyAssociatedModel', :foreign_key => 'ModelWithDoubleAssociation_again_id'
|
95
202
|
|
96
|
-
pg_search_scope :with_associated, :against =>
|
203
|
+
pg_search_scope :with_associated, :against => :title,
|
204
|
+
:associated_against => {:things => :title, :thingamabobs => :title}
|
97
205
|
end
|
98
206
|
end
|
99
207
|
|
100
208
|
it "returns rows that match the query in either its own columns or the columns of the associated model" do
|
101
209
|
included = [
|
102
|
-
|
103
|
-
|
104
|
-
|
210
|
+
ModelWithDoubleAssociation.create!(:title => 'abcdef', :things => [
|
211
|
+
DoublyAssociatedModel.create!(:title => 'foo'),
|
212
|
+
DoublyAssociatedModel.create!(:title => 'bar')
|
105
213
|
]),
|
106
|
-
|
107
|
-
|
108
|
-
|
214
|
+
ModelWithDoubleAssociation.create!(:title => 'ghijkl', :things => [
|
215
|
+
DoublyAssociatedModel.create!(:title => 'foo bar'),
|
216
|
+
DoublyAssociatedModel.create!(:title => 'mnopqr')
|
109
217
|
]),
|
110
|
-
|
218
|
+
ModelWithDoubleAssociation.create!(:title => 'foo bar'),
|
219
|
+
ModelWithDoubleAssociation.create!(:title => 'qwerty', :thingamabobs => [
|
220
|
+
DoublyAssociatedModel.create!(:title => "foo bar")
|
221
|
+
])
|
111
222
|
]
|
112
|
-
excluded =
|
113
|
-
|
223
|
+
excluded = [
|
224
|
+
ModelWithDoubleAssociation.create!(:title => 'stuvwx', :things => [
|
225
|
+
DoublyAssociatedModel.create!(:title => 'abcdef')
|
226
|
+
]),
|
227
|
+
ModelWithDoubleAssociation.create!(:title => 'qwerty', :thingamabobs => [
|
228
|
+
DoublyAssociatedModel.create!(:title => "uiop")
|
114
229
|
])
|
230
|
+
]
|
115
231
|
|
116
|
-
results =
|
232
|
+
results = ModelWithDoubleAssociation.with_associated('foo bar')
|
117
233
|
results.map(&:title).should =~ included.map(&:title)
|
118
|
-
results.should_not include(
|
234
|
+
excluded.each { |object| results.should_not include(object) }
|
119
235
|
end
|
120
236
|
end
|
237
|
+
end
|
121
238
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
model {}
|
130
|
-
end
|
131
|
-
|
132
|
-
with_model :SecondAssociatedModel do
|
133
|
-
table do |t|
|
134
|
-
t.string 'title'
|
135
|
-
end
|
136
|
-
model {}
|
137
|
-
end
|
138
|
-
|
139
|
-
with_model :ModelWithManyAssociations do
|
140
|
-
table do |t|
|
141
|
-
t.string 'title'
|
142
|
-
t.belongs_to 'model_of_second_type'
|
143
|
-
end
|
239
|
+
context "against multiple attributes on one association" do
|
240
|
+
with_model :AssociatedModel do
|
241
|
+
table do |t|
|
242
|
+
t.string 'title'
|
243
|
+
t.text 'author'
|
244
|
+
end
|
245
|
+
end
|
144
246
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
247
|
+
with_model :ModelWithAssociation do
|
248
|
+
table do |t|
|
249
|
+
t.belongs_to 'another_model'
|
250
|
+
end
|
149
251
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
end
|
252
|
+
model do
|
253
|
+
include PgSearch
|
254
|
+
belongs_to :another_model, :class_name => 'AssociatedModel'
|
154
255
|
|
155
|
-
|
156
|
-
matching_second = SecondAssociatedModel.create!(:title => "foo bar")
|
157
|
-
unmatching_second = SecondAssociatedModel.create!(:title => "uiop")
|
158
|
-
|
159
|
-
included = [
|
160
|
-
ModelWithManyAssociations.create!(:title => 'abcdef', :models_of_first_type => [
|
161
|
-
FirstAssociatedModel.create!(:title => 'foo'),
|
162
|
-
FirstAssociatedModel.create!(:title => 'bar')
|
163
|
-
]),
|
164
|
-
ModelWithManyAssociations.create!(:title => 'ghijkl', :models_of_first_type => [
|
165
|
-
FirstAssociatedModel.create!(:title => 'foo bar'),
|
166
|
-
FirstAssociatedModel.create!(:title => 'mnopqr')
|
167
|
-
]),
|
168
|
-
ModelWithManyAssociations.create!(:title => 'foo bar'),
|
169
|
-
ModelWithManyAssociations.create!(:title => 'qwerty', :model_of_second_type => matching_second)
|
170
|
-
]
|
171
|
-
excluded = [
|
172
|
-
ModelWithManyAssociations.create!(:title => 'stuvwx', :models_of_first_type => [
|
173
|
-
FirstAssociatedModel.create!(:title => 'abcdef')
|
174
|
-
]),
|
175
|
-
ModelWithManyAssociations.create!(:title => 'qwerty', :model_of_second_type => unmatching_second)
|
176
|
-
]
|
177
|
-
|
178
|
-
results = ModelWithManyAssociations.with_associated('foo bar')
|
179
|
-
results.map(&:title).should =~ included.map(&:title)
|
180
|
-
excluded.each { |object| results.should_not include(object) }
|
181
|
-
end
|
256
|
+
pg_search_scope :with_associated, :associated_against => {:another_model => [:title, :author]}
|
182
257
|
end
|
258
|
+
end
|
183
259
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
260
|
+
it "should only do one join" do
|
261
|
+
included = [
|
262
|
+
ModelWithAssociation.create!(
|
263
|
+
:another_model => AssociatedModel.create!(
|
264
|
+
:title => "foo",
|
265
|
+
:author => "bar"
|
266
|
+
)
|
267
|
+
),
|
268
|
+
ModelWithAssociation.create!(
|
269
|
+
:another_model => AssociatedModel.create!(
|
270
|
+
:title => "foo bar",
|
271
|
+
:author => "baz"
|
272
|
+
)
|
273
|
+
)
|
274
|
+
]
|
275
|
+
excluded = [
|
276
|
+
ModelWithAssociation.create!(
|
277
|
+
:another_model => AssociatedModel.create!(
|
278
|
+
:title => "foo",
|
279
|
+
:author => "baz"
|
280
|
+
)
|
281
|
+
)
|
282
|
+
]
|
193
283
|
|
194
|
-
|
195
|
-
table do |t|
|
196
|
-
t.string 'title'
|
197
|
-
end
|
284
|
+
results = ModelWithAssociation.with_associated('foo bar')
|
198
285
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
286
|
+
results.to_sql.scan("INNER JOIN").length.should == 1
|
287
|
+
included.each { |object| results.should include(object) }
|
288
|
+
excluded.each { |object| results.should_not include(object) }
|
289
|
+
end
|
203
290
|
|
204
|
-
|
205
|
-
:associated_against => {:things => :title, :thingamabobs => :title}
|
206
|
-
end
|
207
|
-
end
|
291
|
+
end
|
208
292
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
DoublyAssociatedModel.create!(:title => 'bar')
|
214
|
-
]),
|
215
|
-
ModelWithDoubleAssociation.create!(:title => 'ghijkl', :things => [
|
216
|
-
DoublyAssociatedModel.create!(:title => 'foo bar'),
|
217
|
-
DoublyAssociatedModel.create!(:title => 'mnopqr')
|
218
|
-
]),
|
219
|
-
ModelWithDoubleAssociation.create!(:title => 'foo bar'),
|
220
|
-
ModelWithDoubleAssociation.create!(:title => 'qwerty', :thingamabobs => [
|
221
|
-
DoublyAssociatedModel.create!(:title => "foo bar")
|
222
|
-
])
|
223
|
-
]
|
224
|
-
excluded = [
|
225
|
-
ModelWithDoubleAssociation.create!(:title => 'stuvwx', :things => [
|
226
|
-
DoublyAssociatedModel.create!(:title => 'abcdef')
|
227
|
-
]),
|
228
|
-
ModelWithDoubleAssociation.create!(:title => 'qwerty', :thingamabobs => [
|
229
|
-
DoublyAssociatedModel.create!(:title => "uiop")
|
230
|
-
])
|
231
|
-
]
|
232
|
-
|
233
|
-
results = ModelWithDoubleAssociation.with_associated('foo bar')
|
234
|
-
results.map(&:title).should =~ included.map(&:title)
|
235
|
-
excluded.each { |object| results.should_not include(object) }
|
236
|
-
end
|
293
|
+
context "against non-text columns" do
|
294
|
+
with_model :AssociatedModel do
|
295
|
+
table do |t|
|
296
|
+
t.integer 'number'
|
237
297
|
end
|
238
298
|
end
|
239
299
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
t.text 'author'
|
245
|
-
end
|
300
|
+
with_model :Model do
|
301
|
+
table do |t|
|
302
|
+
t.integer 'number'
|
303
|
+
t.belongs_to 'another_model'
|
246
304
|
end
|
247
305
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
end
|
252
|
-
|
253
|
-
model do
|
254
|
-
include PgSearch
|
255
|
-
belongs_to :another_model, :class_name => 'AssociatedModel'
|
306
|
+
model do
|
307
|
+
include PgSearch
|
308
|
+
belongs_to :another_model, :class_name => 'AssociatedModel'
|
256
309
|
|
257
|
-
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
it "should only do one join" do
|
262
|
-
included = [
|
263
|
-
ModelWithAssociation.create!(
|
264
|
-
:another_model => AssociatedModel.create!(
|
265
|
-
:title => "foo",
|
266
|
-
:author => "bar"
|
267
|
-
)
|
268
|
-
),
|
269
|
-
ModelWithAssociation.create!(
|
270
|
-
:another_model => AssociatedModel.create!(
|
271
|
-
:title => "foo bar",
|
272
|
-
:author => "baz"
|
273
|
-
)
|
274
|
-
)
|
275
|
-
]
|
276
|
-
excluded = [
|
277
|
-
ModelWithAssociation.create!(
|
278
|
-
:another_model => AssociatedModel.create!(
|
279
|
-
:title => "foo",
|
280
|
-
:author => "baz"
|
281
|
-
)
|
282
|
-
)
|
283
|
-
]
|
284
|
-
|
285
|
-
results = ModelWithAssociation.with_associated('foo bar')
|
286
|
-
|
287
|
-
results.to_sql.scan("INNER JOIN").length.should == 1
|
288
|
-
included.each { |object| results.should include(object) }
|
289
|
-
excluded.each { |object| results.should_not include(object) }
|
310
|
+
pg_search_scope :with_associated, :associated_against => {:another_model => :number}
|
290
311
|
end
|
312
|
+
end
|
291
313
|
|
314
|
+
it "should cast the columns to text" do
|
315
|
+
associated = AssociatedModel.create!(:number => 123)
|
316
|
+
included = [
|
317
|
+
Model.create!(:number => 123, :another_model => associated),
|
318
|
+
Model.create!(:number => 456, :another_model => associated)
|
319
|
+
]
|
320
|
+
excluded = [
|
321
|
+
Model.create!(:number => 123)
|
322
|
+
]
|
323
|
+
|
324
|
+
results = Model.with_associated('123')
|
325
|
+
results.map(&:number).should =~ included.map(&:number)
|
326
|
+
results.should_not include(excluded)
|
292
327
|
end
|
293
328
|
end
|
294
329
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe PgSearch::Multisearch do
|
4
4
|
with_table "pg_search_documents", {}, &DOCUMENTS_SCHEMA
|
@@ -20,6 +20,12 @@ describe PgSearch::Multisearch do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
describe ".rebuild_sql" do
|
23
|
+
let(:now) { Time.now }
|
24
|
+
|
25
|
+
before do
|
26
|
+
Time.stub(:now => now)
|
27
|
+
end
|
28
|
+
|
23
29
|
context "with one attribute" do
|
24
30
|
it "should generate the proper SQL code" do
|
25
31
|
model = MultisearchableModel
|
@@ -28,12 +34,14 @@ describe PgSearch::Multisearch do
|
|
28
34
|
model.multisearchable :against => :title
|
29
35
|
|
30
36
|
expected_sql = <<-SQL
|
31
|
-
INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content)
|
37
|
+
INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content, created_at, updated_at)
|
32
38
|
SELECT #{connection.quote(model.name)} AS searchable_type,
|
33
39
|
#{model.quoted_table_name}.id AS searchable_id,
|
34
40
|
(
|
35
41
|
coalesce(#{model.quoted_table_name}.title, '')
|
36
|
-
) AS content
|
42
|
+
) AS content,
|
43
|
+
#{connection.quote(connection.quoted_date(now))} AS created_at,
|
44
|
+
#{connection.quote(connection.quoted_date(now))} AS updated_at
|
37
45
|
FROM #{model.quoted_table_name}
|
38
46
|
SQL
|
39
47
|
|
@@ -49,12 +57,14 @@ SQL
|
|
49
57
|
model.multisearchable :against => [:title, :content]
|
50
58
|
|
51
59
|
expected_sql = <<-SQL
|
52
|
-
INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content)
|
60
|
+
INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content, created_at, updated_at)
|
53
61
|
SELECT #{connection.quote(model.name)} AS searchable_type,
|
54
62
|
#{model.quoted_table_name}.id AS searchable_id,
|
55
63
|
(
|
56
64
|
coalesce(#{model.quoted_table_name}.title, '') || ' ' || coalesce(#{model.quoted_table_name}.content, '')
|
57
|
-
) AS content
|
65
|
+
) AS content,
|
66
|
+
#{connection.quote(connection.quoted_date(now))} AS created_at,
|
67
|
+
#{connection.quote(connection.quoted_date(now))} AS updated_at
|
58
68
|
FROM #{model.quoted_table_name}
|
59
69
|
SQL
|
60
70
|
|
data/spec/pg_search_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe "an ActiveRecord model which includes PgSearch" do
|
4
4
|
|
@@ -212,7 +212,7 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
212
212
|
winner = ModelWithPgSearch.create!(:content => 'foo foo')
|
213
213
|
|
214
214
|
results = ModelWithPgSearch.search_content("foo")
|
215
|
-
results[0].
|
215
|
+
results[0].pg_search_rank.should > results[1].pg_search_rank
|
216
216
|
results.should == [winner, loser]
|
217
217
|
end
|
218
218
|
|
@@ -382,6 +382,16 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
382
382
|
end
|
383
383
|
end
|
384
384
|
|
385
|
+
it "adds a #pg_search_rank method to each returned model record" do
|
386
|
+
ModelWithPgSearch.class_eval do
|
387
|
+
pg_search_scope :search_content, :against => :content
|
388
|
+
end
|
389
|
+
|
390
|
+
result = ModelWithPgSearch.search_content("Strip Down").first
|
391
|
+
|
392
|
+
result.pg_search_rank.should be_a(Float)
|
393
|
+
end
|
394
|
+
|
385
395
|
context "with a normalization specified" do
|
386
396
|
before do
|
387
397
|
ModelWithPgSearch.class_eval do
|
@@ -392,11 +402,12 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
392
402
|
}
|
393
403
|
end
|
394
404
|
end
|
405
|
+
|
395
406
|
it "ranks the results for documents with less text higher" do
|
396
407
|
results = ModelWithPgSearch.search_content_with_normalization("down")
|
397
408
|
|
398
409
|
results.map(&:content).should == ["Down", "Strip Down", "Down and Out", "Won't Let You Down"]
|
399
|
-
results.first.
|
410
|
+
results.first.pg_search_rank.should be > results.last.pg_search_rank
|
400
411
|
end
|
401
412
|
end
|
402
413
|
|
@@ -408,11 +419,12 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
408
419
|
:using => :tsearch
|
409
420
|
end
|
410
421
|
end
|
422
|
+
|
411
423
|
it "ranks the results equally" do
|
412
424
|
results = ModelWithPgSearch.search_content_without_normalization("down")
|
413
425
|
|
414
426
|
results.map(&:content).should == ["Strip Down", "Down", "Down and Out", "Won't Let You Down"]
|
415
|
-
results.first.
|
427
|
+
results.first.pg_search_rank.should == results.last.pg_search_rank
|
416
428
|
end
|
417
429
|
end
|
418
430
|
end
|
@@ -421,7 +433,7 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
421
433
|
before do
|
422
434
|
ModelWithPgSearch.class_eval do
|
423
435
|
pg_search_scope :search_weighted_by_array_of_arrays, :against => [[:content, 'B'], [:title, 'A']]
|
424
|
-
|
436
|
+
end
|
425
437
|
end
|
426
438
|
|
427
439
|
it "returns results sorted by weighted rank" do
|
@@ -429,7 +441,7 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
429
441
|
winner = ModelWithPgSearch.create!(:title => 'foo', :content => 'bar')
|
430
442
|
|
431
443
|
results = ModelWithPgSearch.search_weighted_by_array_of_arrays('foo')
|
432
|
-
results[0].
|
444
|
+
results[0].pg_search_rank.should > results[1].pg_search_rank
|
433
445
|
results.should == [winner, loser]
|
434
446
|
end
|
435
447
|
end
|
@@ -446,7 +458,7 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
446
458
|
winner = ModelWithPgSearch.create!(:title => 'foo', :content => 'bar')
|
447
459
|
|
448
460
|
results = ModelWithPgSearch.search_weighted_by_hash('foo')
|
449
|
-
results[0].
|
461
|
+
results[0].pg_search_rank.should > results[1].pg_search_rank
|
450
462
|
results.should == [winner, loser]
|
451
463
|
end
|
452
464
|
end
|
@@ -463,7 +475,7 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
463
475
|
winner = ModelWithPgSearch.create!(:title => 'foo', :content => 'bar')
|
464
476
|
|
465
477
|
results = ModelWithPgSearch.search_weighted('foo')
|
466
|
-
results[0].
|
478
|
+
results[0].pg_search_rank.should > results[1].pg_search_rank
|
467
479
|
results.should == [winner, loser]
|
468
480
|
end
|
469
481
|
end
|
@@ -680,14 +692,14 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
680
692
|
it "should return records with a rank attribute equal to the :ranked_by expression" do
|
681
693
|
ModelWithPgSearch.create!(:content => 'foo', :importance => 10)
|
682
694
|
results = ModelWithPgSearch.search_content_with_importance_as_rank("foo")
|
683
|
-
results.first.
|
695
|
+
results.first.pg_search_rank.should == 10
|
684
696
|
end
|
685
697
|
|
686
698
|
it "should substitute :tsearch with the tsearch rank expression in the :ranked_by expression" do
|
687
699
|
ModelWithPgSearch.create!(:content => 'foo', :importance => 10)
|
688
700
|
|
689
|
-
tsearch_rank = ModelWithPgSearch.search_content_with_default_rank("foo").first.
|
690
|
-
multiplied_rank = ModelWithPgSearch.search_content_with_importance_as_rank_multiplier("foo").first.
|
701
|
+
tsearch_rank = ModelWithPgSearch.search_content_with_default_rank("foo").first.pg_search_rank
|
702
|
+
multiplied_rank = ModelWithPgSearch.search_content_with_importance_as_rank_multiplier("foo").first.pg_search_rank
|
691
703
|
|
692
704
|
multiplied_rank.should be_within(0.001).of(tsearch_rank * 10)
|
693
705
|
end
|
@@ -719,7 +731,7 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
719
731
|
ModelWithPgSearch.create!(:content => 'foo')
|
720
732
|
|
721
733
|
results = ModelWithPgSearch.send(@scope_name, 'foo')
|
722
|
-
results.first.
|
734
|
+
results.first.pg_search_rank.should_not be_nil
|
723
735
|
end
|
724
736
|
end
|
725
737
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -9,7 +9,6 @@ begin
|
|
9
9
|
connection = ActiveRecord::Base.connection
|
10
10
|
postgresql_version = connection.send(:postgresql_version)
|
11
11
|
connection.execute("SELECT 1")
|
12
|
-
puts "postgresql_version = #{postgresql_version}"
|
13
12
|
rescue PGError => e
|
14
13
|
puts "-" * 80
|
15
14
|
puts "Unable to connect to database. Please run:"
|
@@ -66,7 +65,7 @@ RSpec.configure do |config|
|
|
66
65
|
config.extend WithModel
|
67
66
|
end
|
68
67
|
|
69
|
-
RSpec::Matchers::OperatorMatcher.register(ActiveRecord::Relation, '=~', RSpec::Matchers::MatchArray)
|
68
|
+
RSpec::Matchers::OperatorMatcher.register(ActiveRecord::Relation, '=~', RSpec::Matchers::BuiltIn::MatchArray)
|
70
69
|
|
71
70
|
DOCUMENTS_SCHEMA = lambda do |t|
|
72
71
|
t.belongs_to :searchable, :polymorphic => true
|
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.4.
|
4
|
+
version: 0.4.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-05-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '3'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: activesupport
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,7 +37,12 @@ dependencies:
|
|
32
37
|
version: '3'
|
33
38
|
type: :runtime
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3'
|
36
46
|
description: PgSearch builds ActiveRecord named scopes that take advantage of PostgreSQL's
|
37
47
|
full text search
|
38
48
|
email:
|
@@ -101,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
111
|
version: '0'
|
102
112
|
requirements: []
|
103
113
|
rubyforge_project:
|
104
|
-
rubygems_version: 1.8.
|
114
|
+
rubygems_version: 1.8.24
|
105
115
|
signing_key:
|
106
116
|
specification_version: 3
|
107
117
|
summary: PgSearch builds ActiveRecord named scopes that take advantage of PostgreSQL's
|