diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2014-12-20 08:10:05 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2014-12-20 08:10:05 +0000 |
commit | 71172e241169933bbec364afe9268b4b36d9045f (patch) | |
tree | 3d90e1dbde3f0671a1efdf741667221e971cc1c6 | |
parent | c53eb532c2545fd26a72e5d6ac2883d410584cc7 (diff) | |
download | redmine-71172e241169933bbec364afe9268b4b36d9045f.tar.gz redmine-71172e241169933bbec364afe9268b4b36d9045f.zip |
Moved search logic to Redmine::Search (#18631).
git-svn-id: http://svn.redmine.org/redmine/trunk@13769 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r-- | app/controllers/search_controller.rb | 65 | ||||
-rw-r--r-- | lib/redmine/search.rb | 68 |
2 files changed, 85 insertions, 48 deletions
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 13a882a88..ac52f42b6 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -18,12 +18,20 @@ class SearchController < ApplicationController before_filter :find_optional_project + @@search_cache_store ||= ActiveSupport::Cache.lookup_store :memory_store + def index @question = params[:q] || "" @question.strip! @all_words = params[:all_words] ? params[:all_words].present? : true @titles_only = params[:titles_only] ? params[:titles_only].present? : false + # quick jump to an issue + if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i)) + redirect_to issue_path(issue) + return + end + projects_to_search = case params[:scope] when 'all' @@ -36,12 +44,6 @@ class SearchController < ApplicationController @project end - # quick jump to an issue - if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i)) - redirect_to issue_path(issue) - return - end - @object_types = Redmine::Search.available_search_types.dup if projects_to_search.is_a? Project # don't search projects @@ -53,49 +55,18 @@ class SearchController < ApplicationController @scope = @object_types.select {|t| params[t]} @scope = @object_types if @scope.empty? - # extract tokens from the question - # eg. hello "bye bye" => ["hello", "bye bye"] - @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} - # tokens must be at least 2 characters long - @tokens = @tokens.uniq.select {|w| w.length > 1 } - - if !@tokens.empty? - # no more than 5 tokens to search for - @tokens.slice! 5..-1 if @tokens.size > 5 - - limit = 10 + fetcher = Redmine::Search::Fetcher.new( + @question, User.current, @scope, projects_to_search, + :all_words => @all_words, :titles_only => @titles_only + ) - @result_count = 0 - @result_count_by_type = Hash.new {|h,k| h[k] = 0} - ranks_and_ids = [] - - # get all the results ranks and ids - @scope.each do |scope| - klass = scope.singularize.camelcase.constantize - ranks_and_ids_in_scope = klass.search_result_ranks_and_ids(@tokens, User.current, projects_to_search, - :all_words => @all_words, - :titles_only => @titles_only - ) - @result_count_by_type[scope] += ranks_and_ids_in_scope.size - @result_count += ranks_and_ids_in_scope.size - ranks_and_ids += ranks_and_ids_in_scope.map {|r| [scope, r]} - end - @result_pages = Paginator.new @result_count, limit, params['page'] - - # sort results, higher rank and id first - ranks_and_ids.sort! {|a,b| b.last <=> a.last } - ranks_and_ids = ranks_and_ids[@result_pages.offset, limit] || [] - - # load the results to display - results_by_scope = Hash.new {|h,k| h[k] = []} - ranks_and_ids.group_by(&:first).each do |scope, rs| - klass = scope.singularize.camelcase.constantize - results_by_scope[scope] += klass.search_results_from_ids(rs.map(&:last).map(&:last)) - end + if fetcher.tokens.present? + @result_count = fetcher.result_count + @result_count_by_type = fetcher.result_count_by_type + @tokens = fetcher.tokens - @results = ranks_and_ids.map do |scope, r| - results_by_scope[scope].detect {|record| record.id == r.last} - end.compact + @result_pages = Paginator.new @result_count, 10, params['page'] + @results = fetcher.results(@result_pages.offset, @result_pages.per_page) else @question = "" end diff --git a/lib/redmine/search.rb b/lib/redmine/search.rb index 580b1843a..42d3004b2 100644 --- a/lib/redmine/search.rb +++ b/lib/redmine/search.rb @@ -19,7 +19,6 @@ module Redmine module Search mattr_accessor :available_search_types - @@available_search_types = [] class << self @@ -34,6 +33,73 @@ module Redmine end end + class Fetcher + attr_reader :tokens + + def initialize(question, user, scope, projects, options={}) + @user = user + @question = question.strip + @scope = scope + @projects = projects + @options = options + + # extract tokens from the question + # eg. hello "bye bye" => ["hello", "bye bye"] + @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} + # tokens must be at least 2 characters long + @tokens = @tokens.uniq.select {|w| w.length > 1 } + # no more than 5 tokens to search for + @tokens.slice! 5..-1 + end + + def result_count + result_ids.size + end + + def result_count_by_type + ret = Hash.new {|h,k| h[k] = 0} + result_ids.group_by(&:first).each do |scope, ids| + ret[scope] += ids.size + end + ret + end + + def results(offset, limit) + result_ids_to_load = result_ids[offset, limit] || [] + + results_by_scope = Hash.new {|h,k| h[k] = []} + result_ids_to_load.group_by(&:first).each do |scope, scope_and_ids| + klass = scope.singularize.camelcase.constantize + results_by_scope[scope] += klass.search_results_from_ids(scope_and_ids.map(&:last)) + end + + result_ids_to_load.map do |scope, id| + results_by_scope[scope].detect {|record| record.id == id} + end.compact + end + + def result_ids + @ranks_and_ids ||= load_result_ids + end + + private + + def load_result_ids + ret = [] + # get all the results ranks and ids + @scope.each do |scope| + klass = scope.singularize.camelcase.constantize + ranks_and_ids_in_scope = klass.search_result_ranks_and_ids(@tokens, User.current, @projects, @options) + # converts timestamps to integers for faster sort + ret += ranks_and_ids_in_scope.map {|rank, id| [scope, [rank.to_i, id]]} + end + # sort results, higher rank and id first + ret.sort! {|a,b| b.last <=> a.last} + ret.map! {|scope, r| [scope, r.last]} + ret + end + end + module Controller def self.included(base) base.extend(ClassMethods) |