acts_as_indexed 0.6.7 → 0.7.0
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 +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
|