[DRE-commits] [ruby-thinking-sphinx] 01/03: Imported Upstream version 3.2.0

Andrew Lee ajqlee at moszumanska.debian.org
Wed Jun 8 20:35:30 UTC 2016


This is an automated email from the git hooks/post-receive script.

ajqlee pushed a commit to branch master
in repository ruby-thinking-sphinx.

commit 3270f4337ad7f49c113f631d747dfde77004a3f0
Author: Andrew Lee (李健秋) <andrew.lee at collabora.co.uk>
Date:   Thu Jun 9 04:21:34 2016 +0800

    Imported Upstream version 3.2.0
---
 .travis.yml                                        |   9 +-
 Appraisals                                         |   8 +-
 Gemfile                                            |   2 +
 HISTORY                                            |  24 +
 README.textile                                     |   9 +-
 gemfiles/rails_3_2.gemfile                         |   3 +-
 gemfiles/rails_4_0.gemfile                         |   3 +-
 gemfiles/rails_4_1.gemfile                         |   3 +-
 gemfiles/rails_4_2.gemfile                         |   3 +-
 lib/thinking_sphinx.rb                             |   5 +-
 lib/thinking_sphinx/active_record.rb               |   1 +
 .../association_proxy/attribute_finder.rb          |   2 +-
 .../active_record/attribute/type.rb                |   2 +-
 lib/thinking_sphinx/active_record/base.rb          |   2 +-
 .../active_record/callbacks/delete_callbacks.rb    |   2 +-
 .../active_record/callbacks/delta_callbacks.rb     |  10 +-
 .../active_record/callbacks/update_callbacks.rb    |   4 +-
 .../active_record/column_sql_presenter.rb          |   6 +-
 lib/thinking_sphinx/active_record/index.rb         |   8 +-
 lib/thinking_sphinx/active_record/source_joins.rb  |  55 ++
 lib/thinking_sphinx/active_record/sql_builder.rb   |  21 +-
 .../active_record/sql_builder/query.rb             |   7 +
 lib/thinking_sphinx/active_record/sql_source.rb    |  12 +-
 .../active_record/sql_source/template.rb           |   2 +-
 lib/thinking_sphinx/callbacks.rb                   |  18 +
 lib/thinking_sphinx/configuration.rb               |  83 +--
 .../configuration/duplicate_names.rb               |  34 ++
 lib/thinking_sphinx/connection.rb                  |   8 +-
 lib/thinking_sphinx/controller.rb                  |  10 +-
 lib/thinking_sphinx/deletion.rb                    |  13 +
 lib/thinking_sphinx/errors.rb                      |   8 +
 lib/thinking_sphinx/index_set.rb                   |   7 +-
 .../indexing_strategies/all_at_once.rb             |   7 +
 .../indexing_strategies/one_at_a_time.rb           |  14 +
 .../middlewares/active_record_translator.rb        |   2 +-
 lib/thinking_sphinx/middlewares/inquirer.rb        |   2 +-
 lib/thinking_sphinx/middlewares/sphinxql.rb        |   4 +-
 .../middlewares/stale_id_checker.rb                |   2 +-
 lib/thinking_sphinx/middlewares/stale_id_filter.rb |   4 +-
 lib/thinking_sphinx/rake_interface.rb              |  19 +-
 lib/thinking_sphinx/real_time.rb                   |   1 +
 lib/thinking_sphinx/real_time/attribute.rb         |   8 +-
 lib/thinking_sphinx/real_time/index.rb             |   2 +
 lib/thinking_sphinx/real_time/populator.rb         |   6 +-
 lib/thinking_sphinx/real_time/property.rb          |   6 +-
 lib/thinking_sphinx/real_time/transcriber.rb       |  53 +-
 lib/thinking_sphinx/real_time/translator.rb        |  36 ++
 lib/thinking_sphinx/search.rb                      |  10 +
 lib/thinking_sphinx/search/context.rb              |   8 +
 lib/thinking_sphinx/search/stale_ids_exception.rb  |   5 +-
 .../subscribers/populator_subscriber.rb            |   2 +-
 lib/thinking_sphinx/tasks.rb                       |   4 +-
 metadata.yml                                       | 574 ---------------------
 spec/acceptance/remove_deleted_records_spec.rb     |  18 +
 spec/acceptance/searching_with_filters_spec.rb     |  13 +
 spec/internal/app/indices/product_index.rb         |   1 +
 spec/internal/db/schema.rb                         |   1 +
 spec/spec_helper.rb                                |   1 +
 spec/support/json_column.rb                        |  29 ++
 .../callbacks/delete_callbacks_spec.rb             |  10 +
 .../callbacks/update_callbacks_spec.rb             |  10 +
 .../active_record/column_sql_presenter_spec.rb     |  37 ++
 .../active_record/sql_builder_spec.rb              |  24 +-
 .../active_record/sql_source_spec.rb               |  58 ++-
 spec/thinking_sphinx/errors_spec.rb                |   7 +
 .../middlewares/active_record_translator_spec.rb   |  16 +-
 .../middlewares/stale_id_checker_spec.rb           |   1 +
 .../middlewares/stale_id_filter_spec.rb            |  32 +-
 .../callbacks/real_time_callbacks_spec.rb          |   8 +-
 spec/thinking_sphinx_spec.rb                       |   3 +-
 thinking-sphinx.gemspec                            |   2 +-
 71 files changed, 676 insertions(+), 748 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index edf0f73..90f2c29 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,8 @@
 language: ruby
 rvm:
-  - 1.9.3
   - 2.0.0
-  - 2.1.0
+  - 2.1
+  - 2.2
   - jruby-19mode
 before_install:
   - gem update --system
@@ -19,3 +19,8 @@ env:
   - DATABASE=postgresql SPHINX_BIN=/usr/local/sphinx-2.1.9/bin/ SPHINX_VERSION=2.1.9
   - DATABASE=mysql2 SPHINX_BIN=/usr/local/sphinx-2.2.6/bin/ SPHINX_VERSION=2.2.6
   - DATABASE=postgresql SPHINX_BIN=/usr/local/sphinx-2.2.6/bin/ SPHINX_VERSION=2.2.6
+sudo: false
+addons:
+  postgresql: "9.4"
+services:
+  - postgresql
diff --git a/Appraisals b/Appraisals
index 36aa693..77ba02d 100644
--- a/Appraisals
+++ b/Appraisals
@@ -1,15 +1,15 @@
 appraise 'rails_3_2' do
-  gem 'rails',  '~> 3.2.21'
+  gem 'rails',  '~> 3.2.22.2'
 end
 
 appraise 'rails_4_0' do
-  gem 'rails', '~> 4.0.12'
+  gem 'rails', '~> 4.0.13'
 end
 
 appraise 'rails_4_1' do
-  gem 'rails', '~> 4.1.8'
+  gem 'rails', '~> 4.1.15'
 end
 
 appraise 'rails_4_2' do
-  gem 'rails', '~> 4.2.0'
+  gem 'rails', '~> 4.2.6'
 end
diff --git a/Gemfile b/Gemfile
index 851e7be..dc718bc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,5 +5,7 @@ gemspec
 gem 'mysql2', '~> 0.3.12b4', :platform => :ruby
 gem 'pg',     '~> 0.16.0',   :platform => :ruby
 
+gem 'jdbc-mysql',                          '5.1.35',   :platform => :jruby
 gem 'activerecord-jdbcmysql-adapter',      '~> 1.3.4', :platform => :jruby
 gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3.4', :platform => :jruby
+
diff --git a/HISTORY b/HISTORY
index faa4029..b8522eb 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,3 +1,27 @@
+2016-05-13: 3.2.0
+* [FIX] Ensure SQL table aliases are reliable for SQL-backed index queries.
+* [FEATURE] Add JSON attribute support for real-time indices.
+* [FEATURE] Add ability to disable *all* Sphinx-related callbacks via ThinkingSphinx::Callbacks.suspend! and ThinkingSphinx::Callbacks.resume!. Particularly useful for unit tests.
+* [FEATURE] Add native OutOfBoundsError for search queries outside the pagination bounds.
+* [CHANGE] Improved error messages for duplicate property names and missing columns.
+* [FIX] Fixed mysql2 compatibility for memory references (Roman Usherenko).
+* [FIX] Fixed JRuby compatibility with camelCase method names (Brandon Dewitt).
+* [FEATURE] Support MySQL SSL options on a per-index level (@arrtchiu).
+* [CHANGE] Don't populate search results when requesting just the count values (Andrew Roth).
+* [CHANGE] Reset delta column before core indexing begins (reverting behaviour introduced in 3.1.0). See issue #958 for further discussion.
+* [FEATURE] Allow for different indexing strategies (e.g. all at once, or one by one).
+* [FIX] Fix stale id handling for multiple search contexts (Jonathan del Strother).
+* [CHANGE] Use Sphinx's bulk insert ability (Chance Downs).
+* [CHANGE] Reduce memory/object usage for model references (Jonathan del Strother).
+* [CHANGE] Disable deletion callbacks when real-time indices are in place and all other real-time callbacks are disabled.
+* [FIX] Handle quoting of namespaced tables (Roman Usherenko).
+* [FIX] Make preload_indices thread-safe.
+* [FIX] Improved handling of marshalled/demarshalled search results.
+* [FEATURE] Allow rand_seed as a select option (Mattia Gheda).
+* [FEATURE] Add primary_key option for index definitions (Nathaneal Gray).
+* [FEATURE] Add ability to start searchd in the foreground (Andrey Novikov).
+* [CHANGE] Only use ERB to parse the YAML file if ERB is loaded.
+
 2015-06-01: 3.1.4
 * [FIX] Kaminari expects prev_page to be available.
 * [CHANGE] Add a contributor code of conduct.
diff --git a/README.textile b/README.textile
index 97c759b..1024f88 100644
--- a/README.textile
+++ b/README.textile
@@ -1,11 +1,12 @@
 h1. Thinking Sphinx
 
-Thinking Sphinx is a library for connecting ActiveRecord to the Sphinx full-text search tool, and integrates closely with Rails (but also works with other Ruby web frameworks). The current release is v3.1.4.
+Thinking Sphinx is a library for connecting ActiveRecord to the Sphinx full-text search tool, and integrates closely with Rails (but also works with other Ruby web frameworks). The current release is v3.2.0.
 
 h2. Upgrading
 
 Please refer to the release notes for any changes you need to make when upgrading:
 
+* "v3.2.0":https://github.com/pat/thinking-sphinx/releases/tag/v3.2.0
 * "v3.1.4":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.4
 * "v3.1.3":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.3
 * "v3.1.2":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.2
@@ -20,10 +21,10 @@ h2. Installation
 It's a gem, so install it like you would any other gem. You will also need to specify the mysql2 gem if you're using MRI, or jdbc-mysql if you're using JRuby:
 
 <pre><code>gem 'mysql2',          '~> 0.3.18', :platform => :ruby
-gem 'jdbc-mysql',      '~> 5.1.35', :platform => :jruby
-gem 'thinking-sphinx', '~> 3.1.4'</code></pre>
+gem 'jdbc-mysql',      '= 5.1.35', :platform => :jruby
+gem 'thinking-sphinx', '~> 3.2.0'</code></pre>
 
-The MySQL gems mentioned are required for connecting to Sphinx, so please include it even when you're using PostgreSQL for your database.
+The MySQL gems mentioned are required for connecting to Sphinx, so please include it even when you're using PostgreSQL for your database. If you're using JRuby, there is "currently an issue with Sphinx and jdbc-mysql 5.1.36 or newer":http://sphinxsearch.com/forum/view.html?id=13939, so you'll need to stick to nothing more recent than 5.1.35.
 
 You'll also need to install Sphinx - this is covered in "the extended documentation":http://pat.github.io/thinking-sphinx/installing_sphinx.html.
 
diff --git a/gemfiles/rails_3_2.gemfile b/gemfiles/rails_3_2.gemfile
index 56b8bfe..d79a1df 100644
--- a/gemfiles/rails_3_2.gemfile
+++ b/gemfiles/rails_3_2.gemfile
@@ -4,8 +4,9 @@ source "https://rubygems.org"
 
 gem "mysql2", "~> 0.3.12b4", :platform => :ruby
 gem "pg", "~> 0.16.0", :platform => :ruby
+gem "jdbc-mysql", "5.1.35", :platform => :jruby
 gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform => :jruby
 gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform => :jruby
-gem "rails", "~> 3.2.21"
+gem "rails", "~> 3.2.22.2"
 
 gemspec :path => "../"
diff --git a/gemfiles/rails_4_0.gemfile b/gemfiles/rails_4_0.gemfile
index d1ed7fd..902f1a4 100644
--- a/gemfiles/rails_4_0.gemfile
+++ b/gemfiles/rails_4_0.gemfile
@@ -4,8 +4,9 @@ source "https://rubygems.org"
 
 gem "mysql2", "~> 0.3.12b4", :platform => :ruby
 gem "pg", "~> 0.16.0", :platform => :ruby
+gem "jdbc-mysql", "5.1.35", :platform => :jruby
 gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform => :jruby
 gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform => :jruby
-gem "rails", "~> 4.0.12"
+gem "rails", "~> 4.0.13"
 
 gemspec :path => "../"
diff --git a/gemfiles/rails_4_1.gemfile b/gemfiles/rails_4_1.gemfile
index 46c56fe..0cee807 100644
--- a/gemfiles/rails_4_1.gemfile
+++ b/gemfiles/rails_4_1.gemfile
@@ -4,8 +4,9 @@ source "https://rubygems.org"
 
 gem "mysql2", "~> 0.3.12b4", :platform => :ruby
 gem "pg", "~> 0.16.0", :platform => :ruby
+gem "jdbc-mysql", "5.1.35", :platform => :jruby
 gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform => :jruby
 gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform => :jruby
-gem "rails", "~> 4.1.8"
+gem "rails", "~> 4.1.15"
 
 gemspec :path => "../"
diff --git a/gemfiles/rails_4_2.gemfile b/gemfiles/rails_4_2.gemfile
index 8ada8d7..d6ddc23 100644
--- a/gemfiles/rails_4_2.gemfile
+++ b/gemfiles/rails_4_2.gemfile
@@ -4,8 +4,9 @@ source "https://rubygems.org"
 
 gem "mysql2", "~> 0.3.12b4", :platform => :ruby
 gem "pg", "~> 0.16.0", :platform => :ruby
+gem "jdbc-mysql", "5.1.35", :platform => :jruby
 gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform => :jruby
 gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform => :jruby
-gem "rails", "~> 4.2.0"
+gem "rails", "~> 4.2.6"
 
 gemspec :path => "../"
diff --git a/lib/thinking_sphinx.rb b/lib/thinking_sphinx.rb
index c219fca..ee0ed9d 100644
--- a/lib/thinking_sphinx.rb
+++ b/lib/thinking_sphinx.rb
@@ -16,7 +16,7 @@ require 'active_support/core_ext/module/attribute_accessors'
 
 module ThinkingSphinx
   def self.count(query = '', options = {})
-    search(query, options).total_entries
+    search_for_ids(query, options).total_entries
   end
 
   def self.facets(query = '', options = {})
@@ -39,6 +39,7 @@ module ThinkingSphinx
   @before_index_hooks = []
 
   module Subscribers; end
+  module IndexingStrategies; end
 end
 
 # Core
@@ -57,6 +58,8 @@ require 'thinking_sphinx/float_formatter'
 require 'thinking_sphinx/frameworks'
 require 'thinking_sphinx/guard'
 require 'thinking_sphinx/index'
+require 'thinking_sphinx/indexing_strategies/all_at_once'
+require 'thinking_sphinx/indexing_strategies/one_at_a_time'
 require 'thinking_sphinx/index_set'
 require 'thinking_sphinx/masks'
 require 'thinking_sphinx/middlewares'
diff --git a/lib/thinking_sphinx/active_record.rb b/lib/thinking_sphinx/active_record.rb
index c78f668..412c156 100644
--- a/lib/thinking_sphinx/active_record.rb
+++ b/lib/thinking_sphinx/active_record.rb
@@ -23,6 +23,7 @@ require 'thinking_sphinx/active_record/polymorpher'
 require 'thinking_sphinx/active_record/property_query'
 require 'thinking_sphinx/active_record/property_sql_presenter'
 require 'thinking_sphinx/active_record/simple_many_query'
+require 'thinking_sphinx/active_record/source_joins'
 require 'thinking_sphinx/active_record/sql_builder'
 require 'thinking_sphinx/active_record/sql_source'
 
diff --git a/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb b/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb
index 6a165f2..d861f78 100644
--- a/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb
+++ b/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb
@@ -29,7 +29,7 @@ class ThinkingSphinx::ActiveRecord::AssociationProxy::AttributeFinder
     @indices ||= begin
       configuration.preload_indices
       configuration.indices_for_references(
-        *@association.klass.name.underscore.to_sym
+        *ThinkingSphinx::IndexSet.reference_name(@association.klass)
       ).reject &:distributed?
     end
   end
diff --git a/lib/thinking_sphinx/active_record/attribute/type.rb b/lib/thinking_sphinx/active_record/attribute/type.rb
index f85a620..ef5c6e0 100644
--- a/lib/thinking_sphinx/active_record/attribute/type.rb
+++ b/lib/thinking_sphinx/active_record/attribute/type.rb
@@ -72,7 +72,7 @@ class ThinkingSphinx::ActiveRecord::Attribute::Type
 
   def type_from_database
     raise ThinkingSphinx::MissingColumnError,
-      "column #{column_name} does not exist" if database_column.nil?
+      "Cannot determine the database type of column #{column_name}, as it does not exist" if database_column.nil?
 
     return :bigint if big_integer?
 
diff --git a/lib/thinking_sphinx/active_record/base.rb b/lib/thinking_sphinx/active_record/base.rb
index 2822141..ab2b670 100644
--- a/lib/thinking_sphinx/active_record/base.rb
+++ b/lib/thinking_sphinx/active_record/base.rb
@@ -21,7 +21,7 @@ module ThinkingSphinx::ActiveRecord::Base
     end
 
     def search_count(query = nil, options = {})
-      search(query, options).total_entries
+      search_for_ids(query, options).total_entries
     end
 
     def search_for_ids(query = nil, options = {})
diff --git a/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb b/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb
index 8d3c961..88983a4 100644
--- a/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb
+++ b/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb
@@ -4,7 +4,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
   callbacks :after_destroy
 
   def after_destroy
-    return if instance.new_record?
+    return if ThinkingSphinx::Callbacks.suspended? || instance.new_record?
 
     indices.each { |index|
       ThinkingSphinx::Deletion.perform index, instance.id
diff --git a/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb b/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb
index 1789b6b..19b8c50 100644
--- a/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb
+++ b/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb
@@ -4,9 +4,9 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks <
   callbacks :after_commit, :before_save
 
   def after_commit
-    return unless delta_indices? && processors.any? { |processor|
+    return unless !suspended? && delta_indices? && processors.any? { |processor|
       processor.toggled?(instance)
-    } && !ThinkingSphinx::Deltas.suspended?
+    }
 
     delta_indices.each do |index|
       index.delta_processor.index index
@@ -18,7 +18,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks <
   end
 
   def before_save
-    return unless delta_indices?
+    return unless !ThinkingSphinx::Callbacks.suspended? && delta_indices?
 
     processors.each { |processor| processor.toggle instance }
   end
@@ -48,4 +48,8 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks <
   def processors
     delta_indices.collect &:delta_processor
   end
+
+  def suspended?
+    ThinkingSphinx::Callbacks.suspended? || ThinkingSphinx::Deltas.suspended?
+  end
 end
diff --git a/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb b/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb
index 16120bb..61d83bd 100644
--- a/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb
+++ b/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb
@@ -4,7 +4,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks <
   callbacks :after_update
 
   def after_update
-    return unless updates_enabled?
+    return unless !ThinkingSphinx::Callbacks.suspended? && updates_enabled?
 
     indices.each do |index|
       update index unless index.distributed?
@@ -35,7 +35,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks <
   end
 
   def reference
-    instance.class.name.underscore.to_sym
+    ThinkingSphinx::IndexSet.reference_name(instance.class)
   end
 
   def update(index)
diff --git a/lib/thinking_sphinx/active_record/column_sql_presenter.rb b/lib/thinking_sphinx/active_record/column_sql_presenter.rb
index 145a53c..9d70d3f 100644
--- a/lib/thinking_sphinx/active_record/column_sql_presenter.rb
+++ b/lib/thinking_sphinx/active_record/column_sql_presenter.rb
@@ -13,7 +13,7 @@ class ThinkingSphinx::ActiveRecord::ColumnSQLPresenter
     return __name if string?
     return nil unless exists?
 
-    quoted_table = escape_table? ? adapter.quote(table) : table
+    quoted_table = escape_table? ? escape_table(table) : table
 
     "#{quoted_table}.#{adapter.quote __name}"
   end
@@ -24,6 +24,10 @@ class ThinkingSphinx::ActiveRecord::ColumnSQLPresenter
 
   delegate :__stack, :__name, :string?, :to => :column
 
+  def escape_table(table_name)
+    table_name.split('.').map { |t| adapter.quote(t) }.join('.')
+  end
+
   def escape_table?
     table[/[`"]/].nil?
   end
diff --git a/lib/thinking_sphinx/active_record/index.rb b/lib/thinking_sphinx/active_record/index.rb
index eac534b..a6a7d6b 100644
--- a/lib/thinking_sphinx/active_record/index.rb
+++ b/lib/thinking_sphinx/active_record/index.rb
@@ -28,6 +28,10 @@ class ThinkingSphinx::ActiveRecord::Index < Riddle::Configuration::Index
     @facets ||= sources.collect(&:facets).flatten
   end
 
+  def fields
+    sources.collect(&:fields).flatten
+  end
+
   def sources
     interpret_definition!
     super
@@ -44,10 +48,6 @@ class ThinkingSphinx::ActiveRecord::Index < Riddle::Configuration::Index
       adapter_for(model)
   end
 
-  def fields
-    sources.collect(&:fields).flatten
-  end
-
   def interpreter
     ThinkingSphinx::ActiveRecord::Interpreter
   end
diff --git a/lib/thinking_sphinx/active_record/source_joins.rb b/lib/thinking_sphinx/active_record/source_joins.rb
new file mode 100644
index 0000000..d56d6b7
--- /dev/null
+++ b/lib/thinking_sphinx/active_record/source_joins.rb
@@ -0,0 +1,55 @@
+class ThinkingSphinx::ActiveRecord::SourceJoins
+  def self.call(model, source)
+    new(model, source).call
+  end
+
+  def initialize(model, source)
+    @model, @source = model, source
+  end
+
+  def call
+    append_specified_associations
+    append_property_associations
+
+    joins
+  end
+
+  private
+
+  attr_reader :model, :source
+
+  def append_property_associations
+    source.properties.collect(&:columns).each do |columns|
+      columns.each { |column| append_column_associations column }
+    end
+  end
+
+  def append_column_associations(column)
+    return if column.__stack.empty?
+
+    joins.add_join_to column.__stack if column_exists?(column)
+  end
+
+  def append_specified_associations
+    source.associations.reject(&:string?).each do |association|
+      joins.add_join_to association.stack
+    end
+  end
+
+  def column_exists?(column)
+    Joiner::Path.new(model, column.__stack).model
+    true
+  rescue Joiner::AssociationNotFound
+    false
+  end
+
+  def joins
+    @joins ||= begin
+      joins = Joiner::Joins.new model
+      if joins.respond_to?(:join_association_class)
+        joins.join_association_class = ThinkingSphinx::ActiveRecord::JoinAssociation
+      end
+      joins
+    end
+  end
+end
diff --git a/lib/thinking_sphinx/active_record/sql_builder.rb b/lib/thinking_sphinx/active_record/sql_builder.rb
index 974f270..1855b8f 100644
--- a/lib/thinking_sphinx/active_record/sql_builder.rb
+++ b/lib/thinking_sphinx/active_record/sql_builder.rb
@@ -20,12 +20,6 @@ module ThinkingSphinx
         query.to_query
       end
 
-      def sql_query_post_index
-        return [] unless delta_processor && !source.delta?
-
-        [delta_processor.reset_query]
-      end
-
       private
 
       delegate :adapter, :model, :delta_processor, :to => :source
@@ -49,18 +43,9 @@ module ThinkingSphinx
       end
 
       def associations
-        @associations ||= begin
-          joins = Joiner::Joins.new model
-          if joins.respond_to?(:join_association_class)
-            joins.join_association_class = ThinkingSphinx::ActiveRecord::JoinAssociation
-          end
-
-          source.associations.reject(&:string?).each do |association|
-            joins.add_join_to association.stack
-          end
-
-          joins
-        end
+        @associations ||= ThinkingSphinx::ActiveRecord::SourceJoins.call(
+          model, source
+        )
       end
 
       def quote_column(column)
diff --git a/lib/thinking_sphinx/active_record/sql_builder/query.rb b/lib/thinking_sphinx/active_record/sql_builder/query.rb
index 5793526..b93bf12 100644
--- a/lib/thinking_sphinx/active_record/sql_builder/query.rb
+++ b/lib/thinking_sphinx/active_record/sql_builder/query.rb
@@ -18,10 +18,17 @@ module ThinkingSphinx
 
       def filter_by_query_pre
         scope_by_time_zone
+        scope_by_delta_processor
         scope_by_session
         scope_by_utf8
       end
 
+      def scope_by_delta_processor
+        return unless delta_processor && !source.delta?
+
+        self.scope << delta_processor.reset_query
+      end
+
       def scope_by_session
         return unless max_len = source.options[:group_concat_max_len]
 
diff --git a/lib/thinking_sphinx/active_record/sql_source.rb b/lib/thinking_sphinx/active_record/sql_source.rb
index faa1fe6..fcecf69 100644
--- a/lib/thinking_sphinx/active_record/sql_source.rb
+++ b/lib/thinking_sphinx/active_record/sql_source.rb
@@ -61,6 +61,10 @@ module ThinkingSphinx
         options[:primary_key]
       end
 
+      def properties
+        fields + attributes
+      end
+
       def render
         prepare_for_render unless @prepared
 
@@ -74,6 +78,9 @@ module ThinkingSphinx
         @sql_db   ||= settings[:database]
         @sql_port ||= settings[:port]
         @sql_sock ||= settings[:socket]
+        @mysql_ssl_cert ||= settings[:sslcert]
+        @mysql_ssl_key  ||= settings[:sslkey]
+        @mysql_ssl_ca   ||= settings[:sslca]
       end
 
       def type
@@ -119,7 +126,6 @@ module ThinkingSphinx
         @sql_query             = builder.sql_query
         @sql_query_range     ||= builder.sql_query_range
         @sql_query_pre        += builder.sql_query_pre
-        @sql_query_post_index += builder.sql_query_post_index
       end
 
       def config
@@ -136,10 +142,6 @@ module ThinkingSphinx
 
         @prepared = true
       end
-
-      def properties
-        fields + attributes
-      end
     end
   end
 end
diff --git a/lib/thinking_sphinx/active_record/sql_source/template.rb b/lib/thinking_sphinx/active_record/sql_source/template.rb
index 13f4adb..2d45ca9 100644
--- a/lib/thinking_sphinx/active_record/sql_source/template.rb
+++ b/lib/thinking_sphinx/active_record/sql_source/template.rb
@@ -48,6 +48,6 @@ class ThinkingSphinx::ActiveRecord::SQLSource::Template
   end
 
   def primary_key
-    source.model.primary_key.to_sym
+    source.options[:primary_key].to_sym
   end
 end
diff --git a/lib/thinking_sphinx/callbacks.rb b/lib/thinking_sphinx/callbacks.rb
index 0cf2d64..351e969 100644
--- a/lib/thinking_sphinx/callbacks.rb
+++ b/lib/thinking_sphinx/callbacks.rb
@@ -9,6 +9,24 @@ class ThinkingSphinx::Callbacks
     extend mod
   end
 
+  def self.resume!
+    @suspended = false
+  end
+
+  def self.suspend(&block)
+    suspend!
+    yield
+    resume!
+  end
+
+  def self.suspend!
+    @suspended = true
+  end
+
+  def self.suspended?
+    @suspended
+  end
+
   def initialize(instance)
     @instance = instance
   end
diff --git a/lib/thinking_sphinx/configuration.rb b/lib/thinking_sphinx/configuration.rb
index 1dfdb16..963ac76 100644
--- a/lib/thinking_sphinx/configuration.rb
+++ b/lib/thinking_sphinx/configuration.rb
@@ -3,14 +3,16 @@ require 'pathname'
 class ThinkingSphinx::Configuration < Riddle::Configuration
   attr_accessor :configuration_file, :indices_location, :version
   attr_reader :index_paths
-  attr_writer :controller, :index_set_class
+  attr_writer :controller, :index_set_class, :indexing_strategy
 
   delegate :environment, :to => :framework
 
+  @@mutex = Mutex.new
+
   def initialize
     super
 
-    setup
+    reset
   end
 
   def self.instance
@@ -39,7 +41,7 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
 
   def framework=(framework)
     @framework = framework
-    setup
+    reset
     framework
   end
 
@@ -60,6 +62,10 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
     @index_set_class ||= ThinkingSphinx::IndexSet
   end
 
+  def indexing_strategy
+    @indexing_strategy ||= ThinkingSphinx::IndexingStrategies::AllAtOnce
+  end
+
   def indices_for_references(*references)
     index_set_class.new(:references => references).to_a
   end
@@ -69,19 +75,21 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
   end
 
   def preload_indices
-    return if @preloaded_indices
+    @@mutex.synchronize do
+      return if @preloaded_indices
 
-    index_paths.each do |path|
-      Dir["#{path}/**/*.rb"].sort.each do |file|
-        ActiveSupport::Dependencies.require_or_load file
+      index_paths.each do |path|
+        Dir["#{path}/**/*.rb"].sort.each do |file|
+          ActiveSupport::Dependencies.require_or_load file
+        end
       end
-    end
 
-    if settings['distributed_indices'].nil? || settings['distributed_indices']
-      ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
-    end
+      if settings['distributed_indices'].nil? || settings['distributed_indices']
+        ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
+      end
 
-    @preloaded_indices = true
+      @preloaded_indices = true
+    end
   end
 
   def render
@@ -89,6 +97,7 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
 
     ThinkingSphinx::Configuration::ConsistentIds.new(indices).reconcile
     ThinkingSphinx::Configuration::MinimumFields.new(indices).reconcile
+    ThinkingSphinx::Configuration::DuplicateNames.new(indices).reconcile
 
     super
   end
@@ -103,6 +112,28 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
     @settings ||= File.exists?(settings_file) ? settings_to_hash : {}
   end
 
+  def setup
+    @configuration_file = settings['configuration_file'] || framework_root.join(
+      'config', "#{environment}.sphinx.conf"
+    ).to_s
+    @index_paths = engine_index_paths + [framework_root.join('app', 'indices').to_s]
+    @indices_location = settings['indices_location'] || framework_root.join(
+      'db', 'sphinx', environment
+    ).to_s
+    @version = settings['version'] || '2.1.4'
+
+    if settings['common_sphinx_configuration']
+      common.common_sphinx_configuration  = true
+      indexer.common_sphinx_configuration = true
+    end
+
+    configure_searchd
+
+    apply_sphinx_settings!
+
+    @offsets = {}
+  end
+
   private
 
   def configure_searchd
@@ -135,7 +166,10 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
   end
 
   def settings_to_hash
-    contents = YAML.load(ERB.new(File.read(settings_file)).result)
+    input    = File.read settings_file
+    input    = ERB.new(input).result if defined?(ERB)
+
+    contents = YAML.load input
     contents && contents[environment] || {}
   end
 
@@ -143,27 +177,9 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
     framework_root.join 'config', 'thinking_sphinx.yml'
   end
 
-  def setup
+  def reset
     @settings = nil
-    @configuration_file = settings['configuration_file'] || framework_root.join(
-      'config', "#{environment}.sphinx.conf"
-    ).to_s
-    @index_paths = engine_index_paths + [framework_root.join('app', 'indices').to_s]
-    @indices_location = settings['indices_location'] || framework_root.join(
-      'db', 'sphinx', environment
-    ).to_s
-    @version = settings['version'] || '2.1.4'
-
-    if settings['common_sphinx_configuration']
-      common.common_sphinx_configuration  = true
-      indexer.common_sphinx_configuration = true
-    end
-
-    configure_searchd
-
-    apply_sphinx_settings!
-
-    @offsets = {}
+    setup
   end
 
   def tmp_path
@@ -190,4 +206,5 @@ end
 require 'thinking_sphinx/configuration/consistent_ids'
 require 'thinking_sphinx/configuration/defaults'
 require 'thinking_sphinx/configuration/distributed_indices'
+require 'thinking_sphinx/configuration/duplicate_names'
 require 'thinking_sphinx/configuration/minimum_fields'
diff --git a/lib/thinking_sphinx/configuration/duplicate_names.rb b/lib/thinking_sphinx/configuration/duplicate_names.rb
new file mode 100644
index 0000000..f01c543
--- /dev/null
+++ b/lib/thinking_sphinx/configuration/duplicate_names.rb
@@ -0,0 +1,34 @@
+class ThinkingSphinx::Configuration::DuplicateNames
+  def initialize(indices)
+    @indices = indices
+  end
+
+  def reconcile
+    indices.each do |index|
+      return if index.distributed?
+
+      counts_for(index).each do |name, count|
+        next if count <= 1
+
+        raise ThinkingSphinx::DuplicateNameError,
+          "Duplicate field/attribute name '#{name}' in index '#{index.name}'"
+      end
+    end
+  end
+
+  private
+
+  attr_reader :indices
+
+  def counts_for(index)
+    names_for(index).inject({}) do |hash, name|
+      hash[name] ||= 0
+      hash[name] += 1
+      hash
+    end
+  end
+
+  def names_for(index)
+    index.fields.collect(&:name) + index.attributes.collect(&:name)
+  end
+end
diff --git a/lib/thinking_sphinx/connection.rb b/lib/thinking_sphinx/connection.rb
index 76d8f0d..f409c93 100644
--- a/lib/thinking_sphinx/connection.rb
+++ b/lib/thinking_sphinx/connection.rb
@@ -157,13 +157,13 @@ module ThinkingSphinx::Connection
     def set_to_array(set)
       return nil if set.nil?
 
-      meta = set.meta_data
+      meta = set.getMetaData
       rows = []
 
       while set.next
-        rows << (1..meta.column_count).inject({}) do |row, index|
-          name      = meta.column_name index
-          row[name] = set.get_object(index)
+        rows << (1..meta.getColumnCount).inject({}) do |row, index|
+          name      = meta.getColumnName index
+          row[name] = set.getObject(index)
           row
         end
       end
diff --git a/lib/thinking_sphinx/controller.rb b/lib/thinking_sphinx/controller.rb
index add386c..247a1df 100644
--- a/lib/thinking_sphinx/controller.rb
+++ b/lib/thinking_sphinx/controller.rb
@@ -1,10 +1,12 @@
 class ThinkingSphinx::Controller < Riddle::Controller
   def index(*indices)
-    options = indices.extract_options!
-    indices << '--all' if indices.empty?
+    configuration = ThinkingSphinx::Configuration.instance
+    options       = indices.extract_options!
 
-    ThinkingSphinx::Guard::Files.call(indices) do |names|
-      super(*(names + [options]))
+    configuration.indexing_strategy.call(indices) do |index_names|
+      ThinkingSphinx::Guard::Files.call(index_names) do |names|
+        super(*(names + [options]))
+      end
     end
   end
 end
diff --git a/lib/thinking_sphinx/deletion.rb b/lib/thinking_sphinx/deletion.rb
index 61fc97c..15ad08f 100644
--- a/lib/thinking_sphinx/deletion.rb
+++ b/lib/thinking_sphinx/deletion.rb
@@ -32,8 +32,21 @@ class ThinkingSphinx::Deletion
 
   class RealtimeDeletion < ThinkingSphinx::Deletion
     def perform
+      return unless callbacks_enabled?
+
       execute Riddle::Query::Delete.new(name, document_ids_for_keys).to_sql
     end
+
+    private
+
+    def callbacks_enabled?
+      setting = configuration.settings['real_time_callbacks']
+      setting.nil? || setting
+    end
+
+    def configuration
+      ThinkingSphinx::Configuration.instance
+    end
   end
 
   class PlainDeletion < ThinkingSphinx::Deletion
diff --git a/lib/thinking_sphinx/errors.rb b/lib/thinking_sphinx/errors.rb
index 2d37686..6416714 100644
--- a/lib/thinking_sphinx/errors.rb
+++ b/lib/thinking_sphinx/errors.rb
@@ -13,6 +13,8 @@ class ThinkingSphinx::SphinxError < StandardError
       replacement = ThinkingSphinx::ConnectionError.new(
         "Error connecting to Sphinx via the MySQL protocol. #{error.message}"
       )
+    when /offset out of bounds/
+      replacement = ThinkingSphinx::OutOfBoundsError.new(error.message)
     else
       replacement = new(error.message)
     end
@@ -35,6 +37,9 @@ end
 class ThinkingSphinx::ParseError < ThinkingSphinx::QueryError
 end
 
+class ThinkingSphinx::OutOfBoundsError < ThinkingSphinx::QueryError
+end
+
 class ThinkingSphinx::QueryExecutionError < StandardError
   attr_accessor :statement
 end
@@ -50,3 +55,6 @@ end
 
 class ThinkingSphinx::PopulatedResultsError < StandardError
 end
+
+class ThinkingSphinx::DuplicateNameError < StandardError
+end
diff --git a/lib/thinking_sphinx/index_set.rb b/lib/thinking_sphinx/index_set.rb
index 185f49b..5d3ad77 100644
--- a/lib/thinking_sphinx/index_set.rb
+++ b/lib/thinking_sphinx/index_set.rb
@@ -1,5 +1,10 @@
 class ThinkingSphinx::IndexSet
   include Enumerable
+  
+  def self.reference_name(klass)
+    @cached_results ||= {}
+    @cached_results[klass.name] ||= klass.name.underscore.to_sym
+  end
 
   delegate :each, :empty?, :to => :indices
 
@@ -63,7 +68,7 @@ class ThinkingSphinx::IndexSet
 
   def references
     options[:references] || classes_and_ancestors.collect { |klass|
-      klass.name.underscore.to_sym
+      ThinkingSphinx::IndexSet.reference_name(klass)
     }
   end
 
diff --git a/lib/thinking_sphinx/indexing_strategies/all_at_once.rb b/lib/thinking_sphinx/indexing_strategies/all_at_once.rb
new file mode 100644
index 0000000..ba992c9
--- /dev/null
+++ b/lib/thinking_sphinx/indexing_strategies/all_at_once.rb
@@ -0,0 +1,7 @@
+class ThinkingSphinx::IndexingStrategies::AllAtOnce
+  def self.call(indices = [], &block)
+    indices << '--all' if indices.empty?
+
+    block.call indices
+  end
+end
diff --git a/lib/thinking_sphinx/indexing_strategies/one_at_a_time.rb b/lib/thinking_sphinx/indexing_strategies/one_at_a_time.rb
new file mode 100644
index 0000000..27c8c58
--- /dev/null
+++ b/lib/thinking_sphinx/indexing_strategies/one_at_a_time.rb
@@ -0,0 +1,14 @@
+class ThinkingSphinx::IndexingStrategies::OneAtATime
+  def self.call(indices = [], &block)
+    if indices.empty?
+      configuration = ThinkingSphinx::Configuration.instance
+      configuration.preload_indices
+
+      indices = configuration.indices.select { |index|
+        !(index.distributed? || index.type == 'rt')
+      }.collect &:name
+    end
+
+    indices.each { |name| block.call [name] }
+  end
+end
diff --git a/lib/thinking_sphinx/middlewares/active_record_translator.rb b/lib/thinking_sphinx/middlewares/active_record_translator.rb
index 56e4a3c..1299d80 100644
--- a/lib/thinking_sphinx/middlewares/active_record_translator.rb
+++ b/lib/thinking_sphinx/middlewares/active_record_translator.rb
@@ -57,7 +57,7 @@ class ThinkingSphinx::Middlewares::ActiveRecordTranslator <
       @results_for_models ||= model_names.inject({}) do |hash, name|
         model = name.constantize
         hash[name] = model_relation_with_sql_options(model.unscoped).where(
-          model.primary_key => ids_for_model(name)
+          (context.configuration.settings[:primary_key] || model.primary_key || :id) => ids_for_model(name)
         )
 
         hash
diff --git a/lib/thinking_sphinx/middlewares/inquirer.rb b/lib/thinking_sphinx/middlewares/inquirer.rb
index 75bd45d..766072e 100644
--- a/lib/thinking_sphinx/middlewares/inquirer.rb
+++ b/lib/thinking_sphinx/middlewares/inquirer.rb
@@ -45,7 +45,7 @@ class ThinkingSphinx::Middlewares::Inquirer <
 
     def call(raw_results, meta_results)
       context[:results] = raw_results.to_a
-      context[:raw]     = raw_results
+      context[:raw]     = context[:results].dup
       context[:meta]    = meta_results.inject({}) { |hash, row|
         hash[row['Variable_name']] = row['Value']
         hash
diff --git a/lib/thinking_sphinx/middlewares/sphinxql.rb b/lib/thinking_sphinx/middlewares/sphinxql.rb
index 21115e9..d9cf4d2 100644
--- a/lib/thinking_sphinx/middlewares/sphinxql.rb
+++ b/lib/thinking_sphinx/middlewares/sphinxql.rb
@@ -4,7 +4,7 @@ class ThinkingSphinx::Middlewares::SphinxQL <
   SELECT_OPTIONS = [:agent_query_timeout, :boolean_simplify, :comment, :cutoff,
     :field_weights, :global_idf, :idf, :index_weights, :max_matches,
     :max_query_time, :max_predicted_time, :ranker, :retry_count, :retry_delay,
-    :reverse_scan, :sort_method]
+    :reverse_scan, :sort_method, :rand_seed]
 
   def call(contexts)
     contexts.each do |context|
@@ -82,7 +82,7 @@ class ThinkingSphinx::Middlewares::SphinxQL <
 
     def indices_match_classes?
       indices.collect(&:reference).uniq.sort == classes.collect { |klass|
-        klass.name.underscore.to_sym
+        ThinkingSphinx::IndexSet.reference_name(klass)
       }.sort
     end
 
diff --git a/lib/thinking_sphinx/middlewares/stale_id_checker.rb b/lib/thinking_sphinx/middlewares/stale_id_checker.rb
index aec6805..a71ffd0 100644
--- a/lib/thinking_sphinx/middlewares/stale_id_checker.rb
+++ b/lib/thinking_sphinx/middlewares/stale_id_checker.rb
@@ -33,7 +33,7 @@ class ThinkingSphinx::Middlewares::StaleIdChecker <
     end
 
     def raise_exception
-      raise ThinkingSphinx::Search::StaleIdsException, stale_ids
+      raise ThinkingSphinx::Search::StaleIdsException.new(stale_ids, context)
     end
 
     def stale_ids
diff --git a/lib/thinking_sphinx/middlewares/stale_id_filter.rb b/lib/thinking_sphinx/middlewares/stale_id_filter.rb
index 8c5c2ce..db0aed5 100644
--- a/lib/thinking_sphinx/middlewares/stale_id_filter.rb
+++ b/lib/thinking_sphinx/middlewares/stale_id_filter.rb
@@ -11,7 +11,7 @@ class ThinkingSphinx::Middlewares::StaleIdFilter <
     rescue ThinkingSphinx::Search::StaleIdsException => error
       raise error if @retries <= 0
 
-      append_stale_ids error.ids
+      append_stale_ids error.ids, error.context
       ThinkingSphinx::Logger.log :message, log_message
 
       @retries -= 1 and retry
@@ -20,7 +20,7 @@ class ThinkingSphinx::Middlewares::StaleIdFilter <
 
   private
 
-  def append_stale_ids(ids)
+  def append_stale_ids(ids, context)
     @stale_ids |= ids
 
     context.search.options[:without_ids] ||= []
diff --git a/lib/thinking_sphinx/rake_interface.rb b/lib/thinking_sphinx/rake_interface.rb
index 1f020aa..0629580 100644
--- a/lib/thinking_sphinx/rake_interface.rb
+++ b/lib/thinking_sphinx/rake_interface.rb
@@ -45,16 +45,25 @@ class ThinkingSphinx::RakeInterface
     FileUtils.mkdir_p configuration.indices_location
   end
 
-  def start
+  def start(options={})
     raise RuntimeError, 'searchd is already running' if controller.running?
 
     FileUtils.mkdir_p configuration.indices_location
-    controller.start
 
-    if controller.running?
-      puts "Started searchd successfully (pid: #{controller.pid})."
+    if options[:nodetach]
+      unless pid = fork
+        controller.start(options)
+      end
+      Signal.trap('TERM') { Process.kill(:TERM, pid); }
+      Signal.trap('INT')  { Process.kill(:TERM, pid); }
+      Process.wait(pid)
     else
-      puts "Failed to start searchd. Check the log files for more information."
+      controller.start(options)
+      if controller.running?
+        puts "Started searchd successfully (pid: #{controller.pid})."
+      else
+        puts "Failed to start searchd. Check the log files for more information."
+      end
     end
   end
 
diff --git a/lib/thinking_sphinx/real_time.rb b/lib/thinking_sphinx/real_time.rb
index 95e016f..024195d 100644
--- a/lib/thinking_sphinx/real_time.rb
+++ b/lib/thinking_sphinx/real_time.rb
@@ -15,5 +15,6 @@ require 'thinking_sphinx/real_time/index'
 require 'thinking_sphinx/real_time/interpreter'
 require 'thinking_sphinx/real_time/populator'
 require 'thinking_sphinx/real_time/transcriber'
+require 'thinking_sphinx/real_time/translator'
 
 require 'thinking_sphinx/real_time/callbacks/real_time_callbacks'
diff --git a/lib/thinking_sphinx/real_time/attribute.rb b/lib/thinking_sphinx/real_time/attribute.rb
index adc970c..cba627c 100644
--- a/lib/thinking_sphinx/real_time/attribute.rb
+++ b/lib/thinking_sphinx/real_time/attribute.rb
@@ -8,7 +8,9 @@ class ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property
   end
 
   def translate(object)
-    super || default_value
+    output = super || default_value
+
+    json? ? output.to_json : output
   end
 
   private
@@ -16,4 +18,8 @@ class ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property
   def default_value
     type == :string ? '' : 0
   end
+
+  def json?
+    type == :json
+  end
 end
diff --git a/lib/thinking_sphinx/real_time/index.rb b/lib/thinking_sphinx/real_time/index.rb
index 43ea17d..35ec700 100644
--- a/lib/thinking_sphinx/real_time/index.rb
+++ b/lib/thinking_sphinx/real_time/index.rb
@@ -69,6 +69,8 @@ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
       @rt_attr_float
     when :bigint
       attribute.multi? ? @rt_attr_multi_64 : @rt_attr_bigint
+    when :json
+      @rt_attr_json
     else
       raise "Unknown attribute type '#{attribute.type}'"
     end
diff --git a/lib/thinking_sphinx/real_time/populator.rb b/lib/thinking_sphinx/real_time/populator.rb
index e1b8d24..3b6fb33 100644
--- a/lib/thinking_sphinx/real_time/populator.rb
+++ b/lib/thinking_sphinx/real_time/populator.rb
@@ -12,9 +12,9 @@ class ThinkingSphinx::RealTime::Populator
 
     remove_files
 
-    scope.find_each do |instance|
-      transcriber.copy instance
-      instrument 'populated', :instance => instance
+    scope.find_in_batches do |instances|
+      transcriber.copy *instances
+      instrument 'populated', :instances => instances
     end
 
     controller.rotate
diff --git a/lib/thinking_sphinx/real_time/property.rb b/lib/thinking_sphinx/real_time/property.rb
index b2be10d..7e04cef 100644
--- a/lib/thinking_sphinx/real_time/property.rb
+++ b/lib/thinking_sphinx/real_time/property.rb
@@ -14,10 +14,6 @@ class ThinkingSphinx::RealTime::Property
   end
 
   def translate(object)
-    return @column.__name unless @column.__name.is_a?(Symbol)
-
-    base = @column.__stack.inject(object) { |base, node| base.try(node) }
-    base = base.try(@column.__name)
-    base.is_a?(String) ? base.gsub("\u0000", '') : base
+    ThinkingSphinx::RealTime::Translator.call(object, @column)
   end
 end
diff --git a/lib/thinking_sphinx/real_time/transcriber.rb b/lib/thinking_sphinx/real_time/transcriber.rb
index c11e79d..d45bb1b 100644
--- a/lib/thinking_sphinx/real_time/transcriber.rb
+++ b/lib/thinking_sphinx/real_time/transcriber.rb
@@ -3,22 +3,47 @@ class ThinkingSphinx::RealTime::Transcriber
     @index = index
   end
 
-  def copy(instance)
-    return unless instance.persisted? && copy?(instance)
+  def copy(*instances)
+    items = instances.select { |instance|
+      instance.persisted? && copy?(instance)
+    }
+    return unless items.present?
 
-    columns, values = ['id'], [index.document_id_for_key(instance.id)]
-    (index.fields + index.attributes).each do |property|
-      columns << property.name
-      values  << property.translate(instance)
-    end
+    values = items.collect { |instance|
+      TranscribeInstance.call(instance, index, properties)
+    }
 
     insert = Riddle::Query::Insert.new index.name, columns, values
     sphinxql = insert.replace!.to_sql
-    
+
     ThinkingSphinx::Logger.log :query, sphinxql do
       ThinkingSphinx::Connection.take do |connection|
         connection.execute sphinxql
-      end 
+      end
+    end
+  end
+
+  class TranscribeInstance
+    def self.call(instance, index, properties)
+      new(instance, index, properties).call
+    end
+
+    def initialize(instance, index, properties)
+      @instance, @index, @properties = instance, index, properties
+    end
+
+    def call
+      properties.each_with_object([document_id]) do |property, instance_values|
+        instance_values << property.translate(instance)
+      end
+    end
+
+    private
+
+    attr_reader :instance, :index, :properties
+
+    def document_id
+      index.document_id_for_key instance.id
     end
   end
 
@@ -26,6 +51,12 @@ class ThinkingSphinx::RealTime::Transcriber
 
   attr_reader :index
 
+  def columns
+    @columns ||= properties.each_with_object(['id']) do |property, columns|
+      columns << property.name
+    end
+  end
+
   def copy?(instance)
     index.conditions.empty? || index.conditions.all? { |condition|
       case condition
@@ -38,4 +69,8 @@ class ThinkingSphinx::RealTime::Transcriber
       end
     }
   end
+
+  def properties
+    @properties ||= index.fields + index.attributes
+  end
 end
diff --git a/lib/thinking_sphinx/real_time/translator.rb b/lib/thinking_sphinx/real_time/translator.rb
new file mode 100644
index 0000000..df832c4
--- /dev/null
+++ b/lib/thinking_sphinx/real_time/translator.rb
@@ -0,0 +1,36 @@
+class ThinkingSphinx::RealTime::Translator
+  def self.call(object, column)
+    new(object, column).call
+  end
+
+  def initialize(object, column)
+    @object, @column = object, column
+  end
+
+  def call
+    return name   unless name.is_a?(Symbol)
+    return result unless result.is_a?(String)
+
+    result.gsub "\u0000", ''
+  end
+
+  private
+
+  attr_reader :object, :column
+
+  def name
+    @column.__name
+  end
+
+  def owner
+    stack.inject(object) { |previous, node| previous.try node }
+  end
+
+  def result
+    @result ||= owner.try name
+  end
+
+  def stack
+    @column.__stack
+  end
+end
diff --git a/lib/thinking_sphinx/search.rb b/lib/thinking_sphinx/search.rb
index 3743b89..7858595 100644
--- a/lib/thinking_sphinx/search.rb
+++ b/lib/thinking_sphinx/search.rb
@@ -38,6 +38,16 @@ class ThinkingSphinx::Search < Array
     options[:page].to_i
   end
 
+  def marshal_dump
+    populate
+
+    [@populated, @query, @options, @context]
+  end
+
+  def marshal_load(array)
+    @populated, @query, @options, @context = array
+  end
+
   def masks
     @masks ||= @options[:masks] || DEFAULT_MASKS.clone
   end
diff --git a/lib/thinking_sphinx/search/context.rb b/lib/thinking_sphinx/search/context.rb
index 85a3df2..3863293 100644
--- a/lib/thinking_sphinx/search/context.rb
+++ b/lib/thinking_sphinx/search/context.rb
@@ -17,4 +17,12 @@ class ThinkingSphinx::Search::Context
   def []=(key, value)
     @memory[key] = value
   end
+
+  def marshal_dump
+    [@memory.except(:raw, :indices)]
+  end
+
+  def marshal_load(array)
+    @memory = array.first
+  end
 end
diff --git a/lib/thinking_sphinx/search/stale_ids_exception.rb b/lib/thinking_sphinx/search/stale_ids_exception.rb
index 928afa3..c2e888a 100644
--- a/lib/thinking_sphinx/search/stale_ids_exception.rb
+++ b/lib/thinking_sphinx/search/stale_ids_exception.rb
@@ -1,8 +1,9 @@
 class ThinkingSphinx::Search::StaleIdsException < StandardError
-  attr_reader :ids
+  attr_reader :ids, :context
 
-  def initialize(ids)
+  def initialize(ids, context)
     @ids = ids
+    @context = context
   end
 
   def message
diff --git a/lib/thinking_sphinx/subscribers/populator_subscriber.rb b/lib/thinking_sphinx/subscribers/populator_subscriber.rb
index 6fa8061..e65f574 100644
--- a/lib/thinking_sphinx/subscribers/populator_subscriber.rb
+++ b/lib/thinking_sphinx/subscribers/populator_subscriber.rb
@@ -21,7 +21,7 @@ class ThinkingSphinx::Subscribers::PopulatorSubscriber
   end
 
   def populated(event)
-    print '.'
+    print '.' * event.payload[:instances].length
   end
 
   def finish_populating(event)
diff --git a/lib/thinking_sphinx/tasks.rb b/lib/thinking_sphinx/tasks.rb
index 1a59d9e..b7aaaa5 100644
--- a/lib/thinking_sphinx/tasks.rb
+++ b/lib/thinking_sphinx/tasks.rb
@@ -39,7 +39,9 @@ namespace :ts do
 
   desc 'Start the Sphinx daemon'
   task :start => :environment do
-    interface.start
+    options = {}
+    options[:nodetach] = true  if ENV['NODETACH'] == 'true'
+    interface.start(options)
   end
 
   desc 'Stop the Sphinx daemon'
diff --git a/metadata.yml b/metadata.yml
deleted file mode 100644
index dafb4ef..0000000
--- a/metadata.yml
+++ /dev/null
@@ -1,574 +0,0 @@
---- !ruby/object:Gem::Specification
-name: thinking-sphinx
-version: !ruby/object:Gem::Version
-  version: 3.1.4
-platform: ruby
-authors:
-- Pat Allan
-autorequire: 
-bindir: bin
-cert_chain: []
-date: 2015-06-01 00:00:00.000000000 Z
-dependencies:
-- !ruby/object:Gem::Dependency
-  name: activerecord
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 3.1.0
-  type: :runtime
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 3.1.0
-- !ruby/object:Gem::Dependency
-  name: builder
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 2.1.2
-  type: :runtime
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 2.1.2
-- !ruby/object:Gem::Dependency
-  name: joiner
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 0.2.0
-  type: :runtime
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 0.2.0
-- !ruby/object:Gem::Dependency
-  name: middleware
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 0.1.0
-  type: :runtime
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 0.1.0
-- !ruby/object:Gem::Dependency
-  name: innertube
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 1.0.2
-  type: :runtime
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 1.0.2
-- !ruby/object:Gem::Dependency
-  name: riddle
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 1.5.11
-  type: :runtime
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 1.5.11
-- !ruby/object:Gem::Dependency
-  name: appraisal
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 1.0.2
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 1.0.2
-- !ruby/object:Gem::Dependency
-  name: combustion
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 0.4.0
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 0.4.0
-- !ruby/object:Gem::Dependency
-  name: database_cleaner
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 1.2.0
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 1.2.0
-- !ruby/object:Gem::Dependency
-  name: rspec
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 2.13.0
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 2.13.0
-description: An intelligent layer for ActiveRecord (via Rails and Sinatra) for the
-  Sphinx full-text search tool.
-email:
-- pat at freelancing-gods.com
-executables: []
-extensions: []
-extra_rdoc_files: []
-files:
-- ".gitignore"
-- ".travis.yml"
-- Appraisals
-- Gemfile
-- HISTORY
-- LICENCE
-- README.textile
-- Rakefile
-- gemfiles/.gitignore
-- gemfiles/rails_3_2.gemfile
-- gemfiles/rails_4_0.gemfile
-- gemfiles/rails_4_1.gemfile
-- gemfiles/rails_4_2.gemfile
-- lib/thinking-sphinx.rb
-- lib/thinking/sphinx.rb
-- lib/thinking_sphinx.rb
-- lib/thinking_sphinx/active_record.rb
-- lib/thinking_sphinx/active_record/association.rb
-- lib/thinking_sphinx/active_record/association_proxy.rb
-- lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb
-- lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb
-- lib/thinking_sphinx/active_record/attribute.rb
-- lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb
-- lib/thinking_sphinx/active_record/attribute/type.rb
-- lib/thinking_sphinx/active_record/attribute/values.rb
-- lib/thinking_sphinx/active_record/base.rb
-- lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb
-- lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb
-- lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb
-- lib/thinking_sphinx/active_record/column.rb
-- lib/thinking_sphinx/active_record/column_sql_presenter.rb
-- lib/thinking_sphinx/active_record/database_adapters.rb
-- lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb
-- lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb
-- lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb
-- lib/thinking_sphinx/active_record/field.rb
-- lib/thinking_sphinx/active_record/filter_reflection.rb
-- lib/thinking_sphinx/active_record/index.rb
-- lib/thinking_sphinx/active_record/interpreter.rb
-- lib/thinking_sphinx/active_record/join_association.rb
-- lib/thinking_sphinx/active_record/log_subscriber.rb
-- lib/thinking_sphinx/active_record/polymorpher.rb
-- lib/thinking_sphinx/active_record/property.rb
-- lib/thinking_sphinx/active_record/property_query.rb
-- lib/thinking_sphinx/active_record/property_sql_presenter.rb
-- lib/thinking_sphinx/active_record/simple_many_query.rb
-- lib/thinking_sphinx/active_record/sql_builder.rb
-- lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb
-- lib/thinking_sphinx/active_record/sql_builder/query.rb
-- lib/thinking_sphinx/active_record/sql_builder/statement.rb
-- lib/thinking_sphinx/active_record/sql_source.rb
-- lib/thinking_sphinx/active_record/sql_source/template.rb
-- lib/thinking_sphinx/batched_search.rb
-- lib/thinking_sphinx/callbacks.rb
-- lib/thinking_sphinx/capistrano.rb
-- lib/thinking_sphinx/capistrano/v2.rb
-- lib/thinking_sphinx/capistrano/v3.rb
-- lib/thinking_sphinx/configuration.rb
-- lib/thinking_sphinx/configuration/consistent_ids.rb
-- lib/thinking_sphinx/configuration/defaults.rb
-- lib/thinking_sphinx/configuration/distributed_indices.rb
-- lib/thinking_sphinx/configuration/minimum_fields.rb
-- lib/thinking_sphinx/connection.rb
-- lib/thinking_sphinx/controller.rb
-- lib/thinking_sphinx/core.rb
-- lib/thinking_sphinx/core/field.rb
-- lib/thinking_sphinx/core/index.rb
-- lib/thinking_sphinx/core/interpreter.rb
-- lib/thinking_sphinx/core/property.rb
-- lib/thinking_sphinx/core/settings.rb
-- lib/thinking_sphinx/deletion.rb
-- lib/thinking_sphinx/deltas.rb
-- lib/thinking_sphinx/deltas/default_delta.rb
-- lib/thinking_sphinx/deltas/delete_job.rb
-- lib/thinking_sphinx/deltas/index_job.rb
-- lib/thinking_sphinx/distributed.rb
-- lib/thinking_sphinx/distributed/index.rb
-- lib/thinking_sphinx/errors.rb
-- lib/thinking_sphinx/excerpter.rb
-- lib/thinking_sphinx/facet.rb
-- lib/thinking_sphinx/facet_search.rb
-- lib/thinking_sphinx/float_formatter.rb
-- lib/thinking_sphinx/frameworks.rb
-- lib/thinking_sphinx/frameworks/plain.rb
-- lib/thinking_sphinx/frameworks/rails.rb
-- lib/thinking_sphinx/guard.rb
-- lib/thinking_sphinx/guard/file.rb
-- lib/thinking_sphinx/guard/files.rb
-- lib/thinking_sphinx/index.rb
-- lib/thinking_sphinx/index_set.rb
-- lib/thinking_sphinx/logger.rb
-- lib/thinking_sphinx/masks.rb
-- lib/thinking_sphinx/masks/group_enumerators_mask.rb
-- lib/thinking_sphinx/masks/pagination_mask.rb
-- lib/thinking_sphinx/masks/scopes_mask.rb
-- lib/thinking_sphinx/masks/weight_enumerator_mask.rb
-- lib/thinking_sphinx/middlewares.rb
-- lib/thinking_sphinx/middlewares/active_record_translator.rb
-- lib/thinking_sphinx/middlewares/geographer.rb
-- lib/thinking_sphinx/middlewares/glazier.rb
-- lib/thinking_sphinx/middlewares/ids_only.rb
-- lib/thinking_sphinx/middlewares/inquirer.rb
-- lib/thinking_sphinx/middlewares/middleware.rb
-- lib/thinking_sphinx/middlewares/sphinxql.rb
-- lib/thinking_sphinx/middlewares/stale_id_checker.rb
-- lib/thinking_sphinx/middlewares/stale_id_filter.rb
-- lib/thinking_sphinx/middlewares/utf8.rb
-- lib/thinking_sphinx/panes.rb
-- lib/thinking_sphinx/panes/attributes_pane.rb
-- lib/thinking_sphinx/panes/distance_pane.rb
-- lib/thinking_sphinx/panes/excerpts_pane.rb
-- lib/thinking_sphinx/panes/weight_pane.rb
-- lib/thinking_sphinx/query.rb
-- lib/thinking_sphinx/railtie.rb
-- lib/thinking_sphinx/rake_interface.rb
-- lib/thinking_sphinx/real_time.rb
-- lib/thinking_sphinx/real_time/attribute.rb
-- lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb
-- lib/thinking_sphinx/real_time/field.rb
-- lib/thinking_sphinx/real_time/index.rb
-- lib/thinking_sphinx/real_time/index/template.rb
-- lib/thinking_sphinx/real_time/interpreter.rb
-- lib/thinking_sphinx/real_time/populator.rb
-- lib/thinking_sphinx/real_time/property.rb
-- lib/thinking_sphinx/real_time/transcriber.rb
-- lib/thinking_sphinx/scopes.rb
-- lib/thinking_sphinx/search.rb
-- lib/thinking_sphinx/search/batch_inquirer.rb
-- lib/thinking_sphinx/search/context.rb
-- lib/thinking_sphinx/search/glaze.rb
-- lib/thinking_sphinx/search/merger.rb
-- lib/thinking_sphinx/search/query.rb
-- lib/thinking_sphinx/search/stale_ids_exception.rb
-- lib/thinking_sphinx/sinatra.rb
-- lib/thinking_sphinx/sphinxql.rb
-- lib/thinking_sphinx/subscribers/populator_subscriber.rb
-- lib/thinking_sphinx/tasks.rb
-- lib/thinking_sphinx/test.rb
-- lib/thinking_sphinx/utf8.rb
-- lib/thinking_sphinx/wildcard.rb
-- spec/acceptance/association_scoping_spec.rb
-- spec/acceptance/attribute_access_spec.rb
-- spec/acceptance/attribute_updates_spec.rb
-- spec/acceptance/batch_searching_spec.rb
-- spec/acceptance/big_integers_spec.rb
-- spec/acceptance/excerpts_spec.rb
-- spec/acceptance/facets_spec.rb
-- spec/acceptance/geosearching_spec.rb
-- spec/acceptance/grouping_by_attributes_spec.rb
-- spec/acceptance/index_options_spec.rb
-- spec/acceptance/indexing_spec.rb
-- spec/acceptance/paginating_search_results_spec.rb
-- spec/acceptance/real_time_updates_spec.rb
-- spec/acceptance/remove_deleted_records_spec.rb
-- spec/acceptance/search_counts_spec.rb
-- spec/acceptance/search_for_just_ids_spec.rb
-- spec/acceptance/searching_across_models_spec.rb
-- spec/acceptance/searching_across_schemas_spec.rb
-- spec/acceptance/searching_on_fields_spec.rb
-- spec/acceptance/searching_with_filters_spec.rb
-- spec/acceptance/searching_with_sti_spec.rb
-- spec/acceptance/searching_within_a_model_spec.rb
-- spec/acceptance/sorting_search_results_spec.rb
-- spec/acceptance/spec_helper.rb
-- spec/acceptance/specifying_sql_spec.rb
-- spec/acceptance/sphinx_scopes_spec.rb
-- spec/acceptance/sql_deltas_spec.rb
-- spec/acceptance/support/database_cleaner.rb
-- spec/acceptance/support/sphinx_controller.rb
-- spec/acceptance/support/sphinx_helpers.rb
-- spec/acceptance/suspended_deltas_spec.rb
-- spec/fixtures/database.yml
-- spec/internal/app/indices/admin_person_index.rb
-- spec/internal/app/indices/animal_index.rb
-- spec/internal/app/indices/article_index.rb
-- spec/internal/app/indices/bird_index.rb
-- spec/internal/app/indices/book_index.rb
-- spec/internal/app/indices/car_index.rb
-- spec/internal/app/indices/city_index.rb
-- spec/internal/app/indices/product_index.rb
-- spec/internal/app/indices/tee_index.rb
-- spec/internal/app/indices/user_index.rb
-- spec/internal/app/models/admin/person.rb
-- spec/internal/app/models/animal.rb
-- spec/internal/app/models/article.rb
-- spec/internal/app/models/bird.rb
-- spec/internal/app/models/book.rb
-- spec/internal/app/models/car.rb
-- spec/internal/app/models/categorisation.rb
-- spec/internal/app/models/category.rb
-- spec/internal/app/models/city.rb
-- spec/internal/app/models/colour.rb
-- spec/internal/app/models/event.rb
-- spec/internal/app/models/flightless_bird.rb
-- spec/internal/app/models/genre.rb
-- spec/internal/app/models/hardcover.rb
-- spec/internal/app/models/mammal.rb
-- spec/internal/app/models/manufacturer.rb
-- spec/internal/app/models/product.rb
-- spec/internal/app/models/tag.rb
-- spec/internal/app/models/tagging.rb
-- spec/internal/app/models/tee.rb
-- spec/internal/app/models/tweet.rb
-- spec/internal/app/models/user.rb
-- spec/internal/config/database.yml
-- spec/internal/db/schema.rb
-- spec/internal/tmp/.gitkeep
-- spec/spec_helper.rb
-- spec/support/multi_schema.rb
-- spec/support/sphinx_yaml_helpers.rb
-- spec/thinking_sphinx/active_record/association_spec.rb
-- spec/thinking_sphinx/active_record/attribute/type_spec.rb
-- spec/thinking_sphinx/active_record/base_spec.rb
-- spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb
-- spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb
-- spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb
-- spec/thinking_sphinx/active_record/column_spec.rb
-- spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb
-- spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb
-- spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb
-- spec/thinking_sphinx/active_record/database_adapters_spec.rb
-- spec/thinking_sphinx/active_record/field_spec.rb
-- spec/thinking_sphinx/active_record/filter_reflection_spec.rb
-- spec/thinking_sphinx/active_record/index_spec.rb
-- spec/thinking_sphinx/active_record/interpreter_spec.rb
-- spec/thinking_sphinx/active_record/polymorpher_spec.rb
-- spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb
-- spec/thinking_sphinx/active_record/sql_builder_spec.rb
-- spec/thinking_sphinx/active_record/sql_source_spec.rb
-- spec/thinking_sphinx/configuration_spec.rb
-- spec/thinking_sphinx/connection_spec.rb
-- spec/thinking_sphinx/deletion_spec.rb
-- spec/thinking_sphinx/deltas/default_delta_spec.rb
-- spec/thinking_sphinx/deltas_spec.rb
-- spec/thinking_sphinx/errors_spec.rb
-- spec/thinking_sphinx/excerpter_spec.rb
-- spec/thinking_sphinx/facet_search_spec.rb
-- spec/thinking_sphinx/index_set_spec.rb
-- spec/thinking_sphinx/index_spec.rb
-- spec/thinking_sphinx/masks/pagination_mask_spec.rb
-- spec/thinking_sphinx/masks/scopes_mask_spec.rb
-- spec/thinking_sphinx/middlewares/active_record_translator_spec.rb
-- spec/thinking_sphinx/middlewares/geographer_spec.rb
-- spec/thinking_sphinx/middlewares/glazier_spec.rb
-- spec/thinking_sphinx/middlewares/inquirer_spec.rb
-- spec/thinking_sphinx/middlewares/sphinxql_spec.rb
-- spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb
-- spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb
-- spec/thinking_sphinx/panes/attributes_pane_spec.rb
-- spec/thinking_sphinx/panes/distance_pane_spec.rb
-- spec/thinking_sphinx/panes/excerpts_pane_spec.rb
-- spec/thinking_sphinx/panes/weight_pane_spec.rb
-- spec/thinking_sphinx/rake_interface_spec.rb
-- spec/thinking_sphinx/real_time/attribute_spec.rb
-- spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb
-- spec/thinking_sphinx/real_time/field_spec.rb
-- spec/thinking_sphinx/real_time/index_spec.rb
-- spec/thinking_sphinx/real_time/interpreter_spec.rb
-- spec/thinking_sphinx/scopes_spec.rb
-- spec/thinking_sphinx/search/glaze_spec.rb
-- spec/thinking_sphinx/search/query_spec.rb
-- spec/thinking_sphinx/search_spec.rb
-- spec/thinking_sphinx/wildcard_spec.rb
-- spec/thinking_sphinx_spec.rb
-- thinking-sphinx.gemspec
-homepage: https://pat.github.io/thinking-sphinx/
-licenses:
-- MIT
-metadata: {}
-post_install_message: 
-rdoc_options: []
-require_paths:
-- lib
-required_ruby_version: !ruby/object:Gem::Requirement
-  requirements:
-  - - ">="
-    - !ruby/object:Gem::Version
-      version: '0'
-required_rubygems_version: !ruby/object:Gem::Requirement
-  requirements:
-  - - ">="
-    - !ruby/object:Gem::Version
-      version: '0'
-requirements: []
-rubyforge_project: thinking-sphinx
-rubygems_version: 2.2.2
-signing_key: 
-specification_version: 4
-summary: A smart wrapper over Sphinx for ActiveRecord
-test_files:
-- spec/acceptance/association_scoping_spec.rb
-- spec/acceptance/attribute_access_spec.rb
-- spec/acceptance/attribute_updates_spec.rb
-- spec/acceptance/batch_searching_spec.rb
-- spec/acceptance/big_integers_spec.rb
-- spec/acceptance/excerpts_spec.rb
-- spec/acceptance/facets_spec.rb
-- spec/acceptance/geosearching_spec.rb
-- spec/acceptance/grouping_by_attributes_spec.rb
-- spec/acceptance/index_options_spec.rb
-- spec/acceptance/indexing_spec.rb
-- spec/acceptance/paginating_search_results_spec.rb
-- spec/acceptance/real_time_updates_spec.rb
-- spec/acceptance/remove_deleted_records_spec.rb
-- spec/acceptance/search_counts_spec.rb
-- spec/acceptance/search_for_just_ids_spec.rb
-- spec/acceptance/searching_across_models_spec.rb
-- spec/acceptance/searching_across_schemas_spec.rb
-- spec/acceptance/searching_on_fields_spec.rb
-- spec/acceptance/searching_with_filters_spec.rb
-- spec/acceptance/searching_with_sti_spec.rb
-- spec/acceptance/searching_within_a_model_spec.rb
-- spec/acceptance/sorting_search_results_spec.rb
-- spec/acceptance/spec_helper.rb
-- spec/acceptance/specifying_sql_spec.rb
-- spec/acceptance/sphinx_scopes_spec.rb
-- spec/acceptance/sql_deltas_spec.rb
-- spec/acceptance/support/database_cleaner.rb
-- spec/acceptance/support/sphinx_controller.rb
-- spec/acceptance/support/sphinx_helpers.rb
-- spec/acceptance/suspended_deltas_spec.rb
-- spec/fixtures/database.yml
-- spec/internal/app/indices/admin_person_index.rb
-- spec/internal/app/indices/animal_index.rb
-- spec/internal/app/indices/article_index.rb
-- spec/internal/app/indices/bird_index.rb
-- spec/internal/app/indices/book_index.rb
-- spec/internal/app/indices/car_index.rb
-- spec/internal/app/indices/city_index.rb
-- spec/internal/app/indices/product_index.rb
-- spec/internal/app/indices/tee_index.rb
-- spec/internal/app/indices/user_index.rb
-- spec/internal/app/models/admin/person.rb
-- spec/internal/app/models/animal.rb
-- spec/internal/app/models/article.rb
-- spec/internal/app/models/bird.rb
-- spec/internal/app/models/book.rb
-- spec/internal/app/models/car.rb
-- spec/internal/app/models/categorisation.rb
-- spec/internal/app/models/category.rb
-- spec/internal/app/models/city.rb
-- spec/internal/app/models/colour.rb
-- spec/internal/app/models/event.rb
-- spec/internal/app/models/flightless_bird.rb
-- spec/internal/app/models/genre.rb
-- spec/internal/app/models/hardcover.rb
-- spec/internal/app/models/mammal.rb
-- spec/internal/app/models/manufacturer.rb
-- spec/internal/app/models/product.rb
-- spec/internal/app/models/tag.rb
-- spec/internal/app/models/tagging.rb
-- spec/internal/app/models/tee.rb
-- spec/internal/app/models/tweet.rb
-- spec/internal/app/models/user.rb
-- spec/internal/config/database.yml
-- spec/internal/db/schema.rb
-- spec/internal/tmp/.gitkeep
-- spec/spec_helper.rb
-- spec/support/multi_schema.rb
-- spec/support/sphinx_yaml_helpers.rb
-- spec/thinking_sphinx/active_record/association_spec.rb
-- spec/thinking_sphinx/active_record/attribute/type_spec.rb
-- spec/thinking_sphinx/active_record/base_spec.rb
-- spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb
-- spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb
-- spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb
-- spec/thinking_sphinx/active_record/column_spec.rb
-- spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb
-- spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb
-- spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb
-- spec/thinking_sphinx/active_record/database_adapters_spec.rb
-- spec/thinking_sphinx/active_record/field_spec.rb
-- spec/thinking_sphinx/active_record/filter_reflection_spec.rb
-- spec/thinking_sphinx/active_record/index_spec.rb
-- spec/thinking_sphinx/active_record/interpreter_spec.rb
-- spec/thinking_sphinx/active_record/polymorpher_spec.rb
-- spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb
-- spec/thinking_sphinx/active_record/sql_builder_spec.rb
-- spec/thinking_sphinx/active_record/sql_source_spec.rb
-- spec/thinking_sphinx/configuration_spec.rb
-- spec/thinking_sphinx/connection_spec.rb
-- spec/thinking_sphinx/deletion_spec.rb
-- spec/thinking_sphinx/deltas/default_delta_spec.rb
-- spec/thinking_sphinx/deltas_spec.rb
-- spec/thinking_sphinx/errors_spec.rb
-- spec/thinking_sphinx/excerpter_spec.rb
-- spec/thinking_sphinx/facet_search_spec.rb
-- spec/thinking_sphinx/index_set_spec.rb
-- spec/thinking_sphinx/index_spec.rb
-- spec/thinking_sphinx/masks/pagination_mask_spec.rb
-- spec/thinking_sphinx/masks/scopes_mask_spec.rb
-- spec/thinking_sphinx/middlewares/active_record_translator_spec.rb
-- spec/thinking_sphinx/middlewares/geographer_spec.rb
-- spec/thinking_sphinx/middlewares/glazier_spec.rb
-- spec/thinking_sphinx/middlewares/inquirer_spec.rb
-- spec/thinking_sphinx/middlewares/sphinxql_spec.rb
-- spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb
-- spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb
-- spec/thinking_sphinx/panes/attributes_pane_spec.rb
-- spec/thinking_sphinx/panes/distance_pane_spec.rb
-- spec/thinking_sphinx/panes/excerpts_pane_spec.rb
-- spec/thinking_sphinx/panes/weight_pane_spec.rb
-- spec/thinking_sphinx/rake_interface_spec.rb
-- spec/thinking_sphinx/real_time/attribute_spec.rb
-- spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb
-- spec/thinking_sphinx/real_time/field_spec.rb
-- spec/thinking_sphinx/real_time/index_spec.rb
-- spec/thinking_sphinx/real_time/interpreter_spec.rb
-- spec/thinking_sphinx/scopes_spec.rb
-- spec/thinking_sphinx/search/glaze_spec.rb
-- spec/thinking_sphinx/search/query_spec.rb
-- spec/thinking_sphinx/search_spec.rb
-- spec/thinking_sphinx/wildcard_spec.rb
-- spec/thinking_sphinx_spec.rb
diff --git a/spec/acceptance/remove_deleted_records_spec.rb b/spec/acceptance/remove_deleted_records_spec.rb
index 1101afd..9a615cf 100644
--- a/spec/acceptance/remove_deleted_records_spec.rb
+++ b/spec/acceptance/remove_deleted_records_spec.rb
@@ -33,6 +33,24 @@ describe 'Hiding deleted records from search results', :live => true do
       should be_empty
   end
 
+  it "does not remove real-time results when callbacks are disabled" do
+    original = ThinkingSphinx::Configuration.instance.
+      settings['real_time_callbacks']
+    product = Product.create! :name => 'Shiny'
+    Product.search('Shiny', :indices => ['product_core']).to_a.
+      should == [product]
+
+    ThinkingSphinx::Configuration.instance.
+      settings['real_time_callbacks'] = false
+
+    product.destroy
+    Product.search_for_ids('Shiny', :indices => ['product_core']).
+      should_not be_empty
+
+    ThinkingSphinx::Configuration.instance.
+      settings['real_time_callbacks'] = original
+  end
+
   it "deletes STI child classes from parent indices" do
     duck = Bird.create :name => 'Duck'
     index
diff --git a/spec/acceptance/searching_with_filters_spec.rb b/spec/acceptance/searching_with_filters_spec.rb
index b9c3da0..dfd82bc 100644
--- a/spec/acceptance/searching_with_filters_spec.rb
+++ b/spec/acceptance/searching_with_filters_spec.rb
@@ -141,4 +141,17 @@ describe 'Searching with filters', :live => true do
     products = Product.search :with => {:category_ids => [flat.id]}
     products.to_a.should == [pancakes]
   end
+
+  it 'searches with real-time JSON attributes' do
+    pancakes = Product.create :name => 'Pancakes',
+      :options => {'lemon' => 1, 'sugar' => 1, :number => 3}
+    waffles  = Product.create :name => 'Waffles',
+      :options => {'chocolate' => 1, 'sugar' => 1, :number => 1}
+
+    products = Product.search :with => {"options.lemon" => 1}
+    products.to_a.should == [pancakes]
+
+    products = Product.search :with => {"options.sugar" => 1}
+    products.to_a.should == [pancakes, waffles]
+  end if JSONColumn.call
 end
diff --git a/spec/internal/app/indices/product_index.rb b/spec/internal/app/indices/product_index.rb
index 9cbef0d..a54ece1 100644
--- a/spec/internal/app/indices/product_index.rb
+++ b/spec/internal/app/indices/product_index.rb
@@ -4,6 +4,7 @@ ThinkingSphinx::Index.define :product, :with => :real_time do
   indexes name, :sortable => true
 
   has category_ids, :type => :integer, :multi => true
+  has options, :type => :json if JSONColumn.call
 end
 
 if multi_schema.active?
diff --git a/spec/internal/db/schema.rb b/spec/internal/db/schema.rb
index 62e8936..47c3239 100644
--- a/spec/internal/db/schema.rb
+++ b/spec/internal/db/schema.rb
@@ -72,6 +72,7 @@ ActiveRecord::Schema.define do
 
   create_table(:products, :force => true) do |t|
     t.string :name
+    t.json :options if ::JSONColumn.call
   end
 
   create_table(:taggings, :force => true) do |t|
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 00ddd84..d2dbeb0 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -5,6 +5,7 @@ Bundler.require :default, :development
 
 root = File.expand_path File.dirname(__FILE__)
 require "#{root}/support/multi_schema"
+require "#{root}/support/json_column"
 require 'thinking_sphinx/railtie'
 
 Combustion.initialize! :active_record
diff --git a/spec/support/json_column.rb b/spec/support/json_column.rb
new file mode 100644
index 0000000..ac610bc
--- /dev/null
+++ b/spec/support/json_column.rb
@@ -0,0 +1,29 @@
+class JSONColumn
+  include ActiveRecord::ConnectionAdapters
+
+  def self.call
+    new.call
+  end
+
+  def call
+    postgresql? && column?
+  end
+
+  private
+
+  def column?
+    (
+      ActiveRecord::ConnectionAdapters.constants.include?(:PostgreSQLAdapter) &&
+      PostgreSQLAdapter.constants.include?(:TableDefinition) &&
+      PostgreSQLAdapter::TableDefinition.instance_methods.include?(:json)
+    ) || (
+      ActiveRecord::ConnectionAdapters.constants.include?(:PostgreSQL) &&
+      PostgreSQL.constants.include?(:ColumnMethods) &&
+      PostgreSQL::ColumnMethods.instance_methods.include?(:json)
+    )
+  end
+
+  def postgresql?
+    ENV['DATABASE'] == 'postgresql'
+  end
+end
diff --git a/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb b/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb
index dd5eafb..381b10d 100644
--- a/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb
+++ b/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb
@@ -53,5 +53,15 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks do
 
       callbacks.after_destroy
     end
+
+    it 'does nothing if callbacks are suspended' do
+      ThinkingSphinx::Callbacks.suspend!
+
+      ThinkingSphinx::Deletion.should_not_receive(:perform)
+
+      callbacks.after_destroy
+
+      ThinkingSphinx::Callbacks.resume!
+    end
   end
 end
diff --git a/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb b/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb
index 050c488..31705a1 100644
--- a/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb
+++ b/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb
@@ -70,5 +70,15 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
 
       lambda { callbacks.after_update }.should_not raise_error
     end
+
+    it 'does nothing if callbacks are suspended' do
+      ThinkingSphinx::Callbacks.suspend!
+
+      connection.should_not_receive(:execute)
+
+      callbacks.after_update
+
+      ThinkingSphinx::Callbacks.resume!
+    end
   end
 end
diff --git a/spec/thinking_sphinx/active_record/column_sql_presenter_spec.rb b/spec/thinking_sphinx/active_record/column_sql_presenter_spec.rb
new file mode 100644
index 0000000..e64ac72
--- /dev/null
+++ b/spec/thinking_sphinx/active_record/column_sql_presenter_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe ThinkingSphinx::ActiveRecord::ColumnSQLPresenter do
+  describe '#with_table' do
+    let(:model)        { double 'Model' }
+    let(:column)       { double 'Column', :__name => 'column_name',
+      :__stack => [], :string? => false }
+    let(:adapter)      { double 'Adapter' }
+    let(:associations) { double 'Associations' }
+    let(:path)         { double 'Path',
+      :model => double(:column_names => ['column_name']) }
+    let(:presenter) { ThinkingSphinx::ActiveRecord::ColumnSQLPresenter.new(
+      model, column, adapter, associations
+    ) }
+
+    before do
+      stub_const 'Joiner::Path', double(:new => path)
+      adapter.stub(:quote) { |arg| "`#{arg}`" }
+    end
+
+    context "when there's no explicit db name" do
+      before { associations.stub(:alias_for => 'table_name') }
+
+      it 'returns quoted table and column names' do
+        presenter.with_table.should == '`table_name`.`column_name`'
+      end
+    end
+
+    context 'when an eplicit db name is provided' do
+      before { associations.stub(:alias_for => 'db_name.table_name') }
+
+      it 'returns properly quoted table name with column name' do
+        presenter.with_table.should == '`db_name`.`table_name`.`column_name`'
+      end
+    end
+  end
+end
diff --git a/spec/thinking_sphinx/active_record/sql_builder_spec.rb b/spec/thinking_sphinx/active_record/sql_builder_spec.rb
index 7cdda38..e373f67 100644
--- a/spec/thinking_sphinx/active_record/sql_builder_spec.rb
+++ b/spec/thinking_sphinx/active_record/sql_builder_spec.rb
@@ -5,7 +5,7 @@ describe ThinkingSphinx::ActiveRecord::SQLBuilder do
     :fields => [], :attributes => [], :disable_range? => false,
     :delta_processor => nil, :conditions => [], :groupings => [],
     :adapter => adapter, :associations => [], :primary_key => :id,
-    :options => {}) }
+    :options => {}, :properties => []) }
   let(:model)        { double('model', :connection => connection,
     :descends_from_active_record? => true, :column_names => [],
     :inheritance_column => 'type', :unscoped => relation,
@@ -518,22 +518,6 @@ describe ThinkingSphinx::ActiveRecord::SQLBuilder do
     end
   end
 
-  describe 'sql_query_post_index' do
-    let(:processor) { double('processor', :reset_query => 'RESET DELTAS') }
-
-    it "adds a reset delta query if there is a delta processor and this is the core source" do
-      source.stub :delta_processor => processor, :delta? => false
-
-      builder.sql_query_post_index.should include('RESET DELTAS')
-    end
-
-    it "adds no reset delta query if there is a delta processor and this is the delta source" do
-      source.stub :delta_processor => processor, :delta? => true
-
-      builder.sql_query_post_index.should_not include('RESET DELTAS')
-    end
-  end
-
   describe 'sql_query_pre' do
     let(:processor) { double('processor', :reset_query => 'RESET DELTAS') }
 
@@ -542,6 +526,12 @@ describe ThinkingSphinx::ActiveRecord::SQLBuilder do
       adapter.stub :utf8_query_pre => ['SET UTF8']
     end
 
+    it "adds a reset delta query if there is a delta processor and this is the core source" do
+      source.stub :delta_processor => processor
+
+      builder.sql_query_pre.should include('RESET DELTAS')
+    end
+
     it "does not add a reset query if there is no delta processor" do
       builder.sql_query_pre.should_not include('RESET DELTAS')
     end
diff --git a/spec/thinking_sphinx/active_record/sql_source_spec.rb b/spec/thinking_sphinx/active_record/sql_source_spec.rb
index 2616707..f5c0dde 100644
--- a/spec/thinking_sphinx/active_record/sql_source_spec.rb
+++ b/spec/thinking_sphinx/active_record/sql_source_spec.rb
@@ -9,7 +9,7 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
   let(:db_config)  { {:host => 'localhost', :user => 'root',
     :database => 'default'} }
   let(:source)     { ThinkingSphinx::ActiveRecord::SQLSource.new(model,
-    :position => 3) }
+    :position => 3, :primary_key => model.primary_key || :id ) }
   let(:adapter)    { double('adapter') }
 
   before :each do
@@ -53,12 +53,14 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
     let(:processor)       { double('processor') }
     let(:source)          {
       ThinkingSphinx::ActiveRecord::SQLSource.new model,
-        :delta_processor => processor_class
+        :delta_processor => processor_class,
+        :primary_key => model.primary_key || :id
     }
     let(:source_with_options)    {
       ThinkingSphinx::ActiveRecord::SQLSource.new model,
         :delta_processor => processor_class,
-        :delta_options   => { :opt_key => :opt_value }
+        :delta_options   => { :opt_key => :opt_value },
+        :primary_key => model.primary_key || :id
     }
 
     it "loads the processor with the adapter" do
@@ -81,7 +83,8 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
   describe '#delta?' do
     it "returns the given delta setting" do
       source = ThinkingSphinx::ActiveRecord::SQLSource.new model,
-        :delta? => true
+        :delta? => true,
+        :primary_key => model.primary_key || :id
 
       source.should be_a_delta
     end
@@ -90,7 +93,8 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
   describe '#disable_range?' do
     it "returns the given range setting" do
       source = ThinkingSphinx::ActiveRecord::SQLSource.new model,
-        :disable_range? => true
+        :disable_range? => true,
+        :primary_key => model.primary_key || :id
 
       source.disable_range?.should be_true
     end
@@ -129,7 +133,7 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
 
     it "allows for custom names, but adds the position suffix" do
       source = ThinkingSphinx::ActiveRecord::SQLSource.new model,
-        :name => 'people', :position => 2
+        :name => 'people', :position => 2, :primary_key => model.primary_key || :id
 
       source.name.should == 'people_2'
     end
@@ -137,7 +141,8 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
 
   describe '#offset' do
     it "returns the given offset" do
-      source = ThinkingSphinx::ActiveRecord::SQLSource.new model, :offset => 12
+      source = ThinkingSphinx::ActiveRecord::SQLSource.new model,
+        :offset => 12, :primary_key => model.primary_key || :id
 
       source.offset.should == 12
     end
@@ -153,6 +158,19 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
 
       source.options[:utf8?].should be_true
     end
+
+    describe "#primary key" do
+      let(:model)  { double('model', :connection => connection,
+                           :name => 'User', :column_names => [], :inheritance_column => 'type') }
+      let(:source) { ThinkingSphinx::ActiveRecord::SQLSource.new(model,
+                           :position => 3, :primary_key => :custom_key) }
+      let(:template) { ThinkingSphinx::ActiveRecord::SQLSource::Template.new(source) }
+
+      it 'template should allow primary key from options' do
+        template.apply
+        template.source.attributes.collect(&:columns) == :custom_key
+      end
+    end
   end
 
   describe '#render' do
@@ -194,14 +212,6 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
       source.sql_query_pre.should == ['Change Setting']
     end
 
-    it "appends the builder's sql_query_post_index value" do
-      builder.stub! :sql_query_post_index => ['RESET DELTAS']
-
-      source.render
-
-      source.sql_query_post_index.should include('RESET DELTAS')
-    end
-
     it "adds fields with attributes to sql_field_string" do
       source.fields << double('field', :name => 'title', :source_type => nil,
         :with_attribute? => true, :file? => false, :wordcount? => false)
@@ -398,6 +408,24 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
 
       source.sql_sock.should == '/unix/socket'
     end
+
+    it "sets the mysql_ssl_cert from the model's database settings" do
+      source.set_database_settings :sslcert => '/path/to/cert.pem'
+
+      source.mysql_ssl_cert.should  eq '/path/to/cert.pem'
+    end
+
+    it "sets the mysql_ssl_key from the model's database settings" do
+      source.set_database_settings :sslkey => '/path/to/key.pem'
+
+      source.mysql_ssl_key.should  eq '/path/to/key.pem'
+    end
+
+    it "sets the mysql_ssl_ca from the model's database settings" do
+      source.set_database_settings :sslca => '/path/to/ca.pem'
+
+      source.mysql_ssl_ca.should  eq '/path/to/ca.pem'
+    end
   end
 
   describe '#type' do
diff --git a/spec/thinking_sphinx/errors_spec.rb b/spec/thinking_sphinx/errors_spec.rb
index c40b01b..9fe7b31 100644
--- a/spec/thinking_sphinx/errors_spec.rb
+++ b/spec/thinking_sphinx/errors_spec.rb
@@ -33,6 +33,13 @@ describe ThinkingSphinx::SphinxError do
         should be_a(ThinkingSphinx::ConnectionError)
     end
 
+    it 'translates out-of-bounds errors' do
+      error.stub :message => "offset out of bounds (offset=1001, max_matches=1000)"
+
+      ThinkingSphinx::SphinxError.new_from_mysql(error).
+        should be_a(ThinkingSphinx::OutOfBoundsError)
+    end
+
     it 'prefixes the connection error message' do
       error.stub :message => "Can't connect to MySQL server on '127.0.0.1' (61)"
 
diff --git a/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb b/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb
index 366a77d..cc6386b 100644
--- a/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb
+++ b/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb
@@ -11,9 +11,10 @@ describe ThinkingSphinx::Middlewares::ActiveRecordTranslator do
   let(:app)        { double('app', :call => true) }
   let(:middleware) {
     ThinkingSphinx::Middlewares::ActiveRecordTranslator.new app }
-  let(:context)    { {:raw => [], :results => []} }
+  let(:context)    { {:raw => [], :results => [] } }
   let(:model)      { double('model', :primary_key => :id) }
   let(:search)     { double('search', :options => {}) }
+  let(:configuration) { double('configuration', :settings => {:primary_key => :id}) }
 
   def raw_result(id, model_name)
     {'sphinx_internal_id' => id, 'sphinx_internal_class' => model_name}
@@ -22,6 +23,7 @@ describe ThinkingSphinx::Middlewares::ActiveRecordTranslator do
   describe '#call' do
     before :each do
       context.stub :search => search
+      context.stub :configuration => configuration
       model.stub :unscoped => model
     end
 
@@ -101,6 +103,18 @@ describe ThinkingSphinx::Middlewares::ActiveRecordTranslator do
       context[:results].should == [instance_1, instance_2]
     end
 
+    it "handles model without primary key" do
+      no_primary_key_model = double('no primary key model')
+      no_primary_key_model.stub :unscoped => no_primary_key_model
+      model_name = double('article', :constantize => no_primary_key_model)
+      instance   = double('instance', :id => 1)
+      no_primary_key_model.stub :where => [instance]
+
+      context[:results] << raw_result(1, model_name)
+
+      middleware.call [context]
+    end
+
     context 'SQL options' do
       let(:relation) { double('relation', :where => []) }
 
diff --git a/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb b/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb
index 8a9768a..db2149d 100644
--- a/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb
+++ b/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb
@@ -41,6 +41,7 @@ describe ThinkingSphinx::Middlewares::StaleIdChecker do
         middleware.call [context]
       }.should raise_error(ThinkingSphinx::Search::StaleIdsException) { |err|
         err.ids.should == [42]
+        err.context.should == context
       }
     end
   end
diff --git a/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb b/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb
index c272ba0..71621bb 100644
--- a/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb
+++ b/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb
@@ -23,7 +23,7 @@ describe ThinkingSphinx::Middlewares::StaleIdFilter do
         app.stub(:call) do
           @calls ||= 0
           @calls += 1
-          raise ThinkingSphinx::Search::StaleIdsException, [12] if @calls == 1
+          raise ThinkingSphinx::Search::StaleIdsException.new([12], context) if @calls == 1
         end
       end
 
@@ -47,8 +47,8 @@ describe ThinkingSphinx::Middlewares::StaleIdFilter do
         app.stub(:call) do
           @calls ||= 0
           @calls += 1
-          raise ThinkingSphinx::Search::StaleIdsException, [12] if @calls == 1
-          raise ThinkingSphinx::Search::StaleIdsException, [13] if @calls == 2
+          raise ThinkingSphinx::Search::StaleIdsException.new([12], context) if @calls == 1
+          raise ThinkingSphinx::Search::StaleIdsException.new([13], context) if @calls == 2
         end
       end
 
@@ -73,9 +73,9 @@ describe ThinkingSphinx::Middlewares::StaleIdFilter do
           @calls ||= 0
           @calls += 1
 
-          raise ThinkingSphinx::Search::StaleIdsException, [12] if @calls == 1
-          raise ThinkingSphinx::Search::StaleIdsException, [13] if @calls == 2
-          raise ThinkingSphinx::Search::StaleIdsException, [14] if @calls == 3
+          raise ThinkingSphinx::Search::StaleIdsException.new([12], context) if @calls == 1
+          raise ThinkingSphinx::Search::StaleIdsException.new([13], context) if @calls == 2
+          raise ThinkingSphinx::Search::StaleIdsException.new([14], context) if @calls == 3
         end
       end
 
@@ -87,5 +87,25 @@ describe ThinkingSphinx::Middlewares::StaleIdFilter do
         }
       end
     end
+
+    context  'stale ids exceptions with multiple contexts' do
+      let(:context2) { {:raw => [], :results => []} }
+      let(:search2) { double('search2', :options => {}) }
+      before :each do
+        context2.stub :search => search2
+        app.stub(:call) do
+          @calls ||= 0
+          @calls += 1
+          raise ThinkingSphinx::Search::StaleIdsException.new([12], context2) if @calls == 1
+        end
+      end
+
+      it "appends the ids to the without_ids filter in the correct context" do
+        middleware.call [context, context2]
+        search.options[:without_ids].should == nil
+        search2.options[:without_ids].should == [12]
+      end
+    end
+
   end
 end
diff --git a/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb b/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb
index 71d35f5..85d7abc 100644
--- a/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb
+++ b/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb
@@ -33,7 +33,7 @@ describe ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks do
 
     it "creates an insert statement with all fields and attributes" do
       Riddle::Query::Insert.should_receive(:new).
-        with('my_index', ['id', 'name', 'created_at'], [123, 'Foo', time]).
+        with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
         and_return(insert)
 
       callbacks.after_save instance
@@ -62,7 +62,7 @@ describe ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks do
 
       it "creates an insert statement with all fields and attributes" do
         Riddle::Query::Insert.should_receive(:new).
-          with('my_index', ['id', 'name', 'created_at'], [123, 'Foo', time]).
+          with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
           and_return(insert)
 
         callbacks.after_save instance
@@ -94,7 +94,7 @@ describe ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks do
 
       it "creates insert statements with all fields and attributes" do
         Riddle::Query::Insert.should_receive(:new).twice.
-          with('my_index', ['id', 'name', 'created_at'], [123, 'Foo', time]).
+          with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
           and_return(insert)
 
         callbacks.after_save instance
@@ -128,7 +128,7 @@ describe ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks do
 
       it "creates insert statements with all fields and attributes" do
         Riddle::Query::Insert.should_receive(:new).twice.
-          with('my_index', ['id', 'name', 'created_at'], [123, 'Foo', time]).
+          with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
           and_return(insert)
 
         callbacks.after_save instance
diff --git a/spec/thinking_sphinx_spec.rb b/spec/thinking_sphinx_spec.rb
index 5b0e4b3..e563768 100644
--- a/spec/thinking_sphinx_spec.rb
+++ b/spec/thinking_sphinx_spec.rb
@@ -2,7 +2,8 @@ require 'spec_helper'
 
 describe ThinkingSphinx do
   describe '.count' do
-    let(:search) { double('search', :total_entries => 23) }
+    let(:search) { double('search', :total_entries => 23, :populated? => false,
+      :options => {}) }
 
     before :each do
       ThinkingSphinx::Search.stub :new => search
diff --git a/thinking-sphinx.gemspec b/thinking-sphinx.gemspec
index 8902782..536f130 100644
--- a/thinking-sphinx.gemspec
+++ b/thinking-sphinx.gemspec
@@ -3,7 +3,7 @@ $:.push File.expand_path('../lib', __FILE__)
 
 Gem::Specification.new do |s|
   s.name        = 'thinking-sphinx'
-  s.version     = '3.1.4'
+  s.version     = '3.2.0'
   s.platform    = Gem::Platform::RUBY
   s.authors     = ["Pat Allan"]
   s.email       = ["pat at freelancing-gods.com"]

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-thinking-sphinx.git



More information about the Pkg-ruby-extras-commits mailing list