pixeltrix-thinking-sphinx 1.1.5 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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