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 +5 -0
- data/README.rdoc +44 -13
- data/VERSION +1 -1
- data/acts_as_indexed.gemspec +3 -2
- data/lib/acts_as_indexed/search_atom.rb +6 -0
- data/lib/acts_as_indexed/search_index.rb +45 -159
- data/lib/acts_as_indexed/storage.rb +136 -0
- data/lib/acts_as_indexed.rb +1 -4
- data/lib/will_paginate_search.rb +0 -2
- data/test/acts_as_indexed_test.rb +93 -111
- data/test/search_index_test.rb +2 -33
- metadata +5 -4
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
|
-
|
89
|
-
|
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
|
-
*
|
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 = [
|
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
|
-
==
|
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.
|
1
|
+
0.6.7
|
data/acts_as_indexed.gemspec
CHANGED
@@ -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.
|
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{
|
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
|
-
@
|
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 =
|
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
|
24
|
+
def add_record(record)
|
26
25
|
return unless @if_proc.call(record)
|
26
|
+
|
27
27
|
condensed_record = condense_record(record)
|
28
|
-
|
29
|
-
|
30
|
-
@
|
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
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
84
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
data/lib/acts_as_indexed.rb
CHANGED
@@ -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
|
|
data/lib/will_paginate_search.rb
CHANGED
@@ -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],
|
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],
|
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 [],
|
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
|
-
|
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
|
46
|
+
assert result_ids('title').include?(p.id)
|
53
47
|
p.update_attributes(:title => 'No longer special')
|
54
|
-
assert !
|
48
|
+
assert !result_ids('title').include?(p.id)
|
55
49
|
end
|
56
|
-
|
50
|
+
|
57
51
|
def test_simple_queries
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
105
|
-
|
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
|
-
|
110
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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,
|
161
|
-
assert_equal 2,
|
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
|
data/test/search_index_test.rb
CHANGED
@@ -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
|
-
|
23
|
-
|
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:
|
4
|
+
hash: 9
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 6
|
9
|
-
-
|
10
|
-
version: 0.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:
|
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
|