diff --git a/.travis.yml b/.travis.yml index f27d21835..1d98c9002 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,39 +1,30 @@ language: ruby dist: xenial rvm: -- 2.3.8 -- 2.4.6 -- 2.5.5 -- 2.6.3 +- 2.4.10 +- 2.5.8 +- 2.6.6 +- 2.7.1 before_install: -- pip install --upgrade --user awscli - gem update --system -- gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true -- gem install bundler -v '< 2' +- gem install bundler -v '1.17.3' +install: bundle _1.17.3_ install --jobs=3 --retry=3 before_script: - mysql -e 'create database thinking_sphinx;' > /dev/null - psql -c 'create database thinking_sphinx;' -U postgres >/dev/null - "./bin/loadsphinx $SPHINX_VERSION $SPHINX_ENGINE" -- bundle exec appraisal install -script: bundle exec appraisal rspec +- bundle _1.17.3_ exec appraisal install +script: bundle _1.17.3_ exec appraisal rspec env: - global: - - secure: cUPinkilBafqDSPsTkl/PXYc2aXNKUQKXGK8poBBMqKN9/wjfJx1DWgtowDKalekdZELxDhc85Ye3bL1xlW4nLjOu+U6Tkt8eNw2Nhs1flodHzA/RyENdBLr/tBHt43EjkrDehZx5sBHmWQY4miHs8AJz0oKO9Ae2inTOHx9Iuc= matrix: - - DATABASE=mysql2 SPHINX_VERSION=2.1.9 SPHINX_ENGINE=sphinx - - DATABASE=postgresql SPHINX_VERSION=2.1.9 SPHINX_ENGINE=sphinx - DATABASE=mysql2 SPHINX_VERSION=2.2.11 SPHINX_ENGINE=sphinx - DATABASE=postgresql SPHINX_VERSION=2.2.11 SPHINX_ENGINE=sphinx - - DATABASE=mysql2 SPHINX_VERSION=3.0.3 SPHINX_ENGINE=sphinx - - DATABASE=postgresql SPHINX_VERSION=3.0.3 SPHINX_ENGINE=sphinx - - DATABASE=mysql2 SPHINX_VERSION=3.1.1 SPHINX_ENGINE=sphinx - - DATABASE=mysql2 SPHINX_VERSION=2.6.4 SPHINX_ENGINE=manticore - - DATABASE=postgresql SPHINX_VERSION=2.6.4 SPHINX_ENGINE=manticore - - DATABASE=mysql2 SPHINX_VERSION=2.7.5 SPHINX_ENGINE=manticore - - DATABASE=postgresql SPHINX_VERSION=2.7.5 SPHINX_ENGINE=manticore + - DATABASE=mysql2 SPHINX_VERSION=3.2.1 SPHINX_ENGINE=sphinx - DATABASE=mysql2 SPHINX_VERSION=2.8.2 SPHINX_ENGINE=manticore - DATABASE=postgresql SPHINX_VERSION=2.8.2 SPHINX_ENGINE=manticore - # - DATABASE=postgresql SPHINX_VERSION=3.1.1 SPHINX_ENGINE=sphinx + - DATABASE=mysql2 SPHINX_VERSION=3.4.2 SPHINX_ENGINE=manticore + - DATABASE=postgresql SPHINX_VERSION=3.4.2 SPHINX_ENGINE=manticore + # - DATABASE=postgresql SPHINX_VERSION=3.2.1 SPHINX_ENGINE=sphinx sudo: false addons: postgresql: '9.4' diff --git a/Appraisals b/Appraisals index 8bf33612e..9d1ed415d 100644 --- a/Appraisals +++ b/Appraisals @@ -1,22 +1,7 @@ -appraise 'rails_3_2' do - gem 'rails', '~> 3.2.22.2' - gem 'mysql2', '~> 0.3.10', :platform => :ruby -end if RUBY_VERSION.to_f <= 2.3 - -appraise 'rails_4_0' do - gem 'rails', '~> 4.0.13' - gem 'mysql2', '~> 0.3.10', :platform => :ruby -end if RUBY_VERSION.to_f <= 2.3 - -appraise 'rails_4_1' do - gem 'rails', '~> 4.1.15' - gem 'mysql2', '~> 0.3.13', :platform => :ruby -end if RUBY_VERSION.to_f <= 2.3 - appraise 'rails_4_2' do gem 'rails', '~> 4.2.6' gem 'mysql2', '~> 0.4.0', :platform => :ruby -end if RUBY_VERSION.to_f <= 2.3 +end if RUBY_VERSION.to_f <= 2.4 appraise 'rails_5_0' do if RUBY_PLATFORM == "java" diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 940284aea..83aa00b97 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -2,6 +2,26 @@ All notable changes to this project (at least, from v3.0.0 onwards) are documented in this file. +## 5.0.0 - 2020-07-20 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v5.0.0) + +### Added + +* New interface for adding callbacks to indexed models (which is no longer done automatically). Discussed in [#1173](https://github.com/pat/thinking-sphinx/issues/1173) and committed via [#1175](https://github.com/pat/thinking-sphinx/pull/1175). **This is a breaking change - you will need to add these callbacks. See [the full release notes](https://github.com/pat/thinking-sphinx/releases/tag/v5.0.0) for examples.** +* Fields and attributes can be overriden - whichever's defined last with a given name is the definition that's used. This is an edge case, but useful if you want to override any of the default fields/indices. (Requested by @kalsan in [#1172](https://github.com/pat/thinking-sphinx/issues/1172).) +* Custom index_set_class implementations can now expect the `:instances` option to be set alongside `:classes`, which is useful in cases to limit the indices returned if you're splitting index data for given classes/models into shards. (Introduced in PR [#1171](https://github.com/pat/thinking-sphinx/pull/1171) after discussions with @lunaru in [#1166](https://github.com/pat/thinking-sphinx/issues/1166).) + +### Changed + +* Sphinx 2.2.11 or newer is required, or Manticore 2.8.2 or newer. +* Ruby 2.4 or newer is required. +* Rails 4.2 or newer is required. +* Remove internal uses of `send`, replaced with `public_send` as that's available in all supported Ruby versions. +* Deletion statements are simplified by avoiding the need to calculate document keys/offsets (@njakobsen via [#1134](https://github.com/pat/thinking-sphinx/issues/1134)). +* Real-time data is deleted before replacing it, to avoid duplicate data when offsets change (@njakobsen via [#1134](https://github.com/pat/thinking-sphinx/issues/1134)). +* Use `reference_name` as per custom `index_set_class` definitions. Previously, the class method was called on `ThinkingSphinx::IndexSet` even if a custom subclass was configured. (As per discussinos with @kalsan in [#1172](https://github.com/pat/thinking-sphinx/issues/1172).) + ## 4.4.1 - 2019-08-23 [Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v4.4.1) diff --git a/README.textile b/README.textile index 3c1012fc9..dba74ad13 100644 --- a/README.textile +++ b/README.textile @@ -1,22 +1,22 @@ 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 v4.4.1. +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 v5.0.0. h2. Upgrading Please refer to "the changelog":https://github.com/pat/thinking-sphinx/blob/develop/CHANGELOG.markdown and "release notes":https://github.com/pat/thinking-sphinx/releases for any changes you need to make when upgrading. The release notes in particular are quite good at covering breaking changes and more details for new features. -The documentation also has more details on what's involved for upgrading from "v3 to v4":https://freelancing-gods.com/thinking-sphinx/v4/upgrading.html, and "v1/v2 to v3":https://freelancing-gods.com/thinking-sphinx/v3/upgrading.html. +The documentation also has more details on what's involved for upgrading from "v4 to v5":https://freelancing-gods.com/thinking-sphinx/v5/upgrading.html, "v3 to v4":https://freelancing-gods.com/thinking-sphinx/v4/upgrading.html, and "v1/v2 to v3":https://freelancing-gods.com/thinking-sphinx/v3/upgrading.html. 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: -
gem 'mysql2',          '~> 0.3',    :platform => :ruby
+
gem 'mysql2',          '~> 0.4',    :platform => :ruby
 gem 'jdbc-mysql',      '~> 5.1.35', :platform => :jruby
-gem 'thinking-sphinx', '~> 4.4'
+gem 'thinking-sphinx', '~> 5.0'
-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 with a version of Sphinx prior to 2.2.11, 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, or upgrade Sphinx. +The MySQL gems mentioned are required for connecting to Sphinx, so please include it even when you're using PostgreSQL for your database. You'll also need to install Sphinx - this is covered in "the extended documentation":https://freelancing-gods.com/thinking-sphinx/installing_sphinx.html. @@ -29,10 +29,10 @@ h2. Requirements The current release of Thinking Sphinx works with the following versions of its dependencies: |_. Library |_. Minimum |_. Tested Against | -| Ruby | v2.3 | v2.3.8, v2.4.5, v2.5.3, v2.6.1 | -| Sphinx | v2.1.2 | v2.1.9, v2.2.11, v3.0.3, v3.1.1 | -| Manticore | v2.6.3 | v2.6.4, v2.7.5, v2.8.1 | -| ActiveRecord | v3.2 | v3.2..v6.0 | +| Ruby | v2.4 | v2.4, v2.5, v2.6, v2.7 | +| Sphinx | v2.2.11 | v2.2.11, v3.2.1 | +| Manticore | v2.8 | v2.8, v3.4 | +| ActiveRecord | v4.2 | v4.2..v6.0 | It _might_ work with older versions of Ruby, but it's highly recommended to update to a supported release. @@ -40,19 +40,17 @@ It should also work with JRuby, but the test environment on Travis CI has been t h3. Sphinx or Manticore -Thinking Sphinx v3 is currently built for Sphinx 2.1.2 or newer, or Manticore v2.6+. +Thinking Sphinx is currently built for Sphinx 2.2.11 or newer (though it'll likely work with 2.1.x releases), or Manticore v2.8+. h3. Rails and ActiveRecord -Currently Thinking Sphinx 3 is built to support Rails/ActiveRecord 3.2 or newer. If you're using Sinatra and ActiveRecord instead of Rails, that's fine - just make sure you add the @:require => 'thinking_sphinx/sinatra'@ option when listing @thinking-sphinx@ in your Gemfile. +Currently Thinking Sphinx is built to support Rails/ActiveRecord 4.2 or newer. If you're using Sinatra and ActiveRecord instead of Rails, that's fine - just make sure you add the @:require => 'thinking_sphinx/sinatra'@ option when listing @thinking-sphinx@ in your Gemfile. -Please note that if you're referring to polymorphic associations in your index definitions, you'll want to be using Rails/ActiveRecord 4.0 or newer. Supporting polymorphic associations and Rails/ActiveRecord 3.2 is problematic, and likely will not be addressed in the future. - -If you want ActiveRecord 3.1 support, then refer to the 3.0.x releases of Thinking Sphinx. Anything older than that, then you're stuck with Thinking Sphinx v2.x (for Rails/ActiveRecord 3.0) or v1.x (Rails 2.3). Please note that these older versions are no longer actively supported. +If you want ActiveRecord 3.2-4.1 support, then refer to the 4.x releases of Thinking Sphinx. Or, for ActiveRecord 3.1 support, then refer to the 3.0.x releases. Anything older than that, then you're stuck with Thinking Sphinx v2.x (for Rails/ActiveRecord 3.0) or v1.x (Rails 2.3). Please note that these older versions are no longer actively supported. h3. Ruby -You'll need either the standard Ruby (v2.3 or newer) or JRuby (9.1 or newer). +You'll need either the standard Ruby (v2.4 or newer) or JRuby (9.1 or newer). h3. Database Versions @@ -81,4 +79,4 @@ You can then run the unit tests with @rake spec:unit@, the acceptance tests with h2. Licence -Copyright (c) 2007-2019, Thinking Sphinx is developed and maintained by Pat Allan, and is released under the open MIT Licence. Many thanks to "all who have contributed patches":https://github.com/pat/thinking-sphinx/contributors. +Copyright (c) 2007-2020, Thinking Sphinx is developed and maintained by Pat Allan, and is released under the open MIT Licence. Many thanks to "all who have contributed patches":https://github.com/pat/thinking-sphinx/contributors. diff --git a/bin/loadsphinx b/bin/loadsphinx index 19ba49b54..2cdb609f1 100755 --- a/bin/loadsphinx +++ b/bin/loadsphinx @@ -22,6 +22,9 @@ load_sphinx () { 3.1.1) url="http://sphinxsearch.com/files/sphinx-3.1.1-612d99f-linux-amd64.tar.gz" format="gz";; + 3.2.1) + url="http://www.sphinxsearch.com/files/sphinx-3.2.1-f152e0b-linux-amd64.tar.gz" + format="gz";; *) echo "No Sphinx version $version available" exit 1;; @@ -52,6 +55,8 @@ load_manticore () { url="https://github.com/manticoresoftware/manticoresearch/releases/download/2.7.5/manticore_2.7.5-181204-4a31c54-release-stemmer.xenial_amd64-bin.deb";; 2.8.2) url="https://github.com/manticoresoftware/manticoresearch/releases/download/2.8.2/manticore_2.8.2-190402-4e81114-release-stemmer.xenial_amd64-bin.deb";; + 3.4.2) + url="https://github.com/manticoresoftware/manticoresearch/releases/download/3.4.2/manticore_3.4.2-200410-6903305-release.xenial_amd64-bin.deb";; *) echo "No Manticore version $version available" exit 1;; 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 ebbfecfcf..158549706 100644 --- a/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +++ b/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb @@ -31,7 +31,7 @@ def indices @indices ||= begin configuration.preload_indices configuration.indices_for_references( - *ThinkingSphinx::IndexSet.reference_name(@association.klass) + *configuration.index_set_class.reference_name(@association.klass) ).reject &:distributed? end end diff --git a/lib/thinking_sphinx/active_record/base.rb b/lib/thinking_sphinx/active_record/base.rb index 32f3bd848..6d6c882ea 100644 --- a/lib/thinking_sphinx/active_record/base.rb +++ b/lib/thinking_sphinx/active_record/base.rb @@ -4,11 +4,6 @@ module ThinkingSphinx::ActiveRecord::Base extend ActiveSupport::Concern included do - after_destroy ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks - before_save ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks - after_update ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks - after_commit ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks - if ActiveRecord::VERSION::STRING.to_i >= 5 [ ::ActiveRecord::Reflection::HasManyReflection, @@ -17,8 +12,9 @@ module ThinkingSphinx::ActiveRecord::Base reflection_class.include DefaultReflectionAssociations end else - ::ActiveRecord::Associations::CollectionProxy.send :include, + ::ActiveRecord::Associations::CollectionProxy.include( ThinkingSphinx::ActiveRecord::AssociationProxy + ) end end diff --git a/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb b/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb index a60cdec57..a4f8a962f 100644 --- a/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +++ b/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb @@ -27,7 +27,7 @@ def delete_from_sphinx def indices ThinkingSphinx::Configuration.instance.index_set_class.new( - :classes => [instance.class] + :instances => [instance], :classes => [instance.class] ).to_a end end diff --git a/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb b/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb index 828d94e7e..6cdf4422f 100644 --- a/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +++ b/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb @@ -43,8 +43,9 @@ def delta_indices? end def indices - @indices ||= config.index_set_class.new(:classes => [instance.class]). - select { |index| index.type == "plain" } + @indices ||= config.index_set_class.new( + :instances => [instance], :classes => [instance.class] + ).select { |index| index.type == "plain" } end def new_or_changed? diff --git a/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb b/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb index 042632242..ee9add052 100644 --- a/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +++ b/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb @@ -47,7 +47,7 @@ def indices end def reference - ThinkingSphinx::IndexSet.reference_name(instance.class) + configuration.index_set_class.reference_name(instance.class) end def update(index) diff --git a/lib/thinking_sphinx/active_record/interpreter.rb b/lib/thinking_sphinx/active_record/interpreter.rb index 16f749068..a6e736d98 100644 --- a/lib/thinking_sphinx/active_record/interpreter.rb +++ b/lib/thinking_sphinx/active_record/interpreter.rb @@ -13,15 +13,15 @@ def group_by(*columns) end def has(*columns) - __source.attributes += build_properties( + build_properties( ::ThinkingSphinx::ActiveRecord::Attribute, columns - ) + ).each { |attribute| __source.add_attribute attribute } end def indexes(*columns) - __source.fields += build_properties( + build_properties( ::ThinkingSphinx::ActiveRecord::Field, columns - ) + ).each { |field| __source.add_field field } end def join(*columns) diff --git a/lib/thinking_sphinx/active_record/sql_source.rb b/lib/thinking_sphinx/active_record/sql_source.rb index 1b67b2814..b732439d7 100644 --- a/lib/thinking_sphinx/active_record/sql_source.rb +++ b/lib/thinking_sphinx/active_record/sql_source.rb @@ -39,6 +39,18 @@ def adapter @adapter ||= DatabaseAdapters.adapter_for(@model) end + def add_attribute(attribute) + attributes.delete_if { |existing| existing.name == attribute.name } + + attributes << attribute + end + + def add_field(field) + fields.delete_if { |existing| existing.name == field.name } + + fields << field + end + def delta_processor options[:delta_processor].try(:new, adapter, @options[:delta_options] || {}) end diff --git a/lib/thinking_sphinx/active_record/sql_source/template.rb b/lib/thinking_sphinx/active_record/sql_source/template.rb index 9c44a30ee..722d731bb 100644 --- a/lib/thinking_sphinx/active_record/sql_source/template.rb +++ b/lib/thinking_sphinx/active_record/sql_source/template.rb @@ -18,14 +18,14 @@ def apply private def add_attribute(column, name, type, options = {}) - source.attributes << ThinkingSphinx::ActiveRecord::Attribute.new( + source.add_attribute ThinkingSphinx::ActiveRecord::Attribute.new( source.model, ThinkingSphinx::ActiveRecord::Column.new(column), options.merge(:as => name, :type => type) ) end def add_field(column, name, options = {}) - source.fields << ThinkingSphinx::ActiveRecord::Field.new( + source.add_field ThinkingSphinx::ActiveRecord::Field.new( source.model, ThinkingSphinx::ActiveRecord::Column.new(column), options.merge(:as => name) ) diff --git a/lib/thinking_sphinx/callbacks.rb b/lib/thinking_sphinx/callbacks.rb index 17c32a0d8..b8d2d789e 100644 --- a/lib/thinking_sphinx/callbacks.rb +++ b/lib/thinking_sphinx/callbacks.rb @@ -3,6 +3,13 @@ class ThinkingSphinx::Callbacks attr_reader :instance + def self.append(model, reference = nil, options, &block) + reference ||= ThinkingSphinx::Configuration.instance.index_set_class. + reference_name(model) + + ThinkingSphinx::Callbacks::Appender.call(model, reference, options, &block) + end + def self.callbacks(*methods) mod = Module.new methods.each do |method| @@ -33,3 +40,5 @@ def initialize(instance) @instance = instance end end + +require "thinking_sphinx/callbacks/appender" diff --git a/lib/thinking_sphinx/callbacks/appender.rb b/lib/thinking_sphinx/callbacks/appender.rb new file mode 100644 index 000000000..10c339e7d --- /dev/null +++ b/lib/thinking_sphinx/callbacks/appender.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class ThinkingSphinx::Callbacks::Appender + def self.call(model, reference, options, &block) + new(model, reference, options, &block).call + end + + def initialize(model, reference, options, &block) + @model = model + @reference = reference + @options = options + @block = block + end + + def call + model.after_destroy ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks + + if behaviours.include?(:deltas) + model.before_save ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks + model.after_commit ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks + end + + if behaviours.include?(:real_time) + model.after_save ThinkingSphinx::RealTime.callback_for( + reference, path, &block + ) + end + + if behaviours.include?(:updates) + model.after_update( + ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks + ) + end + end + + private + + attr_reader :model, :reference, :options, :block + + def behaviours + options[:behaviours] || [] + end + + def path + options[:path] || [] + end +end diff --git a/lib/thinking_sphinx/core/index.rb b/lib/thinking_sphinx/core/index.rb index 184ea4fc2..469062cba 100644 --- a/lib/thinking_sphinx/core/index.rb +++ b/lib/thinking_sphinx/core/index.rb @@ -11,7 +11,6 @@ module ThinkingSphinx::Core::Index def initialize(reference, options = {}) @reference = reference.to_sym - @docinfo = :extern unless config.settings["skip_docinfo"] @options = options @offset = config.next_offset(options[:offset_as] || reference) @type = 'plain' @@ -40,7 +39,7 @@ def document_id_for_key(key) def interpret_definition! table_exists = model.table_exists? unless table_exists - Rails.logger.info "No table exists for #{model}. Index can not be created" + Rails.logger.info "No table exists for #{model}. Index can not be created" return end return if @interpreted_definition diff --git a/lib/thinking_sphinx/deletion.rb b/lib/thinking_sphinx/deletion.rb index 16cd9b4bc..02ae111bc 100644 --- a/lib/thinking_sphinx/deletion.rb +++ b/lib/thinking_sphinx/deletion.rb @@ -22,10 +22,6 @@ def initialize(index, ids) attr_reader :index, :ids - def document_ids_for_keys - ids.collect { |id| index.document_id_for_key id } - end - def execute(statement) statement = statement.gsub(/\s*\n\s*/, ' ').strip @@ -36,11 +32,28 @@ def execute(statement) end end + class PlainDeletion < ThinkingSphinx::Deletion + def perform + ids.each_slice(1000) do |some_ids| + execute <<-SQL +UPDATE #{name} +SET sphinx_deleted = 1 +WHERE sphinx_internal_id IN (#{some_ids.join(', ')}) + SQL + end + end + end + class RealtimeDeletion < ThinkingSphinx::Deletion def perform return unless callbacks_enabled? - execute Riddle::Query::Delete.new(name, document_ids_for_keys).to_sql + ids.each_slice(1000) do |some_ids| + execute <<-SQL +DELETE FROM #{name} +WHERE sphinx_internal_id IN (#{some_ids.join(', ')}) + SQL + end end private @@ -54,16 +67,4 @@ def configuration ThinkingSphinx::Configuration.instance end end - - class PlainDeletion < ThinkingSphinx::Deletion - def perform - document_ids_for_keys.each_slice(1000) do |document_ids| - execute <<-SQL -UPDATE #{name} -SET sphinx_deleted = 1 -WHERE id IN (#{document_ids.join(', ')}) - SQL - end - end - end end diff --git a/lib/thinking_sphinx/index_set.rb b/lib/thinking_sphinx/index_set.rb index f8a6a4dcb..2e87a1643 100644 --- a/lib/thinking_sphinx/index_set.rb +++ b/lib/thinking_sphinx/index_set.rb @@ -34,11 +34,11 @@ def all_indices end def classes - options[:classes] || [] + options[:classes] || instances.collect(&:class) end def classes_specified? - classes.any? || references_specified? + instances.any? || classes.any? || references_specified? end def classes_and_ancestors @@ -68,6 +68,10 @@ def indices_for_references all_indices.select { |index| references.include? index.reference } end + def instances + options[:instances] || [] + end + def mti_classes classes.reject { |klass| klass.column_names.include?(klass.inheritance_column) @@ -76,7 +80,7 @@ def mti_classes def references options[:references] || classes_and_ancestors.collect { |klass| - ThinkingSphinx::IndexSet.reference_name(klass) + self.class.reference_name(klass) } end diff --git a/lib/thinking_sphinx/middlewares/sphinxql.rb b/lib/thinking_sphinx/middlewares/sphinxql.rb index 1c65505c5..69e01fd96 100644 --- a/lib/thinking_sphinx/middlewares/sphinxql.rb +++ b/lib/thinking_sphinx/middlewares/sphinxql.rb @@ -133,7 +133,7 @@ def indices def indices_match_classes? indices.collect(&:reference).uniq.sort == classes.collect { |klass| - ThinkingSphinx::IndexSet.reference_name(klass) + configuration.index_set_class.reference_name(klass) }.sort end diff --git a/lib/thinking_sphinx/railtie.rb b/lib/thinking_sphinx/railtie.rb index 0a16b9003..8ba1f8583 100644 --- a/lib/thinking_sphinx/railtie.rb +++ b/lib/thinking_sphinx/railtie.rb @@ -7,7 +7,7 @@ class ThinkingSphinx::Railtie < Rails::Railtie initializer 'thinking_sphinx.initialisation' do ActiveSupport.on_load(:active_record) do - ActiveRecord::Base.send :include, ThinkingSphinx::ActiveRecord::Base + ActiveRecord::Base.include ThinkingSphinx::ActiveRecord::Base end if ActiveSupport::VERSION::MAJOR > 5 diff --git a/lib/thinking_sphinx/real_time/index.rb b/lib/thinking_sphinx/real_time/index.rb index 7c7cafb87..634a16f48 100644 --- a/lib/thinking_sphinx/real_time/index.rb +++ b/lib/thinking_sphinx/real_time/index.rb @@ -16,10 +16,14 @@ def initialize(reference, options = {}) end def add_attribute(attribute) + @attributes.delete_if { |existing| existing.name == attribute.name } + @attributes << attribute end def add_field(field) + @fields.delete_if { |existing| existing.name == field.name } + @fields << field end diff --git a/lib/thinking_sphinx/real_time/interpreter.rb b/lib/thinking_sphinx/real_time/interpreter.rb index 6109bd7b3..ba9c11a1d 100644 --- a/lib/thinking_sphinx/real_time/interpreter.rb +++ b/lib/thinking_sphinx/real_time/interpreter.rb @@ -5,16 +5,18 @@ class ThinkingSphinx::RealTime::Interpreter < def has(*columns) options = columns.extract_options! - @index.attributes += columns.collect { |column| + + columns.collect { |column| ::ThinkingSphinx::RealTime::Attribute.new column, options - } + }.each { |attribute| @index.add_attribute attribute } end def indexes(*columns) options = columns.extract_options! - @index.fields += columns.collect { |column| + + columns.collect { |column| ::ThinkingSphinx::RealTime::Field.new column, options - } + }.each { |field| @index.add_field field } append_sortable_attributes columns, options if options[:sortable] end @@ -39,7 +41,7 @@ def where(condition) def append_sortable_attributes(columns, options) options = options.except(:sortable).merge(:type => :string) - @index.attributes += columns.collect { |column| + columns.collect { |column| aliased_name = options[:as] aliased_name ||= column.__name.to_sym if column.respond_to?(:__name) aliased_name ||= column @@ -47,6 +49,6 @@ def append_sortable_attributes(columns, options) options[:as] = "#{aliased_name}_sort".to_sym ::ThinkingSphinx::RealTime::Attribute.new column, options - } + }.each { |attribute| @index.add_attribute attribute } end end diff --git a/lib/thinking_sphinx/real_time/transcriber.rb b/lib/thinking_sphinx/real_time/transcriber.rb index 9b0333f6a..dc8bbc02a 100644 --- a/lib/thinking_sphinx/real_time/transcriber.rb +++ b/lib/thinking_sphinx/real_time/transcriber.rb @@ -11,25 +11,8 @@ def copy(*instances) } return unless items.present? - values = [] - items.each do |instance| - begin - values << ThinkingSphinx::RealTime::TranscribeInstance.call( - instance, index, properties - ) - rescue ThinkingSphinx::TranscriptionError => error - instrument 'error', :error => error - end - end - - 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 + delete_existing items + insert_replacements items end private @@ -55,6 +38,27 @@ def copy?(instance) } end + def delete_existing(instances) + ids = instances.collect(&index.primary_key.to_sym) + + execute <<~SQL.strip + DELETE FROM #{@index.name} WHERE sphinx_internal_id IN (#{ids.join(', ')}) + SQL + end + + def execute(sphinxql) + ThinkingSphinx::Logger.log :query, sphinxql do + ThinkingSphinx::Connection.take do |connection| + connection.execute sphinxql + end + end + end + + def insert_replacements(instances) + insert = Riddle::Query::Insert.new index.name, columns, values(instances) + execute insert.replace!.to_sql + end + def instrument(message, options = {}) ActiveSupport::Notifications.instrument( "#{message}.thinking_sphinx.real_time", options.merge(:index => index) @@ -64,4 +68,16 @@ def instrument(message, options = {}) def properties @properties ||= index.fields + index.attributes end + + def values(instances) + instances.each_with_object([]) do |instance, array| + begin + array << ThinkingSphinx::RealTime::TranscribeInstance.call( + instance, index, properties + ) + rescue ThinkingSphinx::TranscriptionError => error + instrument 'error', :error => error + end + end + end end diff --git a/spec/acceptance/big_integers_spec.rb b/spec/acceptance/big_integers_spec.rb index bde59a847..3f097a515 100644 --- a/spec/acceptance/big_integers_spec.rb +++ b/spec/acceptance/big_integers_spec.rb @@ -52,7 +52,7 @@ context 'with Real-Time' do it 'handles large 32 bit integers with an offset multiplier' do product = Product.create! :name => "Widget" - product.update_attributes :id => 980190962 + product.update :id => 980190962 expect( Product.search('widget', :indices => ['product_core']).to_a ).to eq([product]) diff --git a/spec/acceptance/merging_spec.rb b/spec/acceptance/merging_spec.rb index 99ce86003..7e69e8a16 100644 --- a/spec/acceptance/merging_spec.rb +++ b/spec/acceptance/merging_spec.rb @@ -34,7 +34,7 @@ Book.search("Space", :indices => ["book_core"]).to_a ).to eq([race]) - race.reload.update_attributes :title => "The Hate Race" + race.reload.update :title => "The Hate Race" sleep 0.25 expect( Book.search("Race", :indices => ["book_delta"]).to_a diff --git a/spec/acceptance/real_time_updates_spec.rb b/spec/acceptance/real_time_updates_spec.rb index 13711c118..6417216a0 100644 --- a/spec/acceptance/real_time_updates_spec.rb +++ b/spec/acceptance/real_time_updates_spec.rb @@ -11,7 +11,7 @@ it "handles attributes for sortable fields accordingly" do product = Product.create! :name => 'Red Fish' - product.update_attributes :name => 'Blue Fish' + product.update :name => 'Blue Fish' expect(Product.search('blue fish', :indices => ['product_core']).to_a). to eq([product]) @@ -22,7 +22,7 @@ expect(Admin::Person.search('Death').to_a).to eq([person]) - person.update_attributes :name => 'Mort' + person.update :name => 'Mort' expect(Admin::Person.search('Death').to_a).to be_empty expect(Admin::Person.search('Mort').to_a).to eq([person]) diff --git a/spec/acceptance/sql_deltas_spec.rb b/spec/acceptance/sql_deltas_spec.rb index 0777192cc..ca76a77d6 100644 --- a/spec/acceptance/sql_deltas_spec.rb +++ b/spec/acceptance/sql_deltas_spec.rb @@ -25,7 +25,7 @@ expect(Book.search('Harry').to_a).to eq([book]) - book.reload.update_attributes(:author => 'Terry Pratchett') + book.reload.update(:author => 'Terry Pratchett') sleep 0.25 expect(Book.search('Terry').to_a).to eq([book]) @@ -37,7 +37,7 @@ expect(Book.search('Harry').to_a).to eq([book]) - book.reload.update_attributes(:author => 'Terry Pratchett') + book.reload.update(:author => 'Terry Pratchett') sleep 0.25 expect(Book.search('Harry')).to be_empty @@ -49,7 +49,7 @@ expect(Album.search('Whitloms').to_a).to eq([album]) - album.reload.update_attributes(:artist => 'The Whitlams') + album.reload.update(:artist => 'The Whitlams') sleep 0.25 expect(Book.search('Whitloms')).to be_empty diff --git a/spec/acceptance/suspended_deltas_spec.rb b/spec/acceptance/suspended_deltas_spec.rb index 9623869a7..b5c8c603b 100644 --- a/spec/acceptance/suspended_deltas_spec.rb +++ b/spec/acceptance/suspended_deltas_spec.rb @@ -10,7 +10,7 @@ expect(Book.search('Harry').to_a).to eq([book]) ThinkingSphinx::Deltas.suspend :book do - book.reload.update_attributes(:author => 'Terry Pratchett') + book.reload.update(:author => 'Terry Pratchett') sleep 0.25 expect(Book.search('Terry').to_a).to eq([]) @@ -27,7 +27,7 @@ expect(Book.search('Harry').to_a).to eq([book]) ThinkingSphinx::Deltas.suspend :book do - book.reload.update_attributes(:author => 'Terry Pratchett') + book.reload.update(:author => 'Terry Pratchett') sleep 0.25 expect(Book.search('Terry').to_a).to eq([]) @@ -44,7 +44,7 @@ expect(Book.search('Harry').to_a).to eq([book]) ThinkingSphinx::Deltas.suspend_and_update :book do - book.reload.update_attributes(:author => 'Terry Pratchett') + book.reload.update(:author => 'Terry Pratchett') sleep 0.25 expect(Book.search('Terry').to_a).to eq([]) diff --git a/spec/internal/app/models/admin/person.rb b/spec/internal/app/models/admin/person.rb index 50aa69092..eaa3935dc 100644 --- a/spec/internal/app/models/admin/person.rb +++ b/spec/internal/app/models/admin/person.rb @@ -3,5 +3,7 @@ class Admin::Person < ActiveRecord::Base self.table_name = 'admin_people' - after_save ThinkingSphinx::RealTime.callback_for('admin/person') + ThinkingSphinx::Callbacks.append( + self, 'admin/person', :behaviours => [:sql, :real_time] + ) end diff --git a/spec/internal/app/models/album.rb b/spec/internal/app/models/album.rb index 53aaaee5b..7b6e953e4 100644 --- a/spec/internal/app/models/album.rb +++ b/spec/internal/app/models/album.rb @@ -6,7 +6,9 @@ class Album < ActiveRecord::Base before_validation :set_id, :on => :create before_validation :set_integer_id, :on => :create - after_save ThinkingSphinx::RealTime.callback_for(:album) + ThinkingSphinx::Callbacks.append( + self, :behaviours => [:sql, :real_time, :deltas] + ) validates :id, :presence => true, :uniqueness => true validates :integer_id, :presence => true, :uniqueness => true diff --git a/spec/internal/app/models/animal.rb b/spec/internal/app/models/animal.rb index a6c9a0297..4431ef86b 100644 --- a/spec/internal/app/models/animal.rb +++ b/spec/internal/app/models/animal.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true class Animal < ActiveRecord::Base + ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql]) end diff --git a/spec/internal/app/models/article.rb b/spec/internal/app/models/article.rb index c69a594a0..b2ebf186b 100644 --- a/spec/internal/app/models/article.rb +++ b/spec/internal/app/models/article.rb @@ -4,4 +4,6 @@ class Article < ActiveRecord::Base belongs_to :user has_many :taggings has_many :tags, :through => :taggings + + ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql, :updates]) end diff --git a/spec/internal/app/models/bird.rb b/spec/internal/app/models/bird.rb index bdd035747..a2d11ddc3 100644 --- a/spec/internal/app/models/bird.rb +++ b/spec/internal/app/models/bird.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true class Bird < Animal + ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql]) end diff --git a/spec/internal/app/models/book.rb b/spec/internal/app/models/book.rb index 207b89120..da8c590b8 100644 --- a/spec/internal/app/models/book.rb +++ b/spec/internal/app/models/book.rb @@ -5,6 +5,8 @@ class Book < ActiveRecord::Base has_and_belongs_to_many :genres + ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql, :deltas]) + sphinx_scope(:by_query) { |query| query } sphinx_scope(:by_year) do |year| {:with => {:year => year}} diff --git a/spec/internal/app/models/car.rb b/spec/internal/app/models/car.rb index 0d611c9b0..8651aca86 100644 --- a/spec/internal/app/models/car.rb +++ b/spec/internal/app/models/car.rb @@ -3,5 +3,5 @@ class Car < ActiveRecord::Base belongs_to :manufacturer - after_save ThinkingSphinx::RealTime.callback_for(:car) + ThinkingSphinx::Callbacks.append(self, :behaviours => [:real_time]) end diff --git a/spec/internal/app/models/city.rb b/spec/internal/app/models/city.rb index 07248902d..59c27041b 100644 --- a/spec/internal/app/models/city.rb +++ b/spec/internal/app/models/city.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true class City < ActiveRecord::Base + ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql]) + scope :ordered, lambda { order(:name) } end diff --git a/spec/internal/app/models/product.rb b/spec/internal/app/models/product.rb index a4e40f3b3..2bc519f98 100644 --- a/spec/internal/app/models/product.rb +++ b/spec/internal/app/models/product.rb @@ -4,5 +4,5 @@ class Product < ActiveRecord::Base has_many :categorisations has_many :categories, :through => :categorisations - after_save ThinkingSphinx::RealTime.callback_for(:product) + ThinkingSphinx::Callbacks.append(self, :behaviours => [:real_time]) end diff --git a/spec/internal/app/models/tee.rb b/spec/internal/app/models/tee.rb index c22fbd3d2..84f41919a 100644 --- a/spec/internal/app/models/tee.rb +++ b/spec/internal/app/models/tee.rb @@ -2,4 +2,6 @@ class Tee < ActiveRecord::Base belongs_to :colour + + ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql]) end diff --git a/spec/internal/app/models/user.rb b/spec/internal/app/models/user.rb index 147b2bdeb..26053bc50 100644 --- a/spec/internal/app/models/user.rb +++ b/spec/internal/app/models/user.rb @@ -3,6 +3,8 @@ class User < ActiveRecord::Base has_many :articles + ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql]) + default_scope { order(:id) } scope :recent, lambda { where('created_at > ?', 1.week.ago) } 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 0a8885960..d13a40e50 100644 --- a/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +++ b/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb @@ -19,12 +19,13 @@ module Callbacks; end let(:klass) { double(:name => 'Article') } let(:configuration) { double('configuration', :settings => {'attribute_updates' => true}, - :indices_for_references => [index]) } + :indices_for_references => [index], :index_set_class => set_class) } let(:connection) { double('connection', :execute => '') } let(:index) { double 'index', :name => 'article_core', :sources => [source], :document_id_for_key => 3, :distributed? => false, :type => 'plain', :primary_key => :id} let(:source) { double('source', :attributes => []) } + let(:set_class) { double(:reference_name => :article) } before :each do stub_const 'ThinkingSphinx::Configuration', diff --git a/spec/thinking_sphinx/active_record/index_spec.rb b/spec/thinking_sphinx/active_record/index_spec.rb index 0a167195e..4db95a13e 100644 --- a/spec/thinking_sphinx/active_record/index_spec.rb +++ b/spec/thinking_sphinx/active_record/index_spec.rb @@ -94,18 +94,6 @@ end end - describe '#docinfo' do - it "defaults to extern" do - expect(index.docinfo).to eq(:extern) - end - - it "can be disabled" do - config.settings["skip_docinfo"] = true - - expect(index.docinfo).to be_nil - end - end - describe '#document_id_for_key' do it "calculates the document id based on offset and number of indices" do allow(config).to receive_message_chain(:indices, :count).and_return(5) diff --git a/spec/thinking_sphinx/active_record/interpreter_spec.rb b/spec/thinking_sphinx/active_record/interpreter_spec.rb index d49767d06..d408324df 100644 --- a/spec/thinking_sphinx/active_record/interpreter_spec.rb +++ b/spec/thinking_sphinx/active_record/interpreter_spec.rb @@ -15,8 +15,13 @@ let(:block) { Proc.new { } } before :each do - allow(ThinkingSphinx::ActiveRecord::SQLSource).to receive_messages :new => source - allow(source).to receive_messages :model => model + allow(ThinkingSphinx::ActiveRecord::SQLSource).to receive_messages( + :new => source + ) + + allow(source).to receive_messages( + :model => model, :add_attribute => nil, :add_field => nil + ) end describe '.translate!' do @@ -94,17 +99,15 @@ end it "adds an attribute to the source" do - instance.has column + expect(source).to receive(:add_attribute).with(attribute) - expect(source.attributes).to include(attribute) + instance.has column end it "adds multiple attributes when passed multiple columns" do - instance.has column, column + expect(source).to receive(:add_attribute).with(attribute).twice - expect(source.attributes.select { |saved_attribute| - saved_attribute == attribute - }.length).to eq(2) + instance.has column, column end end @@ -144,17 +147,15 @@ end it "adds a field to the source" do - instance.indexes column + expect(source).to receive(:add_field).with(field) - expect(source.fields).to include(field) + instance.indexes column end it "adds multiple fields when passed multiple columns" do - instance.indexes column, column + expect(source).to receive(:add_field).with(field).twice - expect(source.fields.select { |saved_field| - saved_field == field - }.length).to eq(2) + instance.indexes column, column end end diff --git a/spec/thinking_sphinx/active_record/sql_source_spec.rb b/spec/thinking_sphinx/active_record/sql_source_spec.rb index 97e5ff52e..9b0be0887 100644 --- a/spec/thinking_sphinx/active_record/sql_source_spec.rb +++ b/spec/thinking_sphinx/active_record/sql_source_spec.rb @@ -30,6 +30,44 @@ end end + describe '#add_attribute' do + let(:attribute) { double('attribute', name: 'my_attribute') } + + it "appends attributes to the collection" do + source.add_attribute attribute + + expect(source.attributes.collect(&:name)).to include('my_attribute') + end + + it "replaces attributes with the same name" do + source.add_attribute double('attribute', name: 'my_attribute') + source.add_attribute attribute + + matching = source.attributes.select { |attr| attr.name == attribute.name } + + expect(matching).to eq([attribute]) + end + end + + describe '#add_field' do + let(:field) { double('field', name: 'my_field') } + + it "appends fields to the collection" do + source.add_field field + + expect(source.fields.collect(&:name)).to include('my_field') + end + + it "replaces fields with the same name" do + source.add_field double('field', name: 'my_field') + source.add_field field + + matching = source.fields.select { |fld| fld.name == field.name } + + expect(matching).to eq([field]) + end + end + describe '#attributes' do it "has the internal id attribute by default" do expect(source.attributes.collect(&:name)).to include('sphinx_internal_id') diff --git a/spec/thinking_sphinx/deletion_spec.rb b/spec/thinking_sphinx/deletion_spec.rb index 20f300cd0..d3efd2bd9 100644 --- a/spec/thinking_sphinx/deletion_spec.rb +++ b/spec/thinking_sphinx/deletion_spec.rb @@ -6,7 +6,7 @@ describe '.perform' do let(:connection) { double('connection', :execute => nil) } let(:index) { double('index', :name => 'foo_core', - :document_id_for_key => 14, :type => 'plain', :distributed? => false) } + :type => 'plain', :distributed? => false) } before :each do allow(ThinkingSphinx::Connection).to receive(:take).and_yield(connection) @@ -15,8 +15,9 @@ context 'index is SQL-backed' do it "updates the deleted flag to false" do - expect(connection).to receive(:execute). - with('UPDATE foo_core SET sphinx_deleted = 1 WHERE id IN (14)') + expect(connection).to receive(:execute).with( + 'UPDATE foo_core SET sphinx_deleted = 1 WHERE sphinx_internal_id IN (7)' + ) ThinkingSphinx::Deletion.perform index, 7 end @@ -38,7 +39,7 @@ it "deletes the record to false" do expect(connection).to receive(:execute). - with('DELETE FROM foo_core WHERE id = 14') + with('DELETE FROM foo_core WHERE sphinx_internal_id IN (7)') ThinkingSphinx::Deletion.perform index, 7 end diff --git a/spec/thinking_sphinx/index_set_spec.rb b/spec/thinking_sphinx/index_set_spec.rb index 148cce3f4..3197009bd 100644 --- a/spec/thinking_sphinx/index_set_spec.rb +++ b/spec/thinking_sphinx/index_set_spec.rb @@ -30,6 +30,16 @@ def class_double(name, methods = {}, *superclasses) end describe '#to_a' do + let(:article_index) do + double(:reference => :article, :distributed? => false) + end + let(:opinion_article_index) do + double(:reference => :opinion_article, :distributed? => false) + end + let(:page_index) do + double(:reference => :page, :distributed? => false) + end + it "ensures the indices are loaded" do expect(configuration).to receive(:preload_indices) @@ -50,21 +60,29 @@ def class_double(name, methods = {}, *superclasses) it "uses indices for the given classes" do configuration.indices.replace [ - double(:reference => :article, :distributed? => false), - double(:reference => :opinion_article, :distributed? => false), - double(:reference => :page, :distributed? => false) + article_index, opinion_article_index, page_index ] options[:classes] = [class_double('Article', :column_names => [])] - expect(set.to_a.length).to eq(1) + expect(set.to_a).to eq([article_index]) + end + + it "uses indices for the given instance's class" do + configuration.indices.replace [ + article_index, opinion_article_index, page_index + ] + + instance_class = class_double('Article', :column_names => []) + + options[:instances] = [double(:instance, :class => instance_class)] + + expect(set.to_a).to eq([article_index]) end it "requests indices for any STI superclasses" do configuration.indices.replace [ - double(:reference => :article, :distributed? => false), - double(:reference => :opinion_article, :distributed? => false), - double(:reference => :page, :distributed? => false) + article_index, opinion_article_index, page_index ] article = class_double('Article', :column_names => [:type]) @@ -73,14 +91,12 @@ def class_double(name, methods = {}, *superclasses) options[:classes] = [opinion] - expect(set.to_a.length).to eq(2) + expect(set.to_a).to eq([article_index, opinion_article_index]) end it "does not use MTI superclasses" do configuration.indices.replace [ - double(:reference => :article, :distributed? => false), - double(:reference => :opinion_article, :distributed? => false), - double(:reference => :page, :distributed? => false) + article_index, opinion_article_index, page_index ] article = class_double('Article', :column_names => []) @@ -88,7 +104,7 @@ def class_double(name, methods = {}, *superclasses) options[:classes] = [opinion] - expect(set.to_a.length).to eq(1) + expect(set.to_a).to eq([opinion_article_index]) end it "uses named indices if names are provided" do diff --git a/spec/thinking_sphinx/middlewares/sphinxql_spec.rb b/spec/thinking_sphinx/middlewares/sphinxql_spec.rb index bd8c34f9b..ff966d51c 100644 --- a/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +++ b/spec/thinking_sphinx/middlewares/sphinxql_spec.rb @@ -31,7 +31,7 @@ class SphinxQLSubclass let(:query) { double('query') } let(:configuration) { double('configuration', :settings => {}, index_set_class: set_class) } - let(:set_class) { double(:new => index_set) } + let(:set_class) { double(:new => index_set, :reference_name => :article) } before :each do stub_const 'Riddle::Query::Select', double(:new => sphinx_sql) @@ -115,6 +115,7 @@ class SphinxQLSubclass model = double('model', :connection => double, :ancestors => [ActiveRecord::Base], :name => 'Animal') allow(index_set.first).to receive_messages :reference => :animal + allow(set_class).to receive_messages(:reference_name => :animal) search.options[:classes] = [model] diff --git a/spec/thinking_sphinx/real_time/index_spec.rb b/spec/thinking_sphinx/real_time/index_spec.rb index eb2345783..ffac6960a 100644 --- a/spec/thinking_sphinx/real_time/index_spec.rb +++ b/spec/thinking_sphinx/real_time/index_spec.rb @@ -11,6 +11,44 @@ allow(ThinkingSphinx::Configuration).to receive_messages :instance => config end + describe '#add_attribute' do + let(:attribute) { double('attribute', name: 'my_attribute') } + + it "appends attributes to the collection" do + index.add_attribute attribute + + expect(index.attributes.collect(&:name)).to include('my_attribute') + end + + it "replaces attributes with the same name" do + index.add_attribute double('attribute', name: 'my_attribute') + index.add_attribute attribute + + matching = index.attributes.select { |attr| attr.name == attribute.name } + + expect(matching).to eq([attribute]) + end + end + + describe '#add_field' do + let(:field) { double('field', name: 'my_field') } + + it "appends fields to the collection" do + index.add_field field + + expect(index.fields.collect(&:name)).to include('my_field') + end + + it "replaces fields with the same name" do + index.add_field double('field', name: 'my_field') + index.add_field field + + matching = index.fields.select { |fld| fld.name == field.name } + + expect(matching).to eq([field]) + end + end + describe '#attributes' do it "has the internal id attribute by default" do expect(index.attributes.collect(&:name)).to include('sphinx_internal_id') @@ -31,18 +69,6 @@ end end - describe '#docinfo' do - it "defaults to extern" do - expect(index.docinfo).to eq(:extern) - end - - it "can be disabled" do - config.settings["skip_docinfo"] = true - - expect(index.docinfo).to be_nil - end - end - describe '#document_id_for_key' do it "calculates the document id based on offset and number of indices" do allow(config).to receive_message_chain(:indices, :count).and_return(5) diff --git a/spec/thinking_sphinx/real_time/interpreter_spec.rb b/spec/thinking_sphinx/real_time/interpreter_spec.rb index 291a5e2d8..44b3d6727 100644 --- a/spec/thinking_sphinx/real_time/interpreter_spec.rb +++ b/spec/thinking_sphinx/real_time/interpreter_spec.rb @@ -10,6 +10,10 @@ let(:index) { Struct.new(:attributes, :fields, :options).new([], [], {}) } let(:block) { Proc.new { } } + before :each do + allow(index).to receive_messages(:add_attribute => nil, :add_field => nil) + end + describe '.translate!' do let(:instance) { double('interpreter', :translate! => true) } @@ -51,17 +55,15 @@ end it "adds an attribute to the index" do - instance.has column + expect(index).to receive(:add_attribute).with(attribute) - expect(index.attributes).to include(attribute) + instance.has column end it "adds multiple attributes when passed multiple columns" do - instance.has column, column + expect(index).to receive(:add_attribute).with(attribute).twice - expect(index.attributes.select { |saved_attribute| - saved_attribute == attribute - }.length).to eq(2) + instance.has column, column end end @@ -88,17 +90,15 @@ end it "adds a field to the index" do - instance.indexes column + expect(index).to receive(:add_field).with(field) - expect(index.fields).to include(field) + instance.indexes column end it "adds multiple fields when passed multiple columns" do - instance.indexes column, column + expect(index).to receive(:add_field).with(field).twice - expect(index.fields.select { |saved_field| - saved_field == field - }.length).to eq(2) + instance.indexes column, column end context 'sortable' do @@ -135,9 +135,9 @@ end it "adds an attribute to the index" do - instance.indexes column, :sortable => true + expect(index).to receive(:add_attribute).with(attribute) - expect(index.attributes).to include(attribute) + instance.indexes column, :sortable => true end end end diff --git a/spec/thinking_sphinx/real_time/transcriber_spec.rb b/spec/thinking_sphinx/real_time/transcriber_spec.rb index 1e9b67e07..e18a08114 100644 --- a/spec/thinking_sphinx/real_time/transcriber_spec.rb +++ b/spec/thinking_sphinx/real_time/transcriber_spec.rb @@ -6,7 +6,8 @@ let(:subject) { ThinkingSphinx::RealTime::Transcriber.new index } let(:index) { double 'index', :name => 'foo_core', :conditions => [], :fields => [double(:name => 'field_a'), double(:name => 'field_b')], - :attributes => [double(:name => 'attr_a'), double(:name => 'attr_b')] } + :attributes => [double(:name => 'attr_a'), double(:name => 'attr_b')], + :primary_key => :id } let(:insert) { double :replace! => replace } let(:replace) { double :to_sql => 'REPLACE QUERY' } let(:connection) { double :execute => true } @@ -40,6 +41,13 @@ subject.copy instance_a, instance_b end + it "deletes previous records" do + expect(connection).to receive(:execute). + with('DELETE FROM foo_core WHERE sphinx_internal_id IN (48, 49)') + + subject.copy instance_a, instance_b + end + it "skips instances that aren't in the database" do allow(instance_a).to receive(:persisted?).and_return(false) diff --git a/thinking-sphinx.gemspec b/thinking-sphinx.gemspec index 8d8fde50c..da9589e73 100644 --- a/thinking-sphinx.gemspec +++ b/thinking-sphinx.gemspec @@ -5,7 +5,7 @@ $:.push File.expand_path('../lib', __FILE__) Gem::Specification.new do |s| s.name = 'thinking-sphinx' - s.version = '4.4.1' + s.version = '5.0.0' s.platform = Gem::Platform::RUBY s.authors = ["Pat Allan"] s.email = ["pat@freelancing-gods.com"] @@ -21,9 +21,9 @@ Gem::Specification.new do |s| } s.require_paths = ['lib'] - s.add_runtime_dependency 'activerecord', '>= 3.1.0' + s.add_runtime_dependency 'activerecord', '>= 4.2.0' s.add_runtime_dependency 'builder', '>= 2.1.2' - s.add_runtime_dependency 'joiner', '>= 0.2.0' + s.add_runtime_dependency 'joiner', '>= 0.3.4' s.add_runtime_dependency 'middleware', '>= 0.1.0' s.add_runtime_dependency 'innertube', '>= 1.0.2' s.add_runtime_dependency 'riddle', '~> 2.3'