pixeltrix-thinking-sphinx 1.1.5 → 1.2.1

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 (76) hide show
  1. data/README.textile +147 -0
  2. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  3. data/lib/thinking_sphinx/active_record/delta.rb +14 -1
  4. data/lib/thinking_sphinx/active_record/scopes.rb +37 -0
  5. data/lib/thinking_sphinx/active_record.rb +46 -12
  6. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +9 -1
  7. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +3 -2
  8. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +12 -5
  9. data/lib/thinking_sphinx/association.rb +20 -0
  10. data/lib/thinking_sphinx/attribute.rb +187 -116
  11. data/lib/thinking_sphinx/class_facet.rb +15 -0
  12. data/lib/thinking_sphinx/configuration.rb +46 -14
  13. data/lib/thinking_sphinx/core/string.rb +3 -10
  14. data/lib/thinking_sphinx/deltas/datetime_delta.rb +3 -3
  15. data/lib/thinking_sphinx/deltas/default_delta.rb +9 -6
  16. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
  17. data/lib/thinking_sphinx/deltas/delayed_delta.rb +4 -2
  18. data/lib/thinking_sphinx/deltas.rb +14 -6
  19. data/lib/thinking_sphinx/deploy/capistrano.rb +98 -0
  20. data/lib/thinking_sphinx/excerpter.rb +22 -0
  21. data/lib/thinking_sphinx/facet.rb +68 -18
  22. data/lib/thinking_sphinx/facet_search.rb +134 -0
  23. data/lib/thinking_sphinx/field.rb +7 -97
  24. data/lib/thinking_sphinx/index/builder.rb +255 -201
  25. data/lib/thinking_sphinx/index.rb +28 -343
  26. data/lib/thinking_sphinx/property.rb +160 -0
  27. data/lib/thinking_sphinx/rails_additions.rb +7 -4
  28. data/lib/thinking_sphinx/search.rb +593 -587
  29. data/lib/thinking_sphinx/search_methods.rb +421 -0
  30. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  31. data/lib/thinking_sphinx/source/sql.rb +128 -0
  32. data/lib/thinking_sphinx/source.rb +150 -0
  33. data/lib/thinking_sphinx/tasks.rb +45 -11
  34. data/lib/thinking_sphinx.rb +88 -14
  35. data/rails/init.rb +14 -0
  36. data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +7 -7
  37. data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
  38. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +92 -0
  39. data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +115 -42
  40. data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +4 -5
  41. data/spec/lib/thinking_sphinx/attribute_spec.rb +465 -0
  42. data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +118 -7
  43. data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
  44. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  45. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  46. data/spec/lib/thinking_sphinx/facet_spec.rb +302 -0
  47. data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +26 -17
  48. data/spec/lib/thinking_sphinx/index/builder_spec.rb +355 -0
  49. data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
  50. data/spec/{unit → lib}/thinking_sphinx/index_spec.rb +3 -12
  51. data/spec/lib/thinking_sphinx/rails_additions_spec.rb +191 -0
  52. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  53. data/spec/lib/thinking_sphinx/search_spec.rb +887 -0
  54. data/spec/lib/thinking_sphinx/source_spec.rb +217 -0
  55. data/spec/{unit → lib}/thinking_sphinx_spec.rb +30 -8
  56. data/tasks/distribution.rb +20 -1
  57. data/tasks/testing.rb +7 -15
  58. data/vendor/after_commit/init.rb +3 -0
  59. data/vendor/after_commit/lib/after_commit/active_record.rb +27 -4
  60. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +1 -1
  61. data/vendor/after_commit/lib/after_commit.rb +4 -1
  62. data/vendor/riddle/lib/riddle/client/message.rb +4 -3
  63. data/vendor/riddle/lib/riddle/client.rb +3 -0
  64. data/vendor/riddle/lib/riddle/configuration/section.rb +8 -2
  65. data/vendor/riddle/lib/riddle/controller.rb +1 -1
  66. data/vendor/riddle/lib/riddle.rb +1 -1
  67. metadata +75 -39
  68. data/README +0 -107
  69. data/lib/thinking_sphinx/active_record/search.rb +0 -57
  70. data/lib/thinking_sphinx/collection.rb +0 -142
  71. data/lib/thinking_sphinx/facet_collection.rb +0 -44
  72. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
  73. data/spec/unit/thinking_sphinx/attribute_spec.rb +0 -212
  74. data/spec/unit/thinking_sphinx/collection_spec.rb +0 -14
  75. data/spec/unit/thinking_sphinx/index/builder_spec.rb +0 -5
  76. data/spec/unit/thinking_sphinx/search_spec.rb +0 -59
@@ -9,8 +9,7 @@ module ThinkingSphinx
9
9
  # Enjoy.
10
10
  #
11
11
  class Index
12
- attr_accessor :model, :fields, :attributes, :conditions, :groupings,
13
- :delta_object, :options
12
+ attr_accessor :model, :sources, :delta_object
14
13
 
15
14
  # Create a new index instance by passing in the model it is tied to, and
16
15
  # a block to build it with (optional but recommended). For documentation
@@ -28,15 +27,17 @@ module ThinkingSphinx
28
27
  #
29
28
  def initialize(model, &block)
30
29
  @model = model
31
- @associations = {}
32
- @fields = []
33
- @attributes = []
34
- @conditions = []
35
- @groupings = []
30
+ @sources = []
36
31
  @options = {}
37
32
  @delta_object = nil
38
-
39
- initialize_from_builder(&block) if block_given?
33
+ end
34
+
35
+ def fields
36
+ @sources.collect { |source| source.fields }.flatten
37
+ end
38
+
39
+ def attributes
40
+ @sources.collect { |source| source.attributes }.flatten
40
41
  end
41
42
 
42
43
  def name
@@ -47,99 +48,39 @@ module ThinkingSphinx
47
48
  model.name.underscore.tr(':/\\', '_')
48
49
  end
49
50
 
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
51
+ def prefix_fields
52
+ fields.select { |field| field.prefixes }
80
53
  end
81
54
 
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
- }
55
+ def infix_fields
56
+ fields.select { |field| field.infixes }
107
57
  end
108
58
 
109
- # Flag to indicate whether this index has a corresponding delta index.
110
- #
111
- def delta?
112
- !@delta_object.nil?
59
+ def local_options
60
+ @options
113
61
  end
114
62
 
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
63
+ def options
128
64
  all_index_options = ThinkingSphinx::Configuration.instance.index_options.clone
129
65
  @options.keys.select { |key|
130
- ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s)
66
+ ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) ||
67
+ ThinkingSphinx::Configuration::CustomOptions.include?(key.to_s)
131
68
  }.each { |key| all_index_options[key.to_sym] = @options[key] }
132
69
  all_index_options
133
70
  end
134
-
135
- def quote_column(column)
136
- @model.connection.quote_column_name(column)
71
+
72
+ def delta?
73
+ !@delta_object.nil?
137
74
  end
138
75
 
139
76
  private
140
77
 
78
+ def adapter
79
+ @adapter ||= @model.sphinx_database_adapter
80
+ end
81
+
141
82
  def utf8?
142
- self.index_options[:charset_type] == "utf-8"
83
+ options[:charset_type] == "utf-8"
143
84
  end
144
85
 
145
86
  # Does all the magic with the block provided to the base #initialize.
@@ -147,268 +88,12 @@ module ThinkingSphinx
147
88
  # on it, then pulls all relevant settings - fields, attributes, conditions,
148
89
  # properties - into the new index.
149
90
  #
150
- # Also creates a CRC attribute for the model.
151
- #
152
91
  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
- @attributes << Attribute.new(
257
- FauxColumn.new(crc_column),
258
- :type => :integer,
259
- :as => :class_crc
260
- ) unless @attributes.detect { |attr| attr.alias == :class_crc }
261
-
262
- @attributes << Attribute.new(
263
- FauxColumn.new("'" + (@model.send(:subclasses).collect { |klass|
264
- klass.to_crc32.to_s
265
- } << @model.to_crc32.to_s).join(",") + "'"),
266
- :type => :multi,
267
- :as => :subclass_crcs
268
- ) unless @attributes.detect { |attr| attr.alias == :subclass_crcs }
269
-
270
- @attributes << Attribute.new(
271
- FauxColumn.new("0"),
272
- :type => :integer,
273
- :as => :sphinx_deleted
274
- ) unless @attributes.detect { |attr| attr.alias == :sphinx_deleted }
275
- end
276
-
277
- def set_source_database_settings(source)
278
- config = @model.connection.instance_variable_get(:@config)
279
-
280
- source.sql_host = config[:host] || "localhost"
281
- source.sql_user = config[:username] || config[:user] || ""
282
- source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
283
- source.sql_db = config[:database]
284
- source.sql_port = config[:port]
285
- source.sql_sock = config[:socket]
286
- end
287
-
288
- def set_source_attributes(source)
289
- attributes.each do |attrib|
290
- source.send(attrib.type_to_config) << attrib.config_value
291
- end
292
- end
293
-
294
- def set_source_sql(source, offset, delta = false)
295
- source.sql_query = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
296
- source.sql_query_range = to_sql_query_range(:delta => delta)
297
- source.sql_query_info = to_sql_query_info(offset)
298
-
299
- source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
300
-
301
- if @options[:group_concat_max_len]
302
- source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
303
- end
304
-
305
- source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
306
- end
307
-
308
- def set_source_settings(source)
309
- ThinkingSphinx::Configuration.instance.source_options.each do |key, value|
310
- source.send("#{key}=".to_sym, value)
311
- end
312
-
313
- @options.each do |key, value|
314
- source.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::SourceOptions.include?(key.to_s) && !value.nil?
315
- end
316
- end
317
-
318
- def sql_query_pre_for_core
319
- if self.delta? && !@delta_object.reset_query(@model).blank?
320
- [@delta_object.reset_query(@model)]
321
- else
322
- []
323
- end
92
+ #
324
93
  end
325
94
 
326
95
  def sql_query_pre_for_delta
327
96
  [""]
328
97
  end
329
-
330
- # Generates the big SQL statement to get the data back for all the fields
331
- # and attributes, using all the relevant association joins. If you want
332
- # the version filtered for delta values, send through :delta => true in the
333
- # options. Won't do much though if the index isn't set up to support a
334
- # delta sibling.
335
- #
336
- # Examples:
337
- #
338
- # index.to_sql
339
- # index.to_sql(:delta => true)
340
- #
341
- def to_sql(options={})
342
- assocs = all_associations
343
-
344
- where_clause = ""
345
- if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
346
- where_clause << " AND #{@delta_object.clause(@model, options[:delta])}"
347
- end
348
- unless @conditions.empty?
349
- where_clause << " AND " << @conditions.join(" AND ")
350
- end
351
-
352
- internal_groupings = []
353
- if @model.column_names.include?(@model.inheritance_column)
354
- internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
355
- end
356
-
357
- unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
358
-
359
- sql = <<-SQL
360
- SELECT #{ (
361
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
362
- @fields.collect { |field| field.to_select_sql } +
363
- @attributes.collect { |attribute| attribute.to_select_sql }
364
- ).join(", ") }
365
- FROM #{ @model.table_name }
366
- #{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
367
- WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
368
- AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
369
- #{ where_clause }
370
- GROUP BY #{ (
371
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
372
- @fields.collect { |field| field.to_group_sql }.compact +
373
- @attributes.collect { |attribute| attribute.to_group_sql }.compact +
374
- @groupings + internal_groupings
375
- ).join(", ") }
376
- SQL
377
-
378
- if @model.connection.class.name == "ActiveRecord::ConnectionAdapters::MysqlAdapter"
379
- sql += " ORDER BY NULL"
380
- end
381
-
382
- sql
383
- end
384
-
385
- # Simple helper method for the query info SQL - which is a statement that
386
- # returns the single row for a corresponding id.
387
- #
388
- def to_sql_query_info(offset)
389
- "SELECT * FROM #{@model.quoted_table_name} WHERE " +
390
- " #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
391
- end
392
-
393
- # Simple helper method for the query range SQL - which is a statement that
394
- # returns minimum and maximum id values. These can be filtered by delta -
395
- # so pass in :delta => true to get the delta version of the SQL.
396
- #
397
- def to_sql_query_range(options={})
398
- min_statement = adapter.convert_nulls(
399
- "MIN(#{quote_column(@model.primary_key)})", 1
400
- )
401
- max_statement = adapter.convert_nulls(
402
- "MAX(#{quote_column(@model.primary_key)})", 1
403
- )
404
-
405
- sql = "SELECT #{min_statement}, #{max_statement} " +
406
- "FROM #{@model.quoted_table_name} "
407
- if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
408
- sql << "WHERE #{@delta_object.clause(@model, options[:delta])}"
409
- end
410
-
411
- sql
412
- end
413
98
  end
414
99
  end
@@ -0,0 +1,160 @@
1
+ module ThinkingSphinx
2
+ class Property
3
+ attr_accessor :alias, :columns, :associations, :model, :faceted, :admin
4
+
5
+ def initialize(source, columns, options = {})
6
+ @source = source
7
+ @model = source.model
8
+ @columns = Array(columns)
9
+ @associations = {}
10
+
11
+ raise "Cannot define a field or attribute in #{source.model.name} with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) }
12
+
13
+ @alias = options[:as]
14
+ @faceted = options[:facet]
15
+ @admin = options[:admin]
16
+
17
+ @columns.each { |col|
18
+ @associations[col] = association_stack(col.__stack.clone).each { |assoc|
19
+ assoc.join_to(source.base)
20
+ }
21
+ }
22
+ end
23
+
24
+ # Returns the unique name of the attribute - which is either the alias of
25
+ # the attribute, or the name of the only column - if there is only one. If
26
+ # there isn't, there should be an alias. Else things probably won't work.
27
+ # Consider yourself warned.
28
+ #
29
+ def unique_name
30
+ if @columns.length == 1
31
+ @alias || @columns.first.__name
32
+ else
33
+ @alias
34
+ end
35
+ end
36
+
37
+ def to_facet
38
+ return nil unless @faceted
39
+
40
+ ThinkingSphinx::Facet.new(self)
41
+ end
42
+
43
+ # Get the part of the GROUP BY clause related to this attribute - if one is
44
+ # needed. If not, all you'll get back is nil. The latter will happen if
45
+ # there isn't actually a real column to get data from, or if there's
46
+ # multiple data values (read: a has_many or has_and_belongs_to_many
47
+ # association).
48
+ #
49
+ def to_group_sql
50
+ case
51
+ when is_many?, is_string?, ThinkingSphinx.use_group_by_shortcut?
52
+ nil
53
+ else
54
+ @columns.collect { |column|
55
+ column_with_prefix(column)
56
+ }
57
+ end
58
+ end
59
+
60
+ def changed?(instance)
61
+ return true if is_string? || @columns.any? { |col| !col.__stack.empty? }
62
+
63
+ !@columns.all? { |col|
64
+ instance.respond_to?("#{col.__name.to_s}_changed?") &&
65
+ !instance.send("#{col.__name.to_s}_changed?")
66
+ }
67
+ end
68
+
69
+ def admin?
70
+ admin
71
+ end
72
+
73
+ def public?
74
+ !admin
75
+ end
76
+
77
+ private
78
+
79
+ # Could there be more than one value related to the parent record? If so,
80
+ # then this will return true. If not, false. It's that simple.
81
+ #
82
+ def is_many?
83
+ associations.values.flatten.any? { |assoc| assoc.is_many? }
84
+ end
85
+
86
+ # Returns true if any of the columns are string values, instead of database
87
+ # column references.
88
+ def is_string?
89
+ columns.all? { |col| col.is_string? }
90
+ end
91
+
92
+ def adapter
93
+ @adapter ||= @model.sphinx_database_adapter
94
+ end
95
+
96
+ def quote_with_table(table, column)
97
+ "#{quote_table_name(table)}.#{quote_column(column)}"
98
+ end
99
+
100
+ def quote_column(column)
101
+ @model.connection.quote_column_name(column)
102
+ end
103
+
104
+ def quote_table_name(table_name)
105
+ @model.connection.quote_table_name(table_name)
106
+ end
107
+
108
+ # Indication of whether the columns should be concatenated with a space
109
+ # between each value. True if there's either multiple sources or multiple
110
+ # associations.
111
+ #
112
+ def concat_ws?
113
+ multiple_associations? || @columns.length > 1
114
+ end
115
+
116
+ # Checks whether any column requires multiple associations (which only
117
+ # happens for polymorphic situations).
118
+ #
119
+ def multiple_associations?
120
+ associations.any? { |col,assocs| assocs.length > 1 }
121
+ end
122
+
123
+ # Builds a column reference tied to the appropriate associations. This
124
+ # dives into the associations hash and their corresponding joins to
125
+ # figure out how to correctly reference a column in SQL.
126
+ #
127
+ def column_with_prefix(column)
128
+ if column.is_string?
129
+ column.__name
130
+ elsif associations[column].empty?
131
+ "#{@model.quoted_table_name}.#{quote_column(column.__name)}"
132
+ else
133
+ associations[column].collect { |assoc|
134
+ assoc.has_column?(column.__name) ?
135
+ "#{quote_with_table(assoc.join.aliased_table_name, column.__name)}" :
136
+ nil
137
+ }.compact.join(', ')
138
+ end
139
+ end
140
+
141
+ # Gets a stack of associations for a specific path.
142
+ #
143
+ def association_stack(path, parent = nil)
144
+ assocs = []
145
+
146
+ if parent.nil?
147
+ assocs = @source.association(path.shift)
148
+ else
149
+ assocs = parent.children(path.shift)
150
+ end
151
+
152
+ until path.empty?
153
+ point = path.shift
154
+ assocs = assocs.collect { |assoc| assoc.children(point) }.flatten
155
+ end
156
+
157
+ assocs
158
+ end
159
+ end
160
+ end
@@ -49,10 +49,13 @@ module ThinkingSphinx
49
49
  end
50
50
  end
51
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")
52
+ if ActiveRecord::ConnectionAdapters.constants.include?("MysqlAdapter") or ActiveRecord::Base.respond_to?(:jdbcmysql_connection)
53
+ adapter = ActiveRecord::ConnectionAdapters.const_get(
54
+ defined?(JRUBY_VERSION) ? :JdbcAdapter : :MysqlAdapter
55
+ )
56
+ unless adapter.instance_methods.include?("quote_table_name")
57
+ adapter.send(:include, ThinkingSphinx::MysqlQuotedTableName)
58
+ end
56
59
  end
57
60
 
58
61
  module ThinkingSphinx