summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2014-12-20 08:10:05 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2014-12-20 08:10:05 +0000
commit71172e241169933bbec364afe9268b4b36d9045f (patch)
tree3d90e1dbde3f0671a1efdf741667221e971cc1c6
parentc53eb532c2545fd26a72e5d6ac2883d410584cc7 (diff)
downloadredmine-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.rb65
-rw-r--r--lib/redmine/search.rb68
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)