dpickett-thinking-sphinx 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)