dpickett-thinking-sphinx 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/LICENCE +20 -0
  2. data/README +107 -0
  3. data/lib/thinking_sphinx/active_record/delta.rb +74 -0
  4. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  5. data/lib/thinking_sphinx/active_record/search.rb +57 -0
  6. data/lib/thinking_sphinx/active_record.rb +245 -0
  7. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +34 -0
  8. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +53 -0
  9. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +129 -0
  10. data/lib/thinking_sphinx/association.rb +144 -0
  11. data/lib/thinking_sphinx/attribute.rb +254 -0
  12. data/lib/thinking_sphinx/class_facet.rb +20 -0
  13. data/lib/thinking_sphinx/collection.rb +142 -0
  14. data/lib/thinking_sphinx/configuration.rb +236 -0
  15. data/lib/thinking_sphinx/core/string.rb +22 -0
  16. data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
  17. data/lib/thinking_sphinx/deltas/default_delta.rb +65 -0
  18. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
  19. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
  20. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
  21. data/lib/thinking_sphinx/deltas/delayed_delta.rb +25 -0
  22. data/lib/thinking_sphinx/deltas.rb +22 -0
  23. data/lib/thinking_sphinx/facet.rb +58 -0
  24. data/lib/thinking_sphinx/facet_collection.rb +45 -0
  25. data/lib/thinking_sphinx/field.rb +172 -0
  26. data/lib/thinking_sphinx/index/builder.rb +233 -0
  27. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  28. data/lib/thinking_sphinx/index.rb +432 -0
  29. data/lib/thinking_sphinx/rails_additions.rb +133 -0
  30. data/lib/thinking_sphinx/search.rb +654 -0
  31. data/lib/thinking_sphinx/tasks.rb +128 -0
  32. data/lib/thinking_sphinx.rb +145 -0
  33. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
  34. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
  35. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
  36. data/spec/unit/thinking_sphinx/active_record_spec.rb +256 -0
  37. data/spec/unit/thinking_sphinx/association_spec.rb +247 -0
  38. data/spec/unit/thinking_sphinx/attribute_spec.rb +212 -0
  39. data/spec/unit/thinking_sphinx/collection_spec.rb +14 -0
  40. data/spec/unit/thinking_sphinx/configuration_spec.rb +136 -0
  41. data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
  42. data/spec/unit/thinking_sphinx/field_spec.rb +145 -0
  43. data/spec/unit/thinking_sphinx/index/builder_spec.rb +5 -0
  44. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
  45. data/spec/unit/thinking_sphinx/index_spec.rb +54 -0
  46. data/spec/unit/thinking_sphinx/search_spec.rb +59 -0
  47. data/spec/unit/thinking_sphinx_spec.rb +129 -0
  48. data/tasks/distribution.rb +48 -0
  49. data/tasks/rails.rake +1 -0
  50. data/tasks/testing.rb +86 -0
  51. data/vendor/after_commit/LICENSE +20 -0
  52. data/vendor/after_commit/README +16 -0
  53. data/vendor/after_commit/Rakefile +22 -0
  54. data/vendor/after_commit/init.rb +5 -0
  55. data/vendor/after_commit/lib/after_commit/active_record.rb +91 -0
  56. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
  57. data/vendor/after_commit/lib/after_commit.rb +42 -0
  58. data/vendor/after_commit/test/after_commit_test.rb +53 -0
  59. data/vendor/delayed_job/lib/delayed/job.rb +251 -0
  60. data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
  61. data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
  62. data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
  63. data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
  64. data/vendor/riddle/lib/riddle/client/message.rb +65 -0
  65. data/vendor/riddle/lib/riddle/client/response.rb +84 -0
  66. data/vendor/riddle/lib/riddle/client.rb +619 -0
  67. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
  68. data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
  69. data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
  70. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
  71. data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
  72. data/vendor/riddle/lib/riddle/configuration/section.rb +37 -0
  73. data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
  74. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
  75. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
  76. data/vendor/riddle/lib/riddle/configuration.rb +33 -0
  77. data/vendor/riddle/lib/riddle/controller.rb +44 -0
  78. data/vendor/riddle/lib/riddle.rb +30 -0
  79. metadata +158 -0
@@ -0,0 +1,432 @@
1
+ require 'thinking_sphinx/index/builder'
2
+ require 'thinking_sphinx/index/faux_column'
3
+
4
+ module ThinkingSphinx
5
+ # The Index class is a ruby representation of a Sphinx source (not a Sphinx
6
+ # index - yes, I know it's a little confusing. You'll manage). This is
7
+ # another 'internal' Thinking Sphinx class - if you're using it directly,
8
+ # you either know what you're doing, or messing with things beyond your ken.
9
+ # Enjoy.
10
+ #
11
+ class Index
12
+ attr_accessor :model, :fields, :attributes, :conditions, :groupings,
13
+ :delta_object, :options
14
+
15
+ # Create a new index instance by passing in the model it is tied to, and
16
+ # a block to build it with (optional but recommended). For documentation
17
+ # on the syntax for inside the block, the Builder class is what you want.
18
+ #
19
+ # Quick Example:
20
+ #
21
+ # Index.new(User) do
22
+ # indexes login, email
23
+ #
24
+ # has created_at
25
+ #
26
+ # set_property :delta => true
27
+ # end
28
+ #
29
+ def initialize(model, &block)
30
+ @model = model
31
+ @associations = {}
32
+ @fields = []
33
+ @attributes = []
34
+ @conditions = []
35
+ @groupings = []
36
+ @options = {}
37
+ @delta_object = nil
38
+
39
+ initialize_from_builder(&block) if block_given?
40
+ end
41
+
42
+ def name
43
+ self.class.name(@model)
44
+ end
45
+
46
+ def self.name(model)
47
+ model.name.underscore.tr(':/\\', '_')
48
+ end
49
+
50
+ def to_riddle_for_core(offset, index)
51
+ add_internal_attributes
52
+ link!
53
+
54
+ source = Riddle::Configuration::SQLSource.new(
55
+ "#{name}_core_#{index}", adapter.sphinx_identifier
56
+ )
57
+
58
+ set_source_database_settings source
59
+ set_source_attributes source
60
+ set_source_sql source, offset
61
+ set_source_settings source
62
+
63
+ source
64
+ end
65
+
66
+ def to_riddle_for_delta(offset, index)
67
+ add_internal_attributes
68
+ link!
69
+
70
+ source = Riddle::Configuration::SQLSource.new(
71
+ "#{name}_delta_#{index}", adapter.sphinx_identifier
72
+ )
73
+ source.parent = "#{name}_core_#{index}"
74
+
75
+ set_source_database_settings source
76
+ set_source_attributes source
77
+ set_source_sql source, offset, true
78
+
79
+ source
80
+ end
81
+
82
+ # Link all the fields and associations to their corresponding
83
+ # associations and joins. This _must_ be called before interrogating
84
+ # the index's fields and associations for anything that may reference
85
+ # their SQL structure.
86
+ #
87
+ def link!
88
+ base = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(
89
+ @model, [], nil
90
+ )
91
+
92
+ @fields.each { |field|
93
+ field.model ||= @model
94
+ field.columns.each { |col|
95
+ field.associations[col] = associations(col.__stack.clone)
96
+ field.associations[col].each { |assoc| assoc.join_to(base) }
97
+ }
98
+ }
99
+
100
+ @attributes.each { |attribute|
101
+ attribute.model ||= @model
102
+ attribute.columns.each { |col|
103
+ attribute.associations[col] = associations(col.__stack.clone)
104
+ attribute.associations[col].each { |assoc| assoc.join_to(base) }
105
+ }
106
+ }
107
+ end
108
+
109
+ # Flag to indicate whether this index has a corresponding delta index.
110
+ #
111
+ def delta?
112
+ !@delta_object.nil?
113
+ end
114
+
115
+ def adapter
116
+ @adapter ||= @model.sphinx_database_adapter
117
+ end
118
+
119
+ def prefix_fields
120
+ @fields.select { |field| field.prefixes }
121
+ end
122
+
123
+ def infix_fields
124
+ @fields.select { |field| field.infixes }
125
+ end
126
+
127
+ def index_options
128
+ all_index_options = ThinkingSphinx::Configuration.instance.index_options.clone
129
+ @options.keys.select { |key|
130
+ ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s)
131
+ }.each { |key| all_index_options[key.to_sym] = @options[key] }
132
+ all_index_options
133
+ end
134
+
135
+ def quote_column(column)
136
+ @model.connection.quote_column_name(column)
137
+ end
138
+
139
+ private
140
+
141
+ def utf8?
142
+ self.index_options[:charset_type] == "utf-8"
143
+ end
144
+
145
+ # Does all the magic with the block provided to the base #initialize.
146
+ # Creates a new class subclassed from Builder, and evaluates the block
147
+ # on it, then pulls all relevant settings - fields, attributes, conditions,
148
+ # properties - into the new index.
149
+ #
150
+ # Also creates a CRC attribute for the model.
151
+ #
152
+ def initialize_from_builder(&block)
153
+ builder = Class.new(Builder)
154
+ builder.setup
155
+
156
+ builder.instance_eval &block
157
+
158
+ unless @model.descends_from_active_record?
159
+ stored_class = @model.store_full_sti_class ? @model.name : @model.name.demodulize
160
+ builder.where("#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)} = '#{stored_class}'")
161
+ end
162
+
163
+ set_model = Proc.new { |item| item.model = @model }
164
+
165
+ @fields = builder.fields &set_model
166
+ @attributes = builder.attributes.each &set_model
167
+ @conditions = builder.conditions
168
+ @groupings = builder.groupings
169
+ @delta_object = ThinkingSphinx::Deltas.parse self, builder.properties
170
+ @options = builder.properties
171
+
172
+ is_faceted = Proc.new { |item| item.faceted }
173
+ add_facet = Proc.new { |item| @model.sphinx_facets << item.to_facet }
174
+
175
+ @model.sphinx_facets ||= []
176
+ @fields.select( &is_faceted).each &add_facet
177
+ @attributes.select(&is_faceted).each &add_facet
178
+
179
+ # We want to make sure that if the database doesn't exist, then Thinking
180
+ # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
181
+ # and db:migrate). It's a bit hacky, but I can't think of a better way.
182
+ rescue StandardError => err
183
+ case err.class.name
184
+ when "Mysql::Error", "ActiveRecord::StatementInvalid"
185
+ return
186
+ else
187
+ raise err
188
+ end
189
+ end
190
+
191
+ # Returns all associations used amongst all the fields and attributes.
192
+ # This includes all associations between the model and what the actual
193
+ # columns are from.
194
+ #
195
+ def all_associations
196
+ @all_associations ||= (
197
+ # field associations
198
+ @fields.collect { |field|
199
+ field.associations.values
200
+ }.flatten +
201
+ # attribute associations
202
+ @attributes.collect { |attrib|
203
+ attrib.associations.values
204
+ }.flatten
205
+ ).uniq.collect { |assoc|
206
+ # get ancestors as well as column-level associations
207
+ assoc.ancestors
208
+ }.flatten.uniq
209
+ end
210
+
211
+ # Gets a stack of associations for a specific path.
212
+ #
213
+ def associations(path, parent = nil)
214
+ assocs = []
215
+
216
+ if parent.nil?
217
+ assocs = association(path.shift)
218
+ else
219
+ assocs = parent.children(path.shift)
220
+ end
221
+
222
+ until path.empty?
223
+ point = path.shift
224
+ assocs = assocs.collect { |assoc|
225
+ assoc.children(point)
226
+ }.flatten
227
+ end
228
+
229
+ assocs
230
+ end
231
+
232
+ # Gets the association stack for a specific key.
233
+ #
234
+ def association(key)
235
+ @associations[key] ||= Association.children(@model, key)
236
+ end
237
+
238
+ def crc_column
239
+ if @model.column_names.include?(@model.inheritance_column)
240
+ adapter.cast_to_unsigned(adapter.convert_nulls(
241
+ adapter.crc(adapter.quote_with_table(@model.inheritance_column)),
242
+ @model.to_crc32
243
+ ))
244
+ else
245
+ @model.to_crc32.to_s
246
+ end
247
+ end
248
+
249
+ def add_internal_attributes
250
+ @attributes << Attribute.new(
251
+ FauxColumn.new(@model.primary_key.to_sym),
252
+ :type => :integer,
253
+ :as => :sphinx_internal_id
254
+ ) unless @attributes.detect { |attr| attr.alias == :sphinx_internal_id }
255
+
256
+ unless @attributes.detect { |attr| attr.alias == :class_crc }
257
+ @attributes << Attribute.new(
258
+ FauxColumn.new(crc_column),
259
+ :type => :integer,
260
+ :as => :class_crc,
261
+ :facet => true
262
+ )
263
+
264
+ @model.sphinx_facets << ThinkingSphinx::ClassFacet.new(@attributes.last)
265
+ end
266
+
267
+ if @model.column_names.include?(@model.inheritance_column)
268
+ class_col = FauxColumn.new(
269
+ adapter.convert_nulls(adapter.quote_with_table(@model.inheritance_column), @model.to_s)
270
+ )
271
+ else
272
+ class_col = FauxColumn.new("'#{@model.to_s}'")
273
+ end
274
+
275
+ @attributes << Attribute.new(class_col,
276
+ :type => :string,
277
+ :as => :class
278
+ )
279
+
280
+ @attributes << Attribute.new(
281
+ FauxColumn.new("'" + (@model.send(:subclasses).collect { |klass|
282
+ klass.to_crc32.to_s
283
+ } << @model.to_crc32.to_s).join(",") + "'"),
284
+ :type => :multi,
285
+ :as => :subclass_crcs
286
+ ) unless @attributes.detect { |attr| attr.alias == :subclass_crcs }
287
+
288
+ @attributes << Attribute.new(
289
+ FauxColumn.new("0"),
290
+ :type => :integer,
291
+ :as => :sphinx_deleted
292
+ ) unless @attributes.detect { |attr| attr.alias == :sphinx_deleted }
293
+ end
294
+
295
+ def set_source_database_settings(source)
296
+ config = @model.connection.instance_variable_get(:@config)
297
+
298
+ source.sql_host = config[:host] || "localhost"
299
+ source.sql_user = config[:username] || config[:user] || ""
300
+ source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
301
+ source.sql_db = config[:database]
302
+ source.sql_port = config[:port]
303
+ source.sql_sock = config[:socket]
304
+ end
305
+
306
+ def set_source_attributes(source)
307
+ attributes.each do |attrib|
308
+ source.send(attrib.type_to_config) << attrib.config_value
309
+ end
310
+ end
311
+
312
+ def set_source_sql(source, offset, delta = false)
313
+ source.sql_query = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
314
+ source.sql_query_range = to_sql_query_range(:delta => delta)
315
+ source.sql_query_info = to_sql_query_info(offset)
316
+
317
+ source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
318
+
319
+ if @options[:group_concat_max_len]
320
+ source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
321
+ end
322
+
323
+ source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
324
+ end
325
+
326
+ def set_source_settings(source)
327
+ ThinkingSphinx::Configuration.instance.source_options.each do |key, value|
328
+ source.send("#{key}=".to_sym, value)
329
+ end
330
+
331
+ @options.each do |key, value|
332
+ source.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::SourceOptions.include?(key.to_s) && !value.nil?
333
+ end
334
+ end
335
+
336
+ def sql_query_pre_for_core
337
+ if self.delta? && !@delta_object.reset_query(@model).blank?
338
+ [@delta_object.reset_query(@model)]
339
+ else
340
+ []
341
+ end
342
+ end
343
+
344
+ def sql_query_pre_for_delta
345
+ [""]
346
+ end
347
+
348
+ # Generates the big SQL statement to get the data back for all the fields
349
+ # and attributes, using all the relevant association joins. If you want
350
+ # the version filtered for delta values, send through :delta => true in the
351
+ # options. Won't do much though if the index isn't set up to support a
352
+ # delta sibling.
353
+ #
354
+ # Examples:
355
+ #
356
+ # index.to_sql
357
+ # index.to_sql(:delta => true)
358
+ #
359
+ def to_sql(options={})
360
+ assocs = all_associations
361
+
362
+ where_clause = ""
363
+ if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
364
+ where_clause << " AND #{@delta_object.clause(@model, options[:delta])}"
365
+ end
366
+ unless @conditions.empty?
367
+ where_clause << " AND " << @conditions.join(" AND ")
368
+ end
369
+
370
+ internal_groupings = []
371
+ if @model.column_names.include?(@model.inheritance_column)
372
+ internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
373
+ end
374
+
375
+ unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
376
+
377
+ sql = <<-SQL
378
+ SELECT #{ (
379
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
380
+ @fields.collect { |field| field.to_select_sql } +
381
+ @attributes.collect { |attribute| attribute.to_select_sql }
382
+ ).join(", ") }
383
+ FROM #{ @model.table_name }
384
+ #{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
385
+ WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
386
+ AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
387
+ #{ where_clause }
388
+ GROUP BY #{ (
389
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
390
+ @fields.collect { |field| field.to_group_sql }.compact +
391
+ @attributes.collect { |attribute| attribute.to_group_sql }.compact +
392
+ @groupings + internal_groupings
393
+ ).join(", ") }
394
+ SQL
395
+
396
+ if @model.connection.class.name == "ActiveRecord::ConnectionAdapters::MysqlAdapter"
397
+ sql += " ORDER BY NULL"
398
+ end
399
+
400
+ sql
401
+ end
402
+
403
+ # Simple helper method for the query info SQL - which is a statement that
404
+ # returns the single row for a corresponding id.
405
+ #
406
+ def to_sql_query_info(offset)
407
+ "SELECT * FROM #{@model.quoted_table_name} WHERE " +
408
+ " #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
409
+ end
410
+
411
+ # Simple helper method for the query range SQL - which is a statement that
412
+ # returns minimum and maximum id values. These can be filtered by delta -
413
+ # so pass in :delta => true to get the delta version of the SQL.
414
+ #
415
+ def to_sql_query_range(options={})
416
+ min_statement = adapter.convert_nulls(
417
+ "MIN(#{quote_column(@model.primary_key)})", 1
418
+ )
419
+ max_statement = adapter.convert_nulls(
420
+ "MAX(#{quote_column(@model.primary_key)})", 1
421
+ )
422
+
423
+ sql = "SELECT #{min_statement}, #{max_statement} " +
424
+ "FROM #{@model.quoted_table_name} "
425
+ if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
426
+ sql << "WHERE #{@delta_object.clause(@model, options[:delta])}"
427
+ end
428
+
429
+ sql
430
+ end
431
+ end
432
+ end
@@ -0,0 +1,133 @@
1
+ module ThinkingSphinx
2
+ module HashExcept
3
+ # Returns a new hash without the given keys.
4
+ def except(*keys)
5
+ rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
6
+ reject { |key,| rejected.include?(key) }
7
+ end
8
+
9
+ # Replaces the hash without only the given keys.
10
+ def except!(*keys)
11
+ replace(except(*keys))
12
+ end
13
+ end
14
+ end
15
+
16
+ Hash.send(
17
+ :include, ThinkingSphinx::HashExcept
18
+ ) unless Hash.instance_methods.include?("except")
19
+
20
+ module ThinkingSphinx
21
+ module ArrayExtractOptions
22
+ def extract_options!
23
+ last.is_a?(::Hash) ? pop : {}
24
+ end
25
+ end
26
+ end
27
+
28
+ Array.send(
29
+ :include, ThinkingSphinx::ArrayExtractOptions
30
+ ) unless Array.instance_methods.include?("extract_options!")
31
+
32
+ module ThinkingSphinx
33
+ module AbstractQuotedTableName
34
+ def quote_table_name(name)
35
+ quote_column_name(name)
36
+ end
37
+ end
38
+ end
39
+
40
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.send(
41
+ :include, ThinkingSphinx::AbstractQuotedTableName
42
+ ) unless ActiveRecord::ConnectionAdapters::AbstractAdapter.instance_methods.include?("quote_table_name")
43
+
44
+ module ThinkingSphinx
45
+ module MysqlQuotedTableName
46
+ def quote_table_name(name) #:nodoc:
47
+ quote_column_name(name).gsub('.', '`.`')
48
+ end
49
+ end
50
+ end
51
+
52
+ if ActiveRecord::ConnectionAdapters.constants.include?("MysqlAdapter")
53
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(
54
+ :include, ThinkingSphinx::MysqlQuotedTableName
55
+ ) unless ActiveRecord::ConnectionAdapters::MysqlAdapter.instance_methods.include?("quote_table_name")
56
+ end
57
+
58
+ module ThinkingSphinx
59
+ module ActiveRecordQuotedName
60
+ def quoted_table_name
61
+ self.connection.quote_table_name(self.table_name)
62
+ end
63
+ end
64
+ end
65
+
66
+ ActiveRecord::Base.extend(
67
+ ThinkingSphinx::ActiveRecordQuotedName
68
+ ) unless ActiveRecord::Base.respond_to?("quoted_table_name")
69
+
70
+ module ThinkingSphinx
71
+ module ActiveRecordStoreFullSTIClass
72
+ def store_full_sti_class
73
+ false
74
+ end
75
+ end
76
+ end
77
+
78
+ ActiveRecord::Base.extend(
79
+ ThinkingSphinx::ActiveRecordStoreFullSTIClass
80
+ ) unless ActiveRecord::Base.respond_to?(:store_full_sti_class)
81
+
82
+ module ThinkingSphinx
83
+ module ClassAttributeMethods
84
+ def cattr_reader(*syms)
85
+ syms.flatten.each do |sym|
86
+ next if sym.is_a?(Hash)
87
+ class_eval(<<-EOS, __FILE__, __LINE__)
88
+ unless defined? @@#{sym}
89
+ @@#{sym} = nil
90
+ end
91
+
92
+ def self.#{sym}
93
+ @@#{sym}
94
+ end
95
+
96
+ def #{sym}
97
+ @@#{sym}
98
+ end
99
+ EOS
100
+ end
101
+ end
102
+
103
+ def cattr_writer(*syms)
104
+ options = syms.extract_options!
105
+ syms.flatten.each do |sym|
106
+ class_eval(<<-EOS, __FILE__, __LINE__)
107
+ unless defined? @@#{sym}
108
+ @@#{sym} = nil
109
+ end
110
+
111
+ def self.#{sym}=(obj)
112
+ @@#{sym} = obj
113
+ end
114
+
115
+ #{"
116
+ def #{sym}=(obj)
117
+ @@#{sym} = obj
118
+ end
119
+ " unless options[:instance_writer] == false }
120
+ EOS
121
+ end
122
+ end
123
+
124
+ def cattr_accessor(*syms)
125
+ cattr_reader(*syms)
126
+ cattr_writer(*syms)
127
+ end
128
+ end
129
+ end
130
+
131
+ Class.extend(
132
+ ThinkingSphinx::ClassAttributeMethods
133
+ ) unless Class.respond_to?(:cattr_reader)