To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / lib / plugins / acts_as_searchable / lib / acts_as_searchable.rb @ 1298:4f746d8966dd
History | View | Annotate | Download (5.89 KB)
| 1 | 1115:433d4f72a19b | Chris | # Redmine - project management software
|
|---|---|---|---|
| 2 | 1295:622f24f53b42 | Chris | # Copyright (C) 2006-2013 Jean-Philippe Lang
|
| 3 | 1115:433d4f72a19b | Chris | #
|
| 4 | # This program is free software; you can redistribute it and/or
|
||
| 5 | # modify it under the terms of the GNU General Public License
|
||
| 6 | # as published by the Free Software Foundation; either version 2
|
||
| 7 | # of the License, or (at your option) any later version.
|
||
| 8 | #
|
||
| 9 | # This program is distributed in the hope that it will be useful,
|
||
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
| 12 | # GNU General Public License for more details.
|
||
| 13 | #
|
||
| 14 | # You should have received a copy of the GNU General Public License
|
||
| 15 | # along with this program; if not, write to the Free Software
|
||
| 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
| 17 | |||
| 18 | module Redmine |
||
| 19 | module Acts |
||
| 20 | module Searchable |
||
| 21 | def self.included(base) |
||
| 22 | base.extend ClassMethods
|
||
| 23 | end
|
||
| 24 | |||
| 25 | module ClassMethods |
||
| 26 | # Options:
|
||
| 27 | # * :columns - a column or an array of columns to search
|
||
| 28 | # * :project_key - project foreign key (default to project_id)
|
||
| 29 | # * :date_column - name of the datetime column (default to created_on)
|
||
| 30 | # * :sort_order - name of the column used to sort results (default to :date_column or created_on)
|
||
| 31 | # * :permission - permission required to search the model (default to :view_"objects")
|
||
| 32 | def acts_as_searchable(options = {}) |
||
| 33 | return if self.included_modules.include?(Redmine::Acts::Searchable::InstanceMethods) |
||
| 34 | |||
| 35 | cattr_accessor :searchable_options
|
||
| 36 | self.searchable_options = options
|
||
| 37 | |||
| 38 | if searchable_options[:columns].nil? |
||
| 39 | raise 'No searchable column defined.'
|
||
| 40 | elsif !searchable_options[:columns].is_a?(Array) |
||
| 41 | searchable_options[:columns] = [] << searchable_options[:columns] |
||
| 42 | end
|
||
| 43 | |||
| 44 | searchable_options[:project_key] ||= "#{table_name}.project_id" |
||
| 45 | searchable_options[:date_column] ||= "#{table_name}.created_on" |
||
| 46 | searchable_options[:order_column] ||= searchable_options[:date_column] |
||
| 47 | |||
| 48 | # Should we search custom fields on this model ?
|
||
| 49 | searchable_options[:search_custom_fields] = !reflect_on_association(:custom_values).nil? |
||
| 50 | |||
| 51 | send :include, Redmine::Acts::Searchable::InstanceMethods |
||
| 52 | end
|
||
| 53 | end
|
||
| 54 | |||
| 55 | module InstanceMethods |
||
| 56 | def self.included(base) |
||
| 57 | base.extend ClassMethods
|
||
| 58 | end
|
||
| 59 | |||
| 60 | module ClassMethods |
||
| 61 | # Searches the model for the given tokens
|
||
| 62 | # projects argument can be either nil (will search all projects), a project or an array of projects
|
||
| 63 | # Returns the results and the results count
|
||
| 64 | def search(tokens, projects=nil, options={}) |
||
| 65 | if projects.is_a?(Array) && projects.empty? |
||
| 66 | # no results
|
||
| 67 | return [[], 0] |
||
| 68 | end
|
||
| 69 | |||
| 70 | # TODO: make user an argument
|
||
| 71 | user = User.current
|
||
| 72 | tokens = [] << tokens unless tokens.is_a?(Array) |
||
| 73 | projects = [] << projects unless projects.nil? || projects.is_a?(Array) |
||
| 74 | |||
| 75 | limit_options = {}
|
||
| 76 | limit_options[:limit] = options[:limit] if options[:limit] |
||
| 77 | |||
| 78 | columns = searchable_options[:columns]
|
||
| 79 | columns = columns[0..0] if options[:titles_only] |
||
| 80 | |||
| 81 | token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"}
|
||
| 82 | |||
| 83 | if !options[:titles_only] && searchable_options[:search_custom_fields] |
||
| 84 | 1295:622f24f53b42 | Chris | searchable_custom_field_ids = CustomField.where(:type => "#{self.name}CustomField", :searchable => true).pluck(:id) |
| 85 | 1115:433d4f72a19b | Chris | if searchable_custom_field_ids.any?
|
| 86 | custom_field_sql = "#{table_name}.id IN (SELECT customized_id FROM #{CustomValue.table_name}" +
|
||
| 87 | " WHERE customized_type='#{self.name}' AND customized_id=#{table_name}.id AND LOWER(value) LIKE ?" +
|
||
| 88 | " AND #{CustomValue.table_name}.custom_field_id IN (#{searchable_custom_field_ids.join(',')}))"
|
||
| 89 | token_clauses << custom_field_sql |
||
| 90 | end
|
||
| 91 | end
|
||
| 92 | |||
| 93 | sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') |
||
| 94 | |||
| 95 | 1295:622f24f53b42 | Chris | tokens_conditions = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort]
|
| 96 | 1115:433d4f72a19b | Chris | |
| 97 | 1295:622f24f53b42 | Chris | scope = self.scoped
|
| 98 | 1115:433d4f72a19b | Chris | project_conditions = [] |
| 99 | if searchable_options.has_key?(:permission) |
||
| 100 | project_conditions << Project.allowed_to_condition(user, searchable_options[:permission] || :view_project) |
||
| 101 | elsif respond_to?(:visible) |
||
| 102 | scope = scope.visible(user) |
||
| 103 | else
|
||
| 104 | ActiveSupport::Deprecation.warn "acts_as_searchable with implicit :permission option is deprecated. Add a visible scope to the #{self.name} model or use explicit :permission option." |
||
| 105 | project_conditions << Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym) |
||
| 106 | end
|
||
| 107 | # TODO: use visible scope options instead
|
||
| 108 | project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? |
||
| 109 | project_conditions = project_conditions.empty? ? nil : project_conditions.join(' AND ') |
||
| 110 | |||
| 111 | results = [] |
||
| 112 | results_count = 0
|
||
| 113 | |||
| 114 | 1295:622f24f53b42 | Chris | scope = scope. |
| 115 | includes(searchable_options[:include]).
|
||
| 116 | order("#{searchable_options[:order_column]} " + (options[:before] ? 'DESC' : 'ASC')). |
||
| 117 | where(project_conditions). |
||
| 118 | where(tokens_conditions) |
||
| 119 | |||
| 120 | results_count = scope.count |
||
| 121 | |||
| 122 | scope_with_limit = scope.limit(options[:limit])
|
||
| 123 | if options[:offset] |
||
| 124 | scope_with_limit = scope_with_limit.where("#{searchable_options[:date_column]} #{options[:before] ? '<' : '>'} ?", options[:offset]) |
||
| 125 | end
|
||
| 126 | results = scope_with_limit.all |
||
| 127 | 1115:433d4f72a19b | Chris | |
| 128 | [results, results_count] |
||
| 129 | end
|
||
| 130 | end
|
||
| 131 | end
|
||
| 132 | end
|
||
| 133 | end
|
||
| 134 | end |