Remove dated config.gem
[gitorious:pkong23s-mainline.git] / vendor / plugins / ultrasphinx / lib / ultrasphinx / fields.rb
1
2 module Ultrasphinx
3
4 =begin rdoc
5 This is a special singleton configuration class that stores the index field configurations. Rather than using a magic hash and including relevant behavior in Ultrasphinx::Configure and Ultrasphinx::Search, we unify it here.
6 =end
7
8   class Fields
9     include Singleton
10     include Associations
11     
12     TYPE_MAP = {
13       'string' => 'text', 
14       'text' => 'text', 
15       'integer' => 'integer', 
16       'date' => 'date', 
17       'datetime' => 'date',
18       'timestamp' => 'date',
19       'float' => 'float',
20       'boolean' => 'bool'
21     }
22         
23     attr_accessor :classes, :types
24     
25     def initialize
26       @types = {}
27       @classes = Hash.new([])
28       @groups = []
29     end
30     
31     def groups
32       @groups.compact.sort_by do |string| 
33         string[/= (.*)/, 1]
34       end
35     end
36   
37     def save_and_verify_type(field, new_type, string_sortable, klass)
38       # Smoosh fields together based on their name in the Sphinx query schema
39       field, new_type = field.to_s, TYPE_MAP[new_type.to_s]
40
41       if types[field]
42         # Existing field name; verify its type
43         raise ConfigurationError, "Column type mismatch for #{field.inspect}; was already #{types[field].inspect}, but is now #{new_type.inspect}." unless types[field] == new_type
44         classes[field] = (classes[field] + [klass]).uniq
45
46       else
47         # New field      
48         types[field] = new_type
49         classes[field] = [klass]
50
51         @groups << case new_type
52           when 'integer'
53             "sql_attr_uint = #{field}"
54           when 'float'
55             "sql_attr_float = #{field}"
56           when 'bool'
57             "sql_attr_bool = #{field}"
58           when 'date'
59             "sql_attr_timestamp = #{field}"
60           when 'text' 
61             "sql_attr_str2ordinal = #{field}" if string_sortable
62         end
63       end
64     end
65     
66     def cast(source_string, field)
67       if types[field] == "date"
68         "UNIX_TIMESTAMP(#{source_string})"
69       elsif types[field] == "integer"
70         source_string # "CAST(#{source_string} AS UNSIGNED)"
71       else
72         source_string              
73       end + " AS #{field}"
74     end    
75       
76     def null(field)      
77       case types[field]
78         when 'text'
79           "''"
80         when 'integer', 'float', 'bool'
81           "0"
82         when 'date'
83           "18000" # Midnight on 1/1/1970
84         when nil
85           raise "Field #{field} is missing"
86         else
87           raise "Field #{field} does not have a valid type #{types[field]}."
88       end + " AS #{field}"
89     end
90     
91     def configure(configuration)
92
93       configuration.each do |model, options|        
94
95         klass = model.constantize        
96         save_and_verify_type('class_id', 'integer', nil, klass)
97         save_and_verify_type('class', 'string', nil, klass)
98                 
99         begin
100         
101           # Fields are from the model
102           # We destructively canonicize them back onto the configuration hash
103           options['fields'] = options['fields'].to_a.map do |entry|
104             entry = {'field' => entry} unless entry.is_a? Hash
105
106             extract_table_alias!(entry, klass)
107             extract_field_alias!(entry, klass)
108             
109             unless klass.columns_hash[entry['field']]
110               # XXX I think this is here for migrations
111               Ultrasphinx.say "warning: field #{entry['field']} is not present in #{model}"
112             else
113               save_and_verify_type(entry['as'], klass.columns_hash[entry['field']].type, entry['sortable'], klass)
114               install_facets!(entry, klass)
115             end            
116           end  
117           
118           # Joins are whatever they are in the target       
119           options['include'].to_a.each do |entry|
120             extract_table_alias!(entry, klass)
121             extract_field_alias!(entry, klass)
122             
123             association_model = get_association_model(klass, entry)
124             
125             save_and_verify_type(entry['as'] || entry['field'], association_model.columns_hash[entry['field']].type, entry['sortable'], klass)
126             install_facets!(entry, klass)
127           end  
128           
129           # Regular concats are CHAR, group_concats are BLOB and need to be cast to CHAR
130           options['concatenate'].to_a.each do |entry|
131             extract_table_alias!(entry, klass)
132             save_and_verify_type(entry['as'], 'text', entry['sortable'], klass) 
133             install_facets!(entry, klass)
134           end          
135           
136         rescue ActiveRecord::StatementInvalid
137           Ultrasphinx.say "warning: model #{model} does not exist in the database yet"
138         end  
139       end
140       
141       self
142     end
143     
144     def install_facets!(entry, klass)
145       if entry['facet']
146         save_and_verify_type(entry['as'], 'text', nil, klass) # source must be a string
147         save_and_verify_type("#{entry['as']}_facet", 'integer', nil, klass)
148       end
149       entry
150     end
151     
152     def extract_field_alias!(entry, klass)
153       unless entry['as']    
154         entry['as'] = entry['field'] 
155       end
156     end
157     
158     def extract_table_alias!(entry, klass)
159       unless entry['table_alias']
160         entry['table_alias'] = if entry['field'] and entry['field'].include? "." and entry['association_sql']
161           # This field is referenced by a table alias in association_sql
162           table_alias, entry['field'] = entry['field'].split(".")
163           table_alias
164         elsif get_association(klass, entry)
165           # Refers to the association
166           get_association(klass, entry).name
167         elsif entry['association_sql']
168           # Refers to the association_sql class's table
169           entry['class_name'].constantize.table_name
170         else
171           # Refers to this class
172           klass.table_name
173         end
174       end
175     end
176     
177   end
178 end
179