| [1] | 1 | # This is an add-on to the ActiveRecord::Base class. It allows simple searching to be |
|---|
| 2 | # accomplished by using, for example, @movies = Movie.search("text") |
|---|
| 3 | module ActiveRecord |
|---|
| 4 | class Base |
|---|
| 5 | # Allow the user to set the default searchable fields |
|---|
| 6 | def self.searches_on(*args) |
|---|
| 7 | if not args.empty? and args.first != :all |
|---|
| 8 | @searchable_fields = args.collect { |f| f.to_s } |
|---|
| 9 | end |
|---|
| 10 | end |
|---|
| 11 | |
|---|
| 12 | # Return the default set of fields to search on |
|---|
| 13 | def self.searchable_fields(tables = nil, klass = self) |
|---|
| 14 | # If the model has declared what it searches_on, then use that... |
|---|
| 15 | return @searchable_fields unless @searchable_fields.nil? |
|---|
| 16 | |
|---|
| 17 | # ... otherwise, use all text/varchar fields as the default |
|---|
| 18 | fields = [] |
|---|
| 19 | tables ||= [] |
|---|
| 20 | string_columns = klass.columns.select { |c| c.type == :text or c.type == :string } |
|---|
| 21 | fields = string_columns.collect { |c| klass.table_name + "." + c.name } |
|---|
| 22 | |
|---|
| 23 | if not tables.empty? |
|---|
| 24 | tables.each do |table| |
|---|
| 25 | klass = eval table.to_s.classify |
|---|
| 26 | fields += searchable_fields([], klass) |
|---|
| 27 | end |
|---|
| 28 | end |
|---|
| 29 | |
|---|
| 30 | return fields |
|---|
| 31 | end |
|---|
| 32 | |
|---|
| 33 | # Search the movie database for the given parameters: |
|---|
| 34 | # text = a string to search for |
|---|
| 35 | # :only => an array of fields in which to search for the text; |
|---|
| 36 | # default is 'all text or string columns' |
|---|
| 37 | # :except => an array of fields to exclude from the default searchable columns |
|---|
| 38 | # :case => :sensitive or :insensitive |
|---|
| 39 | # :include => an array of tables to include in the joins. Fields that |
|---|
| 40 | # have searchable text will automatically be included in the default |
|---|
| 41 | # set of :search_columns. |
|---|
| 42 | # :join_include => an array of tables to include in the joins, but only |
|---|
| 43 | # for joining. (Searchable fields will not automatically be included.) |
|---|
| 44 | # :conditions => a string of additional conditions (constraints) |
|---|
| 45 | # :offset => paging offset (integer) |
|---|
| 46 | # :limit => number of rows to return (integer) |
|---|
| 47 | # :order => sort order (order_by SQL snippet) |
|---|
| 48 | def self.search(text = nil, options = {}) |
|---|
| 49 | options.assert_valid_keys(:only, :except, :case, :include, |
|---|
| 50 | :join_include, :conditions, :offset, :limit, :order) |
|---|
| 51 | case_insensitive = true unless options[:case] == :sensitive |
|---|
| 52 | |
|---|
| 53 | # The fields to search (default is all text fields) |
|---|
| 54 | fields = options[:only] || searchable_fields(options[:include]) |
|---|
| 55 | fields -= options[:except] if not options[:except].nil? |
|---|
| 56 | |
|---|
| 57 | # Now build the SQL for the search if there is text to search for |
|---|
| 58 | condition_list = [] |
|---|
| 59 | unless text.nil? |
|---|
| 60 | text_condition = if case_insensitive |
|---|
| 61 | fields.collect { |f| "UCASE(#{f}) LIKE #{sanitize('%'+text.upcase+'%')}" }.join " OR " |
|---|
| 62 | else |
|---|
| 63 | fields.collect { |f| "#{f} LIKE #{sanitize('%'+text+'%')}" }.join " OR " |
|---|
| 64 | end |
|---|
| 65 | |
|---|
| 66 | # Add the text search term's SQL to the conditions string unless |
|---|
| 67 | # the text was nil to begin with. |
|---|
| 68 | condition_list << "(" + text_condition + ")" |
|---|
| 69 | end |
|---|
| 70 | condition_list << "#{options[:conditions]}" if options[:conditions] |
|---|
| 71 | conditions = condition_list.join " AND " |
|---|
| 72 | conditions = nil if conditions.empty? |
|---|
| 73 | |
|---|
| 74 | includes = (options[:include] || []) + (options[:join_include] || []) |
|---|
| 75 | includes = nil if includes.size == 0 |
|---|
| 76 | |
|---|
| 77 | find :all, :include => includes, :conditions => conditions, |
|---|
| 78 | :offset => options[:offset], :limit => options[:limit], :order => options[:order] |
|---|
| 79 | end |
|---|
| 80 | end |
|---|
| 81 | end |
|---|