acts_as_indexed 0.6.6 → 0.6.7

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 CHANGED
@@ -1,3 +1,8 @@
1
+ ===0.6.7 [7th February 2011]
2
+ - find_by_index and paginate_search are no longer deprecated.
3
+ - Improved documentation
4
+ - Storage is now it's own class to allow future development of locking and pluggable backends.
5
+
1
6
  ===0.6.6 [31st August 2010]
2
7
  - Now Heroku compatible out of the box, index is created in tmp when root dir is non-writable. [parndt - Philip Arndt - Great suggestion]
3
8
  - Fixed a require path issue on 1.9.2.
data/README.rdoc CHANGED
@@ -35,7 +35,7 @@ Gemfile (Rails 3.x.x).
35
35
 
36
36
  ==== No Git?
37
37
 
38
- If you don't have git installed, you can download the plugin from the GitHub
38
+ If you don't have git installed, but still want the plugin, you can download the plugin from the GitHub
39
39
  page (http://github.com/dougal/acts_as_indexed) and unpack it into the
40
40
  <tt>vendor/plugins</tt> directory of your rails app.
41
41
 
@@ -85,8 +85,26 @@ or true if it is visible, you can filter the index by doing:
85
85
 
86
86
  === Searching
87
87
 
88
- To search, call the +with_query+ named scope on your model, passing a query as
89
- an argument.
88
+ ==== With Relevance
89
+
90
+ To search with the most relevant matches appearing first, call the
91
+ +find_with_index+ method on your model, passing a query as the first argument.
92
+ The optional +ids_only+ parameter, when set to true, will return only the IDs
93
+ of any matching records.
94
+
95
+ # Returns array of Post objects ordered by relevance.
96
+ my_search_results = Post.find_with_index('my search query')
97
+
98
+ # Pass any of the ActiveRecord find options to the search.
99
+ my_search_results = Post.find_with_index('my search query',{:limit => 10}) # return the first 10 matches.
100
+
101
+ # Returns array of IDs ordered by relevance.
102
+ my_search_results = Post.find_with_index('my search query',{},{:ids_only => true}) # => [12,19,33...
103
+
104
+ ==== Without Relevance (Scope)
105
+
106
+ If the relevance of the results is not important, call the +with_query+ named
107
+ scope on your model, passing a query as an argument.
90
108
 
91
109
  # Returns array of Post objects.
92
110
  my_search_results = Post.with_query('my search query')
@@ -98,15 +116,23 @@ an argument.
98
116
 
99
117
  The following query operators are supported:
100
118
 
101
- * AND:: This is the default option. 'cat dog' will find records matching 'cat' AND 'dog'.
102
- * NOT:: 'cat -dog' will find records matching 'cat' AND NOT 'dog'
103
- * INCLUDE:: 'cat +me' will find records matching 'cat' and 'me', even if 'me' is smaller than the +min_word_size+
104
- * "":: Quoted terms are matched as phrases. '"cat dog"' will find records matching the whole phrase. Quoted terms can be preceded by the NOT operator; 'cat -"big dog"' etc. Quoted terms can include words shorter than the +min_word_size+.
105
- * ^:: Terms that begin with ^ will match records that contain a word starting with the term. '^cat' will find matches containing 'cat', 'catapult', 'caterpillar' etc.
106
- * ^"":: A quoted term that begins with ^ matches any phrase that begin with this phrase. '^"cat d"' will find records matching the whole phrases "cat dog" and "cat dinner". This type of search is useful for autocomplete inputs.
119
+ * AND :: This is the default option. 'cat dog' will find records matching 'cat' AND 'dog'.
120
+ * NOT :: 'cat -dog' will find records matching 'cat' AND NOT 'dog'
121
+ * INCLUDE :: 'cat +me' will find records matching 'cat' and 'me', even if 'me' is smaller than the +min_word_size+
122
+ * "" :: Quoted terms are matched as phrases. '"cat dog"' will find records matching the whole phrase. Quoted terms can be preceded by the NOT operator; 'cat -"big dog"' etc. Quoted terms can include words shorter than the +min_word_size+.
123
+ * ^ :: Terms that begin with ^ will match records that contain a word starting with the term. '^cat' will find matches containing 'cat', 'catapult', 'caterpillar' etc.
124
+ * ^"" :: A quoted term that begins with ^ matches any phrase that begin with this phrase. '^"cat d"' will find records matching the whole phrases "cat dog" and "cat dinner". This type of search is useful for autocomplete inputs.
107
125
 
108
126
  === Pagination
109
127
 
128
+ ==== With Relevance
129
+
130
+ Pagination is supported via the +paginate_search+ method whose first argument is the search query, followed all the standard will_paginate arguments.
131
+
132
+ @images = Image.paginate_search('girl', :page => 1, :per_page => 5)
133
+
134
+ ==== Without Relevance (Scope)
135
+
110
136
  Since +with_query+ is a named scope, WillPaginate can be used in the normal
111
137
  fashion.
112
138
 
@@ -118,7 +144,7 @@ A config block can be provided in your environment files or initializers.
118
144
  Example showing defaults:
119
145
 
120
146
  ActsAsIndexed.configure do |config|
121
- config.index_file = [RAILS_ROOT,'index']
147
+ config.index_file = [Rails.root.to_s,'index']
122
148
  config.index_file_depth = 3
123
149
  config.min_word_size = 3
124
150
  end
@@ -146,10 +172,15 @@ online[http://rdoc.info/projects/dougal/acts_as_indexed/].
146
172
  All of the above are most welcome. mailto:dougal.s@gmail.com
147
173
 
148
174
 
149
- == Credits
150
-
151
- Douglas F Shearer - http://douglasfshearer.com
175
+ == Contributors
152
176
 
177
+ * Douglas F Shearer - http://douglasfshearer.com
178
+ * Thomas Pomfret
179
+ * Philip Arndt
180
+ * Fernanda Lopes
181
+ * Alex Coles
182
+ * Myles Eftos
183
+ * Edward Anderson
153
184
 
154
185
  == Future Releases
155
186
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.6
1
+ 0.6.7
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{acts_as_indexed}
8
- s.version = "0.6.6"
8
+ s.version = "0.6.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Douglas F Shearer"]
12
- s.date = %q{2010-08-31}
12
+ s.date = %q{2011-02-07}
13
13
  s.description = %q{Acts As Indexed is a plugin which provides a pain-free way to add fulltext search to your Ruby on Rails app}
14
14
  s.email = %q{dougal.s@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
29
29
  "lib/acts_as_indexed/configuration.rb",
30
30
  "lib/acts_as_indexed/search_atom.rb",
31
31
  "lib/acts_as_indexed/search_index.rb",
32
+ "lib/acts_as_indexed/storage.rb",
32
33
  "lib/will_paginate_search.rb",
33
34
  "rails/init.rb",
34
35
  "test/abstract_unit.rb",
@@ -58,6 +58,12 @@ module ActsAsIndexed #:nodoc:
58
58
  })
59
59
  end
60
60
 
61
+ # Creates a new SearchAtom with records in other removed from self.
62
+ def -(other)
63
+ records = @records.clone.reject { |name, records| other.records.include?(name) }
64
+ SearchAtom.new(records)
65
+ end
66
+
61
67
  # Returns at atom containing the records and positions of +self+ preceded by +former+
62
68
  # "former latter" or "big dog" where "big" is the former and "dog" is the latter.
63
69
  def preceded_by(former)
@@ -12,42 +12,37 @@ module ActsAsIndexed #:nodoc:
12
12
  # min_word_size:: Smallest query term that will be run through search.
13
13
  # if_proc:: A Proc. If the proc is true, the index gets added, if false if doesn't
14
14
  def initialize(root, index_depth, fields, min_word_size, if_proc=Proc.new{true})
15
- @root = Pathname.new(root.to_s)
15
+ @storage = Storage.new(Pathname.new(root.to_s), index_depth)
16
16
  @fields = fields
17
- @index_depth = index_depth
18
17
  @atoms = {}
19
18
  @min_word_size = min_word_size
20
- @records_size = exists? ? load_record_size : 0
19
+ @records_size = @storage.record_count
21
20
  @if_proc = if_proc
22
21
  end
23
22
 
24
23
  # Adds +record+ to the index.
25
- def add_record(record, no_save=false)
24
+ def add_record(record)
26
25
  return unless @if_proc.call(record)
26
+
27
27
  condensed_record = condense_record(record)
28
- load_atoms(condensed_record)
29
- add_occurences(condensed_record,record.id)
30
- @records_size += 1
31
- self.save unless no_save
28
+ atoms = add_occurences(condensed_record,record.id)
29
+
30
+ @storage.add(atoms)
32
31
  end
33
32
 
34
33
  # Adds multiple records to the index. Accepts an array of +records+.
35
34
  def add_records(records)
36
35
  records.each do |record|
37
- add_record(record, true)
36
+ add_record(record)
38
37
  end
39
- self.save
40
38
  end
41
39
 
42
40
  # Removes +record+ from the index.
43
41
  def remove_record(record)
44
- atoms = condense_record(record)
45
- load_atoms(atoms)
46
- atoms.each do |a|
47
- @atoms[a].remove_record(record.id) if @atoms.has_key?(a)
48
- @records_size -= 1
49
- end
50
- self.save
42
+ condensed_record = condense_record(record)
43
+ atoms = add_occurences(condensed_record,record.id)
44
+
45
+ @storage.remove(atoms)
51
46
  end
52
47
 
53
48
  def update_record(record_new, record_old)
@@ -55,33 +50,11 @@ module ActsAsIndexed #:nodoc:
55
50
  add_record(record_new)
56
51
  end
57
52
 
58
- # Saves the current index partitions to the filesystem.
59
- def save
60
- prepare
61
- atoms_sorted = {}
62
- @atoms.each do |atom_name, records|
63
- (atoms_sorted[encoded_prefix(atom_name)] ||= {})[atom_name] = records
64
- end
65
- atoms_sorted.each do |e_p, atoms|
66
- @root.join(e_p.to_s).open("w+") do |f|
67
- Marshal.dump(atoms,f)
68
- end
69
- end
70
- save_record_size
71
- end
72
-
73
- # Deletes the current model's index from the filesystem.
74
- #--
75
- # TODO: Write a public method that will delete all indexes.
76
- def destroy
77
- @root.delete
78
- end
79
-
80
53
  # Returns an array of IDs for records matching +query+.
81
54
  def search(query)
82
55
  return [] if query.nil?
83
- load_options = { :start => true } if query[/\^/]
84
- load_atoms(cleanup_atoms(query), load_options || {})
56
+
57
+ @atoms = @storage.fetch(cleanup_atoms(query), query[/\^/])
85
58
  queries = parse_query(query.dup)
86
59
  positive = run_queries(queries[:positive])
87
60
  positive_quoted = run_quoted_queries(queries[:positive_quoted])
@@ -95,15 +68,15 @@ module ActsAsIndexed #:nodoc:
95
68
  if queries[:start_quoted].any?
96
69
  results = merge_query_results(results, start_quoted)
97
70
  end
98
-
71
+
99
72
  if queries[:starts_with].any?
100
73
  results = merge_query_results(results, starts_with)
101
74
  end
102
-
75
+
103
76
  if queries[:positive_quoted].any?
104
77
  results = merge_query_results(results, positive_quoted)
105
78
  end
106
-
79
+
107
80
  if queries[:positive].any?
108
81
  results = merge_query_results(results, positive)
109
82
  end
@@ -112,100 +85,32 @@ module ActsAsIndexed #:nodoc:
112
85
  results.delete_if { |r_id, w| negative_results.include?(r_id) }
113
86
  results
114
87
  end
115
-
88
+
89
+ private
90
+
116
91
  def merge_query_results(results1, results2)
117
92
  # Return the other if one is empty.
118
93
  return results1 if results2.empty?
119
94
  return results2 if results1.empty?
120
-
95
+
121
96
  # Delete any records from results 1 that are not in results 2.
122
97
  r1 = results1.delete_if{ |r_id,w| !results2.include?(r_id) }
123
-
124
-
98
+
99
+
125
100
  # Delete any records from results 2 that are not in results 1.
126
101
  r2 = results2.delete_if{ |r_id,w| !results1.include?(r_id) }
127
-
102
+
128
103
  # Merge the results by adding their respective scores.
129
104
  r1.merge(r2) { |r_id,old_val,new_val| old_val + new_val}
130
105
  end
131
-
132
- # Returns true if the index root exists on the FS.
133
- #--
134
- # TODO: Make a private method called 'root_exists?' which checks for the root directory.
135
- def exists?
136
- @root.join('size').exist?
137
- end
138
-
139
- private
140
-
141
- # Gets the size file from the index.
142
- def load_record_size
143
- @root.join('size').open do |f|
144
- Marshal.load(f)
145
- end
146
- end
147
-
148
- # Saves the size to the size file.
149
- def save_record_size
150
- @root.join('size').open('w+') do |f|
151
- Marshal.dump(@records_size,f)
152
- end
153
- end
154
-
155
- # Returns true if the given atom is present.
156
- def include_atom?(atom)
157
- if atom.is_a? Regexp
158
- @atoms.keys.grep(atom).any?
159
- else
160
- @atoms.has_key?(atom)
161
- end
162
- end
163
-
164
- # Returns true if all the given atoms are present.
165
- def include_atoms?(atoms_arr)
166
- atoms_arr.each do |a|
167
- return false unless include_atom?(a)
168
- end
169
- true
170
- end
171
-
172
- # Returns true if the given record is present.
173
- def include_record?(record_id)
174
- @atoms.each do |atomname, atom|
175
- return true if atom.include_record?(record_id)
176
- end
177
- end
178
-
179
- def add_atom(atom)
180
- @atoms[atom] = SearchAtom.new unless include_atom?(atom)
181
- end
182
106
 
183
107
  def add_occurences(condensed_record,record_id)
184
- condensed_record.each_with_index do |atom, i|
185
- add_atom(atom)
186
- @atoms[atom].add_position(record_id, i)
187
- end
188
- end
189
-
190
- def encoded_prefix(atom)
191
- prefix = atom[0,@index_depth]
192
- unless (@prefix_cache ||= {}).has_key?(prefix)
193
- if atom.length > 1
194
- @prefix_cache[prefix] = prefix.split(//).map{|c| encode_character(c)}.join('_')
195
- else
196
- @prefix_cache[prefix] = encode_character(atom)
197
- end
198
- end
199
- @prefix_cache[prefix]
200
- end
201
-
202
- # Allows compatibility with 1.8.6 which has no ord method.
203
- def encode_character(char)
204
- if @@has_ord ||= char.respond_to?(:ord)
205
- char.ord.to_s
206
- else
207
- char[0]
108
+ atoms = {}
109
+ condensed_record.each_with_index do |atom_name, i|
110
+ atoms[atom_name] = SearchAtom.new unless atoms.include?(atom_name)
111
+ atoms[atom_name].add_position(record_id, i)
208
112
  end
113
+ atoms
209
114
  end
210
115
 
211
116
  def parse_query(s)
@@ -254,14 +159,15 @@ module ActsAsIndexed #:nodoc:
254
159
  :positive_quoted => positive_quoted,
255
160
  :starts_with => starts_with,
256
161
  :negative => negative,
257
- :positive => positive }
162
+ :positive => positive
163
+ }
258
164
  end
259
-
165
+
260
166
  def run_queries(atoms, starts_with=false)
261
167
  results = {}
262
168
  atoms.each do |atom|
263
169
  interim_results = {}
264
-
170
+
265
171
  # If these atoms are to be run as 'starts with', make them a Regexp
266
172
  # with a carat.
267
173
  atom = /^#{atom}/ if starts_with
@@ -269,37 +175,37 @@ module ActsAsIndexed #:nodoc:
269
175
  # Get the resulting matches, and break if none exist.
270
176
  matches = get_atom_results(@atoms.keys, atom)
271
177
  break if matches.nil?
272
-
178
+
273
179
  # Grab the record IDs and weightings.
274
180
  interim_results = matches.weightings(@records_size)
275
-
181
+
276
182
  # Merge them with the results obtained already, if any.
277
183
  results = results.empty? ? interim_results : merge_query_results(results, interim_results)
278
-
184
+
279
185
  break if results.empty?
280
-
186
+
281
187
  end
282
188
  results
283
189
  end
284
-
190
+
285
191
  def run_quoted_queries(quoted_atoms, starts_with=false)
286
192
  results = {}
287
193
  quoted_atoms.each do |quoted_atom|
288
194
  interim_results = {}
289
-
195
+
290
196
  break if quoted_atom.empty?
291
-
197
+
292
198
  # If these atoms are to be run as 'starts with', make the final atom a
293
199
  # Regexp with a line-start anchor.
294
200
  quoted_atom[-1] = /^#{quoted_atom.last}/ if starts_with
295
-
201
+
296
202
  # Little bit of memoization.
297
203
  atoms_keys = @atoms.keys
298
-
204
+
299
205
  # Get the matches for the first atom.
300
206
  matches = get_atom_results(atoms_keys, quoted_atom.first)
301
207
  break if matches.nil?
302
-
208
+
303
209
  # Check the index contains all the required atoms.
304
210
  # for each of the others
305
211
  # return atom containing records + positions where current atom is preceded by following atom.
@@ -320,9 +226,9 @@ module ActsAsIndexed #:nodoc:
320
226
 
321
227
  # Merge them with the results obtained already, if any.
322
228
  results = results.empty? ? interim_results : merge_query_results(results, interim_results)
323
-
229
+
324
230
  break if results.empty?
325
-
231
+
326
232
  end
327
233
  results
328
234
  end
@@ -340,26 +246,6 @@ module ActsAsIndexed #:nodoc:
340
246
  end
341
247
  end
342
248
 
343
- def load_atoms(atoms, options={})
344
- # Remove duplicate atoms.
345
- # Remove atoms already in index.
346
- # Calculate prefixes.
347
- # Remove duplicate prefixes.
348
- atoms.uniq.reject{|a| include_atom?(a)}.collect{|a| encoded_prefix(a)}.uniq.each do |name|
349
- pattern = @root.join(name.to_s).to_s
350
- pattern += '*' if options[:start]
351
- Pathname.glob(pattern).each do |atom_file|
352
- atom_file.open do |f|
353
- @atoms.merge!(Marshal.load(f))
354
- end
355
- end
356
- end
357
- end
358
-
359
- def prepare
360
- # Makes the RAILS_ROOT/index/ENVIRONMENT/CLASS directories
361
- @root.mkpath
362
- end
363
249
 
364
250
  def cleanup_atoms(s, limit_size=false, min_size = @min_word_size || 3)
365
251
  atoms = s.downcase.gsub(/\W/,' ').squeeze(' ').split
@@ -0,0 +1,136 @@
1
+ # ActsAsIndexed
2
+ # Copyright (c) 2007 - 2010 Douglas F Shearer.
3
+ # http://douglasfshearer.com
4
+ # Distributed under the MIT license as included with this plugin.
5
+
6
+ module ActsAsIndexed #:nodoc:
7
+ class Storage
8
+
9
+ def initialize(path, prefix_size)
10
+ @path = path
11
+ @prefix_size = prefix_size
12
+ prepare
13
+ end
14
+
15
+ # Takes a hash of atoms and adds these to storage.
16
+ def add(atoms)
17
+ operate(:+, atoms)
18
+
19
+ update_record_count(1)
20
+
21
+ end
22
+
23
+ # Takes a hash of atoms and removes these from storage.
24
+ def remove(atoms)
25
+ operate(:-, atoms)
26
+
27
+ update_record_count(-1)
28
+ end
29
+
30
+ # Takes a string array of atoms names
31
+ # return a hash of the relevant atoms.
32
+ def fetch(atom_names, start=false)
33
+ atoms = {}
34
+
35
+ atom_names.uniq.collect{|a| encoded_prefix(a) }.uniq.each do |prefix|
36
+ pattern = @path.join(prefix.to_s).to_s
37
+ pattern += '*' if start
38
+
39
+ Pathname.glob(pattern).each do |atom_file|
40
+ atom_file.open do |f|
41
+ atoms.merge!(Marshal.load(f))
42
+ end
43
+ end # Pathname.glob
44
+
45
+ end # atom_names.uniq
46
+ atoms
47
+ end # fetch.
48
+
49
+ # Returns the number of records currently stored in this index.
50
+ def record_count
51
+ # TODO: Record count is currently a marshaled integer. Why not store as
52
+ # string integer? Breaks compatibility, so leave until other changes
53
+ # need to be made to the index.
54
+
55
+ @path.join('size').open do |f|
56
+ Marshal.load(f)
57
+ end
58
+
59
+ # This is a bit horrible.
60
+ rescue Errno::ENOENT
61
+ 0
62
+ rescue EOFError
63
+ 0
64
+ end
65
+
66
+ private
67
+
68
+ # Takes atoms and adds or removes them from the index depending on the
69
+ # passed operation.
70
+ def operate(operation, atoms)
71
+ # ActiveSupport always available?
72
+ atoms_sorted = ActiveSupport::OrderedHash.new
73
+
74
+ # Sort the atoms into the appropriate shards for writing to individual
75
+ # files.
76
+ atoms.each do |atom_name, records|
77
+ (atoms_sorted[encoded_prefix(atom_name)] ||= {})[atom_name] = records
78
+ end
79
+
80
+ atoms_sorted.each do |e_p, atoms|
81
+ path = @path.join(e_p.to_s)
82
+
83
+ if path.exist?
84
+ from_file = path.open do |f|
85
+ Marshal.load(f)
86
+ end
87
+ else
88
+ from_file = {}
89
+ end
90
+
91
+ atoms = from_file.merge(atoms){ |k,o,n| o.send(operation, n) }
92
+
93
+ path.open("w+") do |f|
94
+ Marshal.dump(atoms,f)
95
+ end
96
+ end
97
+ end
98
+
99
+ def update_record_count(delta)
100
+ new_count = self.record_count + delta
101
+ new_count = 0 if new_count < 0
102
+
103
+ @path.join('size').open('w+') do |f|
104
+ Marshal.dump(new_count,f)
105
+ end
106
+ end
107
+
108
+ def prepare
109
+ @path.mkpath unless @path.exist?
110
+ end
111
+
112
+ def encoded_prefix(atom)
113
+ prefix = atom[0, @prefix_size]
114
+
115
+ unless (@prefix_cache ||= {}).has_key?(prefix)
116
+ if atom.length > 1
117
+ @prefix_cache[prefix] = prefix.split(//).map{|c| encode_character(c)}.join('_')
118
+ else
119
+ @prefix_cache[prefix] = encode_character(atom)
120
+ end
121
+ end
122
+
123
+ @prefix_cache[prefix]
124
+ end
125
+
126
+ # Allows compatibility with 1.8.6 which has no ord method.
127
+ def encode_character(char)
128
+ if @@has_ord ||= char.respond_to?(:ord)
129
+ char.ord.to_s
130
+ else
131
+ char[0]
132
+ end
133
+ end
134
+
135
+ end
136
+ end
@@ -8,6 +8,7 @@ require 'active_record'
8
8
  require 'acts_as_indexed/configuration'
9
9
  require 'acts_as_indexed/search_index'
10
10
  require 'acts_as_indexed/search_atom'
11
+ require 'acts_as_indexed/storage'
11
12
 
12
13
  module ActsAsIndexed #:nodoc:
13
14
 
@@ -102,8 +103,6 @@ module ActsAsIndexed #:nodoc:
102
103
 
103
104
  def index_remove(record)
104
105
  index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
105
- # record won't be in index if it doesn't exist. Just return true.
106
- return unless index.exists?
107
106
  index.remove_record(record)
108
107
  @query_cache = {}
109
108
  end
@@ -192,12 +191,10 @@ module ActsAsIndexed #:nodoc:
192
191
  # Adds model class singleton methods.
193
192
  module SingletonMethods
194
193
 
195
- # DEPRECATED. Use +with_query+ scope instead.
196
194
  # Finds instances matching the terms passed in +query+.
197
195
  #
198
196
  # See ActsAsIndexed::ClassMethods#search_index.
199
197
  def find_with_index(query='', find_options = {}, options = {})
200
- warn "[DEPRECATION] `find_with_index` is deprecated and will be removed in a later release. Use `with_query(query)` instead."
201
198
  search_index(query, find_options, options)
202
199
  end
203
200
 
@@ -8,9 +8,7 @@ module ActsAsIndexed
8
8
 
9
9
  module Search
10
10
 
11
- # DEPRECATED. Use chained pagination instead.
12
11
  def paginate_search(query, options)
13
- warn "[DEPRECATION] `paginate_search` is deprecated and will be removed in a later release. Use `with_query(query).paginate()` instead."
14
12
  page, per_page, total_entries = wp_parse_options(options)
15
13
 
16
14
  total_entries ||= find_with_index(query,{},{:ids_only => true}).size
@@ -14,164 +14,136 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
14
14
  assert_equal [], Post.find_with_index('badger')
15
15
  post = Post.new(:title => 'badger', :body => 'Thousands of them!')
16
16
  assert post.save
17
- assert_equal original_post_count+1, Post.count
18
- assert_equal [post.id], Post.find_with_index('badger',{},{:ids_only => true})
17
+ assert_equal original_post_count + 1, Post.count
18
+ assert_equal [post.id], result_ids('badger')
19
19
  end
20
-
20
+
21
21
  def test_removes_from_index
22
22
  original_post_count = Post.count
23
- assert_equal [posts(:wikipedia_article_4).id], Post.find_with_index('album',{},{:ids_only => true})
23
+ assert_equal [posts(:wikipedia_article_4).id], result_ids('album')
24
24
  assert Post.find(posts(:wikipedia_article_4).id).destroy
25
- assert_equal [], Post.find_with_index('album',{},{:ids_only => true})
26
- assert_equal original_post_count-1, Post.count
25
+ assert_equal [], result_ids('album')
26
+ assert_equal original_post_count - 1, Post.count
27
27
  end
28
-
28
+
29
29
  def test_search_returns_posts
30
- Post.find_with_index('album').each do |p|
31
- assert_equal Post, p.class
32
- end
33
- end
34
-
35
- def test_scoped_search_returns_posts
36
30
  Post.with_query('album').each do |p|
37
31
  assert_equal Post, p.class
38
32
  end
39
33
  end
40
-
34
+
41
35
  def test_search_returns_post_ids
42
- Post.find_with_index('album',{},{:ids_only => true}).each do |pid|
36
+ result_ids('album').each do |pid|
43
37
  assert p = Post.find(pid)
44
38
  assert_equal Post, p.class
45
39
  end
46
40
  end
47
-
41
+
48
42
  # After a portion of a record has been removed
49
43
  # the portion removes should no longer be in the index.
50
44
  def test_updates_index
51
45
  p = Post.create(:title => 'A special title', :body => 'foo bar bla bla bla')
52
- assert Post.find_with_index('title',{},{:ids_only => true}).include?(p.id)
46
+ assert result_ids('title').include?(p.id)
53
47
  p.update_attributes(:title => 'No longer special')
54
- assert !Post.find_with_index('title',{},{:ids_only => true}).include?(p.id)
48
+ assert !result_ids('title').include?(p.id)
55
49
  end
56
-
50
+
57
51
  def test_simple_queries
58
- assert_equal [], Post.find_with_index(nil)
59
- assert_equal [], Post.find_with_index('')
60
- assert_equal [5, 6], Post.find_with_index('ship',{},{:ids_only => true}).sort
61
- assert_equal [6], Post.find_with_index('foo',{},{:ids_only => true})
62
- assert_equal [6], Post.find_with_index('foo ship',{},{:ids_only => true})
63
- assert_equal [6], Post.find_with_index('ship foo',{},{:ids_only => true})
64
- end
65
-
66
- def test_scoped_simple_queries
67
- assert_equal [], Post.find_with_index(nil)
68
- assert_equal [], Post.with_query('')
69
- assert_equal [5, 6], Post.with_query('ship').map{|r| r.id}.sort
70
- assert_equal [6], Post.with_query('foo').map{|r| r.id}
71
- assert_equal [6], Post.with_query('foo ship').map{|r| r.id}
72
- assert_equal [6], Post.with_query('ship foo').map{|r| r.id}
73
- end
74
-
52
+ assert_equal_array_contents [], result_ids(nil)
53
+ assert_equal_array_contents [], result_ids('')
54
+ assert_equal_array_contents [5, 6], result_ids('ship')
55
+ assert_equal_array_contents [6], result_ids('foo')
56
+ assert_equal_array_contents [6], result_ids('foo ship')
57
+ assert_equal_array_contents [6], result_ids('ship foo')
58
+ end
59
+
75
60
  def test_negative_queries
76
- assert_equal [5, 6], Post.find_with_index('crane',{},{:ids_only => true}).sort
77
- assert_equal [5], Post.find_with_index('crane -foo',{},{:ids_only => true})
78
- assert_equal [5], Post.find_with_index('-foo crane',{},{:ids_only => true})
79
- assert_equal [], Post.find_with_index('-foo') #Edgecase
80
- end
81
-
82
- def test_scoped_negative_queries
83
- assert_equal [5, 6], Post.with_query('crane').map{|r| r.id}.sort
84
- assert_equal [5], Post.with_query('crane -foo').map{|r| r.id}
85
- assert_equal [5], Post.with_query('-foo crane').map{|r| r.id}
86
- assert_equal [], Post.with_query('-foo') #Edgecase
61
+ assert_equal_array_contents [5, 6], result_ids('crane')
62
+ assert_equal_array_contents [5], result_ids('crane -foo')
63
+ assert_equal_array_contents [5], result_ids('-foo crane')
64
+ assert_equal_array_contents [], result_ids('-foo') #Edgecase
87
65
  end
88
-
66
+
89
67
  def test_quoted_queries
90
- assert_equal [5], Post.find_with_index('"crane ship"',{},{:ids_only => true})
91
- assert_equal [6], Post.find_with_index('"crane big"',{},{:ids_only => true})
92
- assert_equal [], Post.find_with_index('foo "crane ship"')
93
- assert_equal [], Post.find_with_index('"crane badger"')
68
+ assert_equal_array_contents [5], result_ids('"crane ship"')
69
+ assert_equal_array_contents [6], result_ids('"crane big"')
70
+ assert_equal_array_contents [], result_ids('foo "crane ship"')
71
+ assert_equal_array_contents [], result_ids('"crane badger"')
94
72
  end
95
-
96
- def test_scoped_quoted_queries
97
- assert_equal [5], Post.with_query('"crane ship"').map{|r| r.id}
98
- assert_equal [6], Post.with_query('"crane big"').map{|r| r.id}
99
- assert_equal [], Post.with_query('foo "crane ship"')
100
- assert_equal [], Post.with_query('"crane badger"')
101
- end
102
-
73
+
103
74
  def test_negative_quoted_queries
104
- assert_equal [6], Post.find_with_index('crane -"crane ship"',{},{:ids_only => true})
105
- assert_equal [], Post.find_with_index('-"crane big"',{},{:ids_only => true}) # Edgecase
75
+ assert_equal_array_contents [6], result_ids('crane -"crane ship"')
76
+ assert_equal_array_contents [], result_ids('-"crane big"') # Edgecase
106
77
  end
107
-
78
+
108
79
  def test_scoped_negative_quoted_queries
109
- assert_equal [6], Post.with_query('crane -"crane ship"').map{|r| r.id}
110
- assert_equal [], Post.with_query('-"crane big"') # Edgecase
80
+ assert_equal_array_contents [6], result_ids('crane -"crane ship"')
81
+ assert_equal_array_contents [], result_ids('-"crane big"') # Edgecase
111
82
  end
112
-
83
+
113
84
  def test_start_queries
114
- assert_equal [6,5], Post.find_with_index('ship ^crane',{},{:ids_only => true})
115
- assert_equal [6,5], Post.find_with_index('^crane ship',{},{:ids_only => true})
116
- assert_equal [6,5], Post.find_with_index('^ship ^crane',{},{:ids_only => true})
117
- assert_equal [6,5], Post.find_with_index('^crane ^ship',{},{:ids_only => true})
118
- assert_equal [6,5], Post.find_with_index('^ship crane',{},{:ids_only => true})
119
- assert_equal [6,5], Post.find_with_index('crane ^ship',{},{:ids_only => true})
120
- assert_equal [6,5], Post.find_with_index('^crane',{},{:ids_only => true})
121
- assert_equal [6,5], Post.find_with_index('^cran',{},{:ids_only => true})
122
- assert_equal [6,5], Post.find_with_index('^cra',{},{:ids_only => true})
123
- assert_equal [6,5,4], Post.find_with_index('^cr',{},{:ids_only => true})
124
- assert_equal [6,5,4,3,2,1], Post.find_with_index('^c',{},{:ids_only => true})
125
- assert_equal [], Post.find_with_index('^notthere',{},{:ids_only => true})
126
- end
127
-
85
+ assert_equal_array_contents [6,5], result_ids('ship ^crane')
86
+ assert_equal_array_contents [6,5], result_ids('^crane ship')
87
+ assert_equal_array_contents [6,5], result_ids('^ship ^crane')
88
+ assert_equal_array_contents [6,5], result_ids('^crane ^ship')
89
+ assert_equal_array_contents [6,5], result_ids('^ship crane')
90
+ assert_equal_array_contents [6,5], result_ids('crane ^ship')
91
+ assert_equal_array_contents [6,5], result_ids('^crane')
92
+ assert_equal_array_contents [6,5], result_ids('^cran')
93
+ assert_equal_array_contents [6,5], result_ids('^cra')
94
+ assert_equal_array_contents [6,5,4], result_ids('^cr')
95
+ assert_equal_array_contents [6,5,4,3,2,1], result_ids('^c')
96
+ assert_equal_array_contents [], result_ids('^notthere')
97
+ end
98
+
128
99
  def test_start_quoted_queries
129
- assert_equal [6,5], Post.find_with_index('^"crane" ship',{},{:ids_only => true})
130
- assert_equal [6,5], Post.find_with_index('ship ^"crane"',{},{:ids_only => true})
131
- assert_equal [5], Post.find_with_index('^"crane ship"',{},{:ids_only => true})
132
- assert_equal [5], Post.find_with_index('^"crane shi"',{},{:ids_only => true})
133
- assert_equal [5], Post.find_with_index('^"crane sh"',{},{:ids_only => true})
134
- assert_equal [5], Post.find_with_index('^"crane s"',{},{:ids_only => true})
135
- assert_equal [6,5], Post.find_with_index('^"crane "',{},{:ids_only => true})
136
- assert_equal [6,5], Post.find_with_index('^"crane"',{},{:ids_only => true})
137
- assert_equal [6,5], Post.find_with_index('^"cran"',{},{:ids_only => true})
138
- assert_equal [6,5], Post.find_with_index('^"cra"',{},{:ids_only => true})
139
- assert_equal [6,5,4], Post.find_with_index('^"cr"',{},{:ids_only => true})
140
- assert_equal [6,5,4,3,2,1], Post.find_with_index('^"c"',{},{:ids_only => true})
141
- end
142
-
100
+ assert_equal_array_contents [6,5], result_ids('^"crane" ship')
101
+ assert_equal_array_contents [6,5], result_ids('ship ^"crane"')
102
+ assert_equal_array_contents [5], result_ids('^"crane ship"')
103
+ assert_equal_array_contents [5], result_ids('^"crane shi"')
104
+ assert_equal_array_contents [5], result_ids('^"crane sh"')
105
+ assert_equal_array_contents [5], result_ids('^"crane s"')
106
+ assert_equal_array_contents [6,5], result_ids('^"crane "')
107
+ assert_equal_array_contents [6,5], result_ids('^"crane"')
108
+ assert_equal_array_contents [6,5], result_ids('^"cran"')
109
+ assert_equal_array_contents [6,5], result_ids('^"cra"')
110
+ assert_equal_array_contents [6,5,4], result_ids('^"cr"')
111
+ assert_equal_array_contents [6,5,4,3,2,1], result_ids('^"c"')
112
+ end
113
+
143
114
  def test_find_options
144
115
  all_results = Post.find_with_index('crane',{},{:ids_only => true})
116
+ assert_equal 2, all_results.size
117
+
145
118
  first_result = Post.find_with_index('crane',{:limit => 1})
146
-
147
119
  assert_equal 1, first_result.size
148
120
  assert_equal all_results.first, first_result.first.id
149
-
121
+
150
122
  second_result = Post.find_with_index('crane',{:limit => 1, :offset => 1})
151
123
  assert_equal 1, second_result.size
152
124
  assert_equal all_results[1], second_result.first.id
153
125
  end
154
-
126
+
155
127
  # When a atom already in a record is duplicated, it removes
156
128
  # all records with that same atom from the index.
157
129
  def test_update_record_bug
158
130
  p = Post.find(6)
159
131
  assert p.update_attributes(:body => p.body + ' crane')
160
- assert_equal 2, Post.find_with_index('crane',{},{:ids_only => true}).size
161
- assert_equal 2, Post.find_with_index('ship',{},{:ids_only => true}).size
132
+ assert_equal 2, result_ids('crane').size
133
+ assert_equal 2, result_ids('ship').size
162
134
  end
163
-
135
+
164
136
  # If an if proc is supplied, the index should only be created if the proc evaluated true
165
137
  def test_create_if
166
138
  Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
167
-
139
+
168
140
  original_post_count = Post.count
169
- assert_equal [], Post.find_with_index('badger')
141
+ assert_equal [], Post.find_with_index('badger', {}, { :no_query_cache => true, :ids_only => true})
170
142
  p = Post.new(:title => 'badger', :body => 'thousands of them!', :visible => true)
171
143
  assert p.save
172
144
  assert_equal original_post_count + 1, Post.count
173
145
  assert_equal [p.id], Post.find_with_index('badger', {}, { :no_query_cache => true, :ids_only => true})
174
-
146
+
175
147
  original_post_count = Post.count
176
148
  assert_equal [], Post.find_with_index('unicorns')
177
149
  p = Post.new(:title => 'unicorns', :body => 'there are none', :visible => false)
@@ -179,49 +151,59 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
179
151
  assert_equal original_post_count + 1, Post.count
180
152
  assert_equal [], Post.find_with_index('unicorns', {}, { :no_query_cache => true, :ids_only => true})
181
153
  end
182
-
154
+
183
155
  # If an index already exists, and an if proc is supplied, and the proc is true, it should still appear in the index
184
156
  def test_update_if_update
185
157
  Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
186
158
  destroy_index
187
-
159
+
188
160
  assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
189
161
  p = Post.find(6)
190
162
  assert p.update_attributes(:visible => true)
191
163
  assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
192
164
  end
193
-
165
+
194
166
  # If an index already exists, and an if proc is supplied, and the proc is false, it should no longer appear in the index
195
167
  def test_update_if_remove
196
168
  Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
197
169
  destroy_index
198
-
170
+
199
171
  assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
200
172
  p = Post.find(6)
201
173
  assert p.update_attributes(:visible => false)
202
174
  assert_equal 0, Post.find_with_index('crane',{},{ :no_query_cache => true, :ids_only => true}).size
203
175
  end
204
-
176
+
205
177
  # If an index doesn't exist, and an if proc is supplied, and the proc is true, it should appear in the index
206
178
  def test_update_if_add
207
179
  Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
208
180
  destroy_index
209
-
181
+
210
182
  assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
211
183
  p = Post.find(5)
212
184
  assert p.update_attributes(:visible => true)
213
185
  assert_equal 2, Post.find_with_index('crane',{},{ :no_query_cache => true, :ids_only => true}).size
214
186
  end
215
-
187
+
216
188
  # If an index doesn't exist, and an if proc is supplied, and the proc is false, then nothing happens
217
189
  def test_update_if_not_in
218
190
  Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
219
191
  destroy_index
220
-
192
+
221
193
  assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
222
194
  p = Post.find(5)
223
195
  assert p.update_attributes(:visible => false)
224
196
  assert_equal 1, Post.find_with_index('crane',{},{ :no_query_cache => true, :ids_only => true}).size
225
197
  end
226
198
 
199
+ private
200
+
201
+ def result_ids(query)
202
+ Post.with_query(query).map { |r| r.id }
203
+ end
204
+
205
+ def assert_equal_array_contents(a, b)
206
+ a.sort == b.sort
207
+ end
208
+
227
209
  end
@@ -6,40 +6,9 @@ class SearchIndexTest < ActiveSupport::TestCase
6
6
  def teardown
7
7
  destroy_index
8
8
  end
9
-
10
- def test_should_check_for_non_existing_index
11
- SearchIndex.any_instance.expects(:exists?).returns(false)
12
- File.expects(:open).never
13
- assert build_search_index
14
- end
15
-
16
- def test_should_check_for_existing_index
17
- SearchIndex.any_instance.expects(:exists?).returns(true)
18
- SearchIndex.any_instance.expects(:load_record_size).returns(0)
19
- assert build_search_index
20
- end
21
9
 
22
- def test_add_record
23
- search_index = build_search_index
24
- mock_record = mock(:id => 123)
25
- mock_condensed_record = ['mock','condensed','record']
26
-
27
- search_index.expects(:condense_record).with(mock_record).returns(mock_condensed_record)
28
- search_index.expects(:load_atoms).with(mock_condensed_record)
29
- search_index.expects(:add_occurences).with(mock_condensed_record,123)
30
-
31
- search_index.add_record(mock_record)
32
- end
33
-
34
- def test_add_records
35
- search_index = build_search_index
36
- mock_records = ['record0', 'record1']
37
-
38
- search_index.expects(:add_record).with('record0', true)
39
- search_index.expects(:add_record).with('record1', true)
40
-
41
- search_index.add_records(mock_records)
42
- end
10
+ # Write new tests for this since most of the existing tests were concerned
11
+ # with the storage routines which have now been moved elsewhere.
43
12
 
44
13
  private
45
14
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_indexed
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
4
+ hash: 9
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 6
9
- - 6
10
- version: 0.6.6
9
+ - 7
10
+ version: 0.6.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - Douglas F Shearer
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-31 00:00:00 +01:00
18
+ date: 2011-02-07 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -41,6 +41,7 @@ files:
41
41
  - lib/acts_as_indexed/configuration.rb
42
42
  - lib/acts_as_indexed/search_atom.rb
43
43
  - lib/acts_as_indexed/search_index.rb
44
+ - lib/acts_as_indexed/storage.rb
44
45
  - lib/will_paginate_search.rb
45
46
  - rails/init.rb
46
47
  - test/abstract_unit.rb