Changeset 320
- Timestamp:
- 02/18/08 20:36:05 (8 months ago)
- Files:
-
- trunk/demo/test/unit/content_test.rb (modified) (1 diff)
- trunk/demo/test/unit/multi_index_test.rb (added)
- trunk/demo/test/unit/remote_index_test.rb (modified) (2 diffs)
- trunk/demo/test/unit/shared_index1_test.rb (modified) (4 diffs)
- trunk/plugin/acts_as_ferret/lib/act_methods.rb (modified) (1 diff)
- trunk/plugin/acts_as_ferret/lib/acts_as_ferret.rb (modified) (11 diffs)
- trunk/plugin/acts_as_ferret/lib/class_methods.rb (modified) (2 diffs)
- trunk/plugin/acts_as_ferret/lib/ferret_find_methods.rb (added)
- trunk/plugin/acts_as_ferret/lib/index.rb (modified) (1 diff)
- trunk/plugin/acts_as_ferret/lib/instance_methods.rb (modified) (1 diff)
- trunk/plugin/acts_as_ferret/lib/local_index.rb (modified) (2 diffs)
- trunk/plugin/acts_as_ferret/lib/multi_index.rb (modified) (3 diffs)
- trunk/plugin/acts_as_ferret/lib/shared_index_class_methods.rb (deleted)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/demo/test/unit/content_test.rb
r319 r320 515 515 assert_equal @another_content.id, contents_from_ferret.first.id 516 516 517 # deprecated options, still supported518 contents_from_ferret = Content.find_with_ferret('title', :num_docs => 1, :first_doc => 1)519 assert_equal 1, contents_from_ferret.size520 assert_equal @another_content.id, contents_from_ferret.first.id521 522 517 end 523 518 trunk/demo/test/unit/remote_index_test.rb
r308 r320 8 8 @srv = ActsAsFerret::RemoteIndex.new :remote => 'druby://localhost:99999', :raise_drb_errors => true 9 9 assert_raise DRb::DRbConnError do 10 @srv.find_id _by_contents 'some query'10 @srv.find_ids 'some query' 11 11 end 12 12 end … … 14 14 def test_does_not_raise_drb_errors 15 15 @srv = ActsAsFerret::RemoteIndex.new :remote => 'druby://localhost:99999', :raise_drb_errors => false 16 total_hits, results = @srv.find_id _by_contents( 'some query' )16 total_hits, results = @srv.find_ids( 'some query' ) 17 17 assert_equal 0, total_hits 18 18 assert results.empty? trunk/demo/test/unit/shared_index1_test.rb
r319 r320 8 8 end 9 9 10 def test_lazy_loading _shared_index11 results = SharedIndex1.find_with_ferret 'first', :lazy => [ :name ], :models => :all10 def test_lazy_loading 11 results = ActsAsFerret::find 'first', 'shared', :lazy => [ :name ] 12 12 assert_equal 2, results.size 13 13 found_lazy_result = false … … 33 33 assert_equal 1, result.size, result.inspect 34 34 assert_equal shared_index1s(:first), result.first 35 36 result = SharedIndex1.find_with_ferret("name:first", :models => [SharedIndex1])37 assert_equal 1, result.size38 assert_equal shared_index1s(:first), result.first39 35 end 40 36 … … 45 41 end 46 42 47 def test_find_with_ ferret_all_classes48 result = SharedIndex1.find_with_ferret("first", :models => :all)43 def test_find_with_index_name 44 result = ActsAsFerret::find("first", 'shared') 49 45 assert_equal 2, result.size 50 46 assert result.include?(shared_index1s(:first)) 51 47 assert result.include?(shared_index2s(:first)) 48 end 52 49 53 result = SharedIndex1.find_with_ferret("name:first", :models => [SharedIndex2]) 50 def test_find_with_class_list 51 result = ActsAsFerret::find("name:first", [SharedIndex1, SharedIndex2]) 54 52 assert_equal 2, result.size 55 53 assert result.include?(shared_index1s(:first)) 56 54 assert result.include?(shared_index2s(:first)) 57 58 55 end 59 56 … … 63 60 64 61 def test_destroy 65 result = SharedIndex1.find_with_ferret("first OR another", :models => :all)62 result = ActsAsFerret::find("first OR another", 'shared') 66 63 assert_equal 4, result.size 67 64 SharedIndex1.destroy(shared_index1s(:first)) 68 result = SharedIndex1.find_with_ferret("first OR another", :models => :all)65 result = ActsAsFerret::find("first OR another", 'shared') 69 66 assert_equal 3, result.size 70 67 shared_index2s(:first).destroy 71 result = SharedIndex1.find_with_ferret("first OR another", :models => :all)68 result = ActsAsFerret::find("first OR another", 'shared') 72 69 assert_equal 2, result.size 73 70 end trunk/plugin/acts_as_ferret/lib/act_methods.rb
r318 r320 26 26 # The default is RAILS_ROOT/index/RAILS_ENV/CLASSNAME. 27 27 # The index directory will be created if it doesn't exist. 28 #29 # single_index:: set this to true to let this class use a Ferret30 # index that is shared by all classes having :single_index set to true.31 # :store_class_name is set to true implicitly, as well as index_dir, so32 # don't bother setting these when using this option. the shared index33 # will be located in index/<RAILS_ENV>/shared .34 #35 # store_class_name:: to make search across multiple models (with either36 # single_index or the multi_search method) useful, set37 # this to true. the model class name will be stored in a keyword field38 # named class_name39 28 # 40 29 # reindex_batch_size:: reindexing is done in batches of this size, default is 1000 trunk/plugin/acts_as_ferret/lib/acts_as_ferret.rb
r319 r320 25 25 require 'ferret' 26 26 27 require 'ferret_find_methods' 27 28 require 'blank_slate' 28 29 require 'bulk_indexer' … … 118 119 index_definition = { 119 120 :index_dir => "#{ActsAsFerret::index_dir}/#{name}", 120 :store_class_name => false,121 121 :name => name, 122 122 :single_index => false, … … 146 146 # be overwritten by the user: 147 147 index_definition[:ferret].update( 148 :key => (index_definition[:store_class_name] ? [:id, :class_name] : :id),148 :key => [:id, :class_name], 149 149 :path => index_definition[:index_dir], 150 150 :auto_flush => true, # slower but more secure in terms of locking problems TODO disable when running in drb mode? … … 180 180 end 181 181 index.register_class(clazz, options) 182 if index.shared?183 # make sure all models using this index get proper class methods184 index.index_definition[:registered_models].each do |clazz|185 clazz.extend SharedIndexClassMethods unless clazz.extended_by.include?(ActsAsFerret::SharedIndexClassMethods)186 end187 end188 182 return index 189 183 end … … 197 191 # count hits for a query with multiple models 198 192 def self.total_hits(query, models, options = {}) 199 get_index_for(*models).total_hits query, options.merge( :models => models )193 find_index(models).total_hits query, options.merge( :models => models ) 200 194 end 201 195 202 196 # find ids of records with multiple models 203 def self.find_ids(query, models, options = {}) 204 get_index_for(*models).find_ids query, options.merge( :models => models ) 205 end 206 207 def self.find(query, models, options = {}, ar_options = {}) 197 # TODO pagination logic? 198 def self.find_ids(query, models, options = {}, &block) 199 find_index(models).find_ids query, options.merge( :models => models ), &block 200 end 201 202 def self.find_index(models_or_index_name) 203 case models_or_index_name 204 when Symbol 205 get_index models_or_index_name 206 when String 207 get_index models_or_index_name.to_sym 208 #when Array 209 # get_index_for models_or_index_name 210 else 211 get_index_for models_or_index_name 212 end 213 end 214 215 def self.find(query, models_or_index_name, options = {}, ar_options = {}) 208 216 # TODO generalize local/remote index so we can remove the workaround below 209 217 # (replace logic in class_methods#find_with_ferret) 210 if Class === models or models.size == 1 211 return (Class === models ? models : models.shift).find_with_ferret query, options, ar_options 212 end 213 index = get_index_for(*models) 218 # maybe put pagination stuff in a module to be included by all index 219 # implementations 220 models = [ models_or_index_name ] if Class === models_or_index_name 221 if models && models.size == 1 222 return models.shift.find_with_ferret query, options, ar_options 223 end 224 index = find_index(models_or_index_name) 214 225 multi = (MultiIndex === index or index.shared?) 215 226 if options[:per_page] … … 254 265 # of all models, is returned. 255 266 def self.get_index_for(*classes) 267 classes.flatten! 256 268 raise ArgumentError.new("no class specified") unless classes.any? 257 269 classes.map!(&:constantize) unless Class === classes.first … … 303 315 key = indexes.map{ |i| i.index_name.to_s }.sort.join(",") 304 316 ActsAsFerret::multi_indexes[key] ||= MultiIndex.new(indexes) 317 end 318 319 # check for per-model conditions and return these if provided 320 def self.conditions_for_model(model, conditions = {}) 321 if Hash === conditions 322 key = model.name.underscore.to_sym 323 conditions = conditions[key] 324 end 325 return conditions 305 326 end 306 327 … … 328 349 id_arrays.each do |model, id_array| 329 350 next if id_array.empty? 330 model_class = begin 331 model.constantize 332 rescue 333 raise "Please use ':store_class_name => true' if you want to use multi_search.\n#{$!}" 334 end 335 336 # check for per-model conditions and take these if provided 337 if conditions = find_options[:conditions] 338 key = model.underscore.to_sym 339 conditions = conditions[key] if Hash === conditions 340 end 351 model_class = model.constantize 341 352 342 353 # merge conditions 354 conditions = conditions_for_model model_class, find_options[:conditions] 343 355 conditions = combine_conditions([ "#{model_class.table_name}.#{model_class.primary_key} in (?)", 344 356 id_array.keys ], 345 357 conditions) 346 347 358 348 359 # check for include association that might only exist on some models in case of multi_search … … 358 369 # fetch 359 370 tmp_result = model_class.find(:all, find_options.merge(:conditions => conditions, 360 :include => filtered_include_options))371 :include => filtered_include_options)) 361 372 362 373 # set scores and rank … … 365 376 end 366 377 # merge with result array 367 result .concattmp_result378 result += tmp_result 368 379 end 369 380 … … 428 439 fi.add_field(:id, :store => :yes, :index => :untokenized) 429 440 # class_name 430 fi.add_field(:class_name, :store => :yes, :index => :untokenized) if index_definition[:store_class_name]441 fi.add_field(:class_name, :store => :yes, :index => :untokenized) 431 442 432 443 # other fields trunk/plugin/acts_as_ferret/lib/class_methods.rb
r319 r320 177 177 end 178 178 179 total_hits, result = find_records_lazy_or_not q, options, find_options179 total_hits, result = aaf_index.find_records q, options.merge(:models => [self]), find_options 180 180 logger.debug "Query: #{q}\ntotal hits: #{total_hits}, results delivered: #{result.size}" 181 181 SearchResults.new(result, total_hits, options[:page], options[:per_page]) … … 299 299 id_arrays.each do |model, id_array| 300 300 next if id_array.empty? 301 begin 302 model = model.constantize 303 # merge conditions 304 conditions = ActsAsFerret::combine_conditions([ "#{model.table_name}.#{model.primary_key} in (?)", id_array.keys ], 305 find_options[:conditions]) 306 opts = find_options.merge :conditions => conditions 307 opts.delete :limit; opts.delete :offset 308 count += model.count opts 309 rescue TypeError 310 raise "#{model} must use :store_class_name option if you want to use multi_search against it.\n#{$!}\n#{$!.backtrace.join("\n")}" 311 end 301 model = model.constantize 302 # merge conditions 303 conditions = ActsAsFerret::combine_conditions([ "#{model.table_name}.#{model.primary_key} in (?)", id_array.keys ], 304 find_options[:conditions]) 305 opts = find_options.merge :conditions => conditions 306 opts.delete :limit; opts.delete :offset 307 count += model.count opts 312 308 end 313 309 count trunk/plugin/acts_as_ferret/lib/index.rb
r319 r320 37 37 logger.info "register class #{clazz} with index #{index_name}" 38 38 index_definition[:registered_models] << clazz 39 index_definition[:store_class_name] = true if shared?40 39 41 40 # merge fields from this acts_as_ferret call with predefined fields trunk/plugin/acts_as_ferret/lib/instance_methods.rb
r317 r320 123 123 logger.debug "creating doc for class: #{self.class.name}, id: #{self.id}" 124 124 returning Ferret::Document.new do |doc| 125 # store the id of each item125 # store the id and class name of each item 126 126 doc[:id] = self.id 127 128 # store the class name if configured to do so 129 doc[:class_name] = self.class.name if aaf_configuration[:store_class_name] 127 doc[:class_name] = self.class.name 130 128 131 129 # iterate through the fields and add them to the document trunk/plugin/acts_as_ferret/lib/local_index.rb
r319 r320 1 1 module ActsAsFerret 2 2 class LocalIndex < AbstractIndex 3 include MoreLikeThis::IndexMethods3 include FerretFindMethods, MoreLikeThis::IndexMethods 4 4 5 5 def initialize(index_name) … … 75 75 end 76 76 77 # Queries the Ferret index to retrieve model class, id, score and the 78 # values of any fields stored in the index for each hit. 79 # If a block is given, these are yielded and the number of total hits is 80 # returned. Otherwise [total_hits, result_array] is returned. 81 def find_ids(query, options = {}) 82 result = [] 83 index = ferret_index 84 logger.debug "query: #{ferret_index.process_query query}" if logger.debug? 85 stored_fields = determine_stored_fields options 86 87 total_hits = index.search_each(query, options) do |hit, score| 88 doc = index[hit] 89 model = index_definition[:store_class_name] ? doc[:class_name] : index_definition[:class_name] 90 # fetch stored fields if lazy loading 91 data = extract_stored_fields(doc, stored_fields) 92 if block_given? 93 yield model, doc[:id], score, data 94 else 95 result << { :model => model, :id => doc[:id], :score => score, :data => data } 96 end 97 end 98 #logger.debug "id_score_model array: #{result.inspect}" 99 return block_given? ? total_hits : [total_hits, result] 77 def searcher 78 ferret_index 100 79 end 101 80 trunk/plugin/acts_as_ferret/lib/multi_index.rb
r319 r320 3 3 # This class can be used to search multiple physical indexes at once. 4 4 class MultiIndex 5 include FerretFindMethods 5 6 attr_accessor :logger 6 7 … … 18 19 end 19 20 20 def find_records(query, options, ar_options) 21 result = [] 22 23 rank = 0 24 if options[:lazy] 25 logger.warn "ar_options #{ar_options} are ignored because :lazy => true" unless ar_options.empty? 26 total_hits = find_ids(query, options) do |model, id, score, data| 27 result << FerretResult.new(model, id, score, rank += 1, data) 28 end 29 else 30 id_arrays = {} 31 32 limit = options.delete(:limit) 33 offset = options.delete(:offset) || 0 34 options[:limit] = :all 35 total_hits = find_ids(query, options) do |model, id, score, data| 36 id_arrays[model] ||= {} 37 id_arrays[model][id] = [ rank += 1, score ] 38 end 39 result = ActsAsFerret::retrieve_records(id_arrays, ar_options) 40 total_hits = result.size if ar_options[:conditions] 41 if limit && limit != :all 42 result = result[offset..limit+offset-1] 43 end 21 def ar_find(query, options = {}, ar_options = {}) 22 limit = options.delete(:limit) 23 offset = options.delete(:offset) || 0 24 options[:limit] = :all 25 total_hits, result = super query, options, ar_options 26 total_hits = result.size if ar_options[:conditions] 27 if limit && limit != :all 28 result = result[offset..limit+offset-1] 44 29 end 45 30 [total_hits, result] 46 31 end 47 32 48 # Queries multiple Ferret indexes to retrieve model class, id and score for49 # each hit. Use the models parameter to give the list of models to search.50 # If a block is given, model, id and score are yielded and the number of51 # total hits is returned. Otherwise [total_hits, result_array] is returned.52 def find_ids(query, options = {})53 result = []54 stored_fields = determine_stored_fields options55 total_hits = search_each(query, options) do |hit, score|56 doc = searcher[hit]57 raise "':store_class_name => true' is required to make searching across multiple models work!" if doc[:class_name].blank?58 # fetch stored fields if lazy loading59 data = extract_stored_fields(doc, stored_fields)60 if block_given?61 yield doc[:class_name], doc[:id], score, doc, data62 else63 result << { :model => doc[:class_name], :id => doc[:id], :score => score, :data => data }64 end65 end66 return block_given? ? total_hits : [ total_hits, result ]67 end68 69 33 def determine_stored_fields(options) 70 34 return nil unless options.has_key?(:lazy) … … 104 68 #end 105 69 #true 70 end 71 72 def shared? 73 false 106 74 end 107 75
