acts_as_indexed 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/CHANGELOG +90 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +137 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/acts_as_indexed.gemspec +67 -0
- data/lib/acts_as_indexed.rb +248 -0
- data/lib/acts_as_indexed/configuration.rb +41 -0
- data/lib/acts_as_indexed/search_atom.rb +104 -0
- data/lib/acts_as_indexed/search_index.rb +325 -0
- data/lib/will_paginate_search.rb +29 -0
- data/rails/init.rb +2 -0
- data/test/abstract_unit.rb +52 -0
- data/test/acts_as_indexed_test.rb +133 -0
- data/test/configuration_test.rb +57 -0
- data/test/database.yml +10 -0
- data/test/fixtures/post.rb +5 -0
- data/test/fixtures/posts.yml +31 -0
- data/test/schema.rb +6 -0
- data/test/search_atom_test.rb +98 -0
- data/test/search_index_test.rb +50 -0
- metadata +94 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
===0.6.2 [11th June 2010]
|
2
|
+
- Now available as a Gem as well as the original plugin. [parndt - Thanks for doing most of the hard work.]
|
3
|
+
|
4
|
+
===0.6.0 [10th June 2010]
|
5
|
+
- Now supports Rails 3.x.x as well as Rails 2.x.x.
|
6
|
+
- Added global configuration options.
|
7
|
+
- Now recommending using with_query scope for searching.
|
8
|
+
- Deprecated find_with_index and will_paginate_search methods.
|
9
|
+
|
10
|
+
===0.5.3 [6th June 2010]
|
11
|
+
- Now supports non-standard table names automatically. [nandalopes]
|
12
|
+
|
13
|
+
===0.5.2 [3rd May 2010]
|
14
|
+
- Fix for Errno::ERANGE error related to certain Math.log calculations. [parndt]
|
15
|
+
- Improved index detection in a shared-directory environment. [bob-p]
|
16
|
+
|
17
|
+
===0.5.1 [11 June 2009]
|
18
|
+
- Fixed Ruby 1.8.6 compatibility.
|
19
|
+
|
20
|
+
===0.5.0 [24 April 2009]
|
21
|
+
- Ruby 1.9 and Rails 2.3 compatibility.
|
22
|
+
- Index location can now be set. Provides Heroku compatibility.
|
23
|
+
- Better errors on bad options.
|
24
|
+
- ActiveRecord order argument overrides ranking returned by find_by_index.
|
25
|
+
- Various test environment improvements
|
26
|
+
- Various Bugfixes
|
27
|
+
|
28
|
+
===0.4.6 [10 August 2008]
|
29
|
+
- Rolled in pagination.
|
30
|
+
|
31
|
+
===0.4.5 [04 February 2008]
|
32
|
+
- Fixed a bug where the find_options :limit would be added to the :offset, which caused incorrectly sized collections to be returned.
|
33
|
+
- Fixed an 'ambiguous column' error when using the :includes find_options key.
|
34
|
+
|
35
|
+
===0.4.4 [29 November 2007]
|
36
|
+
- Fixed a bug causing the weighting section of the code to error out.
|
37
|
+
|
38
|
+
===0.4.3 [27 September 2007]
|
39
|
+
- Fixed a bug causing records to be deleted from index during record updates.
|
40
|
+
|
41
|
+
===0.4.2 [27 September 2007]
|
42
|
+
- Fixed a bug causing identically ranked records to be lost.
|
43
|
+
|
44
|
+
===0.4.1 [22 September 2007]
|
45
|
+
- Fixed a bug in the main search method.
|
46
|
+
|
47
|
+
===0.4.0 [22 September 2007]
|
48
|
+
- Search results now ranked by relevance.
|
49
|
+
|
50
|
+
===0.3.3 [20 September 2007]
|
51
|
+
- Fixed index update bug where deleted atoms were not removed from index.
|
52
|
+
- Improved performance of quoted queries.
|
53
|
+
- Improved performance of index updates.
|
54
|
+
- When building a full index, records are retrieved and indexed in batches to reduce memory consumption.
|
55
|
+
|
56
|
+
===0.3.2 [19 September 2007]
|
57
|
+
- Fixed index update bug.
|
58
|
+
|
59
|
+
===0.3.1 [18 September 2007]
|
60
|
+
- Added RDoc documentation comments.
|
61
|
+
|
62
|
+
===0.3.0 [18 September 2007]
|
63
|
+
- Minor bug fixes.
|
64
|
+
- min_word_size now works properly, with quieries containing small words in
|
65
|
+
quotes or being preceded by a '+' symbol are now searched on.
|
66
|
+
|
67
|
+
===0.2.2 [06 September 2007]
|
68
|
+
- Search now caches query results within a session. Call the search twice in an
|
69
|
+
action? Only runs once!
|
70
|
+
|
71
|
+
===0.2.1 [05 September 2007]
|
72
|
+
- AR find options can now be passed to the search to allow finer control of
|
73
|
+
returned Model Objects.
|
74
|
+
|
75
|
+
===0.2.0 [04 September 2007]
|
76
|
+
- Major performance improvements.
|
77
|
+
- Index segmentation can now be tuned.
|
78
|
+
|
79
|
+
===0.1.1 [31 August 2007]
|
80
|
+
- Added a full set of tests.
|
81
|
+
- Fixed various set-manipulation based errors.
|
82
|
+
- Fixed a bug when searching for quoted phrases.
|
83
|
+
|
84
|
+
===0.1.01 [31 August 2007]
|
85
|
+
|
86
|
+
- Fixed a casting bug occurring when adding non-string fields to the index.
|
87
|
+
|
88
|
+
===0.1 [31 August 2007]
|
89
|
+
|
90
|
+
- Initial release.
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 - 2010 Douglas Shearer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
= acts_as_indexed
|
2
|
+
|
3
|
+
If you find this plugin useful, please consider a donation to show your
|
4
|
+
support!
|
5
|
+
|
6
|
+
http://www.paypal.com/cgi-bin/webscr?cmd=_send-money
|
7
|
+
|
8
|
+
Paypal address: mailto:dougal.s@gmail.com
|
9
|
+
|
10
|
+
|
11
|
+
== Instructions
|
12
|
+
|
13
|
+
This plugin allows boolean-queried fulltext search to be added to any Rails
|
14
|
+
app with no dependencies and minimal setup.
|
15
|
+
|
16
|
+
|
17
|
+
== Resources
|
18
|
+
|
19
|
+
=== Install
|
20
|
+
|
21
|
+
== Rails 2.x.x
|
22
|
+
./script/plugin install git://github.com/dougal/acts_as_indexed.git
|
23
|
+
|
24
|
+
== Rails 3.x.x
|
25
|
+
rails plugin install git://github.com/dougal/acts_as_indexed.git
|
26
|
+
|
27
|
+
=== As a Gem
|
28
|
+
Despite this being slightly against the the original ethos of the project,
|
29
|
+
acts_as_indexed is now available as a Gem as several people have requested it.
|
30
|
+
|
31
|
+
gem install acts_as_indexed
|
32
|
+
|
33
|
+
Make sure to specify the Gem in your environment.rb file (Rails 2.x.x), or the Gemfile (Rails 3.x.x).
|
34
|
+
|
35
|
+
If you don't have git installed, you can download the plugin from the GitHub
|
36
|
+
page (http://github.com/dougal/acts_as_indexed) and unpack it into the
|
37
|
+
<tt>vendor/plugins</tt> directory of your rails app.
|
38
|
+
|
39
|
+
== Usage
|
40
|
+
|
41
|
+
|
42
|
+
=== Setup
|
43
|
+
|
44
|
+
Add +acts_as_indexed+ to the top of any models you want to index, along with a
|
45
|
+
list of the fields you wish to be indexed.
|
46
|
+
|
47
|
+
class Post < ActiveRecord::Base
|
48
|
+
acts_as_indexed :fields => [:title, :body]
|
49
|
+
|
50
|
+
...
|
51
|
+
end
|
52
|
+
|
53
|
+
The fields are not limited to model fields, but can be any instance method of
|
54
|
+
the current model.
|
55
|
+
|
56
|
+
class User < ActiveRecord::Base
|
57
|
+
acts_as_indexed :fields => [:address, :fullname]
|
58
|
+
|
59
|
+
def fullname
|
60
|
+
self.firstname + ' ' + self.lastname
|
61
|
+
end
|
62
|
+
|
63
|
+
...
|
64
|
+
end
|
65
|
+
|
66
|
+
Any of the configuration options in the Further Configuration section can be added as to the acts_as_indexed method call. These will override any defaults or global configuration.
|
67
|
+
|
68
|
+
|
69
|
+
=== Searching
|
70
|
+
|
71
|
+
To search, call the +with_query+ named scope on your model, passing a query as
|
72
|
+
an argument.
|
73
|
+
|
74
|
+
# Returns array of Post objects.
|
75
|
+
my_search_results = Post.with_query('my search query')
|
76
|
+
|
77
|
+
# Chain it with any number of ActiveRecord methods and named_scopes.
|
78
|
+
my_search_results = Post.public.with_query('my search query').find(:all, :limit => 10) # return the first 10 matches which are public.
|
79
|
+
|
80
|
+
|
81
|
+
=== Query Options
|
82
|
+
|
83
|
+
The following query operators are supported:
|
84
|
+
|
85
|
+
* AND:: This is the default option. 'cat dog' will find records matching 'cat' AND 'dog'.
|
86
|
+
* NOT:: 'cat -dog' will find records matching 'cat' AND NOT 'dog'
|
87
|
+
* INCLUDE:: 'cat +me' will find records matching 'cat' and 'me', even if 'me' is smaller than the +min_word_size+
|
88
|
+
* "":: 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+.
|
89
|
+
|
90
|
+
=== Pagination
|
91
|
+
|
92
|
+
Since +with_query+ is a named scope, WillPaginate can be used in the normal
|
93
|
+
fashion.
|
94
|
+
|
95
|
+
@images = Image.with_query('girl').paginate(:page => 1, :per_page => 5)
|
96
|
+
|
97
|
+
=== Further Configuration
|
98
|
+
|
99
|
+
A config block can be provided in your environment files or initializers.
|
100
|
+
Example showing defaults:
|
101
|
+
|
102
|
+
ActsAsIndexed.configure do |config|
|
103
|
+
config.index_file = [RAILS_ROOT,'index']
|
104
|
+
config.index_file_depth = 3
|
105
|
+
config.min_word_size = 3
|
106
|
+
end
|
107
|
+
|
108
|
+
A full rundown of the available configuration options can be found in
|
109
|
+
<tt>lib/acts_as_indexed/configuration.rb</tt>
|
110
|
+
|
111
|
+
== RDoc Documentation
|
112
|
+
|
113
|
+
To generate the RDoc documentation, run the <tt>rake rdoc</tt> task in the
|
114
|
+
acts_as_indexed plugin folder. Then point your web browser at
|
115
|
+
<tt>vendor/plugins/acts_as_indexed/rdoc/index.html</tt>.
|
116
|
+
|
117
|
+
Alternatively, you can view the rdoc documentation
|
118
|
+
online[http://rdoc.info/projects/dougal/acts_as_indexed/].
|
119
|
+
|
120
|
+
== Problems, Comments, Suggestions?
|
121
|
+
|
122
|
+
All of the above are most welcome. mailto:dougal.s@gmail.com
|
123
|
+
|
124
|
+
|
125
|
+
== Credits
|
126
|
+
|
127
|
+
Douglas F Shearer - http:douglasfshearer.com
|
128
|
+
|
129
|
+
|
130
|
+
== Future Releases
|
131
|
+
|
132
|
+
Future releases will be looking to add the following features:
|
133
|
+
* Optional html scrubbing during indexing.
|
134
|
+
* Ranking affected by field weightings.
|
135
|
+
* Support for DataMapper, Sequel and the various MongoDB ORMs.
|
136
|
+
* UTF-8 support. See the current solution here:
|
137
|
+
https://gist.github.com/193903bb4e0d6e5debe1
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the acts_as_indexed plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the acts_as_indexed plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'ActsAsIndexed'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README.rdoc')
|
21
|
+
rdoc.rdoc_files.include('CHANGELOG')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
namespace :rcov do
|
26
|
+
desc "Generate a coverage report in coverage/"
|
27
|
+
task :gen do
|
28
|
+
sh "rcov --output coverage test/*_test.rb"
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Remove generated coverage files."
|
32
|
+
task :clobber do
|
33
|
+
sh "rm -rdf coverage"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
require 'jeweler'
|
39
|
+
Jeweler::Tasks.new do |gemspec|
|
40
|
+
gemspec.name = "acts_as_indexed"
|
41
|
+
gemspec.summary = "Acts As Indexed is a plugin which provides a pain-free way to add fulltext search to your Ruby on Rails app"
|
42
|
+
gemspec.description = gemspec.summary
|
43
|
+
gemspec.email = "dougal.s@gmail.com"
|
44
|
+
gemspec.homepage = "http://github.com/dougal/acts_as_indexed"
|
45
|
+
gemspec.authors = ["Douglas F Shearer"]
|
46
|
+
end
|
47
|
+
Jeweler::GemcutterTasks.new
|
48
|
+
rescue LoadError
|
49
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.6.2
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{acts_as_indexed}
|
8
|
+
s.version = "0.6.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Douglas F Shearer"]
|
12
|
+
s.date = %q{2010-06-11}
|
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
|
+
s.email = %q{dougal.s@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"CHANGELOG",
|
21
|
+
"MIT-LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"acts_as_indexed.gemspec",
|
26
|
+
"lib/acts_as_indexed.rb",
|
27
|
+
"lib/acts_as_indexed/configuration.rb",
|
28
|
+
"lib/acts_as_indexed/search_atom.rb",
|
29
|
+
"lib/acts_as_indexed/search_index.rb",
|
30
|
+
"lib/will_paginate_search.rb",
|
31
|
+
"rails/init.rb",
|
32
|
+
"test/abstract_unit.rb",
|
33
|
+
"test/acts_as_indexed_test.rb",
|
34
|
+
"test/configuration_test.rb",
|
35
|
+
"test/database.yml",
|
36
|
+
"test/fixtures/post.rb",
|
37
|
+
"test/fixtures/posts.yml",
|
38
|
+
"test/schema.rb",
|
39
|
+
"test/search_atom_test.rb",
|
40
|
+
"test/search_index_test.rb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/dougal/acts_as_indexed}
|
43
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.3.7}
|
46
|
+
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}
|
47
|
+
s.test_files = [
|
48
|
+
"test/abstract_unit.rb",
|
49
|
+
"test/acts_as_indexed_test.rb",
|
50
|
+
"test/configuration_test.rb",
|
51
|
+
"test/fixtures/post.rb",
|
52
|
+
"test/schema.rb",
|
53
|
+
"test/search_atom_test.rb",
|
54
|
+
"test/search_index_test.rb"
|
55
|
+
]
|
56
|
+
|
57
|
+
if s.respond_to? :specification_version then
|
58
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
59
|
+
s.specification_version = 3
|
60
|
+
|
61
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
62
|
+
else
|
63
|
+
end
|
64
|
+
else
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,248 @@
|
|
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
|
+
require 'active_record'
|
7
|
+
|
8
|
+
require 'acts_as_indexed/configuration'
|
9
|
+
require 'acts_as_indexed/search_index'
|
10
|
+
require 'acts_as_indexed/search_atom'
|
11
|
+
|
12
|
+
module ActsAsIndexed #:nodoc:
|
13
|
+
|
14
|
+
# Holds the default configuration for acts_as_indexed.
|
15
|
+
|
16
|
+
@configuration = Configuration.new
|
17
|
+
|
18
|
+
# Returns the current configuration for acts_as_indexed.
|
19
|
+
|
20
|
+
def self.configuration
|
21
|
+
@configuration
|
22
|
+
end
|
23
|
+
|
24
|
+
# Call this method to modify defaults in your initializers.
|
25
|
+
#
|
26
|
+
# Example showing defaults:
|
27
|
+
# ActsAsIndexed.configure do |config|
|
28
|
+
# config.index_file = [Rails.root,'index']
|
29
|
+
# config.index_file_depth = 3
|
30
|
+
# config.min_word_size = 3
|
31
|
+
# end
|
32
|
+
|
33
|
+
def self.configure
|
34
|
+
self.configuration ||= Configuration.new
|
35
|
+
yield(configuration)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.included(mod)
|
39
|
+
mod.extend(ClassMethods)
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
|
44
|
+
# Declares a class as searchable.
|
45
|
+
#
|
46
|
+
# ====options:
|
47
|
+
# fields:: Names of fields to include in the index. Symbols pointing to
|
48
|
+
# instance methods of your model may also be given here.
|
49
|
+
# index_file_depth:: Tuning value for the index partitioning. Larger
|
50
|
+
# values result in quicker searches, but slower
|
51
|
+
# indexing. Default is 3.
|
52
|
+
# min_word_size:: Sets the minimum length for a word in a query. Words
|
53
|
+
# shorter than this value are ignored in searches
|
54
|
+
# unless preceded by the '+' operator. Default is 3.
|
55
|
+
# index_file:: Sets the location for the index. By default this is
|
56
|
+
# RAILS_ROOT/index. Specify as an array. Heroku, for
|
57
|
+
# example would use RAILS_ROOT/tmp/index, which would be
|
58
|
+
# set as [Rails.root,'tmp','index]
|
59
|
+
|
60
|
+
def acts_as_indexed(options = {})
|
61
|
+
class_eval do
|
62
|
+
extend ActsAsIndexed::SingletonMethods
|
63
|
+
end
|
64
|
+
include ActsAsIndexed::InstanceMethods
|
65
|
+
|
66
|
+
after_create :add_to_index
|
67
|
+
before_update :update_index
|
68
|
+
after_destroy :remove_from_index
|
69
|
+
|
70
|
+
# scope for Rails 3.x, named_scope for Rails 2.x.
|
71
|
+
if self.respond_to?(:where)
|
72
|
+
scope :with_query, lambda { |query| where("#{table_name}.id IN (?)", search_index(query, {}, {:ids_only => true})) }
|
73
|
+
else
|
74
|
+
named_scope :with_query, lambda { |query| { :conditions => ["#{table_name}.id IN (?)", search_index(query, {}, {:ids_only => true}) ] } }
|
75
|
+
end
|
76
|
+
|
77
|
+
cattr_accessor :aai_config, :aai_fields
|
78
|
+
|
79
|
+
self.aai_fields = options.delete(:fields)
|
80
|
+
raise(ArgumentError, 'no fields specified') if self.aai_fields.nil? || self.aai_fields.empty?
|
81
|
+
|
82
|
+
self.aai_config = ActsAsIndexed.configuration.dup
|
83
|
+
options.each do |k, v|
|
84
|
+
self.aai_config.send("#{k}=", v)
|
85
|
+
end
|
86
|
+
self.aai_config.index_file += [Rails.env, self.name]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Adds the passed +record+ to the index. Index is built if it does not already exist. Clears the query cache.
|
90
|
+
|
91
|
+
def index_add(record)
|
92
|
+
build_index if !File.exists?(File.join(aai_config.index_file))
|
93
|
+
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size)
|
94
|
+
index.add_record(record)
|
95
|
+
index.save
|
96
|
+
@query_cache = {}
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
# Removes the passed +record+ from the index. Clears the query cache.
|
101
|
+
|
102
|
+
def index_remove(record)
|
103
|
+
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size)
|
104
|
+
# record won't be in index if it doesn't exist. Just return true.
|
105
|
+
return true if !index.exists?
|
106
|
+
index.remove_record(record)
|
107
|
+
index.save
|
108
|
+
@query_cache = {}
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
# Updates the index.
|
113
|
+
# 1. Removes the previous version of the record from the index
|
114
|
+
# 2. Adds the new version to the index.
|
115
|
+
|
116
|
+
def index_update(record)
|
117
|
+
build_index if !File.exists?(File.join(aai_config.index_file))
|
118
|
+
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size)
|
119
|
+
#index.remove_record(find(record.id))
|
120
|
+
#index.add_record(record)
|
121
|
+
index.update_record(record,find(record.id))
|
122
|
+
index.save
|
123
|
+
@query_cache = {}
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
# Finds instances matching the terms passed in +query+. Terms are ANDed by
|
128
|
+
# default. Returns an array of model instances or, if +ids_only+ is
|
129
|
+
# true, an array of integer IDs.
|
130
|
+
#
|
131
|
+
# Keeps a cache of matched IDs for the current session to speed up
|
132
|
+
# multiple identical searches.
|
133
|
+
#
|
134
|
+
# ====find_options
|
135
|
+
# Same as ActiveRecord#find options hash. An :order key will override
|
136
|
+
# the relevance ranking
|
137
|
+
#
|
138
|
+
# ====options
|
139
|
+
# ids_only:: Method returns an array of integer IDs when set to true.
|
140
|
+
# no_query_cache:: Turns off the query cache when set to true. Useful for testing.
|
141
|
+
|
142
|
+
def search_index(query, find_options={}, options={})
|
143
|
+
# Clear the query cache off if the key is set.
|
144
|
+
@query_cache = {} if (options.has_key?('no_query_cache') || options[:no_query_cache])
|
145
|
+
if !@query_cache || !@query_cache[query]
|
146
|
+
logger.debug('Query not in cache, running search.')
|
147
|
+
build_index if !File.exists?(File.join(aai_config.index_file))
|
148
|
+
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size)
|
149
|
+
@query_cache = {} if !@query_cache
|
150
|
+
@query_cache[query] = index.search(query)
|
151
|
+
else
|
152
|
+
logger.debug('Query held in cache.')
|
153
|
+
end
|
154
|
+
return @query_cache[query].sort.reverse.map(&:first) if options[:ids_only] || @query_cache[query].empty?
|
155
|
+
|
156
|
+
# slice up the results by offset and limit
|
157
|
+
offset = find_options[:offset] || 0
|
158
|
+
limit = find_options.include?(:limit) ? find_options[:limit] : @query_cache[query].size
|
159
|
+
part_query = @query_cache[query].sort.reverse.slice(offset,limit).map(&:first)
|
160
|
+
|
161
|
+
# Set these to nil as we are dealing with the pagination by setting
|
162
|
+
# exactly what records we want.
|
163
|
+
find_options[:offset] = nil
|
164
|
+
find_options[:limit] = nil
|
165
|
+
|
166
|
+
with_scope :find => find_options do
|
167
|
+
# Doing the find like this eliminates the possibility of errors occuring
|
168
|
+
# on either missing records (out-of-sync) or an empty results array.
|
169
|
+
records = find(:all, :conditions => [ "#{table_name}.id IN (?)", part_query])
|
170
|
+
|
171
|
+
if find_options.include?(:order)
|
172
|
+
records # Just return the records without ranking them.
|
173
|
+
else
|
174
|
+
# Results come back in random order from SQL, so order again.
|
175
|
+
ranked_records = {}
|
176
|
+
records.each do |r|
|
177
|
+
ranked_records[r] = @query_cache[query][r.id]
|
178
|
+
end
|
179
|
+
|
180
|
+
ranked_records.to_a.sort_by{|a| a.last }.reverse.map(&:first)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
# Builds an index from scratch for the current model class.
|
189
|
+
def build_index
|
190
|
+
increment = 500
|
191
|
+
offset = 0
|
192
|
+
while (records = find(:all, :limit => increment, :offset => offset)).size > 0
|
193
|
+
#p "offset is #{offset}, increment is #{increment}"
|
194
|
+
index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size)
|
195
|
+
offset += increment
|
196
|
+
index.add_records(records)
|
197
|
+
index.save
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
# Adds model class singleton methods.
|
204
|
+
module SingletonMethods
|
205
|
+
|
206
|
+
# DEPRECATED. Use +with_query+ scope instead.
|
207
|
+
# Finds instances matching the terms passed in +query+.
|
208
|
+
#
|
209
|
+
# See ActsAsIndexed::ClassMethods#search_index.
|
210
|
+
def find_with_index(query='', find_options = {}, options = {})
|
211
|
+
warn "[DEPRECATION] `find_with_index` is deprecated and will be removed in a later release. Use `with_query(query)` instead."
|
212
|
+
search_index(query, find_options, options)
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
# Adds model class instance methods.
|
218
|
+
# Methods are called automatically by ActiveRecord on +save+, +destroy+,
|
219
|
+
# and +update+ of model instances.
|
220
|
+
module InstanceMethods
|
221
|
+
|
222
|
+
# Adds the current model instance to index.
|
223
|
+
# Called by ActiveRecord on +save+.
|
224
|
+
def add_to_index
|
225
|
+
self.class.index_add(self)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Removes the current model instance to index.
|
229
|
+
# Called by ActiveRecord on +destroy+.
|
230
|
+
def remove_from_index
|
231
|
+
self.class.index_remove(self)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Updates current model instance index.
|
235
|
+
# Called by ActiveRecord on +update+.
|
236
|
+
def update_index
|
237
|
+
self.class.index_update(self)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
# reopen ActiveRecord and include all the above to make
|
244
|
+
# them available to all our models if they want it
|
245
|
+
|
246
|
+
ActiveRecord::Base.class_eval do
|
247
|
+
include ActsAsIndexed
|
248
|
+
end
|