##// END OF EJS Templates
Updates comments for r13811....
Jean-Philippe Lang -
r13430:88ec93ab747a
parent child
Show More
@@ -1,167 +1,167
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module Redmine
19 19 module Acts
20 20 module Searchable
21 21 def self.included(base)
22 22 base.extend ClassMethods
23 23 end
24 24
25 25 module ClassMethods
26 26 # Adds the search methods to the class.
27 27 #
28 28 # Options:
29 29 # * :columns - a column or an array of columns to search
30 30 # * :project_key - project foreign key (default to project_id)
31 31 # * :date_column - name of the datetime column used to sort results (default to :created_on)
32 32 # * :permission - permission required to search the model
33 33 # * :scope - scope used to search results
34 34 # * :preload - associations to preload when loading results for display
35 35 def acts_as_searchable(options = {})
36 36 return if self.included_modules.include?(Redmine::Acts::Searchable::InstanceMethods)
37 37 options.assert_valid_keys(:columns, :project_key, :date_column, :permission, :scope, :preload)
38 38
39 39 cattr_accessor :searchable_options
40 40 self.searchable_options = options
41 41
42 42 if searchable_options[:columns].nil?
43 43 raise 'No searchable column defined.'
44 44 elsif !searchable_options[:columns].is_a?(Array)
45 45 searchable_options[:columns] = [] << searchable_options[:columns]
46 46 end
47 47
48 48 searchable_options[:project_key] ||= "#{table_name}.project_id"
49 49 searchable_options[:date_column] ||= :created_on
50 50
51 51 # Should we search custom fields on this model ?
52 52 searchable_options[:search_custom_fields] = !reflect_on_association(:custom_values).nil?
53 53
54 54 send :include, Redmine::Acts::Searchable::InstanceMethods
55 55 end
56 56 end
57 57
58 58 module InstanceMethods
59 59 def self.included(base)
60 60 base.extend ClassMethods
61 61 end
62 62
63 63 module ClassMethods
64 64 # Searches the model for the given tokens and user visibility.
65 65 # The projects argument can be either nil (will search all projects), a project or an array of projects.
66 66 # Returns an array that contains the rank and id of all results.
67 # In current implementation, the rank is the record timestamp.
67 # In current implementation, the rank is the record timestamp converted as an integer.
68 68 #
69 69 # Valid options:
70 70 # * :titles_only - searches tokens in the first searchable column only
71 71 # * :all_words - searches results that match all token
72 72 # * :limit - maximum number of results to return
73 73 #
74 74 # Example:
75 75 # Issue.search_result_ranks_and_ids("foo")
76 # # => [[Tue, 26 Jun 2007 22:16:00 UTC +00:00, 69], [Mon, 08 Oct 2007 14:31:00 UTC +00:00, 123]]
76 # # => [[1419595329, 69], [1419595622, 123]]
77 77 def search_result_ranks_and_ids(tokens, user=User.current, projects=nil, options={})
78 78 if projects.is_a?(Array) && projects.empty?
79 79 # no results
80 80 return []
81 81 end
82 82
83 83 tokens = [] << tokens unless tokens.is_a?(Array)
84 84 projects = [] << projects if projects.is_a?(Project)
85 85
86 86 columns = searchable_options[:columns]
87 87 columns = columns[0..0] if options[:titles_only]
88 88
89 89 token_clauses = columns.collect {|column| "(#{search_token_match_statement(column)})"}
90 90
91 91 if !options[:titles_only] && searchable_options[:search_custom_fields]
92 92 searchable_custom_fields = CustomField.where(:type => "#{self.name}CustomField", :searchable => true)
93 93 fields_by_visibility = searchable_custom_fields.group_by {|field|
94 94 field.visibility_by_project_condition(searchable_options[:project_key], user, "cfs.custom_field_id")
95 95 }
96 96 # only 1 subquery for all custom fields with the same visibility statement
97 97 fields_by_visibility.each do |visibility, fields|
98 98 ids = fields.map(&:id).join(',')
99 99 sql = "#{table_name}.id IN (SELECT cfs.customized_id FROM #{CustomValue.table_name} cfs" +
100 100 " WHERE cfs.customized_type='#{self.name}' AND cfs.customized_id=#{table_name}.id" +
101 101 " AND cfs.custom_field_id IN (#{ids})" +
102 102 " AND #{search_token_match_statement('cfs.value')}" +
103 103 " AND #{visibility})"
104 104 token_clauses << sql
105 105 end
106 106 end
107 107
108 108 sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ')
109 109
110 110 tokens_conditions = [sql, * (tokens.collect {|w| "%#{w}%"} * token_clauses.size).sort]
111 111
112 112 search_scope(user, projects).
113 113 reorder(searchable_options[:date_column] => :desc, :id => :desc).
114 114 where(tokens_conditions).
115 115 limit(options[:limit]).
116 116 uniq.
117 117 pluck(searchable_options[:date_column], :id).
118 118 # converts timestamps to integers for faster sort
119 119 map {|timestamp, id| [timestamp.to_i, id]}
120 120 end
121 121
122 122 def search_token_match_statement(column, value='?')
123 123 case connection.adapter_name
124 124 when /postgresql/i
125 125 "#{column} ILIKE #{value}"
126 126 else
127 127 "#{column} LIKE #{value}"
128 128 end
129 129 end
130 130 private :search_token_match_statement
131 131
132 132 # Returns the search scope for user and projects
133 133 def search_scope(user, projects)
134 134 scope = (searchable_options[:scope] || self)
135 135 if scope.is_a? Proc
136 136 scope = scope.call
137 137 end
138 138
139 139 if respond_to?(:visible) && !searchable_options.has_key?(:permission)
140 140 scope = scope.visible(user)
141 141 else
142 142 permission = searchable_options[:permission] || :view_project
143 143 scope = scope.where(Project.allowed_to_condition(user, permission))
144 144 end
145 145
146 146 if projects
147 147 scope = scope.where("#{searchable_options[:project_key]} IN (?)", projects.map(&:id))
148 148 end
149 149 scope
150 150 end
151 151 private :search_scope
152 152
153 153 # Returns search results of given ids
154 154 def search_results_from_ids(ids)
155 155 where(:id => ids).preload(searchable_options[:preload]).to_a
156 156 end
157 157
158 158 # Returns search results with same arguments as search_result_ranks_and_ids
159 159 def search_results(*args)
160 160 ranks_and_ids = search_result_ranks_and_ids(*args)
161 161 search_results_from_ids(ranks_and_ids.map(&:last))
162 162 end
163 163 end
164 164 end
165 165 end
166 166 end
167 167 end
General Comments 0
You need to be logged in to leave comments. Login now