2011-04-05 16:55:19 +04:00
# Redmine - project management software
2012-04-02 17:50:12 +04:00
# Copyright (C) 2006-2012 Jean-Philippe Lang
2007-09-27 21:28:22 +04:00
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
2012-04-02 17:50:12 +04:00
#
2007-09-27 21:28:22 +04:00
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
2012-04-02 17:50:12 +04:00
#
2007-09-27 21:28:22 +04:00
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module Redmine
module Acts
module Searchable
2012-04-02 17:50:12 +04:00
def self . included ( base )
2007-09-27 21:28:22 +04:00
base . extend ClassMethods
2012-04-02 17:50:12 +04:00
end
2007-09-27 21:28:22 +04:00
module ClassMethods
2008-07-20 21:26:07 +04:00
# Options:
# * :columns - a column or an array of columns to search
# * :project_key - project foreign key (default to project_id)
# * :date_column - name of the datetime column (default to created_on)
# * :sort_order - name of the column used to sort results (default to :date_column or created_on)
# * :permission - permission required to search the model (default to :view_"objects")
2007-09-27 21:28:22 +04:00
def acts_as_searchable ( options = { } )
return if self . included_modules . include? ( Redmine :: Acts :: Searchable :: InstanceMethods )
2012-04-02 17:50:12 +04:00
2007-09-27 21:28:22 +04:00
cattr_accessor :searchable_options
self . searchable_options = options
if searchable_options [ :columns ] . nil?
raise 'No searchable column defined.'
elsif ! searchable_options [ :columns ] . is_a? ( Array )
searchable_options [ :columns ] = [ ] << searchable_options [ :columns ]
end
2008-07-20 21:31:11 +04:00
searchable_options [ :project_key ] || = " #{ table_name } .project_id "
searchable_options [ :date_column ] || = " #{ table_name } .created_on "
2008-07-20 21:26:07 +04:00
searchable_options [ :order_column ] || = searchable_options [ :date_column ]
2012-04-02 17:50:12 +04:00
2007-12-14 21:54:55 +03:00
# Should we search custom fields on this model ?
searchable_options [ :search_custom_fields ] = ! reflect_on_association ( :custom_values ) . nil?
2012-04-02 17:50:12 +04:00
2007-09-27 21:28:22 +04:00
send :include , Redmine :: Acts :: Searchable :: InstanceMethods
end
end
module InstanceMethods
def self . included ( base )
base . extend ClassMethods
end
module ClassMethods
2008-07-20 21:26:07 +04:00
# Searches the model for the given tokens
2008-05-18 20:15:22 +04:00
# projects argument can be either nil (will search all projects), a project or an array of projects
2008-07-20 21:26:07 +04:00
# Returns the results and the results count
2008-07-10 16:31:49 +04:00
def search ( tokens , projects = nil , options = { } )
2011-12-17 22:23:53 +04:00
if projects . is_a? ( Array ) && projects . empty?
# no results
return [ [ ] , 0 ]
end
2011-04-05 16:55:19 +04:00
# TODO: make user an argument
user = User . current
2007-09-27 21:28:22 +04:00
tokens = [ ] << tokens unless tokens . is_a? ( Array )
2008-05-18 20:15:22 +04:00
projects = [ ] << projects unless projects . nil? || projects . is_a? ( Array )
2012-04-02 17:50:12 +04:00
2007-09-27 21:28:22 +04:00
find_options = { :include = > searchable_options [ :include ] }
2008-07-20 21:26:07 +04:00
find_options [ :order ] = " #{ searchable_options [ :order_column ] } " + ( options [ :before ] ? 'DESC' : 'ASC' )
2012-04-02 17:50:12 +04:00
2008-07-20 21:26:07 +04:00
limit_options = { }
limit_options [ :limit ] = options [ :limit ] if options [ :limit ]
if options [ :offset ]
limit_options [ :conditions ] = " ( #{ searchable_options [ :date_column ] } " + ( options [ :before ] ? '<' : '>' ) + " ' #{ connection . quoted_date ( options [ :offset ] ) } ') "
end
2012-04-02 17:50:12 +04:00
2007-10-15 20:53:39 +04:00
columns = searchable_options [ :columns ]
2008-07-21 23:19:06 +04:00
columns = columns [ 0 .. 0 ] if options [ :titles_only ]
2012-04-02 17:50:12 +04:00
2007-12-14 21:54:55 +03:00
token_clauses = columns . collect { | column | " (LOWER( #{ column } ) LIKE ?) " }
2012-04-02 17:50:12 +04:00
2007-12-14 21:54:55 +03:00
if ! options [ :titles_only ] && searchable_options [ :search_custom_fields ]
searchable_custom_field_ids = CustomField . find ( :all ,
:select = > 'id' ,
:conditions = > { :type = > " #{ self . name } CustomField " ,
:searchable = > true } ) . collect ( & :id )
if searchable_custom_field_ids . any?
custom_field_sql = " #{ table_name } .id IN (SELECT customized_id FROM #{ CustomValue . table_name } " +
" WHERE customized_type=' #{ self . name } ' AND customized_id= #{ table_name } .id AND LOWER(value) LIKE ? " +
" AND #{ CustomValue . table_name } .custom_field_id IN ( #{ searchable_custom_field_ids . join ( ',' ) } )) "
token_clauses << custom_field_sql
end
end
2012-04-02 17:50:12 +04:00
2008-01-21 21:52:45 +03:00
sql = ( [ '(' + token_clauses . join ( ' OR ' ) + ')' ] * tokens . size ) . join ( options [ :all_words ] ? ' AND ' : ' OR ' )
2012-04-02 17:50:12 +04:00
2010-02-17 23:20:51 +03:00
find_options [ :conditions ] = [ sql , * ( tokens . collect { | w | " % #{ w . downcase } % " } * token_clauses . size ) . sort ]
2012-04-02 17:50:12 +04:00
2011-04-05 16:55:19 +04:00
scope = self
2008-05-18 20:15:22 +04:00
project_conditions = [ ]
2011-04-05 16:55:19 +04:00
if searchable_options . has_key? ( :permission )
project_conditions << Project . allowed_to_condition ( user , searchable_options [ :permission ] || :view_project )
elsif respond_to? ( :visible )
scope = scope . visible ( user )
else
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. "
project_conditions << Project . allowed_to_condition ( user , " view_ #{ self . name . underscore . pluralize } " . to_sym )
end
# TODO: use visible scope options instead
2008-05-18 20:15:22 +04:00
project_conditions << " #{ searchable_options [ :project_key ] } IN ( #{ projects . collect ( & :id ) . join ( ',' ) } ) " unless projects . nil?
2011-04-05 16:55:19 +04:00
project_conditions = project_conditions . empty? ? nil : project_conditions . join ( ' AND ' )
2012-04-02 17:50:12 +04:00
2008-07-20 21:26:07 +04:00
results = [ ]
results_count = 0
2012-01-01 22:23:45 +04:00
scope = scope . scoped ( { :conditions = > project_conditions } ) . scoped ( find_options )
results_count = scope . count ( :all )
results = scope . find ( :all , limit_options )
2008-07-20 21:26:07 +04:00
[ results , results_count ]
2007-09-27 21:28:22 +04:00
end
end
end
end
end
end