pg_search 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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", ">=2.4"
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
@@ -16,11 +16,6 @@ task "doc" do
16
16
  bundle_exec("rspec --format d spec")
17
17
  end
18
18
 
19
- desc "Launch autotest"
20
- task "autotest" do
21
- bundle_exec("autotest -s rspec2")
22
- end
23
-
24
19
  namespace "doc" do
25
20
  desc "Generate README and preview in browser"
26
21
  task "readme" do
data/lib/pg_search.rb CHANGED
@@ -23,8 +23,8 @@ module PgSearch
23
23
  end
24
24
  end
25
25
 
26
- def rank
27
- attributes['pg_search_rank'].to_f
26
+ def pg_search_rank
27
+ read_attribute(:pg_search_rank).to_f
28
28
  end
29
29
 
30
30
  class << self
@@ -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 do |column|
34
+ content_expressions = columns.map { |column|
29
35
  %Q{coalesce(:model_table.#{column}, '')}
30
- end.join(" || ' ' || ")
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
 
@@ -75,7 +75,7 @@ class AddPgSearchDmetaphoneSupportFunctions < ActiveRecord::Migration
75
75
  if ActiveRecord::Base.connection.send(:postgresql_version) < 80400
76
76
  ActiveRecord::Base.connection.execute(<<-SQL)
77
77
  #{uninstall_unnest_sql}
78
- end
78
+ SQL
79
79
  end
80
80
  end
81
81
  end
@@ -1,3 +1,3 @@
1
1
  module PgSearch
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.2"
3
3
  end
@@ -1,294 +1,329 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require "spec_helper"
2
2
 
3
3
  describe PgSearch do
4
4
  context "joining to another table" do
5
- context "with Arel support" do
6
- context "without an :against" do
7
- with_model :AssociatedModel do
8
- table do |t|
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
- with_model :ModelWithoutAgainst do
14
- table do |t|
15
- t.string "title"
16
- t.belongs_to :another_model
17
- end
12
+ with_model :ModelWithoutAgainst do
13
+ table do |t|
14
+ t.string "title"
15
+ t.belongs_to :another_model
16
+ end
18
17
 
19
- model do
20
- include PgSearch
21
- belongs_to :another_model, :class_name => 'AssociatedModel'
18
+ model do
19
+ include PgSearch
20
+ belongs_to :another_model, :class_name => 'AssociatedModel'
22
21
 
23
- pg_search_scope :with_another, :associated_against => {:another_model => :title}
24
- end
22
+ pg_search_scope :with_another, :associated_against => {:another_model => :title}
25
23
  end
24
+ end
26
25
 
27
- it "returns rows that match the query in the columns of the associated model only" do
28
- associated = AssociatedModel.create!(:title => 'abcdef')
29
- included = [
30
- ModelWithoutAgainst.create!(:title => 'abcdef', :another_model => associated),
31
- ModelWithoutAgainst.create!(:title => 'ghijkl', :another_model => associated)
32
- ]
33
- excluded = [
34
- ModelWithoutAgainst.create!(:title => 'abcdef')
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
- results = ModelWithoutAgainst.with_another('abcdef')
38
- results.map(&:title).should =~ included.map(&:title)
39
- results.should_not include(excluded)
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
- context "through a belongs_to association" do
44
- with_model :AssociatedModel do
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 :ModelWithBelongsTo do
138
+ with_model :ModelWithManyAssociations do
51
139
  table do |t|
52
140
  t.string 'title'
53
- t.belongs_to 'another_model'
141
+ t.belongs_to 'model_of_second_type'
54
142
  end
55
143
 
56
144
  model do
57
145
  include PgSearch
58
- belongs_to :another_model, :class_name => 'AssociatedModel'
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, :associated_against => {:another_model => :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
- associated = AssociatedModel.create!(:title => 'abcdef')
155
+ matching_second = SecondAssociatedModel.create!(:title => "foo bar")
156
+ unmatching_second = SecondAssociatedModel.create!(:title => "uiop")
157
+
66
158
  included = [
67
- ModelWithBelongsTo.create!(:title => 'ghijkl', :another_model => associated),
68
- ModelWithBelongsTo.create!(:title => 'abcdef')
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 = ModelWithBelongsTo.with_associated('abcdef')
177
+ results = ModelWithManyAssociations.with_associated('foo bar')
74
178
  results.map(&:title).should =~ included.map(&:title)
75
- results.should_not include(excluded)
179
+ excluded.each { |object| results.should_not include(object) }
76
180
  end
77
181
  end
78
182
 
79
- context "through a has_many association" do
80
- with_model :AssociatedModelWithHasMany do
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 'ModelWithHasMany'
187
+ t.belongs_to 'ModelWithDoubleAssociation'
188
+ t.belongs_to 'ModelWithDoubleAssociation_again'
84
189
  end
190
+ model {}
85
191
  end
86
192
 
87
- with_model :ModelWithHasMany do
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 :other_models, :class_name => 'AssociatedModelWithHasMany', :foreign_key => 'ModelWithHasMany_id'
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 => [:title], :associated_against => {:other_models => :title}
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
- ModelWithHasMany.create!(:title => 'abcdef', :other_models => [
103
- AssociatedModelWithHasMany.create!(:title => 'foo'),
104
- AssociatedModelWithHasMany.create!(:title => 'bar')
210
+ ModelWithDoubleAssociation.create!(:title => 'abcdef', :things => [
211
+ DoublyAssociatedModel.create!(:title => 'foo'),
212
+ DoublyAssociatedModel.create!(:title => 'bar')
105
213
  ]),
106
- ModelWithHasMany.create!(:title => 'ghijkl', :other_models => [
107
- AssociatedModelWithHasMany.create!(:title => 'foo bar'),
108
- AssociatedModelWithHasMany.create!(:title => 'mnopqr')
214
+ ModelWithDoubleAssociation.create!(:title => 'ghijkl', :things => [
215
+ DoublyAssociatedModel.create!(:title => 'foo bar'),
216
+ DoublyAssociatedModel.create!(:title => 'mnopqr')
109
217
  ]),
110
- ModelWithHasMany.create!(:title => 'foo bar')
218
+ ModelWithDoubleAssociation.create!(:title => 'foo bar'),
219
+ ModelWithDoubleAssociation.create!(:title => 'qwerty', :thingamabobs => [
220
+ DoublyAssociatedModel.create!(:title => "foo bar")
221
+ ])
111
222
  ]
112
- excluded = ModelWithHasMany.create!(:title => 'stuvwx', :other_models => [
113
- AssociatedModelWithHasMany.create!(:title => 'abcdef')
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 = ModelWithHasMany.with_associated('foo bar')
232
+ results = ModelWithDoubleAssociation.with_associated('foo bar')
117
233
  results.map(&:title).should =~ included.map(&:title)
118
- results.should_not include(excluded)
234
+ excluded.each { |object| results.should_not include(object) }
119
235
  end
120
236
  end
237
+ end
121
238
 
122
- context "across multiple associations" do
123
- context "on different tables" do
124
- with_model :FirstAssociatedModel do
125
- table do |t|
126
- t.string 'title'
127
- t.belongs_to 'ModelWithManyAssociations'
128
- end
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
- model do
146
- include PgSearch
147
- has_many :models_of_first_type, :class_name => 'FirstAssociatedModel', :foreign_key => 'ModelWithManyAssociations_id'
148
- belongs_to :model_of_second_type, :class_name => 'SecondAssociatedModel'
247
+ with_model :ModelWithAssociation do
248
+ table do |t|
249
+ t.belongs_to 'another_model'
250
+ end
149
251
 
150
- pg_search_scope :with_associated, :against => :title,
151
- :associated_against => {:models_of_first_type => :title, :model_of_second_type => :title}
152
- end
153
- end
252
+ model do
253
+ include PgSearch
254
+ belongs_to :another_model, :class_name => 'AssociatedModel'
154
255
 
155
- it "returns rows that match the query in either its own columns or the columns of the associated model" do
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
- context "on the same table" do
185
- with_model :DoublyAssociatedModel do
186
- table do |t|
187
- t.string 'title'
188
- t.belongs_to 'ModelWithDoubleAssociation'
189
- t.belongs_to 'ModelWithDoubleAssociation_again'
190
- end
191
- model {}
192
- end
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
- with_model :ModelWithDoubleAssociation do
195
- table do |t|
196
- t.string 'title'
197
- end
284
+ results = ModelWithAssociation.with_associated('foo bar')
198
285
 
199
- model do
200
- include PgSearch
201
- has_many :things, :class_name => 'DoublyAssociatedModel', :foreign_key => 'ModelWithDoubleAssociation_id'
202
- has_many :thingamabobs, :class_name => 'DoublyAssociatedModel', :foreign_key => 'ModelWithDoubleAssociation_again_id'
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
- pg_search_scope :with_associated, :against => :title,
205
- :associated_against => {:things => :title, :thingamabobs => :title}
206
- end
207
- end
291
+ end
208
292
 
209
- it "returns rows that match the query in either its own columns or the columns of the associated model" do
210
- included = [
211
- ModelWithDoubleAssociation.create!(:title => 'abcdef', :things => [
212
- DoublyAssociatedModel.create!(:title => 'foo'),
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
- context "against multiple attributes on one association" do
241
- with_model :AssociatedModel do
242
- table do |t|
243
- t.string 'title'
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
- with_model :ModelWithAssociation do
249
- table do |t|
250
- t.belongs_to 'another_model'
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
- pg_search_scope :with_associated, :associated_against => {:another_model => [:title, :author]}
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 File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require "spec_helper"
2
2
 
3
3
  describe PgSearch::Document do
4
4
  with_table "pg_search_documents", {}, &DOCUMENTS_SCHEMA
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
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
 
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require "spec_helper"
2
2
 
3
3
  describe PgSearch::Multisearchable do
4
4
  with_table "pg_search_documents", {}, &DOCUMENTS_SCHEMA
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
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].rank.should > results[1].rank
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.rank.should be > results.last.rank
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.rank.should == results.last.rank
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
- end
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].rank.should > results[1].rank
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].rank.should > results[1].rank
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].rank.should > results[1].rank
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.rank.should == 10
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.rank
690
- multiplied_rank = ModelWithPgSearch.search_content_with_importance_as_rank_multiplier("foo").first.rank
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.rank.should_not be_nil
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.1
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-02-13 00:00:00.000000000 Z
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: &70164267785940 !ruby/object:Gem::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: *70164267785940
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: &70164267784280 !ruby/object:Gem::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: *70164267784280
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.10
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