acts_as_indexed 0.6.7 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +12 -5
- data/Gemfile +2 -0
- data/Gemfile.lock +9 -9
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -9
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/acts_as_indexed.gemspec +36 -35
- data/lib/acts_as_indexed.rb +8 -185
- data/lib/acts_as_indexed/class_methods.rb +160 -0
- data/lib/acts_as_indexed/configuration.rb +14 -11
- data/lib/acts_as_indexed/instance_methods.rb +32 -0
- data/lib/acts_as_indexed/search_atom.rb +1 -1
- data/lib/acts_as_indexed/search_index.rb +21 -16
- data/lib/acts_as_indexed/singleton_methods.rb +20 -0
- data/lib/acts_as_indexed/storage.rb +75 -26
- data/lib/will_paginate_search.rb +1 -1
- data/test/acts_as_indexed_test.rb +11 -0
- data/test/search_index_test.rb +2 -2
- metadata +11 -9
- data/.gitignore +0 -6
data/CHANGELOG
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
+
===0.7.0 [11th February 2011]
|
2
|
+
- Threadsafe support. Index files are now locked for changes, and atomically written.
|
3
|
+
- Configurable case-sensitivity.
|
4
|
+
- Improved performance of index builds.
|
5
|
+
- Now warns on old version of the index.
|
6
|
+
- Upgrade instructions added to README. [ionas - Florent Guilleux]
|
7
|
+
|
1
8
|
===0.6.7 [7th February 2011]
|
2
9
|
- find_by_index and paginate_search are no longer deprecated.
|
3
|
-
- Improved documentation
|
4
|
-
- Storage is now
|
10
|
+
- Improved documentation.
|
11
|
+
- Storage is now its own class to allow future development of locking and pluggable backends.
|
5
12
|
|
6
13
|
===0.6.6 [31st August 2010]
|
7
14
|
- Now Heroku compatible out of the box, index is created in tmp when root dir is non-writable. [parndt - Philip Arndt - Great suggestion]
|
@@ -86,15 +93,15 @@
|
|
86
93
|
|
87
94
|
===0.3.0 [18 September 2007]
|
88
95
|
- Minor bug fixes.
|
89
|
-
- min_word_size now works properly, with queries containing small words in
|
96
|
+
- min_word_size now works properly, with queries containing small words in
|
90
97
|
quotes or being preceded by a '+' symbol are now searched on.
|
91
98
|
|
92
99
|
===0.2.2 [06 September 2007]
|
93
|
-
- Search now caches query results within a session. Call the search twice in an
|
100
|
+
- Search now caches query results within a session. Call the search twice in an
|
94
101
|
action? Only runs once!
|
95
102
|
|
96
103
|
===0.2.1 [05 September 2007]
|
97
|
-
- AR find options can now be passed to the search to allow finer control of
|
104
|
+
- AR find options can now be passed to the search to allow finer control of
|
98
105
|
returned Model Objects.
|
99
106
|
|
100
107
|
===0.2.0 [04 September 2007]
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,19 +1,18 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
gemcutter (0.6.1)
|
5
4
|
git (1.2.5)
|
6
|
-
jeweler (1.
|
7
|
-
|
5
|
+
jeweler (1.5.2)
|
6
|
+
bundler (~> 1.0.0)
|
8
7
|
git (>= 1.2.5)
|
9
|
-
|
10
|
-
|
11
|
-
mocha (0.9.8)
|
8
|
+
rake
|
9
|
+
mocha (0.9.11)
|
12
10
|
rake
|
13
11
|
rake (0.8.7)
|
14
|
-
|
15
|
-
|
16
|
-
sqlite3-ruby (1.3.
|
12
|
+
rcov (0.9.9)
|
13
|
+
sqlite3 (1.3.3)
|
14
|
+
sqlite3-ruby (1.3.3)
|
15
|
+
sqlite3 (>= 1.3.3)
|
17
16
|
|
18
17
|
PLATFORMS
|
19
18
|
ruby
|
@@ -21,4 +20,5 @@ PLATFORMS
|
|
21
20
|
DEPENDENCIES
|
22
21
|
jeweler
|
23
22
|
mocha
|
23
|
+
rcov
|
24
24
|
sqlite3-ruby
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -35,9 +35,14 @@ Gemfile (Rails 3.x.x).
|
|
35
35
|
|
36
36
|
==== No Git?
|
37
37
|
|
38
|
-
If you don't have git installed, but still want the plugin, you can download
|
39
|
-
page (http://github.com/dougal/acts_as_indexed) and
|
40
|
-
<tt>vendor/plugins</tt> directory of your rails app.
|
38
|
+
If you don't have git installed, but still want the plugin, you can download
|
39
|
+
the plugin from the GitHub page (http://github.com/dougal/acts_as_indexed) and
|
40
|
+
unpack it into the <tt>vendor/plugins</tt> directory of your rails app.
|
41
|
+
|
42
|
+
=== Upgrade
|
43
|
+
|
44
|
+
When upgrading to a new version of acts_as_indexed it is recommended you
|
45
|
+
delete the index directory and allow it to be rebuilt.
|
41
46
|
|
42
47
|
|
43
48
|
== Usage
|
@@ -97,7 +102,7 @@ of any matching records.
|
|
97
102
|
|
98
103
|
# Pass any of the ActiveRecord find options to the search.
|
99
104
|
my_search_results = Post.find_with_index('my search query',{:limit => 10}) # return the first 10 matches.
|
100
|
-
|
105
|
+
|
101
106
|
# Returns array of IDs ordered by relevance.
|
102
107
|
my_search_results = Post.find_with_index('my search query',{},{:ids_only => true}) # => [12,19,33...
|
103
108
|
|
@@ -128,7 +133,7 @@ The following query operators are supported:
|
|
128
133
|
==== With Relevance
|
129
134
|
|
130
135
|
Pagination is supported via the +paginate_search+ method whose first argument is the search query, followed all the standard will_paginate arguments.
|
131
|
-
|
136
|
+
|
132
137
|
@images = Image.paginate_search('girl', :page => 1, :per_page => 5)
|
133
138
|
|
134
139
|
==== Without Relevance (Scope)
|
@@ -141,12 +146,11 @@ fashion.
|
|
141
146
|
=== Further Configuration
|
142
147
|
|
143
148
|
A config block can be provided in your environment files or initializers.
|
144
|
-
Example showing
|
149
|
+
Example showing changing the min word size:
|
145
150
|
|
146
151
|
ActsAsIndexed.configure do |config|
|
147
|
-
config.index_file = [Rails.root.to_s,'index']
|
148
|
-
config.index_file_depth = 3
|
149
152
|
config.min_word_size = 3
|
153
|
+
# More config as required...
|
150
154
|
end
|
151
155
|
|
152
156
|
A full rundown of the available configuration options can be found in
|
@@ -188,5 +192,5 @@ Future releases will be looking to add the following features:
|
|
188
192
|
* Optional html scrubbing during indexing.
|
189
193
|
* Ranking affected by field weightings.
|
190
194
|
* Support for DataMapper, Sequel and the various MongoDB ORMs.
|
191
|
-
* UTF-8 support. See the current solution in the following Gist:
|
195
|
+
* UTF-8 support. See the current solution in the following Gist:
|
192
196
|
https://gist.github.com/193903bb4e0d6e5debe1
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.7.0
|
data/acts_as_indexed.gemspec
CHANGED
@@ -1,60 +1,61 @@
|
|
1
1
|
# Generated by jeweler
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{acts_as_indexed}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.7.0"
|
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{2011-02-
|
12
|
+
s.date = %q{2011-02-11}
|
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 = [
|
16
16
|
"README.rdoc"
|
17
17
|
]
|
18
18
|
s.files = [
|
19
|
-
"
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
19
|
+
"CHANGELOG",
|
20
|
+
"Gemfile",
|
21
|
+
"Gemfile.lock",
|
22
|
+
"MIT-LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"acts_as_indexed.gemspec",
|
27
|
+
"lib/acts_as_indexed.rb",
|
28
|
+
"lib/acts_as_indexed/class_methods.rb",
|
29
|
+
"lib/acts_as_indexed/configuration.rb",
|
30
|
+
"lib/acts_as_indexed/instance_methods.rb",
|
31
|
+
"lib/acts_as_indexed/search_atom.rb",
|
32
|
+
"lib/acts_as_indexed/search_index.rb",
|
33
|
+
"lib/acts_as_indexed/singleton_methods.rb",
|
34
|
+
"lib/acts_as_indexed/storage.rb",
|
35
|
+
"lib/will_paginate_search.rb",
|
36
|
+
"rails/init.rb",
|
37
|
+
"test/abstract_unit.rb",
|
38
|
+
"test/acts_as_indexed_test.rb",
|
39
|
+
"test/configuration_test.rb",
|
40
|
+
"test/database.yml",
|
41
|
+
"test/fixtures/post.rb",
|
42
|
+
"test/fixtures/posts.yml",
|
43
|
+
"test/schema.rb",
|
44
|
+
"test/search_atom_test.rb",
|
45
|
+
"test/search_index_test.rb"
|
44
46
|
]
|
45
47
|
s.homepage = %q{http://github.com/dougal/acts_as_indexed}
|
46
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
47
48
|
s.require_paths = ["lib"]
|
48
49
|
s.rubygems_version = %q{1.3.7}
|
49
50
|
s.summary = %q{Acts As Indexed is a plugin which provides a pain-free way to add fulltext search to your Ruby on Rails app}
|
50
51
|
s.test_files = [
|
51
52
|
"test/abstract_unit.rb",
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
"test/acts_as_indexed_test.rb",
|
54
|
+
"test/configuration_test.rb",
|
55
|
+
"test/fixtures/post.rb",
|
56
|
+
"test/schema.rb",
|
57
|
+
"test/search_atom_test.rb",
|
58
|
+
"test/search_index_test.rb"
|
58
59
|
]
|
59
60
|
|
60
61
|
if s.respond_to? :specification_version then
|
data/lib/acts_as_indexed.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# ActsAsIndexed
|
2
|
-
# Copyright (c) 2007 -
|
2
|
+
# Copyright (c) 2007 - 2011 Douglas F Shearer.
|
3
3
|
# http://douglasfshearer.com
|
4
4
|
# Distributed under the MIT license as included with this plugin.
|
5
5
|
|
6
6
|
require 'active_record'
|
7
7
|
|
8
|
+
require 'acts_as_indexed/class_methods'
|
9
|
+
require 'acts_as_indexed/instance_methods'
|
10
|
+
require 'acts_as_indexed/singleton_methods'
|
8
11
|
require 'acts_as_indexed/configuration'
|
9
12
|
require 'acts_as_indexed/search_index'
|
10
13
|
require 'acts_as_indexed/search_atom'
|
@@ -12,6 +15,10 @@ require 'acts_as_indexed/storage'
|
|
12
15
|
|
13
16
|
module ActsAsIndexed #:nodoc:
|
14
17
|
|
18
|
+
# This is the last version of the plugin where the index structure was
|
19
|
+
# changed in some manner. Is only changed when necessary, not every release.
|
20
|
+
INDEX_VERSION = '0.6.8'
|
21
|
+
|
15
22
|
# Holds the default configuration for acts_as_indexed.
|
16
23
|
|
17
24
|
@configuration = Configuration.new
|
@@ -40,190 +47,6 @@ module ActsAsIndexed #:nodoc:
|
|
40
47
|
mod.extend(ClassMethods)
|
41
48
|
end
|
42
49
|
|
43
|
-
module ClassMethods
|
44
|
-
|
45
|
-
# Declares a class as searchable.
|
46
|
-
#
|
47
|
-
# ====options:
|
48
|
-
# fields:: Names of fields to include in the index. Symbols pointing to
|
49
|
-
# instance methods of your model may also be given here.
|
50
|
-
# index_file_depth:: Tuning value for the index partitioning. Larger
|
51
|
-
# values result in quicker searches, but slower
|
52
|
-
# indexing. Default is 3.
|
53
|
-
# min_word_size:: Sets the minimum length for a word in a query. Words
|
54
|
-
# shorter than this value are ignored in searches
|
55
|
-
# unless preceded by the '+' operator. Default is 3.
|
56
|
-
# index_file:: Sets the location for the index. By default this is
|
57
|
-
# RAILS_ROOT/index. Specify as an array. Heroku, for
|
58
|
-
# example would use RAILS_ROOT/tmp/index, which would be
|
59
|
-
# set as [Rails.root,'tmp','index]
|
60
|
-
|
61
|
-
def acts_as_indexed(options = {})
|
62
|
-
class_eval do
|
63
|
-
extend ActsAsIndexed::SingletonMethods
|
64
|
-
end
|
65
|
-
include ActsAsIndexed::InstanceMethods
|
66
|
-
|
67
|
-
after_create :add_to_index
|
68
|
-
before_update :update_index
|
69
|
-
after_destroy :remove_from_index
|
70
|
-
|
71
|
-
# scope for Rails 3.x, named_scope for Rails 2.x.
|
72
|
-
if self.respond_to?(:where)
|
73
|
-
scope :with_query, lambda { |query| where("#{table_name}.id IN (?)", search_index(query, {}, {:ids_only => true})) }
|
74
|
-
else
|
75
|
-
named_scope :with_query, lambda { |query| { :conditions => ["#{table_name}.id IN (?)", search_index(query, {}, {:ids_only => true}) ] } }
|
76
|
-
end
|
77
|
-
|
78
|
-
cattr_accessor :aai_config, :aai_fields
|
79
|
-
|
80
|
-
self.aai_fields = options.delete(:fields)
|
81
|
-
raise(ArgumentError, 'no fields specified') if self.aai_fields.nil? || self.aai_fields.empty?
|
82
|
-
|
83
|
-
self.aai_config = ActsAsIndexed.configuration.dup
|
84
|
-
self.aai_config.if_proc = options.delete(:if)
|
85
|
-
options.each do |k, v|
|
86
|
-
self.aai_config.send("#{k}=", v)
|
87
|
-
end
|
88
|
-
|
89
|
-
# Add the Rails environment and this model's name to the index file path.
|
90
|
-
self.aai_config.index_file = self.aai_config.index_file.join(Rails.env, self.name)
|
91
|
-
end
|
92
|
-
|
93
|
-
# Adds the passed +record+ to the index. Index is built if it does not already exist. Clears the query cache.
|
94
|
-
|
95
|
-
def index_add(record)
|
96
|
-
build_index unless aai_config.index_file.directory?
|
97
|
-
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
|
98
|
-
index.add_record(record)
|
99
|
-
@query_cache = {}
|
100
|
-
end
|
101
|
-
|
102
|
-
# Removes the passed +record+ from the index. Clears the query cache.
|
103
|
-
|
104
|
-
def index_remove(record)
|
105
|
-
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
|
106
|
-
index.remove_record(record)
|
107
|
-
@query_cache = {}
|
108
|
-
end
|
109
|
-
|
110
|
-
# Updates the index.
|
111
|
-
# 1. Removes the previous version of the record from the index
|
112
|
-
# 2. Adds the new version to the index.
|
113
|
-
|
114
|
-
def index_update(record)
|
115
|
-
build_index unless aai_config.index_file.directory?
|
116
|
-
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
|
117
|
-
index.update_record(record,find(record.id))
|
118
|
-
@query_cache = {}
|
119
|
-
end
|
120
|
-
|
121
|
-
# Finds instances matching the terms passed in +query+. Terms are ANDed by
|
122
|
-
# default. Returns an array of model instances or, if +ids_only+ is
|
123
|
-
# true, an array of integer IDs.
|
124
|
-
#
|
125
|
-
# Keeps a cache of matched IDs for the current session to speed up
|
126
|
-
# multiple identical searches.
|
127
|
-
#
|
128
|
-
# ====find_options
|
129
|
-
# Same as ActiveRecord#find options hash. An :order key will override
|
130
|
-
# the relevance ranking
|
131
|
-
#
|
132
|
-
# ====options
|
133
|
-
# ids_only:: Method returns an array of integer IDs when set to true.
|
134
|
-
# no_query_cache:: Turns off the query cache when set to true. Useful for testing.
|
135
|
-
|
136
|
-
def search_index(query, find_options={}, options={})
|
137
|
-
# Clear the query cache off if the key is set.
|
138
|
-
@query_cache = {} if (options.has_key?('no_query_cache') || options[:no_query_cache])
|
139
|
-
if !@query_cache || !@query_cache[query]
|
140
|
-
logger.debug('Query not in cache, running search.')
|
141
|
-
build_index unless aai_config.index_file.directory?
|
142
|
-
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
|
143
|
-
(@query_cache ||= {})[query] = index.search(query)
|
144
|
-
else
|
145
|
-
logger.debug('Query held in cache.')
|
146
|
-
end
|
147
|
-
return @query_cache[query].sort.reverse.map{|r| r.first} if options[:ids_only] || @query_cache[query].empty?
|
148
|
-
|
149
|
-
# slice up the results by offset and limit
|
150
|
-
offset = find_options[:offset] || 0
|
151
|
-
limit = find_options.include?(:limit) ? find_options[:limit] : @query_cache[query].size
|
152
|
-
part_query = @query_cache[query].sort.reverse.slice(offset,limit).map{|r| r.first}
|
153
|
-
|
154
|
-
# Set these to nil as we are dealing with the pagination by setting
|
155
|
-
# exactly what records we want.
|
156
|
-
find_options[:offset] = nil
|
157
|
-
find_options[:limit] = nil
|
158
|
-
|
159
|
-
with_scope :find => find_options do
|
160
|
-
# Doing the find like this eliminates the possibility of errors occuring
|
161
|
-
# on either missing records (out-of-sync) or an empty results array.
|
162
|
-
records = find(:all, :conditions => [ "#{table_name}.id IN (?)", part_query])
|
163
|
-
|
164
|
-
if find_options.include?(:order)
|
165
|
-
records # Just return the records without ranking them.
|
166
|
-
else
|
167
|
-
# Results come back in random order from SQL, so order again.
|
168
|
-
ranked_records = {}
|
169
|
-
records.each do |r|
|
170
|
-
ranked_records[r] = @query_cache[query][r.id]
|
171
|
-
end
|
172
|
-
|
173
|
-
ranked_records.to_a.sort_by{|a| a.last }.reverse.map{|r| r.first}
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
private
|
180
|
-
|
181
|
-
# Builds an index from scratch for the current model class.
|
182
|
-
def build_index
|
183
|
-
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
|
184
|
-
find_in_batches({ :batch_size => 500 }) do |records|
|
185
|
-
index.add_records(records)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
end
|
190
|
-
|
191
|
-
# Adds model class singleton methods.
|
192
|
-
module SingletonMethods
|
193
|
-
|
194
|
-
# Finds instances matching the terms passed in +query+.
|
195
|
-
#
|
196
|
-
# See ActsAsIndexed::ClassMethods#search_index.
|
197
|
-
def find_with_index(query='', find_options = {}, options = {})
|
198
|
-
search_index(query, find_options, options)
|
199
|
-
end
|
200
|
-
|
201
|
-
end
|
202
|
-
|
203
|
-
# Adds model class instance methods.
|
204
|
-
# Methods are called automatically by ActiveRecord on +save+, +destroy+,
|
205
|
-
# and +update+ of model instances.
|
206
|
-
module InstanceMethods
|
207
|
-
|
208
|
-
# Adds the current model instance to index.
|
209
|
-
# Called by ActiveRecord on +save+.
|
210
|
-
def add_to_index
|
211
|
-
self.class.index_add(self)
|
212
|
-
end
|
213
|
-
|
214
|
-
# Removes the current model instance to index.
|
215
|
-
# Called by ActiveRecord on +destroy+.
|
216
|
-
def remove_from_index
|
217
|
-
self.class.index_remove(self)
|
218
|
-
end
|
219
|
-
|
220
|
-
# Updates current model instance index.
|
221
|
-
# Called by ActiveRecord on +update+.
|
222
|
-
def update_index
|
223
|
-
self.class.index_update(self)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
50
|
end
|
228
51
|
|
229
52
|
# reopen ActiveRecord and include all the above to make
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# ActsAsIndexed
|
2
|
+
# Copyright (c) 2007 - 2011 Douglas F Shearer.
|
3
|
+
# http://douglasfshearer.com
|
4
|
+
# Distributed under the MIT license as included with this plugin.
|
5
|
+
|
6
|
+
module ActsAsIndexed
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Declares a class as searchable.
|
11
|
+
#
|
12
|
+
# ====options:
|
13
|
+
# fields:: Names of fields to include in the index. Symbols pointing to
|
14
|
+
# instance methods of your model may also be given here.
|
15
|
+
# index_file_depth:: Tuning value for the index partitioning. Larger
|
16
|
+
# values result in quicker searches, but slower
|
17
|
+
# indexing. Default is 3.
|
18
|
+
# min_word_size:: Sets the minimum length for a word in a query. Words
|
19
|
+
# shorter than this value are ignored in searches
|
20
|
+
# unless preceded by the '+' operator. Default is 3.
|
21
|
+
# index_file:: Sets the location for the index. By default this is
|
22
|
+
# RAILS_ROOT/index. Specify as an array. Heroku, for
|
23
|
+
# example would use RAILS_ROOT/tmp/index, which would be
|
24
|
+
# set as [Rails.root,'tmp','index]
|
25
|
+
|
26
|
+
def acts_as_indexed(options = {})
|
27
|
+
class_eval do
|
28
|
+
extend ActsAsIndexed::SingletonMethods
|
29
|
+
end
|
30
|
+
include ActsAsIndexed::InstanceMethods
|
31
|
+
|
32
|
+
after_create :add_to_index
|
33
|
+
before_update :update_index
|
34
|
+
after_destroy :remove_from_index
|
35
|
+
|
36
|
+
# scope for Rails 3.x, named_scope for Rails 2.x.
|
37
|
+
if self.respond_to?(:where)
|
38
|
+
scope :with_query, lambda { |query| where("#{table_name}.id IN (?)", search_index(query, {}, {:ids_only => true})) }
|
39
|
+
else
|
40
|
+
named_scope :with_query, lambda { |query| { :conditions => ["#{table_name}.id IN (?)", search_index(query, {}, {:ids_only => true}) ] } }
|
41
|
+
end
|
42
|
+
|
43
|
+
cattr_accessor :aai_config, :aai_fields
|
44
|
+
|
45
|
+
self.aai_fields = options.delete(:fields)
|
46
|
+
raise(ArgumentError, 'no fields specified') if self.aai_fields.nil? || self.aai_fields.empty?
|
47
|
+
|
48
|
+
self.aai_config = ActsAsIndexed.configuration.dup
|
49
|
+
self.aai_config.if_proc = options.delete(:if)
|
50
|
+
options.each do |k, v|
|
51
|
+
self.aai_config.send("#{k}=", v)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add the Rails environment and this model's name to the index file path.
|
55
|
+
self.aai_config.index_file = self.aai_config.index_file.join(Rails.env, self.name)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Adds the passed +record+ to the index. Index is built if it does not already exist. Clears the query cache.
|
59
|
+
|
60
|
+
def index_add(record)
|
61
|
+
build_index unless aai_config.index_file.directory?
|
62
|
+
index = new_index
|
63
|
+
index.add_record(record)
|
64
|
+
@query_cache = {}
|
65
|
+
end
|
66
|
+
|
67
|
+
# Removes the passed +record+ from the index. Clears the query cache.
|
68
|
+
|
69
|
+
def index_remove(record)
|
70
|
+
index = new_index
|
71
|
+
index.remove_record(record)
|
72
|
+
@query_cache = {}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Updates the index.
|
76
|
+
# 1. Removes the previous version of the record from the index
|
77
|
+
# 2. Adds the new version to the index.
|
78
|
+
|
79
|
+
def index_update(record)
|
80
|
+
build_index unless aai_config.index_file.directory?
|
81
|
+
index = new_index
|
82
|
+
index.update_record(record,find(record.id))
|
83
|
+
@query_cache = {}
|
84
|
+
end
|
85
|
+
|
86
|
+
# Finds instances matching the terms passed in +query+. Terms are ANDed by
|
87
|
+
# default. Returns an array of model instances or, if +ids_only+ is
|
88
|
+
# true, an array of integer IDs.
|
89
|
+
#
|
90
|
+
# Keeps a cache of matched IDs for the current session to speed up
|
91
|
+
# multiple identical searches.
|
92
|
+
#
|
93
|
+
# ====find_options
|
94
|
+
# Same as ActiveRecord#find options hash. An :order key will override
|
95
|
+
# the relevance ranking
|
96
|
+
#
|
97
|
+
# ====options
|
98
|
+
# ids_only:: Method returns an array of integer IDs when set to true.
|
99
|
+
# no_query_cache:: Turns off the query cache when set to true. Useful for testing.
|
100
|
+
|
101
|
+
def search_index(query, find_options={}, options={})
|
102
|
+
# Clear the query cache off if the key is set.
|
103
|
+
@query_cache = {} if (options.has_key?('no_query_cache') || options[:no_query_cache])
|
104
|
+
if !@query_cache || !@query_cache[query]
|
105
|
+
logger.debug('Query not in cache, running search.')
|
106
|
+
build_index unless aai_config.index_file.directory?
|
107
|
+
index = new_index
|
108
|
+
(@query_cache ||= {})[query] = index.search(query)
|
109
|
+
else
|
110
|
+
logger.debug('Query held in cache.')
|
111
|
+
end
|
112
|
+
return @query_cache[query].sort.reverse.map{|r| r.first} if options[:ids_only] || @query_cache[query].empty?
|
113
|
+
|
114
|
+
# slice up the results by offset and limit
|
115
|
+
offset = find_options[:offset] || 0
|
116
|
+
limit = find_options.include?(:limit) ? find_options[:limit] : @query_cache[query].size
|
117
|
+
part_query = @query_cache[query].sort.reverse.slice(offset,limit).map{|r| r.first}
|
118
|
+
|
119
|
+
# Set these to nil as we are dealing with the pagination by setting
|
120
|
+
# exactly what records we want.
|
121
|
+
find_options[:offset] = nil
|
122
|
+
find_options[:limit] = nil
|
123
|
+
|
124
|
+
with_scope :find => find_options do
|
125
|
+
# Doing the find like this eliminates the possibility of errors occuring
|
126
|
+
# on either missing records (out-of-sync) or an empty results array.
|
127
|
+
records = find(:all, :conditions => [ "#{table_name}.id IN (?)", part_query])
|
128
|
+
|
129
|
+
if find_options.include?(:order)
|
130
|
+
records # Just return the records without ranking them.
|
131
|
+
else
|
132
|
+
# Results come back in random order from SQL, so order again.
|
133
|
+
ranked_records = {}
|
134
|
+
records.each do |r|
|
135
|
+
ranked_records[r] = @query_cache[query][r.id]
|
136
|
+
end
|
137
|
+
|
138
|
+
ranked_records.to_a.sort_by{|a| a.last }.reverse.map{|r| r.first}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def new_index
|
147
|
+
SearchIndex.new(aai_fields, aai_config)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Builds an index from scratch for the current model class.
|
151
|
+
def build_index
|
152
|
+
index = new_index
|
153
|
+
find_in_batches({ :batch_size => 500 }) do |records|
|
154
|
+
index.add_records(records)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# ActsAsIndexed
|
2
|
-
# Copyright (c) 2007 -
|
2
|
+
# Copyright (c) 2007 - 2011 Douglas F Shearer.
|
3
3
|
# http://douglasfshearer.com
|
4
4
|
# Distributed under the MIT license as included with this plugin.
|
5
5
|
|
@@ -22,24 +22,27 @@ module ActsAsIndexed
|
|
22
22
|
attr_reader :min_word_size
|
23
23
|
|
24
24
|
# Proc that allows you to turn on or off index for a record.
|
25
|
-
# Useful if you don't want
|
26
|
-
#
|
25
|
+
# Useful if you don't want an object to be placed in the index, such as a
|
26
|
+
# draft post.
|
27
27
|
attr_accessor :if_proc
|
28
28
|
|
29
|
+
# Enable or disable case sensitivity.
|
30
|
+
# Set to true to enable.
|
31
|
+
# Default is false.
|
32
|
+
attr_accessor :case_sensitive
|
33
|
+
|
29
34
|
def initialize
|
30
|
-
@index_file
|
35
|
+
@index_file = nil
|
31
36
|
@index_file_depth = 3
|
32
|
-
@min_word_size
|
33
|
-
@if_proc
|
37
|
+
@min_word_size = 3
|
38
|
+
@if_proc = if_proc
|
39
|
+
@case_sensitive = false
|
34
40
|
end
|
35
41
|
|
36
42
|
# Since we cannot expect Rails to be available on load, it is best to put
|
37
43
|
# off setting the index_file attribute until as late as possible.
|
38
44
|
def index_file
|
39
|
-
|
40
|
-
@index_file = default_index_file
|
41
|
-
end
|
42
|
-
@index_file
|
45
|
+
@index_file ||= default_index_file
|
43
46
|
end
|
44
47
|
|
45
48
|
def index_file=(file_path)
|
@@ -67,7 +70,7 @@ module ActsAsIndexed
|
|
67
70
|
end
|
68
71
|
|
69
72
|
private
|
70
|
-
|
73
|
+
|
71
74
|
def default_index_file
|
72
75
|
if Rails.root.writable?
|
73
76
|
Rails.root.join('index')
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# ActsAsIndexed
|
2
|
+
# Copyright (c) 2007 - 2011 Douglas F Shearer.
|
3
|
+
# http://douglasfshearer.com
|
4
|
+
# Distributed under the MIT license as included with this plugin.
|
5
|
+
|
6
|
+
module ActsAsIndexed
|
7
|
+
|
8
|
+
# Adds model class instance methods.
|
9
|
+
# Methods are called automatically by ActiveRecord on +save+, +destroy+,
|
10
|
+
# and +update+ of model instances.
|
11
|
+
module InstanceMethods
|
12
|
+
|
13
|
+
# Adds the current model instance to index.
|
14
|
+
# Called by ActiveRecord on +save+.
|
15
|
+
def add_to_index
|
16
|
+
self.class.index_add(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Removes the current model instance to index.
|
20
|
+
# Called by ActiveRecord on +destroy+.
|
21
|
+
def remove_from_index
|
22
|
+
self.class.index_remove(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Updates current model instance index.
|
26
|
+
# Called by ActiveRecord on +update+.
|
27
|
+
def update_index
|
28
|
+
self.class.index_update(self)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -1,23 +1,21 @@
|
|
1
1
|
# ActsAsIndexed
|
2
|
-
# Copyright (c) 2007 -
|
2
|
+
# Copyright (c) 2007 - 2011 Douglas F Shearer.
|
3
3
|
# http://douglasfshearer.com
|
4
4
|
# Distributed under the MIT license as included with this plugin.
|
5
5
|
|
6
6
|
module ActsAsIndexed #:nodoc:
|
7
7
|
class SearchIndex
|
8
8
|
|
9
|
-
# root:: Location of index on filesystem as a Pathname.
|
10
|
-
# index_depth:: Degree of index partitioning.
|
11
9
|
# fields:: Fields or instance methods of ActiveRecord model to be indexed.
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
@storage = Storage.new(Pathname.new(root.to_s), index_depth)
|
10
|
+
# config:: ActsAsIndexed::Configuration instance.
|
11
|
+
def initialize(fields, config)
|
12
|
+
@storage = Storage.new(Pathname.new(config.index_file.to_s), config.index_file_depth)
|
16
13
|
@fields = fields
|
17
14
|
@atoms = {}
|
18
|
-
@min_word_size = min_word_size
|
15
|
+
@min_word_size = config.min_word_size
|
19
16
|
@records_size = @storage.record_count
|
20
|
-
@
|
17
|
+
@case_sensitive = config.case_sensitive
|
18
|
+
@if_proc = config.if_proc
|
21
19
|
end
|
22
20
|
|
23
21
|
# Adds +record+ to the index.
|
@@ -25,23 +23,30 @@ module ActsAsIndexed #:nodoc:
|
|
25
23
|
return unless @if_proc.call(record)
|
26
24
|
|
27
25
|
condensed_record = condense_record(record)
|
28
|
-
atoms = add_occurences(condensed_record,record.id)
|
29
|
-
|
26
|
+
atoms = add_occurences(condensed_record, record.id)
|
27
|
+
|
30
28
|
@storage.add(atoms)
|
31
29
|
end
|
32
30
|
|
33
31
|
# Adds multiple records to the index. Accepts an array of +records+.
|
34
32
|
def add_records(records)
|
33
|
+
atoms = {}
|
34
|
+
|
35
35
|
records.each do |record|
|
36
|
-
|
36
|
+
next unless @if_proc.call(record)
|
37
|
+
|
38
|
+
condensed_record = condense_record(record)
|
39
|
+
atoms = add_occurences(condensed_record, record.id, atoms)
|
37
40
|
end
|
41
|
+
|
42
|
+
@storage.add(atoms)
|
38
43
|
end
|
39
44
|
|
40
45
|
# Removes +record+ from the index.
|
41
46
|
def remove_record(record)
|
42
47
|
condensed_record = condense_record(record)
|
43
48
|
atoms = add_occurences(condensed_record,record.id)
|
44
|
-
|
49
|
+
|
45
50
|
@storage.remove(atoms)
|
46
51
|
end
|
47
52
|
|
@@ -104,8 +109,7 @@ module ActsAsIndexed #:nodoc:
|
|
104
109
|
r1.merge(r2) { |r_id,old_val,new_val| old_val + new_val}
|
105
110
|
end
|
106
111
|
|
107
|
-
def add_occurences(condensed_record,record_id)
|
108
|
-
atoms = {}
|
112
|
+
def add_occurences(condensed_record, record_id, atoms={})
|
109
113
|
condensed_record.each_with_index do |atom_name, i|
|
110
114
|
atoms[atom_name] = SearchAtom.new unless atoms.include?(atom_name)
|
111
115
|
atoms[atom_name].add_position(record_id, i)
|
@@ -248,7 +252,8 @@ module ActsAsIndexed #:nodoc:
|
|
248
252
|
|
249
253
|
|
250
254
|
def cleanup_atoms(s, limit_size=false, min_size = @min_word_size || 3)
|
251
|
-
|
255
|
+
s = @case_sensitive ? s : s.downcase
|
256
|
+
atoms = s.gsub(/\W/,' ').squeeze(' ').split
|
252
257
|
return atoms unless limit_size
|
253
258
|
atoms.reject{|w| w.size < min_size}
|
254
259
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# ActsAsIndexed
|
2
|
+
# Copyright (c) 2007 - 2011 Douglas F Shearer.
|
3
|
+
# http://douglasfshearer.com
|
4
|
+
# Distributed under the MIT license as included with this plugin.
|
5
|
+
|
6
|
+
module ActsAsIndexed
|
7
|
+
|
8
|
+
# Adds model class singleton methods.
|
9
|
+
module SingletonMethods
|
10
|
+
|
11
|
+
# Finds instances matching the terms passed in +query+.
|
12
|
+
#
|
13
|
+
# See ActsAsIndexed::ClassMethods#search_index.
|
14
|
+
def find_with_index(query='', find_options = {}, options = {})
|
15
|
+
search_index(query, find_options, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -1,13 +1,19 @@
|
|
1
1
|
# ActsAsIndexed
|
2
|
-
# Copyright (c) 2007 -
|
2
|
+
# Copyright (c) 2007 - 2011 Douglas F Shearer.
|
3
3
|
# http://douglasfshearer.com
|
4
4
|
# Distributed under the MIT license as included with this plugin.
|
5
5
|
|
6
6
|
module ActsAsIndexed #:nodoc:
|
7
7
|
class Storage
|
8
8
|
|
9
|
+
class OldIndexVersion < Exception;end
|
10
|
+
|
11
|
+
INDEX_FILE_EXTENSION = '.ind'
|
12
|
+
TEMP_FILE_EXTENSION = '.tmp'
|
13
|
+
|
9
14
|
def initialize(path, prefix_size)
|
10
15
|
@path = path
|
16
|
+
@size_path = path.join('size')
|
11
17
|
@prefix_size = prefix_size
|
12
18
|
prepare
|
13
19
|
end
|
@@ -17,7 +23,6 @@ module ActsAsIndexed #:nodoc:
|
|
17
23
|
operate(:+, atoms)
|
18
24
|
|
19
25
|
update_record_count(1)
|
20
|
-
|
21
26
|
end
|
22
27
|
|
23
28
|
# Takes a hash of atoms and removes these from storage.
|
@@ -35,6 +40,7 @@ module ActsAsIndexed #:nodoc:
|
|
35
40
|
atom_names.uniq.collect{|a| encoded_prefix(a) }.uniq.each do |prefix|
|
36
41
|
pattern = @path.join(prefix.to_s).to_s
|
37
42
|
pattern += '*' if start
|
43
|
+
pattern += INDEX_FILE_EXTENSION
|
38
44
|
|
39
45
|
Pathname.glob(pattern).each do |atom_file|
|
40
46
|
atom_file.open do |f|
|
@@ -48,13 +54,7 @@ module ActsAsIndexed #:nodoc:
|
|
48
54
|
|
49
55
|
# Returns the number of records currently stored in this index.
|
50
56
|
def record_count
|
51
|
-
|
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
|
57
|
+
@size_path.read.to_i
|
58
58
|
|
59
59
|
# This is a bit horrible.
|
60
60
|
rescue Errno::ENOENT
|
@@ -78,35 +78,56 @@ module ActsAsIndexed #:nodoc:
|
|
78
78
|
end
|
79
79
|
|
80
80
|
atoms_sorted.each do |e_p, atoms|
|
81
|
-
path = @path.join(e_p.to_s)
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
81
|
+
path = @path.join(e_p.to_s + INDEX_FILE_EXTENSION)
|
82
|
+
|
83
|
+
lock_file(path) do
|
84
|
+
|
85
|
+
if path.exist?
|
86
|
+
from_file = path.open do |f|
|
87
|
+
Marshal.load(f)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
from_file = {}
|
86
91
|
end
|
87
|
-
else
|
88
|
-
from_file = {}
|
89
|
-
end
|
90
92
|
|
91
|
-
|
93
|
+
atoms = from_file.merge(atoms){ |k,o,n| o.send(operation, n) }
|
92
94
|
|
93
|
-
|
94
|
-
|
95
|
-
|
95
|
+
write_file(path) do |f|
|
96
|
+
Marshal.dump(atoms,f)
|
97
|
+
end
|
98
|
+
end # end lock.
|
99
|
+
|
96
100
|
end
|
97
101
|
end
|
98
102
|
|
99
103
|
def update_record_count(delta)
|
100
|
-
|
101
|
-
|
104
|
+
lock_file(@size_path) do
|
105
|
+
new_count = self.record_count + delta
|
106
|
+
new_count = 0 if new_count < 0
|
102
107
|
|
103
|
-
|
104
|
-
|
108
|
+
write_file(@size_path) do |f|
|
109
|
+
f.write(new_count)
|
110
|
+
end
|
105
111
|
end
|
106
112
|
end
|
107
113
|
|
108
114
|
def prepare
|
109
|
-
|
115
|
+
version_path = @path.join('version')
|
116
|
+
|
117
|
+
if @path.exist?
|
118
|
+
unless version_path.exist? && version_path.read == ActsAsIndexed::INDEX_VERSION
|
119
|
+
raise OldIndexVersion, "Index was created prior to version #{ActsAsIndexed::INDEX_VERSION}. Please delete it, it will be rebuilt automatically."
|
120
|
+
end
|
121
|
+
|
122
|
+
else
|
123
|
+
@path.mkpath
|
124
|
+
|
125
|
+
# Do we need to lock for this? I don't think so as it is only ever making
|
126
|
+
# a creation, not a modification.
|
127
|
+
write_file(version_path) do |f|
|
128
|
+
f.write(ActsAsIndexed::INDEX_VERSION)
|
129
|
+
end
|
130
|
+
end
|
110
131
|
end
|
111
132
|
|
112
133
|
def encoded_prefix(atom)
|
@@ -132,5 +153,33 @@ module ActsAsIndexed #:nodoc:
|
|
132
153
|
end
|
133
154
|
end
|
134
155
|
|
156
|
+
def write_file(file_path)
|
157
|
+
new_file = file_path.to_s
|
158
|
+
tmp_file = new_file + TEMP_FILE_EXTENSION
|
159
|
+
|
160
|
+
File.open(tmp_file, 'w+') do |f|
|
161
|
+
yield(f)
|
162
|
+
end
|
163
|
+
|
164
|
+
FileUtils.mv(tmp_file, new_file)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Borrowed from Rails' ActiveSupport FileStore. Also under MIT licence.
|
168
|
+
# Lock a file for a block so only one process can modify it at a time.
|
169
|
+
def lock_file(file_path, &block) # :nodoc:
|
170
|
+
if file_path.exist?
|
171
|
+
file_path.open('r') do |f|
|
172
|
+
begin
|
173
|
+
f.flock File::LOCK_EX
|
174
|
+
yield
|
175
|
+
ensure
|
176
|
+
f.flock File::LOCK_UN
|
177
|
+
end
|
178
|
+
end
|
179
|
+
else
|
180
|
+
yield
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
135
184
|
end
|
136
185
|
end
|
data/lib/will_paginate_search.rb
CHANGED
@@ -196,6 +196,17 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
|
|
196
196
|
assert_equal 1, Post.find_with_index('crane',{},{ :no_query_cache => true, :ids_only => true}).size
|
197
197
|
end
|
198
198
|
|
199
|
+
def test_case_insensitive
|
200
|
+
Post.acts_as_indexed :fields => [:title, :body], :case_sensitive => true
|
201
|
+
destroy_index
|
202
|
+
|
203
|
+
assert_equal 1, Post.find_with_index('Ellis', {}, { :no_query_cache => true, :ids_only => true}).size
|
204
|
+
assert_equal 0, Post.find_with_index('ellis', {}, { :no_query_cache => true, :ids_only => true}).size
|
205
|
+
|
206
|
+
assert_equal 3, Post.find_with_index('The', {}, { :no_query_cache => true, :ids_only => true}).size
|
207
|
+
assert_equal 5, Post.find_with_index('the', {}, { :no_query_cache => true, :ids_only => true}).size
|
208
|
+
end
|
209
|
+
|
199
210
|
private
|
200
211
|
|
201
212
|
def result_ids(query)
|
data/test/search_index_test.rb
CHANGED
@@ -12,8 +12,8 @@ class SearchIndexTest < ActiveSupport::TestCase
|
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
|
-
def build_search_index(
|
16
|
-
SearchIndex.new(
|
15
|
+
def build_search_index(fields, config)
|
16
|
+
SearchIndex.new(fields, config)
|
17
17
|
end
|
18
18
|
|
19
19
|
end
|
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:
|
5
|
-
prerelease:
|
4
|
+
hash: 3
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
- 6
|
9
8
|
- 7
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 0.7.0
|
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: 2011-02-
|
18
|
+
date: 2011-02-11 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -28,7 +28,6 @@ extensions: []
|
|
28
28
|
extra_rdoc_files:
|
29
29
|
- README.rdoc
|
30
30
|
files:
|
31
|
-
- .gitignore
|
32
31
|
- CHANGELOG
|
33
32
|
- Gemfile
|
34
33
|
- Gemfile.lock
|
@@ -38,9 +37,12 @@ files:
|
|
38
37
|
- VERSION
|
39
38
|
- acts_as_indexed.gemspec
|
40
39
|
- lib/acts_as_indexed.rb
|
40
|
+
- lib/acts_as_indexed/class_methods.rb
|
41
41
|
- lib/acts_as_indexed/configuration.rb
|
42
|
+
- lib/acts_as_indexed/instance_methods.rb
|
42
43
|
- lib/acts_as_indexed/search_atom.rb
|
43
44
|
- lib/acts_as_indexed/search_index.rb
|
45
|
+
- lib/acts_as_indexed/singleton_methods.rb
|
44
46
|
- lib/acts_as_indexed/storage.rb
|
45
47
|
- lib/will_paginate_search.rb
|
46
48
|
- rails/init.rb
|
@@ -58,8 +60,8 @@ homepage: http://github.com/dougal/acts_as_indexed
|
|
58
60
|
licenses: []
|
59
61
|
|
60
62
|
post_install_message:
|
61
|
-
rdoc_options:
|
62
|
-
|
63
|
+
rdoc_options: []
|
64
|
+
|
63
65
|
require_paths:
|
64
66
|
- lib
|
65
67
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -83,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
85
|
requirements: []
|
84
86
|
|
85
87
|
rubyforge_project:
|
86
|
-
rubygems_version: 1.
|
88
|
+
rubygems_version: 1.5.2
|
87
89
|
signing_key:
|
88
90
|
specification_version: 3
|
89
91
|
summary: Acts As Indexed is a plugin which provides a pain-free way to add fulltext search to your Ruby on Rails app
|