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 |
---|