acts_as_indexed 0.7.8 → 0.8.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/.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