acts_as_indexed 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ ===0.6.3 [5th July 2010]
2
+ - index file path can now be definited as a Pathname as well as an array. [parndt]
3
+ - Can now define which records are indexed and which are not via an :if proc. [madpilot]
4
+ - Lots of tidying up. [parndt]
5
+ - Rails 3 fixes. [myabc]
6
+
1
7
  ===0.6.2 [11th June 2010]
2
8
  - Now available as a Gem as well as the original plugin. [parndt - Thanks for doing most of the hard work.]
3
9
 
data/README.rdoc CHANGED
@@ -1,16 +1,16 @@
1
1
  = acts_as_indexed
2
2
 
3
- If you find this plugin useful, please consider a donation to show your
3
+ If you find this plugin useful, please consider a donation to show your
4
4
  support!
5
5
 
6
6
  http://www.paypal.com/cgi-bin/webscr?cmd=_send-money
7
-
7
+
8
8
  Paypal address: mailto:dougal.s@gmail.com
9
-
9
+
10
10
 
11
11
  == Instructions
12
12
 
13
- This plugin allows boolean-queried fulltext search to be added to any Rails
13
+ This plugin allows boolean-queried fulltext search to be added to any Rails
14
14
  app with no dependencies and minimal setup.
15
15
 
16
16
 
@@ -25,19 +25,19 @@ app with no dependencies and minimal setup.
25
25
  rails plugin install git://github.com/dougal/acts_as_indexed.git
26
26
 
27
27
  === As a Gem
28
- Despite this being slightly against the the original ethos of the project,
28
+ Despite this being slightly against the the original ethos of the project,
29
29
  acts_as_indexed is now available as a Gem as several people have requested it.
30
-
30
+
31
31
  gem install acts_as_indexed
32
-
32
+
33
33
  Make sure to specify the Gem in your environment.rb file (Rails 2.x.x), or the Gemfile (Rails 3.x.x).
34
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.
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
38
 
39
- == Usage
40
39
 
40
+ == Usage
41
41
 
42
42
  === Setup
43
43
 
@@ -46,7 +46,7 @@ list of the fields you wish to be indexed.
46
46
 
47
47
  class Post < ActiveRecord::Base
48
48
  acts_as_indexed :fields => [:title, :body]
49
-
49
+
50
50
  ...
51
51
  end
52
52
 
@@ -62,22 +62,33 @@ the current model.
62
62
 
63
63
  ...
64
64
  end
65
-
65
+
66
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
67
 
68
+ You can specify proc that needs to evaluate to true before the item gets indexed.
69
+ This is useful if you only want items with a certain state to be included.
70
+ The Proc is passed the current object's instance so you are able to test against that.
71
+
72
+ For example, if you have a visible column that is false if the post is hidden, or true
73
+ if it is visible, you can filter the index by doing:
74
+
75
+ class Post < ActiveRecord::Base
76
+ acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible? }
77
+ ...
78
+ end
68
79
 
69
80
  === Searching
70
81
 
71
- To search, call the +with_query+ named scope on your model, passing a query as
82
+ To search, call the +with_query+ named scope on your model, passing a query as
72
83
  an argument.
73
84
 
74
85
  # Returns array of Post objects.
75
86
  my_search_results = Post.with_query('my search query')
76
-
87
+
77
88
  # Chain it with any number of ActiveRecord methods and named_scopes.
78
89
  my_search_results = Post.public.with_query('my search query').find(:all, :limit => 10) # return the first 10 matches which are public.
79
-
80
-
90
+
91
+
81
92
  === Query Options
82
93
 
83
94
  The following query operators are supported:
@@ -89,7 +100,7 @@ The following query operators are supported:
89
100
 
90
101
  === Pagination
91
102
 
92
- Since +with_query+ is a named scope, WillPaginate can be used in the normal
103
+ Since +with_query+ is a named scope, WillPaginate can be used in the normal
93
104
  fashion.
94
105
 
95
106
  @images = Image.with_query('girl').paginate(:page => 1, :per_page => 5)
@@ -108,15 +119,17 @@ Example showing defaults:
108
119
  A full rundown of the available configuration options can be found in
109
120
  <tt>lib/acts_as_indexed/configuration.rb</tt>
110
121
 
122
+
111
123
  == RDoc Documentation
112
124
 
113
125
  To generate the RDoc documentation, run the <tt>rake rdoc</tt> task in the
114
126
  acts_as_indexed plugin folder. Then point your web browser at
115
127
  <tt>vendor/plugins/acts_as_indexed/rdoc/index.html</tt>.
116
128
 
117
- Alternatively, you can view the rdoc documentation
129
+ Alternatively, you can view the rdoc documentation
118
130
  online[http://rdoc.info/projects/dougal/acts_as_indexed/].
119
131
 
132
+
120
133
  == Problems, Comments, Suggestions?
121
134
 
122
135
  All of the above are most welcome. mailto:dougal.s@gmail.com
@@ -133,5 +146,5 @@ Future releases will be looking to add the following features:
133
146
  * Optional html scrubbing during indexing.
134
147
  * Ranking affected by field weightings.
135
148
  * Support for DataMapper, Sequel and the various MongoDB ORMs.
136
- * UTF-8 support. See the current solution here:
149
+ * UTF-8 support. See the current solution here:
137
150
  https://gist.github.com/193903bb4e0d6e5debe1
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.2
1
+ 0.6.3
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{acts_as_indexed}
8
- s.version = "0.6.2"
8
+ s.version = "0.6.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Douglas F Shearer"]
12
- s.date = %q{2010-06-11}
12
+ s.date = %q{2010-07-05}
13
13
  s.description = %q{Acts As Indexed is a plugin which provides a pain-free way to add fulltext search to your Ruby on Rails app}
14
14
  s.email = %q{dougal.s@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -6,36 +6,56 @@
6
6
  module ActsAsIndexed
7
7
  # Used to set up and modify settings for acts_as_indexed.
8
8
  class Configuration
9
-
9
+
10
10
  # Sets the location for the index. Specify as an array. Heroku, for
11
11
  # example would use RAILS_ROOT/tmp/index, which would be set as
12
12
  # [Rails.root,'tmp','index]
13
13
  attr_accessor :index_file
14
-
14
+
15
15
  # Tuning value for the index partitioning. Larger values result in quicker
16
16
  # searches, but slower indexing. Default is 3.
17
17
  attr_reader :index_file_depth
18
-
18
+
19
19
  # Sets the minimum length for a word in a query. Words shorter than this
20
20
  # value are ignored in searches unless preceded by the '+' operator.
21
21
  # Default is 3.
22
22
  attr_reader :min_word_size
23
-
23
+
24
+ # Proc that allows you to turn on or off index for a record.
25
+ # Useful if you don't want the index to be updated if the target model is
26
+ # should not return up in results, such as a draft post.
27
+ attr_accessor :if_proc
28
+
24
29
  def initialize
25
- @index_file = [Rails.root, 'index']
30
+ @index_file = Rails.root.join('index') if Rails.root
26
31
  @index_file_depth = 3
27
32
  @min_word_size = 3
33
+ @if_proc = if_proc
34
+ end
35
+
36
+ def index_file=(file_path)
37
+ # Under the old syntax this was an array of path parts.
38
+ # If this is still using the array then rewrite to a Pathname.
39
+ if file_path.is_a?(Pathname)
40
+ @index_file = file_path
41
+ else
42
+ @index_file = Pathname.new(file_path.collect{|part| part.to_s}.join(File::SEPARATOR))
43
+ end
28
44
  end
29
-
45
+
30
46
  def index_file_depth=(val)
31
47
  raise(ArgumentError, 'index_file_depth cannot be less than one (1)') if val < 1
32
48
  @index_file_depth = val
33
49
  end
34
-
50
+
35
51
  def min_word_size=(val)
36
52
  raise(ArgumentError, 'min_word_size cannot be less than one (1)') if val < 1
37
53
  @min_word_size = val
38
54
  end
39
-
55
+
56
+ def if_proc
57
+ @if_proc ||= Proc.new{true}
58
+ end
59
+
40
60
  end
41
- end
61
+ end
@@ -25,7 +25,7 @@ module ActsAsIndexed #:nodoc:
25
25
 
26
26
  # Adds +record_id+ to the stored records.
27
27
  def add_record(record_id)
28
- @records[record_id] = [] if !include_record?(record_id)
28
+ @records[record_id] = [] unless include_record?(record_id)
29
29
  end
30
30
 
31
31
  # Adds +pos+ to the array of positions for +record_id+.
@@ -64,7 +64,7 @@ module ActsAsIndexed #:nodoc:
64
64
  pos.each do |p|
65
65
  # Check if previous position is in former.
66
66
  if former.include_position?(record_id,p-1)
67
- matches.add_record(record_id) if !matches.include_record?(record_id)
67
+ matches.add_record(record_id) unless matches.include_record?(record_id)
68
68
  matches.add_position(record_id,p)
69
69
  end
70
70
  end
@@ -6,21 +6,24 @@
6
6
  module ActsAsIndexed #:nodoc:
7
7
  class SearchIndex
8
8
 
9
- # root:: Location of index on filesystem.
9
+ # root:: Location of index on filesystem as a Pathname.
10
10
  # index_depth:: Degree of index partitioning.
11
11
  # fields:: Fields or instance methods of ActiveRecord model to be indexed.
12
12
  # min_word_size:: Smallest query term that will be run through search.
13
- def initialize(root, index_depth, fields, min_word_size)
14
- @root = root
13
+ # if_proc:: A Proc. If the proc is true, the index gets added, if false if doesn't
14
+ def initialize(root, index_depth, fields, min_word_size, if_proc=Proc.new{true})
15
+ @root = Pathname.new(root.to_s)
15
16
  @fields = fields
16
17
  @index_depth = index_depth
17
18
  @atoms = {}
18
19
  @min_word_size = min_word_size
19
20
  @records_size = exists? ? load_record_size : 0
21
+ @if_proc = if_proc
20
22
  end
21
23
 
22
24
  # Adds +record+ to the index.
23
25
  def add_record(record)
26
+ return @records_size unless @if_proc.call(record)
24
27
  condensed_record = condense_record(record)
25
28
  load_atoms(condensed_record)
26
29
  add_occurences(condensed_record,record.id)
@@ -57,11 +60,13 @@ module ActsAsIndexed #:nodoc:
57
60
  @atoms[a].remove_record(record_new.id) if @atoms.has_key?(a)
58
61
  end
59
62
 
60
- # Add the new version to the appropriate atoms.
61
- load_atoms(new_atoms)
62
- # TODO: Make a version of this method that takes the
63
- # atomised version of the record.
64
- add_occurences(new_atoms, record_new.id)
63
+ if @if_proc.call(record_new)
64
+ # Add the new version to the appropriate atoms.
65
+ load_atoms(new_atoms)
66
+ # TODO: Make a version of this method that takes the
67
+ # atomised version of the record.
68
+ add_occurences(new_atoms, record_new.id)
69
+ end
65
70
  end
66
71
 
67
72
  # Saves the current index partitions to the filesystem.
@@ -69,13 +74,11 @@ module ActsAsIndexed #:nodoc:
69
74
  prepare
70
75
  atoms_sorted = {}
71
76
  @atoms.each do |atom_name, records|
72
- e_p = encoded_prefix(atom_name)
73
- atoms_sorted[e_p] = {} if !atoms_sorted.has_key?(e_p)
74
- atoms_sorted[e_p][atom_name] = records
77
+ (atoms_sorted[encoded_prefix(atom_name)] ||= {})[atom_name] = records
75
78
  end
76
79
  atoms_sorted.each do |e_p, atoms|
77
80
  #p "Saving #{e_p}."
78
- File.open(File.join(@root + [e_p.to_s]),'w+') do |f|
81
+ @root.join(e_p.to_s).open("w+") do |f|
79
82
  Marshal.dump(atoms,f)
80
83
  end
81
84
  end
@@ -86,8 +89,7 @@ module ActsAsIndexed #:nodoc:
86
89
  #--
87
90
  # TODO: Write a public method that will delete all indexes.
88
91
  def destroy
89
- FileUtils.rm_rf(@root)
90
- true
92
+ @root.delete
91
93
  end
92
94
 
93
95
  # Returns an array of IDs for records matching +query+.
@@ -100,11 +102,11 @@ module ActsAsIndexed #:nodoc:
100
102
  negative = run_queries(queries[:negative])
101
103
  negative_quoted = run_quoted_queries(queries[:negative_quoted])
102
104
 
103
- if !queries[:positive].empty? && !queries[:positive_quoted].empty?
104
- p = positive.delete_if{ |r_id,w| !positive_quoted.include?(r_id) }
105
- pq = positive_quoted.delete_if{ |r_id,w| !positive.include?(r_id) }
105
+ if queries[:positive_quoted].any? && queries[:positive].any?
106
+ p = positive.delete_if{ |r_id,w| positive_quoted.exclude?(r_id) }
107
+ pq = positive_quoted.delete_if{ |r_id,w| positive.exclude?(r_id) }
106
108
  results = p.merge(pq) { |r_id,old_val,new_val| old_val + new_val}
107
- elsif !queries[:positive].empty?
109
+ elsif queries[:positive].any?
108
110
  results = positive
109
111
  else
110
112
  results = positive_quoted
@@ -120,21 +122,22 @@ module ActsAsIndexed #:nodoc:
120
122
  #--
121
123
  # TODO: Make a private method called 'root_exists?' which checks for the root directory.
122
124
  def exists?
123
- File.exists?(File.join(@root + ['size']))
125
+ @root.join('size').exist?
124
126
  end
125
127
 
126
128
  private
127
129
 
128
130
  # Gets the size file from the index.
129
131
  def load_record_size
130
- File.open(File.join(@root + ['size'])) do |f|
131
- (Marshal.load(f))
132
+ #p "About to load #{@root.join('size')}"
133
+ @root.join('size').open do |f|
134
+ Marshal.load(f)
132
135
  end
133
136
  end
134
137
 
135
138
  # Saves the size to the size file.
136
139
  def save_record_size
137
- File.open(File.join(@root + ['size']),'w+') do |f|
140
+ @root.join('size').open('w+') do |f|
138
141
  Marshal.dump(@records_size,f)
139
142
  end
140
143
  end
@@ -147,7 +150,7 @@ module ActsAsIndexed #:nodoc:
147
150
  # Returns true if all the given atoms are present.
148
151
  def include_atoms?(atoms_arr)
149
152
  atoms_arr.each do |a|
150
- return false if !include_atom?(a)
153
+ return false unless include_atom?(a)
151
154
  end
152
155
  true
153
156
  end
@@ -160,7 +163,7 @@ module ActsAsIndexed #:nodoc:
160
163
  end
161
164
 
162
165
  def add_atom(atom)
163
- @atoms[atom] = SearchAtom.new if !include_atom?(atom)
166
+ @atoms[atom] = SearchAtom.new unless include_atom?(atom)
164
167
  end
165
168
 
166
169
  def add_occurences(condensed_record,record_id)
@@ -173,10 +176,8 @@ module ActsAsIndexed #:nodoc:
173
176
 
174
177
  def encoded_prefix(atom)
175
178
  prefix = atom[0,@index_depth]
176
- if !@prefix_cache || !@prefix_cache.has_key?(prefix)
177
- @prefix_cache = {} if !@prefix_cache
178
- len = atom.length
179
- if len > 1
179
+ unless (@prefix_cache ||= {}).has_key?(prefix)
180
+ if atom.length > 1
180
181
  @prefix_cache[prefix] = prefix.split(//).map{|c| encode_character(c)}.join('_')
181
182
  else
182
183
  @prefix_cache[prefix] = encode_character(atom)
@@ -261,7 +262,7 @@ module ActsAsIndexed #:nodoc:
261
262
  # return atom containing records + positions where current atom is preceded by following atom.
262
263
  # end
263
264
  # return records from final atom.
264
- next if !include_atoms?(quoted_atom)
265
+ next unless include_atoms?(quoted_atom)
265
266
  matches = @atoms[quoted_atom.first]
266
267
  quoted_atom[1..-1].each do |atom_name|
267
268
  matches = @atoms[atom_name].preceded_by(matches)
@@ -290,8 +291,8 @@ module ActsAsIndexed #:nodoc:
290
291
  # Calculate prefixes.
291
292
  # Remove duplicate prefixes.
292
293
  atoms.uniq.reject{|a| include_atom?(a)}.collect{|a| encoded_prefix(a)}.uniq.each do |name|
293
- if File.exists?(File.join(@root + [name.to_s]))
294
- File.open(File.join(@root + [name.to_s])) do |f|
294
+ if (atom_file = @root.join(name.to_s)).exist?
295
+ atom_file.open do |f|
295
296
  @atoms.merge!(Marshal.load(f))
296
297
  end
297
298
  end
@@ -299,27 +300,25 @@ module ActsAsIndexed #:nodoc:
299
300
  end
300
301
 
301
302
  def prepare
302
- # Makes the RAILS_ROOT/index directory
303
- Dir.mkdir(File.join(@root[0,2])) if !File.exists?(File.join(@root[0,2]))
304
- # Makes the RAILS_ROOT/index/ENVIRONMENT directory
305
- Dir.mkdir(File.join(@root[0,3])) if !File.exists?(File.join(@root[0,3]))
306
- # Makes the RAILS_ROOT/index/ENVIRONMENT/CLASS directory
307
- Dir.mkdir(File.join(@root)) if !File.exists?(File.join(@root))
303
+ # Makes the RAILS_ROOT/index/ENVIRONMENT/CLASS directories
304
+ @root.mkpath
308
305
  end
309
306
 
310
307
  def cleanup_atoms(s, limit_size=false, min_size = @min_word_size || 3)
311
308
  atoms = s.downcase.gsub(/\W/,' ').squeeze(' ').split
312
- return atoms if !limit_size
309
+ return atoms unless limit_size
313
310
  atoms.reject{|w| w.size < min_size}
314
311
  end
315
312
 
316
313
  def condense_record(record)
317
- record_condensed = ''
314
+ condensed = []
318
315
  @fields.each do |f|
319
- record_condensed += ' ' + record.send(f).to_s if record.send(f)
316
+ if (value = record.send(f)).present?
317
+ condensed << value.to_s
318
+ end
320
319
  end
321
- cleanup_atoms(record_condensed)
320
+ cleanup_atoms(condensed.join(' '))
322
321
  end
323
322
 
324
323
  end
325
- end
324
+ end
@@ -10,13 +10,13 @@ require 'acts_as_indexed/search_index'
10
10
  require 'acts_as_indexed/search_atom'
11
11
 
12
12
  module ActsAsIndexed #:nodoc:
13
-
13
+
14
14
  # Holds the default configuration for acts_as_indexed.
15
-
15
+
16
16
  @configuration = Configuration.new
17
17
 
18
18
  # Returns the current configuration for acts_as_indexed.
19
-
19
+
20
20
  def self.configuration
21
21
  @configuration
22
22
  end
@@ -29,7 +29,7 @@ module ActsAsIndexed #:nodoc:
29
29
  # config.index_file_depth = 3
30
30
  # config.min_word_size = 3
31
31
  # end
32
-
32
+
33
33
  def self.configure
34
34
  self.configuration ||= Configuration.new
35
35
  yield(configuration)
@@ -52,7 +52,7 @@ module ActsAsIndexed #:nodoc:
52
52
  # min_word_size:: Sets the minimum length for a word in a query. Words
53
53
  # shorter than this value are ignored in searches
54
54
  # unless preceded by the '+' operator. Default is 3.
55
- # index_file:: Sets the location for the index. By default this is
55
+ # index_file:: Sets the location for the index. By default this is
56
56
  # RAILS_ROOT/index. Specify as an array. Heroku, for
57
57
  # example would use RAILS_ROOT/tmp/index, which would be
58
58
  # set as [Rails.root,'tmp','index]
@@ -64,7 +64,7 @@ module ActsAsIndexed #:nodoc:
64
64
  include ActsAsIndexed::InstanceMethods
65
65
 
66
66
  after_create :add_to_index
67
- before_update :update_index
67
+ before_update :update_index
68
68
  after_destroy :remove_from_index
69
69
 
70
70
  # scope for Rails 3.x, named_scope for Rails 2.x.
@@ -73,24 +73,27 @@ module ActsAsIndexed #:nodoc:
73
73
  else
74
74
  named_scope :with_query, lambda { |query| { :conditions => ["#{table_name}.id IN (?)", search_index(query, {}, {:ids_only => true}) ] } }
75
75
  end
76
-
76
+
77
77
  cattr_accessor :aai_config, :aai_fields
78
78
 
79
79
  self.aai_fields = options.delete(:fields)
80
80
  raise(ArgumentError, 'no fields specified') if self.aai_fields.nil? || self.aai_fields.empty?
81
-
81
+
82
82
  self.aai_config = ActsAsIndexed.configuration.dup
83
+ self.aai_config.if_proc = options.delete(:if)
83
84
  options.each do |k, v|
84
85
  self.aai_config.send("#{k}=", v)
85
86
  end
86
- self.aai_config.index_file += [Rails.env, self.name]
87
+
88
+ # Add the Rails environment and this model's name to the index file path.
89
+ self.aai_config.index_file = self.aai_config.index_file.join(Rails.env, self.name)
87
90
  end
88
91
 
89
92
  # Adds the passed +record+ to the index. Index is built if it does not already exist. Clears the query cache.
90
93
 
91
94
  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)
95
+ build_index unless aai_config.index_file.directory?
96
+ index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
94
97
  index.add_record(record)
95
98
  index.save
96
99
  @query_cache = {}
@@ -100,22 +103,22 @@ module ActsAsIndexed #:nodoc:
100
103
  # Removes the passed +record+ from the index. Clears the query cache.
101
104
 
102
105
  def index_remove(record)
103
- index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size)
106
+ index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
104
107
  # record won't be in index if it doesn't exist. Just return true.
105
- return true if !index.exists?
108
+ return true unless index.exists?
106
109
  index.remove_record(record)
107
110
  index.save
108
111
  @query_cache = {}
109
112
  true
110
113
  end
111
-
114
+
112
115
  # Updates the index.
113
116
  # 1. Removes the previous version of the record from the index
114
117
  # 2. Adds the new version to the index.
115
-
118
+
116
119
  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)
120
+ build_index unless aai_config.index_file.directory?
121
+ index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
119
122
  #index.remove_record(find(record.id))
120
123
  #index.add_record(record)
121
124
  index.update_record(record,find(record.id))
@@ -133,7 +136,7 @@ module ActsAsIndexed #:nodoc:
133
136
  #
134
137
  # ====find_options
135
138
  # Same as ActiveRecord#find options hash. An :order key will override
136
- # the relevance ranking
139
+ # the relevance ranking
137
140
  #
138
141
  # ====options
139
142
  # ids_only:: Method returns an array of integer IDs when set to true.
@@ -144,30 +147,29 @@ module ActsAsIndexed #:nodoc:
144
147
  @query_cache = {} if (options.has_key?('no_query_cache') || options[:no_query_cache])
145
148
  if !@query_cache || !@query_cache[query]
146
149
  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)
150
+ build_index unless aai_config.index_file.directory?
151
+ index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
152
+ (@query_cache ||= {})[query] = index.search(query)
151
153
  else
152
154
  logger.debug('Query held in cache.')
153
155
  end
154
156
  return @query_cache[query].sort.reverse.map(&:first) if options[:ids_only] || @query_cache[query].empty?
155
-
157
+
156
158
  # slice up the results by offset and limit
157
159
  offset = find_options[:offset] || 0
158
160
  limit = find_options.include?(:limit) ? find_options[:limit] : @query_cache[query].size
159
161
  part_query = @query_cache[query].sort.reverse.slice(offset,limit).map(&:first)
160
-
162
+
161
163
  # Set these to nil as we are dealing with the pagination by setting
162
164
  # exactly what records we want.
163
165
  find_options[:offset] = nil
164
166
  find_options[:limit] = nil
165
-
167
+
166
168
  with_scope :find => find_options do
167
169
  # Doing the find like this eliminates the possibility of errors occuring
168
170
  # on either missing records (out-of-sync) or an empty results array.
169
171
  records = find(:all, :conditions => [ "#{table_name}.id IN (?)", part_query])
170
-
172
+
171
173
  if find_options.include?(:order)
172
174
  records # Just return the records without ranking them.
173
175
  else
@@ -176,11 +178,11 @@ module ActsAsIndexed #:nodoc:
176
178
  records.each do |r|
177
179
  ranked_records[r] = @query_cache[query][r.id]
178
180
  end
179
-
181
+
180
182
  ranked_records.to_a.sort_by{|a| a.last }.reverse.map(&:first)
181
183
  end
182
184
  end
183
-
185
+
184
186
  end
185
187
 
186
188
  private
@@ -191,7 +193,7 @@ module ActsAsIndexed #:nodoc:
191
193
  offset = 0
192
194
  while (records = find(:all, :limit => increment, :offset => offset)).size > 0
193
195
  #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)
196
+ index = SearchIndex.new(aai_config.index_file, aai_config.index_file_depth, aai_fields, aai_config.min_word_size, aai_config.if_proc)
195
197
  offset += increment
196
198
  index.add_records(records)
197
199
  index.save
@@ -245,4 +247,4 @@ end
245
247
 
246
248
  ActiveRecord::Base.class_eval do
247
249
  include ActsAsIndexed
248
- end
250
+ end
@@ -2,20 +2,20 @@
2
2
  # Copyright (c) 2007 - 2010 Douglas F Shearer.
3
3
  # http://douglasfshearer.com
4
4
 
5
- module WillPaginate
5
+ module ActsAsIndexed
6
6
 
7
- module Finder
7
+ module WillPaginate
8
8
 
9
- module ClassMethods
9
+ module Search
10
10
 
11
11
  # DEPRECATED. Use chained pagination instead.
12
12
  def paginate_search(query, options)
13
13
  warn "[DEPRECATION] `paginate_search` is deprecated and will be removed in a later release. Use `with_query(query).paginate()` instead."
14
14
  page, per_page, total_entries = wp_parse_options(options)
15
-
15
+
16
16
  total_entries ||= find_with_index(query,{},{:ids_only => true}).size
17
17
 
18
- returning WillPaginate::Collection.new(page, per_page, total_entries) do |pager|
18
+ returning ::WillPaginate::Collection.new(page, per_page, total_entries) do |pager|
19
19
  options.update :offset => pager.offset, :limit => pager.per_page
20
20
 
21
21
  options = options.delete_if {|key, value| [:page, :per_page].include?(key) }
@@ -27,3 +27,7 @@ module WillPaginate
27
27
  end
28
28
  end
29
29
  end
30
+
31
+ class ActiveRecord::Base
32
+ extend ActsAsIndexed::WillPaginate::Search
33
+ end
@@ -9,44 +9,45 @@ require 'mocha'
9
9
  # Do this before requiring AAI.
10
10
  class Rails
11
11
  def self.root
12
- Dir.pwd
12
+ Pathname.new(Dir.pwd)
13
13
  end
14
14
  def self.env
15
15
  'test'
16
16
  end
17
17
  end
18
18
 
19
- require File.dirname(__FILE__) + '/../lib/acts_as_indexed'
19
+ test_path = Pathname.new(File.expand_path('../', __FILE__))
20
+ require test_path.parent.join('lib', 'acts_as_indexed').to_s
20
21
 
21
- ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/test.log')
22
- ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
22
+ ActiveRecord::Base.logger = Logger.new(test_path.join('test.log').to_s)
23
+ ActiveRecord::Base.configurations = YAML::load(IO.read(test_path.join('database.yml').to_s))
23
24
  ActiveRecord::Base.establish_connection(ENV['DB'] || 'sqlite3')
24
25
 
25
26
  # Load Schema
26
- load(File.dirname(__FILE__) + '/schema.rb')
27
+ load(test_path.join('schema.rb').to_s)
27
28
 
28
29
  # Load model.
29
- $LOAD_PATH.unshift(File.dirname(__FILE__) + '/fixtures/')
30
+ $LOAD_PATH.unshift(test_path.join('fixtures').to_s)
30
31
 
31
32
  class ActiveSupport::TestCase #:nodoc:
32
33
  include ActiveRecord::TestFixtures
33
- self.fixture_path = File.dirname(__FILE__) + '/fixtures/'
34
+ self.fixture_path = Pathname.new(File.expand_path('../', __FILE__)).join('fixtures').to_s
34
35
  self.use_transactional_fixtures = true
35
36
  self.use_instantiated_fixtures = false
36
-
37
+
37
38
  def destroy_index
38
39
  `rm -rdf #{index_loc}`
39
40
  end
40
-
41
+
41
42
  def build_index
42
43
  # Makes a query to invoke the index build.
43
44
  assert_equal [], Post.find_with_index('badger')
44
- assert File.exists?(index_loc)
45
+ assert index_loc.exist?
45
46
  true
46
47
  end
47
-
48
+
48
49
  def index_loc
49
- File.join(Rails.root,'index')
50
+ Rails.root.join('index')
50
51
  end
51
-
52
+
52
53
  end
@@ -1,9 +1,11 @@
1
- require File.dirname(__FILE__) + '/abstract_unit'
1
+ require File.expand_path("../abstract_unit", __FILE__)
2
2
 
3
3
  class ActsAsIndexedTest < ActiveSupport::TestCase
4
4
  fixtures :posts
5
5
 
6
6
  def teardown
7
+ # need to do this to work with the :if Proc tests.
8
+ Post.acts_as_indexed :fields => [:title, :body]
7
9
  destroy_index
8
10
  end
9
11
 
@@ -49,7 +51,7 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
49
51
  p = Post.create(:title => 'A special title', :body => 'foo bar bla bla bla')
50
52
  assert Post.find_with_index('title',{},{:ids_only => true}).include?(p.id)
51
53
  p.update_attributes(:title => 'No longer special')
52
- assert !Post.find_with_index('title',{},{:ids_only => true}).include?(p.id)
54
+ assert Post.find_with_index('title',{},{:ids_only => true}).exclude?(p.id)
53
55
  end
54
56
 
55
57
  def test_simple_queries
@@ -120,14 +122,76 @@ class ActsAsIndexedTest < ActiveSupport::TestCase
120
122
  assert_equal all_results[1], second_result.first.id
121
123
  end
122
124
 
123
- # When a atom already in a record is duplicated, it removes
125
+ # When a atom already in a record is duplicated, it removes
124
126
  # all records with that same atom from the index.
125
127
  def test_update_record_bug
126
- assert_equal 2, Post.find_with_index('crane',{},{:ids_only => true}).size
127
128
  p = Post.find(6)
128
129
  assert p.update_attributes(:body => p.body + ' crane')
129
130
  assert_equal 2, Post.find_with_index('crane',{},{:ids_only => true}).size
130
131
  assert_equal 2, Post.find_with_index('ship',{},{:ids_only => true}).size
131
132
  end
132
-
133
+
134
+ # If an if proc is supplied, the index should only be created if the proc evaluated true
135
+ def test_create_if
136
+ Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
137
+
138
+ original_post_count = Post.count
139
+ assert_equal [], Post.find_with_index('badger')
140
+ p = Post.new(:title => 'badger', :body => 'thousands of them!', :visible => true)
141
+ assert p.save
142
+ assert_equal original_post_count + 1, Post.count
143
+ assert_equal [p.id], Post.find_with_index('badger', {}, { :no_query_cache => true, :ids_only => true})
144
+
145
+ original_post_count = Post.count
146
+ assert_equal [], Post.find_with_index('unicorns')
147
+ p = Post.new(:title => 'unicorns', :body => 'there are none', :visible => false)
148
+ assert p.save
149
+ assert_equal original_post_count + 1, Post.count
150
+ assert_equal [], Post.find_with_index('unicorns', {}, { :no_query_cache => true, :ids_only => true})
151
+ end
152
+
153
+ # If an index already exists, and an if proc is supplied, and the proc is true, it should still appear in the index
154
+ def test_update_if_update
155
+ Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
156
+ destroy_index
157
+
158
+ assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
159
+ p = Post.find(6)
160
+ assert p.update_attributes(:visible => true)
161
+ assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
162
+ end
163
+
164
+ # If an index already exists, and an if proc is supplied, and the proc is false, it should no longer appear in the index
165
+ def test_update_if_remove
166
+ Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
167
+ destroy_index
168
+
169
+ assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
170
+ p = Post.find(6)
171
+ assert p.update_attributes(:visible => false)
172
+ assert_equal 0, Post.find_with_index('crane',{},{ :no_query_cache => true, :ids_only => true}).size
173
+ end
174
+
175
+ # If an index doesn't exist, and an if proc is supplied, and the proc is true, it should appear in the index
176
+ def test_update_if_add
177
+ Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
178
+ destroy_index
179
+
180
+ assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
181
+ p = Post.find(5)
182
+ assert p.update_attributes(:visible => true)
183
+ assert_equal 2, Post.find_with_index('crane',{},{ :no_query_cache => true, :ids_only => true}).size
184
+ end
185
+
186
+ # If an index doesn't exist, and an if proc is supplied, and the proc is false, then nothing happens
187
+ def test_update_if_not_in
188
+ Post.acts_as_indexed :fields => [:title, :body], :if => Proc.new { |post| post.visible }
189
+ destroy_index
190
+
191
+ assert_equal 1, Post.find_with_index('crane', {}, { :no_query_cache => true, :ids_only => true}).size
192
+ p = Post.find(5)
193
+ assert p.update_attributes(:visible => false)
194
+ assert_equal 1, Post.find_with_index('crane',{},{ :no_query_cache => true, :ids_only => true}).size
195
+ end
196
+
133
197
  end
@@ -1,12 +1,12 @@
1
- require File.dirname(__FILE__) + '/abstract_unit'
1
+ require File.expand_path("../abstract_unit", __FILE__)
2
2
  include ActsAsIndexed
3
3
 
4
- class ConfigurationTest < ActiveSupport::TestCase
5
-
4
+ class ConfigurationTest < ActiveSupport::TestCase
5
+
6
6
  def test_default_index_file_should_be_set
7
- assert_equal [Rails.root, 'index'], config.index_file
7
+ assert_equal Rails.root.join('index'), config.index_file
8
8
  end
9
-
9
+
10
10
  def test_default_index_file_depth_should_be_set
11
11
  assert_equal 3, config.index_file_depth
12
12
  end
@@ -14,26 +14,26 @@ class ConfigurationTest < ActiveSupport::TestCase
14
14
  def test_default_min_word_size_should_be_set
15
15
  assert_equal 3, config.min_word_size
16
16
  end
17
-
17
+
18
18
  def test_index_file_should_be_writeable
19
19
  config.index_file = [Rails.root, 'my_index']
20
- assert_equal [Rails.root, 'my_index'], config.index_file
20
+ assert_equal Rails.root.join('my_index'), config.index_file
21
21
  end
22
-
22
+
23
23
  def test_index_file_depth_should_be_writeable
24
24
  config.index_file_depth = 5
25
25
  assert_equal 5, config.index_file_depth
26
26
  end
27
-
27
+
28
28
  def test_index_file_depth_should_raise_on_lower_than_1_value
29
29
  assert_nothing_raised(ArgumentError) { config.index_file_depth = 1 }
30
-
30
+
31
31
  e = assert_raise(ArgumentError) { config.index_file_depth = 0 }
32
32
  assert_equal 'index_file_depth cannot be less than one (1)', e.message
33
-
33
+
34
34
  assert_raise(ArgumentError) { config.index_file_depth = -12 }
35
35
  end
36
-
36
+
37
37
  def test_min_word_size_should_be_writeable
38
38
  config.min_word_size = 7
39
39
  assert_equal 7, config.min_word_size
@@ -47,11 +47,11 @@ class ConfigurationTest < ActiveSupport::TestCase
47
47
 
48
48
  assert_raise(ArgumentError) { config.min_word_size = -12 }
49
49
  end
50
-
50
+
51
51
  private
52
-
52
+
53
53
  def config
54
54
  @config ||=ActsAsIndexed::Configuration.new
55
55
  end
56
-
56
+
57
57
  end
@@ -4,28 +4,34 @@ wikipedia_article_1:
4
4
  id: 1
5
5
  title: Body Count (video game)
6
6
  body: Body Count is a 1994 First-person shooter for the Sega Mega Drive. It is one of the few games that make use of the Menacer light gun and the Mega Mouse. \n In the U.S. the game was released on the Sega Channel.
7
-
7
+ visible: 1
8
+
8
9
  wikipedia_article_2:
9
10
  id: 2
10
11
  title: Julien Ellis
11
12
  body: Julien Ellis is a ice hockey goalie, born in Sorel, Quebec on January 27, 1986. He is currently 6'0" and weighs approximately 177 pounds. He wears number 34 and catches left. \n Julien played his entire junior hockey career in the QMJHL with the Shawinigan Cataractes. He was there from 2002 through the 2006 season, and played a total of 173 regular season games for them. During his time there, he recorded eight shutouts, as well as a career high .921 save percentage and 2.41 goals against average. \n Julien was chosen in round six of the 2004 NHL Entry Draft by the Vancouver Canucks, making him 189th overall pick and the 5th pick for Vancouver. \n His 2006-07 season was spent with the Victoria Salmon Kings of the ECHL, where he played 37 games and made 1,212 saves. Julien was called up the the Manitoba Moose of the AHL several times, where he played eight games.
13
+ visible: 1
12
14
 
13
15
  wikipedia_article_3:
14
16
  id: 3
15
17
  title: Tuen Mun River
16
18
  body: The Tuen Mun River is a river in Tuen Mun, New Territories, Hong Kong. It has many tributaries, with major ones coming from Lam Tei, Kau Keng Shan, Hung Shui Hang and Nai Wai. It flows south, splitting Tuen Mun into a west side and an east side. It eventually feeds into the Tuen Mun Typhoon Shelter, which is part of Castle Peak Bay.
19
+ visible: 0
17
20
 
18
21
  wikipedia_article_4:
19
22
  id: 4
20
23
  title: So Happily Unsatisfied
21
24
  body: So Happily Unsatisfied is an album that was recorded by the band Nine Days. It was intended to be the follow-up to their successful major-label debut, The Madding Crowd from 2000. The release date of the album was repeatedly delayed by Sony until the band was ultimately dropped. In the interim, the album had leaked onto the internet. The band has also put the whole album on their official website for the public to download.
25
+ visible: 1
22
26
 
23
27
  wikipedia_article_5:
24
28
  id: 5
25
29
  title: SS Cornhusker State (T-ACS-6)
26
30
  body: SS Cornhusker State (T-ACS-6) is a crane ship in ready reserve for the United States Navy. She is stationed at Cheatham Annex in Williamsburg, Virginia and is in ready reserve under the Military Sealift Command. The ship was named for the state of Nebraska, which is also known as the Cornhusker State. \n The ship was built by the Bath Iron Works. Her keel was laid on 27 November 1967, launched on 2 November 1968, and delivered 20 June 1969 as CV Stag Hound (MA 207). \n Stag Hound was acquired by the US Navy from the Maritime Administration in 1986 and was converted throughout 1987. She re-entered service as Cornhusker State on 12 March 1988, and has been in ready reserve since 1993.
31
+ visible: 0
27
32
 
28
33
  article_similar_to_5:
29
34
  id: 6
30
35
  title: An article I made up by myself!
31
- body: crane crane big ship foo
36
+ body: crane crane big ship foo
37
+ visible: 1
data/test/schema.rb CHANGED
@@ -2,5 +2,6 @@ ActiveRecord::Schema.define :version => 0 do
2
2
  create_table :posts, :force => true do |t|
3
3
  t.column :title, :string
4
4
  t.column :body, :text
5
+ t.column :visible, :boolean
5
6
  end
6
7
  end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/abstract_unit'
1
+ require File.expand_path("../abstract_unit", __FILE__)
2
2
  include ActsAsIndexed
3
3
 
4
4
  class SearchAtomTest < ActiveSupport::TestCase
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/abstract_unit'
1
+ require File.expand_path("../abstract_unit", __FILE__)
2
2
  include ActsAsIndexed
3
3
 
4
4
  class SearchIndexTest < ActiveSupport::TestCase
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_indexed
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 1
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 6
9
- - 2
10
- version: 0.6.2
9
+ - 3
10
+ version: 0.6.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Douglas F Shearer
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-06-11 00:00:00 +01:00
18
+ date: 2010-07-05 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies: []
21
21