acts_as_indexed 0.7.8 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
7
+
8
+ gemfile:
9
+ - Gemfile
10
+ - gemfiles/rails2_3.gemfile
11
+ - gemfiles/rails3_0.gemfile
12
+ - gemfiles/rails3_1.gemfile
13
+ - gemfiles/rails3_2.gemfile
data/CHANGELOG CHANGED
@@ -1,5 +1,16 @@
1
+ ===0.8.0
2
+ - Fixed bug where intentianal hyphenation was treated as a negative query. Fixes #31.
3
+ - Fixed bug where will_paginate_search was not being required. Fixes #23.
4
+ - Fixed bug where quoted phrases were matched across field boundaries. [novalis - David Turner]
5
+ - Fixed bug where records with indentical match-rankings were returned in different orders under different ruby implementations.
6
+ - Storage is now process and thread-safe. Fixes issue #34. [rsamoilov - Roman Samoilov]
7
+ - Added configuration option to force is-Windows mode for storage. Fixes issues #32, #39.
8
+ - Added multiple Gemfiles for TravisCI. https://travis-ci.org/dougal/acts_as_indexed
9
+ - Acts as Indexed can now be tested stand-alone without a generated Rails app.
10
+ - ModelKlass.build_index is now a public method
11
+
1
12
  ===0.7.8 [14 March 2011]
2
- - Fixed bug with file renaming on Windows. Fixes issue #21. [gabynamian - Gabriel Namiman]
13
+ - Fixed bug with file renaming on Windows. Fixes issue #21. [gabynamiman - Gabriel Namiman]
3
14
 
4
15
  ===0.7.7 [14 November 2011]
5
16
  - Fixed bug with out-of-date indexes on Windows. Fixes issue #20. [parndt - Philip Arndt]
data/Gemfile CHANGED
@@ -1,8 +1,9 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  group :test do
4
- gem "jeweler"
5
4
  gem "mocha"
6
- gem "sqlite3-ruby"
5
+ gem "sqlite3", "~> 1.3.5"
7
6
  gem "rcov"
7
+ gem "activerecord"
8
+ gem "will_paginate", "~> 3.0.3"
8
9
  end
data/Gemfile.lock CHANGED
@@ -1,24 +1,35 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- git (1.2.5)
5
- jeweler (1.5.2)
6
- bundler (~> 1.0.0)
7
- git (>= 1.2.5)
8
- rake
4
+ activemodel (3.2.8)
5
+ activesupport (= 3.2.8)
6
+ builder (~> 3.0.0)
7
+ activerecord (3.2.8)
8
+ activemodel (= 3.2.8)
9
+ activesupport (= 3.2.8)
10
+ arel (~> 3.0.2)
11
+ tzinfo (~> 0.3.29)
12
+ activesupport (3.2.8)
13
+ i18n (~> 0.6)
14
+ multi_json (~> 1.0)
15
+ arel (3.0.2)
16
+ builder (3.0.4)
17
+ i18n (0.6.1)
9
18
  mocha (0.9.11)
10
19
  rake
20
+ multi_json (1.3.7)
11
21
  rake (0.8.7)
12
22
  rcov (0.9.9)
13
- sqlite3 (1.3.3)
14
- sqlite3-ruby (1.3.3)
15
- sqlite3 (>= 1.3.3)
23
+ sqlite3 (1.3.6)
24
+ tzinfo (0.3.33)
25
+ will_paginate (3.0.3)
16
26
 
17
27
  PLATFORMS
18
28
  ruby
19
29
 
20
30
  DEPENDENCIES
21
- jeweler
31
+ activerecord
22
32
  mocha
23
33
  rcov
24
- sqlite3-ruby
34
+ sqlite3 (~> 1.3.5)
35
+ will_paginate (~> 3.0.3)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007 - 2011 Douglas Shearer
1
+ Copyright (c) 2007 - 2012 Douglas Shearer
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -16,30 +16,23 @@ app with no dependencies and minimal setup.
16
16
 
17
17
  == Resources
18
18
 
19
- === Install
19
+ === Installation
20
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.
21
+ ==== Add to your gemfile
30
22
 
31
23
  gem install acts_as_indexed
32
24
 
33
- Make sure to specify the Gem in your environment.rb file (Rails 2.x.x), or the
34
- Gemfile (Rails 3.x.x).
25
+ Run <tt>bundle install</tt>. Done.
35
26
 
36
- ==== No Git?
27
+ ==== Still on Rails 2.x.x without Bundler?
28
+
29
+ ./script/plugin install git://github.com/dougal/acts_as_indexed.git
37
30
 
38
31
  If you don't have git installed, but still want the plugin, you can download
39
32
  the plugin from the GitHub page (http://github.com/dougal/acts_as_indexed) and
40
33
  unpack it into the <tt>vendor/plugins</tt> directory of your rails app.
41
34
 
42
- === Upgrade
35
+ === Upgrading
43
36
 
44
37
  When upgrading to a new version of acts_as_indexed it is recommended you
45
38
  delete the index directory and allow it to be rebuilt.
@@ -121,18 +114,18 @@ scope on your model, passing a query as an argument.
121
114
 
122
115
  The following query operators are supported:
123
116
 
124
- * AND :: This is the default option. 'cat dog' will find records matching 'cat' AND 'dog'.
125
- * NOT :: 'cat -dog' will find records matching 'cat' AND NOT 'dog'
126
- * INCLUDE :: 'cat +me' will find records matching 'cat' and 'me', even if 'me' is smaller than the +min_word_size+
127
- * "" :: 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+.
128
- * ^ :: Terms that begin with ^ will match records that contain a word starting with the term. '^cat' will find matches containing 'cat', 'catapult', 'caterpillar' etc.
129
- * ^"" :: A quoted term that begins with ^ matches any phrase that begin with this phrase. '^"cat d"' will find records matching the whole phrases "cat dog" and "cat dinner". This type of search is useful for autocomplete inputs.
117
+ AND :: This is the default option. 'cat dog' will find records matching 'cat' AND 'dog'.
118
+ NOT :: 'cat -dog' will find records matching 'cat' AND NOT 'dog'
119
+ INCLUDE :: 'cat +me' will find records matching 'cat' and 'me', even if 'me' is smaller than the +min_word_size+
120
+ "" :: 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+.
121
+ ^ :: Terms that begin with ^ will match records that contain a word starting with the term. '^cat' will find matches containing 'cat', 'catapult', 'caterpillar' etc.
122
+ ^"" :: A quoted term that begins with ^ matches any phrase that begin with this phrase. '^"cat d"' will find records matching the whole phrases "cat dog" and "cat dinner". This type of search is useful for autocomplete inputs.
130
123
 
131
124
  === Pagination
132
125
 
133
126
  ==== With Relevance
134
127
 
135
- Pagination is supported via the +paginate_search+ method whose first argument is the search query, followed all the standard will_paginate arguments.
128
+ Pagination is supported via the +paginate_search+ method whose first argument is the search query, followed by all the standard will_paginate arguments.
136
129
 
137
130
  @images = Image.paginate_search('girl', :page => 1, :per_page => 5)
138
131
 
@@ -160,14 +153,12 @@ A full rundown of the available configuration options can be found in
160
153
 
161
154
  Acts As Indexed supports Heroku out-of-the-box. The index is created in the
162
155
  tmp directory, which is the only writeable part of the Heroku dyno filesystem.
156
+ Please read Heroku's documentation(
157
+ https://devcenter.heroku.com/articles/read-only-filesystem) regarding their file-system.
163
158
 
164
159
  == RDoc Documentation
165
160
 
166
- To generate the RDoc documentation, run the <tt>rake rdoc</tt> task in the
167
- acts_as_indexed plugin folder. Then point your web browser at
168
- <tt>vendor/plugins/acts_as_indexed/rdoc/index.html</tt>.
169
-
170
- Alternatively, you can view the rdoc documentation
161
+ View the rdoc documentation
171
162
  online[http://rdoc.info/projects/dougal/acts_as_indexed/].
172
163
 
173
164
 
@@ -178,6 +169,9 @@ All of the above are most welcome. mailto:dougal.s@gmail.com
178
169
 
179
170
  == Contributors
180
171
 
172
+ A huge thanks to all the contributors to this library. Without them many
173
+ bugfixes and features wouldn't have happened.
174
+
181
175
  * Douglas F Shearer - http://douglasfshearer.com
182
176
  * Thomas Pomfret
183
177
  * Philip Arndt
@@ -189,13 +183,16 @@ All of the above are most welcome. mailto:dougal.s@gmail.com
189
183
  * Ben Anderson
190
184
  * Theron Toomey
191
185
  * Uģis Ozols
186
+ * Gabriel Namiman
187
+ * Roman Samoilov
188
+ * David Turner
192
189
 
193
190
 
194
- == Future Releases
191
+ == Unicode (UTF8) Support
192
+
193
+ At the moment acts_as_indexed only works with Unicode characters when used in
194
+ the following way:
195
195
 
196
- Future releases will be looking to add the following features:
197
- * Optional html scrubbing during indexing.
198
- * Ranking affected by field weightings.
199
- * Support for DataMapper, Sequel and the various MongoDB ORMs.
200
- * UTF-8 support. See the current solution in the following Gist:
201
196
  https://gist.github.com/193903bb4e0d6e5debe1
197
+
198
+ I have rewritten the tokenization process to allow easier handling of this in the future.
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
- require 'rdoc/task'
4
3
 
5
4
  desc 'Default: run unit tests.'
6
5
  task :default => :test
@@ -13,16 +12,6 @@ Rake::TestTask.new(:test) do |t|
13
12
  t.verbose = true
14
13
  end
15
14
 
16
- desc 'Generate documentation for the acts_as_indexed plugin.'
17
- Rake::RDocTask.new(:rdoc) do |rdoc|
18
- rdoc.rdoc_dir = 'rdoc'
19
- rdoc.title = 'ActsAsIndexed'
20
- rdoc.options << '--line-numbers' << '--inline-source'
21
- rdoc.rdoc_files.include('README.rdoc')
22
- rdoc.rdoc_files.include('CHANGELOG')
23
- rdoc.rdoc_files.include('lib/**/*.rb')
24
- end
25
-
26
15
  namespace :rcov do
27
16
  desc "Generate a coverage report in coverage/"
28
17
  task :gen do
@@ -35,6 +24,22 @@ namespace :rcov do
35
24
  end
36
25
  end
37
26
 
27
+ begin
28
+ require 'sdoc'
29
+
30
+ desc 'Generate documentation for the acts_as_indexed plugin.'
31
+ RDoc::Task.new(:rdoc) do |rdoc|
32
+ rdoc.rdoc_dir = 'rdoc'
33
+ rdoc.title = 'ActsAsIndexed'
34
+ rdoc.options << '--line-numbers' << '--inline-source'
35
+ rdoc.rdoc_files.include('README.rdoc')
36
+ rdoc.rdoc_files.include('CHANGELOG')
37
+ rdoc.rdoc_files.include('lib/**/*.rb')
38
+ end
39
+ rescue LoadError
40
+ puts "sdoc not installed"
41
+ end
42
+
38
43
  begin
39
44
  require 'jeweler'
40
45
  Jeweler::Tasks.new do |gemspec|
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.8
1
+ 0.8.0
@@ -5,17 +5,18 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "acts_as_indexed"
8
- s.version = "0.7.8"
8
+ s.version = "0.8.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 = "2012-03-14"
12
+ s.date = "2012-12-20"
13
13
  s.description = "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 = "dougal.s@gmail.com"
15
15
  s.extra_rdoc_files = [
16
16
  "README.rdoc"
17
17
  ]
18
18
  s.files = [
19
+ ".travis.yml",
19
20
  "CHANGELOG",
20
21
  "Gemfile",
21
22
  "Gemfile.lock",
@@ -24,14 +25,21 @@ Gem::Specification.new do |s|
24
25
  "Rakefile",
25
26
  "VERSION",
26
27
  "acts_as_indexed.gemspec",
28
+ "gemfiles/rails2_3.gemfile",
29
+ "gemfiles/rails3_0.gemfile",
30
+ "gemfiles/rails3_1.gemfile",
31
+ "gemfiles/rails3_2.gemfile",
27
32
  "lib/acts_as_indexed.rb",
28
33
  "lib/acts_as_indexed/class_methods.rb",
29
34
  "lib/acts_as_indexed/configuration.rb",
30
35
  "lib/acts_as_indexed/instance_methods.rb",
36
+ "lib/acts_as_indexed/pre_tokenizer.rb",
31
37
  "lib/acts_as_indexed/search_atom.rb",
32
38
  "lib/acts_as_indexed/search_index.rb",
33
39
  "lib/acts_as_indexed/singleton_methods.rb",
34
40
  "lib/acts_as_indexed/storage.rb",
41
+ "lib/acts_as_indexed/token_normalizer.rb",
42
+ "lib/acts_as_indexed/tokenizer.rb",
35
43
  "lib/will_paginate_search.rb",
36
44
  "rails/init.rb",
37
45
  "test/abstract_unit.rb",
@@ -40,13 +48,17 @@ Gem::Specification.new do |s|
40
48
  "test/database.yml",
41
49
  "test/fixtures/post.rb",
42
50
  "test/fixtures/posts.yml",
51
+ "test/pre_tokenizer_test.rb",
43
52
  "test/schema.rb",
44
53
  "test/search_atom_test.rb",
45
- "test/search_index_test.rb"
54
+ "test/search_index_test.rb",
55
+ "test/token_normalizer_test.rb",
56
+ "test/tokenizer_test.rb",
57
+ "test/will_paginate_search_test.rb"
46
58
  ]
47
59
  s.homepage = "http://github.com/dougal/acts_as_indexed"
48
60
  s.require_paths = ["lib"]
49
- s.rubygems_version = "1.8.11"
61
+ s.rubygems_version = "1.8.23"
50
62
  s.summary = "Acts As Indexed is a plugin which provides a pain-free way to add fulltext search to your Ruby on Rails app"
51
63
 
52
64
  if s.respond_to? :specification_version then
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rake"
4
+ gem "mocha", "~> 0.9.11"
5
+ gem "sqlite3", "~> 1.3.5"
6
+ gem "activerecord", "~> 2.3.14"
7
+ gem "will_paginate", "~> 3.0.3"
8
+
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rake"
4
+ gem "mocha", "~> 0.9.11"
5
+ gem "sqlite3", "~> 1.3.5"
6
+ gem "activerecord", "~> 3.0.17"
7
+ gem "will_paginate", "~> 3.0.3"
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rake"
4
+ gem "mocha", "~> 0.9.11"
5
+ gem "sqlite3", "~> 1.3.5"
6
+ gem "activerecord", "~> 3.1.8"
7
+ gem "will_paginate", "~> 3.0.3"
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rake"
4
+ gem "mocha", "~> 0.9.11"
5
+ gem "sqlite3", "~> 1.3.5"
6
+ gem "activerecord", "~> 3.2.9"
7
+ gem "will_paginate", "~> 3.0.3"
@@ -1,8 +1,3 @@
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
1
  require 'active_record'
7
2
 
8
3
  require 'acts_as_indexed/class_methods'
@@ -12,6 +7,13 @@ require 'acts_as_indexed/configuration'
12
7
  require 'acts_as_indexed/search_index'
13
8
  require 'acts_as_indexed/search_atom'
14
9
  require 'acts_as_indexed/storage'
10
+ require 'acts_as_indexed/pre_tokenizer'
11
+ require 'acts_as_indexed/tokenizer'
12
+ require 'acts_as_indexed/token_normalizer'
13
+
14
+ if defined?(WillPaginate)
15
+ require 'will_paginate_search'
16
+ end
15
17
 
16
18
  module ActsAsIndexed #:nodoc:
17
19
 
@@ -31,7 +33,7 @@ module ActsAsIndexed #:nodoc:
31
33
 
32
34
  # Call this method to modify defaults in your initializers.
33
35
  #
34
- # Example showing defaults:
36
+ # Example showing some defaults:
35
37
  # ActsAsIndexed.configure do |config|
36
38
  # config.index_file = [Rails.root,'index']
37
39
  # config.index_file_depth = 3
@@ -1,8 +1,3 @@
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
1
  module ActsAsIndexed
7
2
 
8
3
  module ClassMethods
@@ -59,7 +54,7 @@ module ActsAsIndexed
59
54
  def index_add(record)
60
55
  return if self.aai_config.disable_auto_indexing
61
56
 
62
- build_index unless aai_config.index_file.directory?
57
+ build_index
63
58
  index = new_index
64
59
  index.add_record(record)
65
60
  @query_cache = {}
@@ -82,7 +77,7 @@ module ActsAsIndexed
82
77
  def index_update(record)
83
78
  return if self.aai_config.disable_auto_indexing
84
79
 
85
- build_index unless aai_config.index_file.directory?
80
+ build_index
86
81
  index = new_index
87
82
  index.update_record(record,find(record.id))
88
83
  @query_cache = {}
@@ -110,7 +105,7 @@ module ActsAsIndexed
110
105
 
111
106
  # Run the query if not already in cache.
112
107
  if !@query_cache || !@query_cache[query]
113
- build_index unless aai_config.index_file.directory?
108
+ build_index
114
109
  (@query_cache ||= {})[query] = new_index.search(query)
115
110
  end
116
111
 
@@ -129,7 +124,7 @@ module ActsAsIndexed
129
124
  # slice up the results by offset and limit
130
125
  offset = find_options[:offset] || 0
131
126
  limit = find_options.include?(:limit) ? find_options[:limit] : @query_cache[query].size
132
- part_query = @query_cache[query].sort_by{ |r| r.last }.slice(offset,limit).map{ |r| r.first }
127
+ part_query = sort(@query_cache[query]).slice(offset,limit).map{ |r| r.first }
133
128
 
134
129
  # Set these to nil as we are dealing with the pagination by setting
135
130
  # exactly what records we want.
@@ -147,33 +142,56 @@ module ActsAsIndexed
147
142
  if find_options.include?(:order)
148
143
  records # Just return the records without ranking them.
149
144
 
150
- else
151
- # Results come back in random order from SQL, so order again.
152
- ranked_records = {}
153
- records.each do |r|
154
- ranked_records[r] = @query_cache[query][r.id]
155
- end
145
+ else
146
+ # Results come back in random order from SQL, so order again.
147
+ ranked_records = ActiveSupport::OrderedHash.new
148
+ records.each do |r|
149
+ ranked_records[r] = @query_cache[query][r.id]
150
+ end
156
151
 
157
- ranked_records.to_a.sort_by{ |a| a.last }.map{ |r| r.first}
158
- end
152
+ sort(ranked_records.to_a).map{ |r| r.first }
153
+ end
159
154
  end
160
155
 
161
156
  end
162
157
 
163
- private
164
-
165
- def new_index
166
- SearchIndex.new(aai_fields, aai_config)
167
- end
168
-
169
158
  # Builds an index from scratch for the current model class.
159
+ # Does not run if the index already exists.
160
+
170
161
  def build_index
162
+ return if aai_config.index_file.directory?
163
+
171
164
  index = new_index
172
165
  find_in_batches({ :batch_size => 500 }) do |records|
173
166
  index.add_records(records)
174
167
  end
175
168
  end
176
169
 
170
+ private
171
+
172
+ # If two records or record IDs have the same rank, sort them by ID.
173
+ # This prevents a different order being returned by different Rubies.
174
+ def sort(ranked_records)
175
+ ranked_records.sort { |a, b|
176
+ a_score = a.last
177
+ a_id = a.first.is_a?(Fixnum) ? a.first : a.first.id
178
+
179
+ b_score = b.last
180
+ b_id = b.first.is_a?(Fixnum) ? b.first : b.first.id
181
+
182
+ if a_score == b_score
183
+ a_id <=> b_id
184
+ else
185
+ a_score <=> b_score
186
+ end
187
+
188
+ }
189
+ end
190
+
191
+ def new_index
192
+ SearchIndex.new(aai_fields, aai_config)
193
+ end
194
+
177
195
  end
178
196
 
179
197
  end
@@ -1,8 +1,3 @@
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
1
  module ActsAsIndexed
7
2
  # Used to set up and modify settings for acts_as_indexed.
8
3
  class Configuration
@@ -35,6 +30,11 @@ module ActsAsIndexed
35
30
  # Default is false.
36
31
  attr_accessor :disable_auto_indexing
37
32
 
33
+ # Disable advanced features not compatible with the Windows filesystem.
34
+ # Set to true to disable.
35
+ # Default is guessed depending on current platform.
36
+ attr_writer :is_windows_filesystem
37
+
38
38
  def initialize
39
39
  @index_file = nil
40
40
  @index_file_depth = 3
@@ -42,6 +42,7 @@ module ActsAsIndexed
42
42
  @if_proc = if_proc
43
43
  @case_sensitive = false
44
44
  @disable_auto_indexing = false
45
+ @is_windows_filesystem = RUBY_PLATFORM[/mswin32|mingw|cygwin/]
45
46
  end
46
47
 
47
48
  # Since we cannot expect Rails to be available on load, it is best to put
@@ -74,5 +75,9 @@ module ActsAsIndexed
74
75
  @if_proc ||= Proc.new{true}
75
76
  end
76
77
 
78
+ def is_windows_filesystem?
79
+ !!@is_windows_filesystem
80
+ end
81
+
77
82
  end
78
83
  end
@@ -1,10 +1,5 @@
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
1
  module ActsAsIndexed
7
-
2
+
8
3
  # Adds model class instance methods.
9
4
  # Methods are called automatically by ActiveRecord on +save+, +destroy+,
10
5
  # and +update+ of model instances.
@@ -28,5 +23,5 @@ module ActsAsIndexed
28
23
  self.class.index_update(self)
29
24
  end
30
25
  end
31
-
26
+
32
27
  end
@@ -0,0 +1,11 @@
1
+ module ActsAsIndexed
2
+ class PreTokenizer
3
+
4
+ # Strips all non-word characters and returns the resulting
5
+ # string.
6
+ def self.process(str)
7
+ str.gsub(/\W/,' ')
8
+ end
9
+
10
+ end
11
+ end
@@ -1,8 +1,3 @@
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
1
  module ActsAsIndexed #:nodoc:
7
2
  class SearchAtom
8
3
 
@@ -16,7 +11,7 @@ module ActsAsIndexed #:nodoc:
16
11
 
17
12
  attr_reader :records
18
13
 
19
- def initialize(records={})
14
+ def initialize(records=ActiveSupport::OrderedHash.new)
20
15
  @records = records
21
16
  end
22
17
 
@@ -68,7 +63,7 @@ module ActsAsIndexed #:nodoc:
68
63
  # "former latter" or "big dog" where "big" is the former and "dog" is the latter.
69
64
  def preceded_by(former)
70
65
  matches = SearchAtom.new
71
- latter = {}
66
+ latter = ActiveSupport::OrderedHash.new
72
67
  former.record_ids.each do |rid|
73
68
  latter[rid] = @records[rid] if @records[rid]
74
69
  end
@@ -91,7 +86,7 @@ module ActsAsIndexed #:nodoc:
91
86
  # Returns a hash of record_ids and weightings for each record in the
92
87
  # atom.
93
88
  def weightings(records_size)
94
- out = {}
89
+ out = ActiveSupport::OrderedHash.new
95
90
  @records.each do |r_id, pos|
96
91
 
97
92
  # Fixes a bug when the records_size is zero. i.e. The only record
@@ -1,17 +1,12 @@
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
1
  module ActsAsIndexed #:nodoc:
7
2
  class SearchIndex
8
3
 
9
4
  # fields:: Fields or instance methods of ActiveRecord model to be indexed.
10
5
  # config:: ActsAsIndexed::Configuration instance.
11
6
  def initialize(fields, config)
12
- @storage = Storage.new(Pathname.new(config.index_file.to_s), config.index_file_depth)
7
+ @storage = Storage.new(config)
13
8
  @fields = fields
14
- @atoms = {}
9
+ @atoms = ActiveSupport::OrderedHash.new
15
10
  @min_word_size = config.min_word_size
16
11
  @records_size = @storage.record_count
17
12
  @case_sensitive = config.case_sensitive
@@ -30,7 +25,7 @@ module ActsAsIndexed #:nodoc:
30
25
 
31
26
  # Adds multiple records to the index. Accepts an array of +records+.
32
27
  def add_records(records)
33
- atoms = {}
28
+ atoms = ActiveSupport::OrderedHash.new
34
29
 
35
30
  records.each do |record|
36
31
  next unless @if_proc.call(record)
@@ -68,7 +63,7 @@ module ActsAsIndexed #:nodoc:
68
63
  starts_with = run_queries(queries[:starts_with], true)
69
64
  start_quoted = run_quoted_queries(queries[:start_quoted], true)
70
65
 
71
- results = {}
66
+ results = ActiveSupport::OrderedHash.new
72
67
 
73
68
  if queries[:start_quoted].any?
74
69
  results = merge_query_results(results, start_quoted)
@@ -109,7 +104,7 @@ module ActsAsIndexed #:nodoc:
109
104
  r1.merge(r2) { |r_id,old_val,new_val| old_val + new_val}
110
105
  end
111
106
 
112
- def add_occurences(condensed_record, record_id, atoms={})
107
+ def add_occurences(condensed_record, record_id, atoms=ActiveSupport::OrderedHash.new)
113
108
  condensed_record.each_with_index do |atom_name, i|
114
109
  atoms[atom_name] = SearchAtom.new unless atoms.include?(atom_name)
115
110
  atoms[atom_name].add_position(record_id, i)
@@ -144,9 +139,12 @@ module ActsAsIndexed #:nodoc:
144
139
  end
145
140
 
146
141
  # Find -foo.
142
+ # Ignores instances where a dash is used as a hyphen.
147
143
  negative = []
148
- while neg = s.slice!(/-[\S]*/)
149
- negative << cleanup_atoms(neg).first
144
+ s.gsub!(/^(.*\s)?-(\S*)/) do |match|
145
+ negative << cleanup_atoms($2).first
146
+
147
+ $1
150
148
  end
151
149
 
152
150
  # Find +foo
@@ -168,9 +166,9 @@ module ActsAsIndexed #:nodoc:
168
166
  end
169
167
 
170
168
  def run_queries(atoms, starts_with=false)
171
- results = {}
169
+ results = ActiveSupport::OrderedHash.new
172
170
  atoms.each do |atom|
173
- interim_results = {}
171
+ interim_results = ActiveSupport::OrderedHash.new
174
172
 
175
173
  # If these atoms are to be run as 'starts with', make them a Regexp
176
174
  # with a carat.
@@ -193,9 +191,9 @@ module ActsAsIndexed #:nodoc:
193
191
  end
194
192
 
195
193
  def run_quoted_queries(quoted_atoms, starts_with=false)
196
- results = {}
194
+ results = ActiveSupport::OrderedHash.new
197
195
  quoted_atoms.each do |quoted_atom|
198
- interim_results = {}
196
+ interim_results = ActiveSupport::OrderedHash.new
199
197
 
200
198
  break if quoted_atom.empty?
201
199
 
@@ -251,21 +249,26 @@ module ActsAsIndexed #:nodoc:
251
249
  end
252
250
 
253
251
 
254
- def cleanup_atoms(s, limit_size=false, min_size = @min_word_size || 3)
255
- s = @case_sensitive ? s : s.downcase
256
- atoms = s.gsub(/\W/,' ').squeeze(' ').split
257
- return atoms unless limit_size
258
- atoms.reject{|w| w.size < min_size}
252
+ def cleanup_atoms(s, limit_size=false)
253
+ pre_tokenized = PreTokenizer.process(s)
254
+ tokenized = Tokenizer.process(pre_tokenized)
255
+ TokenNormalizer.process(tokenized, :normalize_case => !@case_sensitive, :min_token_length => !limit_size ? @min_token_length : false)
259
256
  end
260
257
 
261
258
  def condense_record(record)
262
- condensed = []
259
+ atoms = []
260
+
263
261
  @fields.each do |f|
264
262
  if (value = record.send(f)).present?
265
- condensed << value.to_s
263
+ atoms += cleanup_atoms(value.to_s)
264
+
265
+ #U+3000 separates fields so that quoted terms cannot match across
266
+ #fields
267
+ atoms << "\u3000"
266
268
  end
267
269
  end
268
- cleanup_atoms(condensed.join(' '))
270
+
271
+ atoms
269
272
  end
270
273
 
271
274
  end
@@ -1,8 +1,3 @@
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
1
  module ActsAsIndexed
7
2
 
8
3
  # Adds model class singleton methods.
@@ -17,4 +12,4 @@ module ActsAsIndexed
17
12
 
18
13
  end
19
14
 
20
- end
15
+ end
@@ -1,8 +1,3 @@
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
1
  module ActsAsIndexed #:nodoc:
7
2
  class Storage
8
3
 
@@ -11,10 +6,11 @@ module ActsAsIndexed #:nodoc:
11
6
  INDEX_FILE_EXTENSION = '.ind'
12
7
  TEMP_FILE_EXTENSION = '.tmp'
13
8
 
14
- def initialize(path, prefix_size)
15
- @path = path
16
- @size_path = path.join('size')
17
- @prefix_size = prefix_size
9
+ def initialize(config)
10
+ @path = Pathname.new(config.index_file.to_s)
11
+ @size_path = @path.join('size')
12
+ @prefix_size = config.index_file_depth
13
+ @is_windows_filesystem = config.is_windows_filesystem?
18
14
  prepare
19
15
  end
20
16
 
@@ -35,7 +31,7 @@ module ActsAsIndexed #:nodoc:
35
31
  # Takes a string array of atoms names
36
32
  # return a hash of the relevant atoms.
37
33
  def fetch(atom_names, start=false)
38
- atoms = {}
34
+ atoms = ActiveSupport::OrderedHash.new
39
35
 
40
36
  atom_names.uniq.collect{|a| encoded_prefix(a) }.uniq.each do |prefix|
41
37
  pattern = @path.join(prefix.to_s).to_s
@@ -74,7 +70,7 @@ module ActsAsIndexed #:nodoc:
74
70
  # Sort the atoms into the appropriate shards for writing to individual
75
71
  # files.
76
72
  atoms.each do |atom_name, records|
77
- (atoms_sorted[encoded_prefix(atom_name)] ||= {})[atom_name] = records
73
+ (atoms_sorted[encoded_prefix(atom_name)] ||= ActiveSupport::OrderedHash.new)[atom_name] = records
78
74
  end
79
75
 
80
76
  atoms_sorted.each do |e_p, atoms|
@@ -87,7 +83,7 @@ module ActsAsIndexed #:nodoc:
87
83
  Marshal.load(f)
88
84
  end
89
85
  else
90
- from_file = {}
86
+ from_file = ActiveSupport::OrderedHash.new
91
87
  end
92
88
 
93
89
  atoms = from_file.merge(atoms){ |k,o,n| o.send(operation, n) }
@@ -134,7 +130,7 @@ module ActsAsIndexed #:nodoc:
134
130
  def encoded_prefix(atom)
135
131
  prefix = atom[0, @prefix_size]
136
132
 
137
- unless (@prefix_cache ||= {}).has_key?(prefix)
133
+ unless (@prefix_cache ||= ActiveSupport::OrderedHash.new).has_key?(prefix)
138
134
  if atom.length > 1
139
135
  @prefix_cache[prefix] = prefix.split(//).map{|c| encode_character(c)}.join('_')
140
136
  else
@@ -155,42 +151,48 @@ module ActsAsIndexed #:nodoc:
155
151
  end
156
152
 
157
153
  def write_file(file_path)
158
- new_file = file_path.to_s
159
- tmp_file = new_file + TEMP_FILE_EXTENSION
154
+ new_file_name = file_path.to_s
155
+ temp_file_name = new_file_name + TEMP_FILE_EXTENSION
160
156
 
161
157
  # Windows doesn't seem to play nice with writing then moving the file.
162
158
  # https://github.com/dougal/acts_as_indexed/issues/15
163
- writeable_file = windows? ? new_file : tmp_file
159
+ writeable_file = windows? ? new_file_name : temp_file_name
164
160
 
165
161
  File.open(writeable_file, 'w+') do |f|
166
162
  yield(f)
167
163
  end
168
164
 
169
- FileUtils.mv(tmp_file, new_file) unless windows?
165
+ FileUtils.mv(temp_file_name, new_file_name) unless windows?
170
166
  end
171
167
 
168
+ @@file_lock = Mutex.new
169
+
172
170
  # Borrowed from Rails' ActiveSupport FileStore. Also under MIT licence.
173
- # Lock a file for a block so only one process can modify it at a time.
171
+ # Lock a file for a block so only one process or thread can modify it at a time.
174
172
  def lock_file(file_path, &block) # :nodoc:
175
- # Windows does not support file locking.
176
- if !windows? && file_path.exist?
177
- file_path.open('r+') do |f|
178
- begin
179
- f.flock File::LOCK_EX
180
- yield
181
- ensure
182
- f.flock File::LOCK_UN
173
+ @@file_lock.synchronize do
174
+
175
+ # Windows does not support file locking.
176
+ if !windows? && file_path.exist?
177
+ file_path.open('r+') do |f|
178
+ begin
179
+ f.flock File::LOCK_EX
180
+ yield
181
+ ensure
182
+ f.flock File::LOCK_UN
183
+ end
183
184
  end
185
+ else
186
+ yield
184
187
  end
185
- else
186
- yield
188
+
187
189
  end
188
190
  end
189
191
 
190
192
  # Checking for windows all the time seems costly.
191
193
  # Write a separate windows storage class, and use it at runtime?
192
194
  def windows?
193
- @@is_windows ||= RUBY_PLATFORM[/mswin32|mingw|cygwin/]
195
+ @is_windows_filesystem
194
196
  end
195
197
 
196
198
  end
@@ -0,0 +1,21 @@
1
+ module ActsAsIndexed
2
+ class TokenNormalizer
3
+
4
+ # Takes an array of tokens.
5
+ # - Downcases the tokens when :normalize_case option is passed.
6
+ # - Removes tokens of :min_token_length when option is passed.
7
+ # Returns the resulting array of tokens.
8
+ def self.process(arr, options={})
9
+ if options[:normalize_case]
10
+ arr = arr.map{ |t| t.downcase }
11
+ end
12
+
13
+ if options[:min_token_length]
14
+ arr = arr.reject{ |w| w.size < options[:min_token_length] }
15
+ end
16
+
17
+ arr
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ module ActsAsIndexed
2
+ class Tokenizer
3
+
4
+ # Takes a string of space-separated tokens, returns an array of those tokens.
5
+ def self.process(str)
6
+ str.split
7
+ end
8
+
9
+ end
10
+ end
@@ -1,7 +1,3 @@
1
- # WillPaginateSearch
2
- # Copyright (c) 2007 - 2011 Douglas F Shearer.
3
- # http://douglasfshearer.com
4
-
5
1
  module ActsAsIndexed
6
2
 
7
3
  module WillPaginate
@@ -1,6 +1,8 @@
1
1
  require 'test/unit'
2
2
  require 'fileutils'
3
3
  require 'rubygems'
4
+
5
+ require 'bundler/setup'
4
6
  require 'active_record'
5
7
  require 'active_record/fixtures'
6
8
  require 'mocha'
@@ -16,6 +18,10 @@ class Rails
16
18
  end
17
19
  end
18
20
 
21
+ # Load will_paginate.
22
+ require 'will_paginate'
23
+ require 'will_paginate/collection'
24
+
19
25
  test_path = Pathname.new(File.expand_path('../', __FILE__))
20
26
  require test_path.parent.join('lib', 'acts_as_indexed').to_s
21
27
 
@@ -36,7 +42,7 @@ class ActiveSupport::TestCase #:nodoc:
36
42
  self.use_instantiated_fixtures = false
37
43
 
38
44
  def destroy_index
39
- `rm -rdf #{index_loc}`
45
+ FileUtils.rm_rf(index_loc)
40
46
  end
41
47
 
42
48
  def build_index
@@ -66,7 +66,8 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
66
66
  queries = {
67
67
  'crane -foo' => [5],
68
68
  '-foo crane' => [5],
69
- '-foo' => [] # edgecase
69
+ '-foo' => [], # negative only edgecase.
70
+ 're-entered' => [5] # actually a hyphen edgecase.
70
71
  }
71
72
 
72
73
  run_queries(queries)
@@ -112,8 +113,8 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
112
113
  '^crane' => [6,5] ,
113
114
  '^cran' => [6,5],
114
115
  '^cra' => [6,5],
115
- '^cr' => [6,5,4],
116
- '^c' => [5,2,1,6,3,4],
116
+ '^cr' => [6,4,5],
117
+ '^c' => [5,2,1,3,6,4],
117
118
  '^notthere' => []
118
119
  }
119
120
 
@@ -132,25 +133,29 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
132
133
  '^"crane"' => [6,5],
133
134
  '^"cran"' => [6,5],
134
135
  '^"cra"' => [6,5],
135
- '^"cr"' => [6,5,4],
136
- '^"c"' => [5,2,1,6,3,4],
136
+ '^"cr"' => [6,4,5],
137
+ '^"c"' => [5,2,1,3,6,4],
137
138
  }
138
139
 
139
140
  run_queries(queries)
140
141
  end
141
142
 
143
+
144
+ # NOTE: This test always fails for Rails 2.3. A bug somewhere in either
145
+ # Rails or the SQLite adaptor which causes the offset to be ignored.
146
+ # The offending assertions are not run in CI as a result.
142
147
  def test_find_options
143
148
  # limit.
144
149
  assert_equal [6], Post.find_with_index('^cr', { :limit => 1 }, :ids_only => true)
145
150
  assert_equal [6], Post.find_with_index('^cr', { :limit => 1 }).map{ |r| r.id }
146
151
 
147
152
  # offset
148
- assert_equal [5,4], Post.find_with_index('^cr', { :offset => 1 }, :ids_only => true)
149
- assert_equal [5,4], Post.find_with_index('^cr', { :offset => 1 }).map{ |r| r.id }
153
+ assert_equal [4,5], Post.find_with_index('^cr', { :offset => 1 }, :ids_only => true)
154
+ assert_equal [4,5], Post.find_with_index('^cr', { :offset => 1 }).map{ |r| r.id }
150
155
 
151
156
  # limit and offset
152
- assert_equal [5], Post.find_with_index('^cr', { :limit => 1, :offset => 1 }, :ids_only => true)
153
- assert_equal [5], Post.find_with_index('^cr', { :limit => 1, :offset => 1 }).map{ |r| r.id }
157
+ assert_equal [4], Post.find_with_index('^cr', { :limit => 1, :offset => 1 }, :ids_only => true)
158
+ assert_equal [4], Post.find_with_index('^cr', { :limit => 1, :offset => 1 }).map{ |r| r.id }
154
159
 
155
160
  # order
156
161
  assert_equal [6,5,4,3,2,1], Post.find_with_index('^c', { :order => 'id desc' }).map{ |r| r.id }
@@ -160,18 +165,20 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
160
165
  assert_equal [6,5,4], Post.find_with_index('^c', { :order => 'id desc' , :limit => 3}).map{ |r| r.id }
161
166
  assert_equal [1,2,3,4], Post.find_with_index('^c', { :order => 'id', :limit => 4 }).map{ |r| r.id }
162
167
 
163
- # order and offset
164
- assert_equal [5,4,3,2,1], Post.find_with_index('^c', { :order => 'id desc' , :offset => 1}).map{ |r| r.id }
165
- assert_equal [3,4,5,6], Post.find_with_index('^c', { :order => 'id', :offset => 2 }).map{ |r| r.id }
166
-
167
168
  # order, limit and offset
168
169
  assert_equal [5,4,3], Post.find_with_index('^c', { :order => 'id desc' , :limit => 3, :offset => 1}).map{ |r| r.id }
169
170
  assert_equal [3,4], Post.find_with_index('^c', { :order => 'id', :limit => 2, :offset => 2 }).map{ |r| r.id }
171
+
172
+ # order and offset
173
+ unless ENV['CI'] && !Post.respond_to?(:where) # Rails < 3 does not respond to arel methods.
174
+ assert_equal [5,4,3,2,1], Post.find_with_index('^c', { :order => 'id desc' , :offset => 1}).map{ |r| r.id }
175
+ assert_equal [3,4,5,6], Post.find_with_index('^c', { :order => 'id', :offset => 2 }).map{ |r| r.id }
176
+ end
170
177
  end
171
178
 
172
179
  def test_should_error_when_ids_only_combined_with_finder_options
173
180
  expected_message = "ids_only can not be combined with find option keys other than :offset or :limit"
174
-
181
+
175
182
  error = assert_raise(ArgumentError) do
176
183
  Post.find_with_index('foo', { :order => 'id' }, :ids_only => true)
177
184
  end
@@ -266,6 +273,11 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
266
273
  assert_equal 5, Post.find_with_index('the', {}, { :no_query_cache => true, :ids_only => true}).size
267
274
  end
268
275
 
276
+ def test_queries_across_field_boundaries
277
+ assert_equal [], Post.find_with_index('"Ellis Julien"', { :limit => 1 }, :ids_only => true)
278
+ assert_equal [], Post.find_with_index('"myself crane"', { :limit => 1 }, :ids_only => true)
279
+ end
280
+
269
281
  private
270
282
 
271
283
  def run_queries(queries)
@@ -15,6 +15,15 @@ class ConfigurationTest < ActiveSupport::TestCase
15
15
  assert_equal 3, config.min_word_size
16
16
  end
17
17
 
18
+ def test_default_is_windows_filesystem_should_be_set
19
+ assert_equal !!RUBY_PLATFORM[/mswin32|mingw|cygwin/], config.is_windows_filesystem?
20
+ end
21
+
22
+ def tests_is_windows_filesystem_should_be_writeable
23
+ config.is_windows_filesystem = 'banana'
24
+ assert config.is_windows_filesystem?
25
+ end
26
+
18
27
  def test_index_file_should_be_writeable
19
28
  config.index_file = [Rails.root, 'my_index']
20
29
  assert_equal Rails.root.join('my_index'), config.index_file
@@ -0,0 +1,10 @@
1
+ require 'abstract_unit'
2
+ include ActsAsIndexed
3
+
4
+ class PreTokenizerTest < ActiveSupport::TestCase
5
+
6
+ def test_strips_non_word_characters
7
+ assert_equal "Chocolate Chip Cookies ", PreTokenizer.process("Chocolate-Chip Cookies!")
8
+ end
9
+
10
+ end
@@ -0,0 +1,26 @@
1
+ require 'abstract_unit'
2
+ include ActsAsIndexed
3
+
4
+ class TokenNormalizerTest < ActiveSupport::TestCase
5
+
6
+ def test_leaves_case_of_tokens_untouched
7
+ assert_equal ["Chocolate", "Chip", "Cookies"], TokenNormalizer.process(["Chocolate", "Chip", "Cookies"])
8
+ end
9
+
10
+ def test_downcases_tokens
11
+ assert_equal ["chocolate", "chip", "cookies"], TokenNormalizer.process(["Chocolate", "Chip", "Cookies"], :normalize_case => true)
12
+ end
13
+
14
+ def test_limits_min_length_to_five
15
+ assert_equal ["Chocolate", "Cookies"], TokenNormalizer.process(["Chocolate", "Chip", "Cookies"], :min_token_length => 5)
16
+ end
17
+
18
+ def test_limits_min_length_to_four
19
+ assert_equal ["Love", "Chocolate", "Chip", "Cookies"], TokenNormalizer.process(["I", "Love", "Chocolate", "Chip", "Cookies"], :min_token_length => 4)
20
+ end
21
+
22
+ def test_downcases_and_limits_min_length_to_four
23
+ assert_equal ["love", "chocolate", "chip", "cookies"], TokenNormalizer.process(["I", "Love", "Chocolate", "Chip", "Cookies"], :normalize_case => true, :min_token_length => 4)
24
+ end
25
+
26
+ end
@@ -0,0 +1,14 @@
1
+ require 'abstract_unit'
2
+ include ActsAsIndexed
3
+
4
+ class TokenizerTest < ActiveSupport::TestCase
5
+
6
+ def test_splits_tokens_to_array
7
+ assert_equal ["Chocolate", "Chip", "Cookies"], Tokenizer.process("Chocolate Chip Cookies ")
8
+ end
9
+
10
+ def test_deals_with_multiple_spaces
11
+ assert_equal ["Chocolate", "Chip", "Cookies"], Tokenizer.process("Chocolate Chip Cookies ")
12
+ end
13
+
14
+ end
@@ -0,0 +1,26 @@
1
+ require 'abstract_unit'
2
+
3
+ class WillPaginateSearchTest < ActiveSupport::TestCase
4
+ fixtures :posts
5
+
6
+ def teardown
7
+ # need to do this to work with the :if Proc tests.
8
+ Post.acts_as_indexed :fields => [:title, :body]
9
+ destroy_index
10
+ end
11
+
12
+ def test_paginate_search
13
+
14
+ assert_equal [5,2,1,3,6,4], paginate_search(1, 10)
15
+ assert_equal [5,2,1], paginate_search(1, 3)
16
+ assert_equal [3,6,4], paginate_search(2, 3)
17
+ end
18
+
19
+ private
20
+
21
+ # Returns relevant IDs.
22
+ def paginate_search(page, per_page)
23
+ Post.paginate_search('^c', :page => page, :per_page => per_page).map { |p| p.id }
24
+ end
25
+
26
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_indexed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.8
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-14 00:00:00.000000000 Z
12
+ date: 2012-12-20 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Acts As Indexed is a plugin which provides a pain-free way to add fulltext
15
15
  search to your Ruby on Rails app
@@ -19,6 +19,7 @@ extensions: []
19
19
  extra_rdoc_files:
20
20
  - README.rdoc
21
21
  files:
22
+ - .travis.yml
22
23
  - CHANGELOG
23
24
  - Gemfile
24
25
  - Gemfile.lock
@@ -27,14 +28,21 @@ files:
27
28
  - Rakefile
28
29
  - VERSION
29
30
  - acts_as_indexed.gemspec
31
+ - gemfiles/rails2_3.gemfile
32
+ - gemfiles/rails3_0.gemfile
33
+ - gemfiles/rails3_1.gemfile
34
+ - gemfiles/rails3_2.gemfile
30
35
  - lib/acts_as_indexed.rb
31
36
  - lib/acts_as_indexed/class_methods.rb
32
37
  - lib/acts_as_indexed/configuration.rb
33
38
  - lib/acts_as_indexed/instance_methods.rb
39
+ - lib/acts_as_indexed/pre_tokenizer.rb
34
40
  - lib/acts_as_indexed/search_atom.rb
35
41
  - lib/acts_as_indexed/search_index.rb
36
42
  - lib/acts_as_indexed/singleton_methods.rb
37
43
  - lib/acts_as_indexed/storage.rb
44
+ - lib/acts_as_indexed/token_normalizer.rb
45
+ - lib/acts_as_indexed/tokenizer.rb
38
46
  - lib/will_paginate_search.rb
39
47
  - rails/init.rb
40
48
  - test/abstract_unit.rb
@@ -43,9 +51,13 @@ files:
43
51
  - test/database.yml
44
52
  - test/fixtures/post.rb
45
53
  - test/fixtures/posts.yml
54
+ - test/pre_tokenizer_test.rb
46
55
  - test/schema.rb
47
56
  - test/search_atom_test.rb
48
57
  - test/search_index_test.rb
58
+ - test/token_normalizer_test.rb
59
+ - test/tokenizer_test.rb
60
+ - test/will_paginate_search_test.rb
49
61
  homepage: http://github.com/dougal/acts_as_indexed
50
62
  licenses: []
51
63
  post_install_message:
@@ -66,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
78
  version: '0'
67
79
  requirements: []
68
80
  rubyforge_project:
69
- rubygems_version: 1.8.11
81
+ rubygems_version: 1.8.23
70
82
  signing_key:
71
83
  specification_version: 3
72
84
  summary: Acts As Indexed is a plugin which provides a pain-free way to add fulltext