thinking-sphinx 3.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.travis.yml +6 -3
  2. data/HISTORY +18 -0
  3. data/README.textile +31 -8
  4. data/gemfiles/rails_3_1.gemfile +2 -2
  5. data/gemfiles/rails_3_2.gemfile +2 -2
  6. data/lib/thinking/sphinx.rb +1 -0
  7. data/lib/thinking_sphinx.rb +1 -0
  8. data/lib/thinking_sphinx/active_record.rb +2 -0
  9. data/lib/thinking_sphinx/active_record/association.rb +8 -0
  10. data/lib/thinking_sphinx/active_record/associations.rb +25 -5
  11. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +1 -5
  12. data/lib/thinking_sphinx/active_record/column.rb +12 -0
  13. data/lib/thinking_sphinx/active_record/database_adapters.rb +7 -0
  14. data/lib/thinking_sphinx/active_record/filtered_reflection.rb +49 -0
  15. data/lib/thinking_sphinx/active_record/interpreter.rb +6 -0
  16. data/lib/thinking_sphinx/active_record/polymorpher.rb +50 -0
  17. data/lib/thinking_sphinx/active_record/property.rb +6 -0
  18. data/lib/thinking_sphinx/active_record/property_query.rb +1 -1
  19. data/lib/thinking_sphinx/active_record/property_sql_presenter.rb +7 -1
  20. data/lib/thinking_sphinx/active_record/sql_builder.rb +7 -2
  21. data/lib/thinking_sphinx/active_record/sql_source.rb +5 -1
  22. data/lib/thinking_sphinx/capistrano.rb +64 -0
  23. data/lib/thinking_sphinx/configuration.rb +10 -1
  24. data/lib/thinking_sphinx/connection.rb +20 -0
  25. data/lib/thinking_sphinx/errors.rb +24 -0
  26. data/lib/thinking_sphinx/middlewares/sphinxql.rb +4 -1
  27. data/lib/thinking_sphinx/real_time/transcriber.rb +1 -1
  28. data/lib/thinking_sphinx/search/batch_inquirer.rb +3 -1
  29. data/lib/thinking_sphinx/search/glaze.rb +3 -3
  30. data/spec/acceptance/index_options_spec.rb +5 -0
  31. data/spec/acceptance/searching_on_fields_spec.rb +1 -0
  32. data/spec/acceptance/specifying_sql_spec.rb +107 -0
  33. data/spec/acceptance/sql_deltas_spec.rb +9 -0
  34. data/spec/acceptance/support/sphinx_controller.rb +1 -0
  35. data/spec/internal/app/models/event.rb +3 -0
  36. data/spec/internal/app/models/hardcover.rb +3 -0
  37. data/spec/internal/db/schema.rb +7 -1
  38. data/spec/thinking_sphinx/active_record/associations_spec.rb +2 -1
  39. data/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb +3 -3
  40. data/spec/thinking_sphinx/active_record/column_spec.rb +23 -0
  41. data/spec/thinking_sphinx/active_record/database_adapters_spec.rb +18 -0
  42. data/spec/thinking_sphinx/active_record/filtered_reflection_spec.rb +141 -0
  43. data/spec/thinking_sphinx/active_record/polymorpher_spec.rb +65 -0
  44. data/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb +28 -4
  45. data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +11 -1
  46. data/spec/thinking_sphinx/configuration_spec.rb +24 -0
  47. data/spec/thinking_sphinx/connection_spec.rb +82 -0
  48. data/spec/thinking_sphinx/errors_spec.rb +36 -0
  49. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +25 -0
  50. data/spec/thinking_sphinx/search/glaze_spec.rb +3 -0
  51. data/thinking-sphinx.gemspec +1 -1
  52. metadata +25 -3
@@ -1,13 +1,16 @@
1
1
  rvm:
2
2
  - 1.9.2
3
3
  - 1.9.3
4
- before_install: gem update --system
4
+ before_install:
5
+ - gem update --system
6
+ - curl -O http://fs-packages.s3.amazonaws.com/fs-sphinx-2.0.6_i386_12.04.deb
7
+ - sudo dpkg -i fs-sphinx-2.0.6_i386_12.04.deb
5
8
  before_script:
6
9
  - "mysql -e 'create database thinking_sphinx;' > /dev/null"
7
10
  - "psql -c 'create database thinking_sphinx;' -U postgres >/dev/null"
8
11
  env:
9
- - DATABASE=mysql2
10
- - DATABASE=postgresql
12
+ - DATABASE=mysql2 SPHINX_BIN=/usr/local/sphinx-2.0.6/bin/ SPHINX_VERSION=2.0.6
13
+ - DATABASE=postgresql SPHINX_BIN=/usr/local/sphinx-2.0.6/bin/ SPHINX_VERSION=2.0.6
11
14
  gemfile:
12
15
  - gemfiles/rails_3_1.gemfile
13
16
  - gemfiles/rails_3_2.gemfile
data/HISTORY CHANGED
@@ -1,3 +1,21 @@
1
+ 2013-02-04: 3.0.1
2
+ * [FEATURE] Provide Capistrano deployment tasks (David Celis).
3
+ * [FEATURE] Allow specifying of Sphinx version. Is only useful for Flying Sphinx purposes at this point - has no impact on Riddle or Sphinx.
4
+ * [FEATURE] Support new JDBC configuration style (when JDBC can be used) (Kyle Stevens).
5
+ * [FIX] Referring to associations via polymorphic associations in an index definition now works.
6
+ * [FEATURE] Mysql2::Errors are wrapped as ThinkingSphinx::SphinxErrors, with subclasses of SyntaxError and ParseError used appropriately. Syntax and parse errors do not prompt a retry on a new connection.
7
+ * [CHANGE] Use connection pool for search queries. If a query fails, it will be retried on a new connection before raising if necessary.
8
+ * [CHANGE] Glaze always passes methods through to the underlying ActiveRecord::Base object if they don't exist on any of the panes.
9
+ * [FIX] Don't override foreign keys for polymorphic association replacements.
10
+ * [FIX] Quote namespaced model names in class field condition.
11
+ * [FEATURE] Polymorphic associations can be used within index definitions when the appropriate classes are set out.
12
+ * [FEATURE] Allow custom strings for SQL joins in index definitions.
13
+ * [FIX] New lines are maintained and escaped in custom source queries.
14
+ * [FIX] Subclasses of indexed models fire delta callbacks properly.
15
+ * [FIX] Thinking Sphinx can be loaded via thinking/sphinx, to satisfy Bundler.
16
+ * [FEATURE] indexer and searchd settings are added to the appropriate objects from config/thinking_sphinx.yml (@ygelfand).
17
+ * [FIX] New lines are maintained and escaped in sql_query values.
18
+
1
19
  2013-01-02: 3.0.0
2
20
  * [CHANGE] Updating Riddle dependency to 1.5.4.
3
21
  * [FIX] Respect source options as well as underlying settings via the set_property method in index definitions.
@@ -1,15 +1,19 @@
1
1
  h1. Thinking Sphinx
2
2
 
3
- Welcome to Thinking Sphinx version 3 - a complete rewrite from past versions. Right now it is a work-in-progress, though it does include most of the more popular features from TS 2 and earlier. It's also currently built for Rails 3.1 or newer only.
4
-
5
- Still interested? Well, read on!
3
+ Welcome to Thinking Sphinx version 3 - a complete rewrite from past versions, built for Rails 3.1 or newer only.
6
4
 
7
5
  h2. Installation
8
6
 
9
- There's only a pre-release gem available at this point - or you can use the git repository reference (the edge branch is what you're after). Also, you'll need to specify the Mysql2 gem as well (this is not an inbuilt dependency because JRuby, when supported, will need something different):
7
+ It's a gem, so install it like you would any other gem. You will also need to specify the Mysql2 gem as well (this is not an inbuilt dependency because JRuby, when supported, will need something different):
10
8
 
11
9
  <pre><code>gem 'mysql2', '0.3.12b4'
12
- gem 'thinking-sphinx', '3.0.0.pre'</code></pre>
10
+ gem 'thinking-sphinx', '3.0.0'</code></pre>
11
+
12
+ The mysql2 gem is required for connecting to Sphinx, so please include it even when you're using PostgreSQL for your database.
13
+
14
+ As for Sphinx itself, you need to make sure it's compiled with MySQL support, even if you're using PostgreSQL as your database - which is normally the default, but Homebrew can be too smart sometimes. If that's your compiling tool of choice, make sure you install with the @--mysql@ flag:
15
+
16
+ <pre><code>brew install sphinx --mysql</code></pre>
13
17
 
14
18
  h2. Usage
15
19
 
@@ -26,6 +30,11 @@ end</code></pre>
26
30
 
27
31
  You'll notice the first argument is the model name downcased and as a symbol, and we are specifying the processor - @:active_record@. Everything inside the block is just like previous versions of Thinking Sphinx. Same goes for @config/thinking_sphinx.yml@ (formerly @config/sphinx.yml@).
28
32
 
33
+ When you're defining indices for namespaced models, use a lowercase string with /'s for namespacing as the model reference:
34
+
35
+ <pre><code># For a model named Blog::Article:
36
+ ThinkingSphinx::Index.define 'blog/article', :with => :active_record</code></pre>
37
+
29
38
  Other changes:
30
39
  * SphinxQL is now used instead of the old socket connections (hence the dependency on the @mysql2@ gem).
31
40
  * You'll need to include @ThinkingSphinx::Scopes@ into your models if you want to use Sphinx scopes. Default scopes can be set as follows:
@@ -65,7 +74,7 @@ end</code></pre>
65
74
  :classes => [User, AdminUser, SupportUser]</code></pre>
66
75
 
67
76
  * The option @:rank_mode@ has now become @:ranker@ - and the options (as strings or symbols) are as follows: proximity_bm25, bm25, none, wordcount, proximity, matchany, and fieldmask.
68
- * There are no explicit sorting modes - all sorting must be on attributes followed by ASC or DESC. For example: @:order => '@weight DESC, created_at ASC'@.
77
+ * There are no explicit sorting modes - all sorting must be on attributes followed by ASC or DESC. For example: <code>:order => '@weight DESC, created_at ASC'</code>.
69
78
  * If you specify just an attribute name as a symbol for the @:order@ option, it will be given the ascending direction by default. So, @:order => :created_at@ is equivalent to @:order => 'created_at ASC'@.
70
79
  * If you want to use a calculated expression for sorting, you must specify the expression as a new attribute, then use that attribute in your @:order@ option. This is done using the @:select@ option to specify extra columns available in the underlying SphinxQL (_not_ ActiveRecord/SQL) query.
71
80
 
@@ -146,7 +155,11 @@ ThinkingSphinx::Index.define :article, :with => :active_record, :delta => Occasi
146
155
  # ...
147
156
  end</code></pre>
148
157
 
149
- * When you're defining indices for namespaced models, use a lowercase string with /'s for namespacing as the model reference: @ThinkingSphinx::Index.define 'blog/article', :with => :active_record@.
158
+ * Polymorphic associations used within index definitions must be declared with the corresponding models. This is much better than the old approach of querying the database on the *_type column to determine what models to join against.
159
+
160
+ <pre><code>indexes events.eventable.name
161
+
162
+ polymorphs events.eventable, :to => %w(Page Post User)</code></pre>
150
163
 
151
164
  h2. Search Middleware
152
165
 
@@ -176,6 +189,17 @@ All panes namespaced to @ThinkingSphinx::Panes@, and the @DistancePane@ is autom
176
189
 
177
190
  If you wish to add your own panes, go ahead. The only requirement is that the initializer must accept three arguments: the search context, the underlying search result object, and a hash of the raw values from Sphinx.
178
191
 
192
+ h2. Deployment with Capistrano
193
+
194
+ Thinking Sphinx comes with several Capistrano tasks to help ease deployment of your applications. Just require the recipes:
195
+
196
+ ```ruby
197
+ # config/deploy.rb
198
+ require 'thinking_sphinx/capistrano'
199
+ ```
200
+
201
+ When running `cap deploy:setup` to get your server ready for deployments, Thinking Sphinx will also set up a shared folder for your indexes. Before finalizing a deployment, these indexes will be symlinked into the release path for use. When you deploy your application for the first time using `cap deploy:cold`, your indexes will be built for you and the search daemon will be started. Run `cap -T` to see all of the deployment tasks.
202
+
179
203
  h2. Limitations
180
204
 
181
205
  Almost all functionality from v2 releases are implemented, though it's worth noting that some settings haven't yet been brought across, and a handful of the smaller features don't yet exist either. Some may actually not return... we'll see.
@@ -186,7 +210,6 @@ May or may not be added:
186
210
  * Bitmask weighting helper
187
211
  * Timezone support (for databases not using UTC)
188
212
  * Abstract Inheritance support (maybe - not sure this is something many of people want).
189
- * Capistrano Tasks
190
213
  * Facet support for arrays of strings.
191
214
 
192
215
  h3. Sphinx Versions
@@ -2,9 +2,9 @@
2
2
 
3
3
  source :rubygems
4
4
 
5
- gem "riddle", "1.5.3"
6
- gem "combustion", :git=>"git://github.com/pat/combustion.git", :ref=>"45f50e64c3"
5
+ gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
7
6
  gem "pg", "~> 0.11.0", :platform=>:ruby
7
+ gem "activerecord-jdbcmysql-adapter", "~> 1.1.3", :platform=>:jruby
8
8
  gem "activerecord-jdbcpostgresql-adapter", "~> 1.1.3", :platform=>:jruby
9
9
  gem "rails", "~> 3.1.0"
10
10
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  source :rubygems
4
4
 
5
- gem "riddle", "1.5.3"
6
- gem "combustion", :git=>"git://github.com/pat/combustion.git", :ref=>"45f50e64c3"
5
+ gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
7
6
  gem "pg", "~> 0.11.0", :platform=>:ruby
7
+ gem "activerecord-jdbcmysql-adapter", "~> 1.1.3", :platform=>:jruby
8
8
  gem "activerecord-jdbcpostgresql-adapter", "~> 1.1.3", :platform=>:jruby
9
9
  gem "rails", "~> 3.2.0"
10
10
 
@@ -0,0 +1 @@
1
+ require 'thinking_sphinx'
@@ -35,6 +35,7 @@ require 'thinking_sphinx/callbacks'
35
35
  require 'thinking_sphinx/core'
36
36
  require 'thinking_sphinx/configuration'
37
37
  require 'thinking_sphinx/connection'
38
+ require 'thinking_sphinx/errors'
38
39
  require 'thinking_sphinx/excerpter'
39
40
  require 'thinking_sphinx/facet'
40
41
  require 'thinking_sphinx/facet_search'
@@ -13,9 +13,11 @@ require 'thinking_sphinx/active_record/base'
13
13
  require 'thinking_sphinx/active_record/column'
14
14
  require 'thinking_sphinx/active_record/database_adapters'
15
15
  require 'thinking_sphinx/active_record/field'
16
+ require 'thinking_sphinx/active_record/filtered_reflection'
16
17
  require 'thinking_sphinx/active_record/index'
17
18
  require 'thinking_sphinx/active_record/interpreter'
18
19
  require 'thinking_sphinx/active_record/log_subscriber'
20
+ require 'thinking_sphinx/active_record/polymorpher'
19
21
  require 'thinking_sphinx/active_record/property_query'
20
22
  require 'thinking_sphinx/active_record/property_sql_presenter'
21
23
  require 'thinking_sphinx/active_record/sql_builder'
@@ -6,4 +6,12 @@ class ThinkingSphinx::ActiveRecord::Association
6
6
  def stack
7
7
  @column.__stack + [@column.__name]
8
8
  end
9
+
10
+ def string?
11
+ @column.is_a?(String)
12
+ end
13
+
14
+ def to_s
15
+ @column.to_s
16
+ end
9
17
  end
@@ -15,7 +15,7 @@ class ThinkingSphinx::ActiveRecord::Associations
15
15
  def aggregate_for?(stack)
16
16
  return false if stack.empty?
17
17
 
18
- joins_for(stack).any? { |join|
18
+ joins_for(stack).compact.any? { |join|
19
19
  [:has_many, :has_and_belongs_to_many].include?(
20
20
  join.reflection.macro
21
21
  )
@@ -29,7 +29,14 @@ class ThinkingSphinx::ActiveRecord::Associations
29
29
  end
30
30
 
31
31
  def join_values
32
- @joins.values
32
+ @joins.values.compact
33
+ end
34
+
35
+ def model_for(stack)
36
+ return model if stack.empty?
37
+
38
+ join = join_for(stack)
39
+ join.nil? ? nil : join.reflection.klass
33
40
  end
34
41
 
35
42
  private
@@ -40,9 +47,14 @@ class ThinkingSphinx::ActiveRecord::Associations
40
47
 
41
48
  def join_for(stack)
42
49
  @joins[stack] ||= begin
43
- JoinDependency::JoinAssociation.new(
44
- reflection_for(stack), base, parent_join_for(stack)
45
- ).tap { |join| join.join_type = Arel::OuterJoin }
50
+ reflection = reflection_for stack
51
+ reflection.nil? ? nil : JoinDependency::JoinAssociation.new(
52
+ reflection, base, parent_join_for(stack)
53
+ ).tap { |join|
54
+ join.join_type = Arel::OuterJoin
55
+
56
+ rewrite_conditions_for join
57
+ }
46
58
  end
47
59
  end
48
60
 
@@ -65,4 +77,12 @@ class ThinkingSphinx::ActiveRecord::Associations
65
77
  def reflection_for(stack)
66
78
  parent_for(stack).active_record.reflections[stack.last]
67
79
  end
80
+
81
+ def rewrite_conditions_for(join)
82
+ conditions = Array(join.conditions).flatten
83
+ conditions.each do |condition|
84
+ condition.gsub! /::ts_join_alias::/,
85
+ model.connection.quote_table_name(join.parent.aliased_table_name)
86
+ end
87
+ end
68
88
  end
@@ -42,14 +42,10 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks <
42
42
  end
43
43
 
44
44
  def indices
45
- @indices ||= config.indices_for_references reference
45
+ @indices ||= ThinkingSphinx::IndexSet.new [instance.class], []
46
46
  end
47
47
 
48
48
  def processors
49
49
  delta_indices.collect &:delta_processor
50
50
  end
51
-
52
- def reference
53
- instance.class.name.underscore.to_sym
54
- end
55
51
  end
@@ -8,6 +8,18 @@ class ThinkingSphinx::ActiveRecord::Column
8
8
  @name
9
9
  end
10
10
 
11
+ def __path
12
+ @stack + [@name]
13
+ end
14
+
15
+ def __replace(stack, replacements)
16
+ return [self] if string? || __stack[0..stack.length-1] != stack
17
+
18
+ replacements.collect { |replacement|
19
+ self.class.new *(replacement + __stack[stack.length..-1]), __name
20
+ }
21
+ end
22
+
11
23
  def __stack
12
24
  @stack
13
25
  end
@@ -28,6 +28,13 @@ module ThinkingSphinx::ActiveRecord::DatabaseAdapters
28
28
  :mysql
29
29
  when "jdbcpostgresql"
30
30
  :postgresql
31
+ when "jdbc"
32
+ match = /^jdbc:(?<adapter>mysql|postgresql):\/\//.match(model.connection.config[:url])
33
+ if match
34
+ match[:adapter].to_sym
35
+ else
36
+ model.connection.config[:adapter]
37
+ end
31
38
  else
32
39
  model.connection.config[:adapter]
33
40
  end
@@ -0,0 +1,49 @@
1
+ class ThinkingSphinx::ActiveRecord::FilteredReflection <
2
+ ActiveRecord::Reflection::AssociationReflection
3
+
4
+ class Options
5
+ attr_reader :reflection, :class_name, :options
6
+
7
+ delegate :foreign_type, :active_record, :to => :reflection
8
+
9
+ def initialize(reflection, class_name)
10
+ @reflection, @class_name = reflection, class_name
11
+ @options = reflection.options.clone
12
+ end
13
+
14
+ def filtered
15
+ options.delete :polymorphic
16
+ options[:class_name] = class_name
17
+ options[:foreign_key] ||= "#{reflection.name}_id"
18
+
19
+ case options[:conditions]
20
+ when nil
21
+ options[:conditions] = condition
22
+ when Array
23
+ options[:conditions] << condition
24
+ when Hash
25
+ options[:conditions].merge!(reflection.foreign_type => options[:class_name])
26
+ else
27
+ options[:conditions] << " AND #{condition}"
28
+ end
29
+
30
+ options
31
+ end
32
+
33
+ private
34
+
35
+ def condition
36
+ "::ts_join_alias::.#{quoted_foreign_type} = '#{class_name}'"
37
+ end
38
+
39
+ def quoted_foreign_type
40
+ active_record.connection.quote_column_name foreign_type
41
+ end
42
+ end
43
+
44
+ def self.clone_with_filter(reflection, name, class_name)
45
+ options = Options.new(reflection, class_name).filtered
46
+
47
+ new reflection.macro, name, options, reflection.active_record
48
+ end
49
+ end
@@ -28,6 +28,12 @@ class ThinkingSphinx::ActiveRecord::Interpreter <
28
28
  }
29
29
  end
30
30
 
31
+ def polymorphs(column, options)
32
+ __source.polymorphs << ::ThinkingSphinx::ActiveRecord::Polymorpher.new(
33
+ __source, column, options[:to]
34
+ )
35
+ end
36
+
31
37
  def sanitize_sql(*arguments)
32
38
  __source.model.send :sanitize_sql, *arguments
33
39
  end
@@ -0,0 +1,50 @@
1
+ class ThinkingSphinx::ActiveRecord::Polymorpher
2
+ def initialize(source, column, class_names)
3
+ @source, @column, @class_names = source, column, class_names
4
+ end
5
+
6
+ def morph!
7
+ append_reflections
8
+ morph_properties
9
+ end
10
+
11
+ private
12
+
13
+ attr_reader :source, :column, :class_names
14
+
15
+ def append_reflections
16
+ mappings.each do |class_name, name|
17
+ klass.reflections[name] ||= ThinkingSphinx::ActiveRecord::
18
+ FilteredReflection.clone_with_filter(reflection, name, class_name)
19
+ end
20
+ end
21
+
22
+ def mappings
23
+ @mappings ||= class_names.inject({}) do |hash, class_name|
24
+ hash[class_name] = "#{column.__name}_#{class_name.downcase}".to_sym
25
+ hash
26
+ end
27
+ end
28
+
29
+ def morphed_stacks
30
+ @morphed_stacks ||= mappings.values.collect { |key|
31
+ column.__stack + [key]
32
+ }
33
+ end
34
+
35
+ def morph_properties
36
+ (source.fields + source.attributes).each do |property|
37
+ property.rebase column.__path, :to => morphed_stacks
38
+ end
39
+ end
40
+
41
+ def reflection
42
+ @reflection ||= klass.reflections[column.__name]
43
+ end
44
+
45
+ def klass
46
+ @klass ||= column.__stack.inject(source.model) { |parent, key|
47
+ parent.reflections[key].klass
48
+ }
49
+ end
50
+ end
@@ -12,6 +12,12 @@ class ThinkingSphinx::ActiveRecord::Property
12
12
  }
13
13
  end
14
14
 
15
+ def rebase(associations, options)
16
+ @columns = columns.inject([]) do |array, column|
17
+ array + column.__replace(associations, options[:to])
18
+ end
19
+ end
20
+
15
21
  def name
16
22
  (options[:as] || columns.first.__name).to_s
17
23
  end
@@ -7,7 +7,7 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
7
7
  identifier = [type, property.name].compact.join(' ')
8
8
  queries = []
9
9
  if column.string?
10
- queries << column.__name
10
+ queries << column.__name.strip.gsub(/\n/, "\\\n")
11
11
  else
12
12
  queries << to_sql
13
13
  queries << range_sql if ranged?
@@ -38,8 +38,14 @@ class ThinkingSphinx::ActiveRecord::PropertySQLPresenter
38
38
  clause
39
39
  end
40
40
 
41
+ def column_exists?(column)
42
+ model = associations.model_for(column.__stack)
43
+ model && model.column_names.include?(column.__name.to_s)
44
+ end
45
+
41
46
  def column_with_table(column)
42
47
  return column.__name if column.string?
48
+ return nil unless column_exists?(column)
43
49
 
44
50
  "#{associations.alias_for(column.__stack)}.#{adapter.quote column.__name}"
45
51
  end
@@ -47,7 +53,7 @@ class ThinkingSphinx::ActiveRecord::PropertySQLPresenter
47
53
  def columns_with_table
48
54
  property.columns.collect { |column|
49
55
  column_with_table(column)
50
- }.join(', ')
56
+ }.compact.join(', ')
51
57
  end
52
58
 
53
59
  def concatenating?