diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..afde5bb9d --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,99 @@ +version: 2.1 + +orbs: + ruby: circleci/ruby@1.0 + +workflows: + test: + jobs: + - test: + name: "Sphinx 2.2" + sphinx_version: 2.2.11 + sphinx_engine: sphinx + debian: jessie + ruby: '2.4.6' + matrix: + parameters: + database: [ 'mysql2', 'postgresql' ] + rails: [ '4_2', '5_0', '5_1', '5_2' ] + +jobs: + test: + parameters: + ruby: + type: string + rails: + type: string + database: + type: string + sphinx_version: + type: string + sphinx_engine: + type: string + debian: + type: string + + docker: + - image: circleci/ruby:<< parameters.ruby >>-<< parameters.debian >> + + - image: circleci/postgres:10 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: thinking_sphinx + POSTGRES_DB: thinking_sphinx + + - image: circleci/mysql:5.7 + environment: + MYSQL_ROOT_PASSWORD: thinking_sphinx + MYSQL_DATABASE: thinking_sphinx + + working_directory: ~/app + + steps: + - checkout + + - restore_cache: + keys: + - v1-dependencies-<< parameters.ruby >>-<< parameters.rails >> + + - run: + name: install bundler + command: | + export BUNDLER_VERSION=1.17.3 + export BUNDLE_PATH=vendor/bundle + gem install bundler:$BUNDLER_VERSION + + - run: + name: install dependencies + command: | + bundle install --jobs=4 --retry=3 --path vendor/bundle + bundle update + + - run: + name: set up appraisal + command: bundle exec appraisal generate + + - run: + name: update gems + environment: + BUNDLE_GEMFILE: "./gemfiles/rails_<< parameters.rails >>.gemfile" + command: bundle update + + - save_cache: + paths: + - ./vendor/bundle + key: v1-dependencies-<< parameters.ruby >>-<< parameters.rails >> + + - run: + name: set up sphinx + command: "./bin/loadsphinx << parameters.sphinx_version >> << parameters.sphinx_engine >>" + + - run: + name: tests + environment: + CI: "true" + DATABASE: << parameters.database >> + SPHINX_VERSION: << parameters.sphinx_version >> + SPHINX_ENGINE: << parameters.sphinx_engine >> + BUNDLE_GEMFILE: "./gemfiles/rails_<< parameters.rails >>.gemfile" + command: bundle exec rspec diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 000000000..3a026b4e5 --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,46 @@ +name: "Test" +description: "Run RSpec in given environment" +inputs: + ruby-version: + description: "Ruby version" + required: true + rails-version: + description: "Rails version" + required: true + sphinx-version: + description: "Sphinx version" + required: true + sphinx-engine: + description: "Sphinx engine" + required: true + database: + description: "Database engine" + required: true +runs: + using: "composite" + steps: + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ inputs.ruby-version }} + bundler-cache: true + - name: Set up Sphinx + shell: bash + run: | + ./bin/loadsphinx ${{ inputs.sphinx-version }} ${{ inputs.sphinx-engine }} + - name: Set up Appraisal + shell: bash + run: "bundle exec appraisal generate" + - name: Install Appraisal's gems + shell: bash + env: + BUNDLE_GEMFILE: "gemfiles/rails_${{ inputs.rails-version }}.gemfile" + run: bundle install + - name: Test + shell: bash + env: + CI: "true" + DATABASE: ${{ inputs.database }} + SPHINX_VERSION: ${{ inputs.sphinx-version }} + SPHINX_ENGINE: ${{ inputs.sphinx-engine }} + BUNDLE_GEMFILE: "gemfiles/rails_${{ inputs.rails-version }}.gemfile" + run: "bundle exec rspec" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..79b850124 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,129 @@ +name: test + +on: [push, pull_request] + +jobs: + sphinx: + runs-on: ubuntu-22.04 + + strategy: + fail-fast: false + matrix: + ruby: [ '2.7', '3.0', '3.1', '3.2' ] + rails: [ '5_0', '5_1', '5_2', '6_0', '6_1', '7_0', '7_1' ] + database: [ 'mysql2', 'postgresql' ] + sphinx_version: [ '2.2.11', '3.4.1' ] + sphinx_engine: [ 'sphinx' ] + exclude: + - database: 'postgresql' + sphinx_version: '3.4.1' + sphinx_engine: 'sphinx' + - ruby: '3.0' + rails: '5_0' + - ruby: '3.0' + rails: '5_1' + - ruby: '3.0' + rails: '5_2' + - ruby: '3.1' + rails: '5_0' + - ruby: '3.1' + rails: '5_1' + - ruby: '3.1' + rails: '5_2' + - ruby: '3.2' + rails: '5_0' + - ruby: '3.2' + rails: '5_1' + - ruby: '3.2' + rails: '5_2' + + services: + postgres: + image: postgres:10 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: thinking_sphinx + POSTGRES_DB: thinking_sphinx + ports: ['5432:5432'] + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + mysql: + image: mysql:5.7 + env: + MYSQL_ROOT_PASSWORD: thinking_sphinx + MYSQL_DATABASE: thinking_sphinx + ports: ['3306:3306'] + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/test + with: + ruby-version: ${{ matrix.ruby }} + rails-version: ${{ matrix.rails }} + sphinx-version: ${{ matrix.sphinx_version }} + sphinx-engine: ${{ matrix.sphinx_engine }} + database: ${{ matrix.database }} + timeout-minutes: 12 + + manticore: + runs-on: ubuntu-22.04 + + strategy: + fail-fast: false + matrix: + ruby: [ '2.7', '3.0', '3.1', '3.2' ] + rails: [ '5_0', '5_1', '5_2', '6_0', '6_1', '7_0', '7_1' ] + database: [ 'mysql2', 'postgresql' ] + sphinx_version: [ '4.0.2', '6.0.0' ] + sphinx_engine: [ 'manticore' ] + exclude: + - ruby: '3.0' + rails: '5_0' + - ruby: '3.0' + rails: '5_1' + - ruby: '3.0' + rails: '5_2' + - ruby: '3.1' + rails: '5_0' + - ruby: '3.1' + rails: '5_1' + - ruby: '3.1' + rails: '5_2' + - ruby: '3.2' + rails: '5_0' + - ruby: '3.2' + rails: '5_1' + - ruby: '3.2' + rails: '5_2' + + services: + postgres: + image: postgres:10 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: thinking_sphinx + POSTGRES_DB: thinking_sphinx + ports: ['5432:5432'] + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: thinking_sphinx + MYSQL_DATABASE: thinking_sphinx + ports: ['3306:3306'] + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/test + with: + ruby-version: ${{ matrix.ruby }} + rails-version: ${{ matrix.rails }} + sphinx-version: ${{ matrix.sphinx_version }} + sphinx-engine: ${{ matrix.sphinx_engine }} + database: ${{ matrix.database }} + timeout-minutes: 12 diff --git a/.gitignore b/.gitignore index 3ef667d9f..66ac7a2c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,17 @@ *.gem +*.sublime-* .bundle +.overmind.env .rbx +.rspec +.tool-versions +data/* +gemfiles Gemfile.lock -*.sublime-* pkg/* spec/internal/config/test.sphinx.conf spec/internal/db/sphinx -spec/internal/log/*.log +spec/internal/log !spec/internal/tmp/.gitkeep spec/internal/tmp/* tmp diff --git a/.travis.yml b/.travis.yml index fa3722781..1daa3cfee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,36 @@ language: ruby +dist: xenial rvm: - - 1.9.3 - - 2.0.0 - - 2.1.0 - - jruby-19mode +- 2.4.10 +- 2.5.8 +- 2.6.6 +- 2.7.1 before_install: - - gem update --system +- gem update --system +- 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" +- 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 _1.17.3_ exec appraisal install +script: bundle _1.17.3_ exec appraisal rspec env: - - DATABASE=mysql2 SPHINX_BIN=/usr/local/sphinx-2.0.10/bin/ SPHINX_VERSION=2.0.10 - - DATABASE=postgresql SPHINX_BIN=/usr/local/sphinx-2.0.10/bin/ SPHINX_VERSION=2.0.10 - - DATABASE=mysql2 SPHINX_BIN=/usr/local/sphinx-2.1.8/bin/ SPHINX_VERSION=2.1.8 - - DATABASE=postgresql SPHINX_BIN=/usr/local/sphinx-2.1.8/bin/ SPHINX_VERSION=2.1.8 - - DATABASE=mysql2 SPHINX_BIN=/usr/local/sphinx-2.2.2-beta/bin/ SPHINX_VERSION=2.2.2 - - DATABASE=postgresql SPHINX_BIN=/usr/local/sphinx-2.2.2-beta/bin/ SPHINX_VERSION=2.2.2 -gemfile: - - gemfiles/rails_3_2.gemfile - - gemfiles/rails_4_0.gemfile - - gemfiles/rails_4_1.gemfile - - gemfiles/rails_4_2.gemfile + matrix: + - 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.3.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=mysql2 SPHINX_VERSION=3.5.0 SPHINX_ENGINE=manticore + - DATABASE=postgresql SPHINX_VERSION=3.5.0 SPHINX_ENGINE=manticore + # - DATABASE=postgresql SPHINX_VERSION=3.3.1 SPHINX_ENGINE=sphinx +sudo: false +addons: + postgresql: '9.4' + apt: + packages: + - libodbc1 +services: +- mysql +- postgresql diff --git a/Appraisals b/Appraisals index 36aa693ad..59cda54c2 100644 --- a/Appraisals +++ b/Appraisals @@ -1,15 +1,53 @@ -appraise 'rails_3_2' do - gem 'rails', '~> 3.2.21' -end +appraise 'rails_4_2' do + gem 'rails', '~> 4.2.6' + gem 'mysql2', '~> 0.4.0', :platform => :ruby +end if RUBY_VERSION.to_f <= 2.4 -appraise 'rails_4_0' do - gem 'rails', '~> 4.0.12' -end +appraise 'rails_5_0' do + if RUBY_PLATFORM == "java" + gem 'rails', '5.0.6' + else + gem 'rails', '~> 5.0.7' + end -appraise 'rails_4_1' do - gem 'rails', '~> 4.1.8' -end + gem 'mysql2', '~> 0.4.0', :platform => :ruby -appraise 'rails_4_2' do - gem 'rails', '~> 4.2.0' -end + gem 'jdbc-mysql', '~> 5.1.36', :platform => :jruby + gem 'activerecord-jdbcmysql-adapter', '~> 50.0', :platform => :jruby + gem 'activerecord-jdbcpostgresql-adapter', '~> 50.0', :platform => :jruby +end if (RUBY_PLATFORM != "java" || ENV["SPHINX_VERSION"].to_f > 2.1) && RUBY_VERSION.to_f < 3.0 + +appraise 'rails_5_1' do + gem 'rails', '~> 5.1.0' + gem 'mysql2', '~> 0.4.0', :platform => :ruby +end if RUBY_PLATFORM != 'java' && RUBY_VERSION.to_f < 3.0 + +appraise 'rails_5_2' do + gem 'rails', '~> 5.2.0' + gem 'mysql2', '~> 0.5.0', :platform => :ruby + gem 'pg', '~> 1.0', :platform => :ruby +end if RUBY_PLATFORM != 'java' && RUBY_VERSION.to_f < 3.0 + +appraise 'rails_6_0' do + gem 'rails', '~> 6.0.0' + gem 'mysql2', '~> 0.5.0', :platform => :ruby + gem 'pg', '~> 1.0', :platform => :ruby +end if RUBY_PLATFORM != 'java' && RUBY_VERSION.to_f >= 2.5 + +appraise 'rails_6_1' do + gem 'rails', '~> 6.1.0' + gem 'mysql2', '~> 0.5.0', :platform => :ruby + gem 'pg', '~> 1.0', :platform => :ruby +end if RUBY_PLATFORM != 'java' && RUBY_VERSION.to_f >= 2.5 + +appraise 'rails_7_0' do + gem 'rails', '~> 7.0.0' + gem 'mysql2', '~> 0.5.0', :platform => :ruby + gem 'pg', '~> 1.0', :platform => :ruby +end if RUBY_PLATFORM != 'java' && RUBY_VERSION.to_f >= 2.7 + +appraise 'rails_7_1' do + gem 'rails', '~> 7.1.0' + gem 'mysql2', '~> 0.5.0', :platform => :ruby + gem 'pg', '~> 1.0', :platform => :ruby +end if RUBY_PLATFORM != 'java' && RUBY_VERSION.to_f >= 2.7 diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown new file mode 100644 index 000000000..5ed4d753a --- /dev/null +++ b/CHANGELOG.markdown @@ -0,0 +1,771 @@ +# Changelog + +All notable changes to this project (at least, from v3.0.0 onwards) are documented in this file. + +## 5.6.0 - 2024-07-07 + +### Added + +* Support for Manticore 6.0 ([#1242](https://github.com/pat/thinking-sphinx/pull/1242)) +* `sphinx`-prefixed search methods, in case the standard `search` is overridden from something unrelated. ([#1265](https://github.com/pat/thinking-sphinx/pull/1265)) +* `none` / `search_none` scopes that can be chained to searches and will return no results. +* Added `ThinkingSphinx::Processor#sync` to synchronise updates/deletions based on a real-time index's scope, by @akostadinov in [@1258](https://github.com/pat/thinking-sphinx/pull/1258). + +### Changed + +* Improved Rails 7.1 support, by @jdelstrother in [#1252](https://github.com/pat/thinking-sphinx/pull/1252). + +### Fixed + +* Handle both SQL and RT indices correctly for inheritance column checks, by @akostadinov in [#1249](https://github.com/pat/thinking-sphinx/pull/1249). +* Ensure tests and CI work with recent Manticore versions, by @jdelstrother in [#1263](https://github.com/pat/thinking-sphinx/pull/1263). +* Use `rm -rf` to delete test and temporary directories (instead of `rm -r`). + +## 5.5.1 - 2022-12-31 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v5.5.1) + +### Changed + +* Fixed total count of results in pagination information for Manticore 5.0+, by disabling the cutoff limit. ([#1239](https://github.com/pat/thinking-sphinx/pull/1239)). + +## 5.5.0 - 2022-12-30 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v5.5.0) + +### Added + +* ThinkingSphinx::Processor, a public interface to perform index-related operations on model instances or model name/id combinations. In collaboration with @akostadinov ([#1215](https://github.com/pat/thinking-sphinx/issues/1215)). + +### Changed + +* Confirmed support by testing against Ruby 3.1 and 3.2 by @jdelStrother ([#1237](https://github.com/pat/thinking-sphinx/pull/1237)). + +### Fixed + +* Fix YAML loading, by @aepyornis ([#1217](https://github.com/pat/thinking-sphinx/pull/1217)). +* Further fixes for File.exist? instead of the deprecated File.exists?, by @funsim ([#1221](https://github.com/pat/thinking-sphinx/pull/1221)) and @graaf ([1233](https://github.com/pat/thinking-sphinx/pull/1233)). +* Treat unknown column errors as QueryErrors, so retrying the query occurs automatically. +* Fix MariaDB error handling. + +## 5.4.0 - 2021-12-21 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v5.4.0) + +### Added + +* Rails 7 support, including contributions from @anthonyshull in [#1205](https://github.com/pat/thinking-sphinx/pull/1205). + +### Changed + +* Confirmed support by testing against Manticore 4.0 and Sphinx 3.4. + +### Fixed + +* Include instance_exec in ThinkingSphinx::Search::CORE_METHODS by @jdelStrother in [#1210](https://github.com/pat/thinking-sphinx/pull/1210). +* Use File.exist? instead of the deprecated File.exists? ([#1211](https://github.com/pat/thinking-sphinx/issues/1211)). + +## 5.3.0 - 2021-08-19 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v5.3.0) + +### Changed + +* StaleIdsExceptions now include a URL in their error message with recommendations on how to resolve the problem. +* Fire real-time callbacks on `after_commit` (including deletions) to ensure data is fully persisted to the database before updating Sphinx. More details in [#1204](https://github.com/pat/thinking-sphinx/pull/1204). + +### Fixed + +* Ensure Thinking Sphinx's ActiveRecord components are loaded by either Rails' after_initialise hook or ActiveSupport's on_load notification, because the order of these two events are not consistent. +* Remove `app/indices` from eager_load_paths in Rails 4.2 and 5, to match the behaviour in 6. + +## 5.2.1 - 2021-08-09 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v5.2.1) + +### Fixed + +* Ensure ActiveRecord components are loaded for rake tasks, but only after the Rails application has initialised. More details in [#1199](https://github.com/pat/thinking-sphinx/issues/1199). + +## 5.2.0 - 2021-06-12 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v5.2.0) + +### Added + +* Confirmed support for Ruby 3.0. +* Orphaned records in real-time indices can now be cleaned up without running `rails ts:rebuild`. Disabled by default, can be enabled by setting `real_time_tidy` to true per environment in `config/thinking_sphinx.yml` (and will need `ts:rebuild` to restructure indices upon initial deploy). More details in [#1192](https://github.com/pat/thinking-sphinx/pull/1192). + +### Fixed + +* Avoid loading ActiveRecord during Rails initialisation so app configuration can still have an impact ([@jdelStrother](https://github.com/jdelStrother) in [#1194](https://github.com/pat/thinking-sphinx/pull/1194)). +* Remove `app/indices` (in both the Rails app and engines) from Rails' eager load paths, which was otherwise leading to indices being loaded more than once. (See [#1191](https://github.com/pat/thinking-sphinx/issues/1191) and [#1195](https://github.com/pat/thinking-sphinx/issues/1195)). + +## 5.1.0 - 2020-12-28 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v5.1.0) + +### Added + +* Support for Sphinx v3.3 and Manticore v3.5. +* Support for Rails 6.1 (via [joiner](https://rubygems.org/gems/joiner) v0.6.0). + +### Changed + +* `enable_star` is no longer available as a configuration option, as it's been enabled by default in Sphinx since v2.2.2, and is no longer allowed in Sphinx v3.3.1. +* All timestamp attributes are now considered plain integer values from Sphinx's perspective. Sphinx was already expecting integers, but since Sphinx v3.3.1 it doesn't recognise timestamps as a data type. There is no functional difference with this change - Thinking Sphinx was always converting times to their UNIX epoch integer values. +* Allow configuration of the maximum statement length ([@kalsan](https://github.com/kalsan) in [#1179](https://github.com/pat/thinking-sphinx/pull/1179)). +* Respect `:path` values to navigate associations for Thinking Sphinx callbacks on SQL-backed indices. Discussed in [#1182](https://github.com/pat/thinking-sphinx/issues/1182). + +### Fixed + +* Don't attempt to update delta flags on frozen model instances. + +## 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 discussions 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) + +### Changed + +* Automatically remove `app/indices` from Zeitwerk's autoload paths in Rails 6.0 onwards (if using Zeitwerk as the autoloader). + +## 4.4.0 - 2019-08-21 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v4.4.0) + +### Added + +* Confirmed Rails 6.0 support. +* Added ability to have custom real-time index processors (which handles all indices) and populators (which handles a particular index). These are available to get/set via `ThinkingSphinx::RealTime.processor` and `ThinkingSphinx::RealTime.populator` (and discussed in more detail in the [release notes](https://github.com/pat/thinking-sphinx/releases/tag/v4.4.0)). + +### Changed + +* Improve failure message when tables don't exist for models associated with Sphinx indices ([Kiril Mitov](https://github.com/thebravoman) in [#1139](https://github.com/pat/thinking-sphinx/pull/1139)). + +### Fixed + +* Injected has-many/habtm collection search calls as default extensions to associations in Rails 5+, as it's a more reliable approach in Rails 6.0.0. + +## 4.3.2 - 2019-07-10 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v4.3.2) + +### Fixed + +* Reverted loading change behaviour from v4.3.1 for Rails v5 ([Eduardo J.](https://github.com/eduardoj) in [#1138](https://github.com/pat/thinking-sphinx/pull/1138)). + +## 4.3.1 - 2019-06-27 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v4.3.1) + +### Fixed + +* Fixed loading of index files to work with Rails 6 and Zeitwerk ([#1137](https://github.com/pat/thinking-sphinx/issues/1137)). + +## 4.3.0 - 2019-05-18 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v4.3.0) + +### Added + +* Allow overriding of Sphinx's running state, which is useful when Sphinx commands are interacting with a remote Sphinx daemon. As per discussions in [#1131](https://github.com/pat/thinking-sphinx/pull/1131). +* Allow skipping of directory creation, as per discussions in [#1131](https://github.com/pat/thinking-sphinx/pull/1131). + +### Fixed + +* Use ActiveSupport's lock monitor where possible (Rails 5.1.5 onwards) to avoid database deadlocks. Essential investigation by [Jonathan del Strother](https://github.com/jdelstrother) ([#1132](https://github.com/pat/thinking-sphinx/pull/1132)). +* Allow facet searching on distributed indices ([#1135](https://github.com/pat/thinking-sphinx/pull/1132)). + +## 4.2.0 - 2019-03-09 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v4.2.0) + +### Added + +* Allow changing the default encoding for MySQL database connections from utf8 to something else via the `mysql_encoding` setting in `config/thinking_sphinx.yml`. In the next significant release, the default will change to utf8mb4 (which is supported in MySQL 5.5.3 and newer). +* Added Rails 6.0 and Manticore 2.8 to the test matrix. + +### Changed + +* Use Arel's SQL literals for generated order clauses, to avoid warnings from Rails 6. + +### Fixed + +* Fix usage of alternative primary keys in update and deletion callbacks and attribute access. +* Ensure `respond_to?` takes Sphinx scopes into account ([Jonathan del Strother](https://github.com/jdelstrother) in [#1124](https://github.com/pat/thinking-sphinx/pull/1124)). +* Add `:excerpts` as a known option for search requests. +* Fix depolymorphed association join construction with Rails 6.0.0.beta2. +* Reset ThinkingSphinx::Configuration's cached values when Rails reloads, to avoid holding onto stale references to ActiveRecord models ([#1125](https://github.com/pat/thinking-sphinx/issues/1125)). +* Don't join against associations in `sql_query` if they're only used by query-sourced properties ([Hans de Graaff](https://github.com/graaff) in [#1127](https://github.com/pat/thinking-sphinx/pull/1127)). + +## 4.1.0 - 2018-12-28 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v4.1.0) + +### Added + +* The `:sql` search option can now accept per-model settings with model names as keys. e.g. `ThinkingSphinx.search "foo", :sql => {'Article' => {:include => :user}}` (Sergey Malykh in [#1120](https://github.com/pat/thinking-sphinx/pull/1120)). + +### Changed + +* Drop MRI 2.2 from the test matrix, and thus no longer officially supported (though the code will likely continue to work with 2.2 for a while). +* Added MRI 2.6, Sphinx 3.1 and Manticore 2.7 to the test matrix. + +### Fixed + +* Real-time indices now work with non-default integer primary keys (alongside UUIDs or other non-integer primary keys). + +## 4.0.0 - 2018-04-10 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v4.0.0) + +### Added + +* Support Sphinx 3.0. +* Allow disabling of docinfo setting via `skip_docinfo: true` in `config/thinking_sphinx.yml`. +* Support merging of delta indices into their core counterparts using ts:merge. +* Support UNIX sockets as an alternative for TCP connections to the daemon (MRI-only). +* Translate relative paths to absolute when generating configuration when `absolute_paths: true` is set per environment in `config/thinking_sphinx.yml`. + +### Changed + +* Drop Sphinx 2.0 support. +* Drop auto-typing of filter values. +* INDEX_FILTER environment variable is applied when running ts:index on SQL-backed indices. +* Drop MRI 2.0/2.1 support. +* Display a useful error message if processing real-time indices but the daemon isn't running. +* Refactor interface code into separate command classes, and allow for a custom rake interface. +* Add frozen_string_literal pragma comments. +* Log exceptions when processing real-time indices, but don't stop. +* Update polymorphic properties to support Rails 5.2. +* Allow configuration of the index guard approach. +* Output a warning if guard files exist when calling ts:index. +* Delete index guard files as part of ts:rebuild and ts:clear. + +### Fixed + +* Handle situations where no exit code is provided for Sphinx binary calls. +* Don't attempt to interpret indices for models that don't have a database table. + +## 3.4.2 - 2017-09-29 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.4.2) + +### Changed + +* Allow use of deletion callbacks for rollback events. +* Remove extra deletion code in the Populator - it's also being done by the real-time rake interface. + +### Fixed + +* Real-time callback syntax for namespaced models accepts a string (as documented). +* Fix up logged warnings. +* Add missing search options to known values to avoid incorrect warnings. + +## 3.4.1 - 2017-08-29 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.4.1) + +### Changed + +* Treat "Lost connection to MySQL server" as a connection error (Manuel Schnitzer). + +### Fixed + +* Index normalisation will now work even when index model tables don't exist. + +## 3.4.0 - 2017-08-28 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.4.0) + +### Added + +* Rake tasks are now unified, so the original tasks will operate on real-time indices as well. +* Output warnings when unknown options are used in search calls. +* Allow generation of a single real-time index (Tim Brown). +* Automatically use UTF8 in Sphinx for encodings that are extensions of UTF8. +* Basic type checking for attribute filters. + +### Changed + +* Delta callback logic now prioritises checking for high level settings rather than model changes. +* Allow for unsaved records when calculating document ids (and return nil). +* Display SphinxQL deletion statements in the log. +* Add support for Ruby's frozen string literals feature. +* Use saved_changes if it's available (in Rails 5.1+). +* Set a default connection timeout of 5 seconds. +* Don't search multi-table inheritance ancestors. +* Handle non-computable queries as parse errors. + +### Fixed + +* Index normalisation now occurs consistently, and removes unneccesary sphinx_internal_class_name fields from real-time indices. +* Fix Sphinx connections in JRuby. +* Fix long SphinxQL query handling in JRuby. +* Always close the SphinxQL connection if Innertube's asking (@cmaion). +* Get bigint primary keys working in Rails 5.1. +* Fix handling of attached starts of Sphinx (via Henne Vogelsang). +* Fix multi-field conditions. +* Use the base class of STI models for polymorphic join generation (via Andrés Cirugeda). +* Ensure ts:index now respects rake silent/quiet flags. + +## 3.3.0 - 2016-12-13 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.3.0) + +### Added + +* Real-time callbacks can now be used with after_commit hooks if that's preferred over after_save. +* Allow for custom batch sizes when populating real-time indices. + +### Changed + +* Only toggle the delta value if the record has changed or is new (rather than on every single save call). +* Delta indexing is now quiet by default (rather than verbose). +* Use Riddle's reworked command interface for interacting with Sphinx's command-line tools. +* Respect Rake's quiet and silent flags for the Thinking Sphinx rake tasks. +* ts:start and ts:stop tasks default to verbose. +* Sort engine paths for loading indices to ensure they're consistent. +* Custom exception class for invalid database adapters. +* Memoize the default primary keys per context. + +### Fixed + +* Explicit source method in the SQLQuery Builder instead of relying on method missing, thus avoiding any global methods named 'source' (Asaf Bartov). +* Load indices before deleting index files, to ensure the files are actually found and deleted. +* Avoid loading ActiveRecord earlier than necessary. This avoids loading Rails out of order, which caused problems with Rails 5. +* Handle queries that are too long for Sphinx. +* Improve Rails 5 / JRuby support. +* Fixed handling of multiple field tokens in wildcarding logic. +* Ensure custom primary key columns are handled consistently (Julio Monteiro). + +## 3.2.0 - 2016-05-13 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.2.0) + +### Added + +* Add JSON attribute support for real-time indices. +* Add ability to disable *all* Sphinx-related callbacks via ThinkingSphinx::Callbacks.suspend! and ThinkingSphinx::Callbacks.resume!. Particularly useful for unit tests. +* Add native OutOfBoundsError for search queries outside the pagination bounds. +* Support MySQL SSL options on a per-index level (@arrtchiu). +* Allow for different indexing strategies (e.g. all at once, or one by one). +* Allow rand_seed as a select option (Mattia Gheda). +* Add primary_key option for index definitions (Nathaneal Gray). +* Add ability to start searchd in the foreground (Andrey Novikov). + +### Changed + +* Improved error messages for duplicate property names and missing columns. +* Don't populate search results when requesting just the count values (Andrew Roth). +* Reset delta column before core indexing begins (reverting behaviour introduced in 3.1.0). See issue #958 for further discussion. +* Use Sphinx's bulk insert ability (Chance Downs). +* Reduce memory/object usage for model references (Jonathan del Strother). +* Disable deletion callbacks when real-time indices are in place and all other real-time callbacks are disabled. +* Only use ERB to parse the YAML file if ERB is loaded. + +### Fixed + +* Ensure SQL table aliases are reliable for SQL-backed index queries. +* Fixed mysql2 compatibility for memory references (Roman Usherenko). +* Fixed JRuby compatibility with camelCase method names (Brandon Dewitt). +* Fix stale id handling for multiple search contexts (Jonathan del Strother). +* Handle quoting of namespaced tables (Roman Usherenko). +* Make preload_indices thread-safe. +* Improved handling of marshalled/demarshalled search results. + +## 3.1.4 - 2015-06-01 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.1.4) + +### Added + +* Add JSON as a Sphinx type for attributes (Daniel Vandersluis). +* minimal_group_by? can now be set in config/thinking_sphinx.yml to automatically apply to all index definitions. + +### Changed + +* Add a contributor code of conduct. +* Remove polymorphic association and HABTM query support (when related to Thinking Sphinx) when ActiveRecord 3.2 is involved. +* Remove default charset_type - no longer required for Sphinx 2.2. +* Removing sql_query_info setting, as it's no longer used by Sphinx (nor is it actually used by Thinking Sphinx). + +### Fixed + +* Kaminari expects prev_page to be available. +* Don't try to delete guard files if they don't exist (@exAspArk). +* Handle database settings reliably, now that ActiveRecord 4.2 uses strings all the time. +* More consistent with escaping table names. +* Bug fix for association creation (with polymophic fields/attributes). + +## 3.1.3 - 2015-01-21 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.1.3) + +### Added + +* Allow for custom offset references with the :offset_as option - thus one model across many schemas with Apartment can be treated differently. +* Allow for custom IndexSet classes. + +### Changed + +* Log excerpt SphinxQL queries just like the search queries. +* Load Railtie if Rails::Railtie is defined, instead of just Rails (Andrew Cone). +* Convert raw Sphinx results to an array when querying (Bryan Ricker). +* Add bigint support for real-time indices, and use bigints for the sphinx_internal_id attribute (mapped to model primary keys) (Chance Downs). + +### Fixed + +* Generate de-polymorphised associations properly for Rails 4.2 +* Use reflect_on_association instead of reflections, to stick to the public ActiveRecord::Base API. +* Don't load ActiveRecord early - fixes a warning in Rails 4.2. +* Don't double-up on STI filtering, already handled by Rails. + +## 3.1.2 - 2014-11-04 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.1.2) + +### Added + +* Allow for custom paths for index files using :path option in the ThinkingSphinx::Index.define call. +* Allow the binlog path to be an empty string (Bobby Uhlenbrock). +* Add status task to report on whether Sphinx is running. +* Real-time index callbacks can take a block for dynamic scoping. +* Allow casting of document ids pre-offset as bigints (via big_documents_id option). + +### Changed + +* regenerate task now only deletes index files for real-time indices. +* Raise an exception when a populated search query is modified (as it can't be requeried). +* Log indices that aren't processed due to guard files existing. +* Paginate records by 1000 results at a time when flagging as deleted. +* Default the Capistrano TS Rails environment to use rails_env, and then fall back to stage. +* rebuild task uses clear between stopping the daemon and indexing. + +### Fixed + +* Ensure indexing guard files are removed when an exception is raised (Bobby Uhlenbrock). +* Don't update real-time indices for objects that are not persisted (Chance Downs). +* Use STI base class for polymorphic association replacements. +* Convert database setting keys to symbols for consistency with Rails (@dimko). +* Field weights and other search options are now respected from set_property. +* Models with more than one index have correct facet counts (using Sphinx 2.1.x or newer). +* Some association fixes for Rails 4.1. +* Clear connections when raising connection errors. + +## 3.1.1 - 2014-04-22 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.1.1) + +### Added + +* Allow for common section in generated Sphinx configuration files for Sphinx 2.2.x (disabled by default, though) (Trevor Smith). +* Basic support for HABTM associations and MVAs with query/ranged-query sources. +* Real-time indices callbacks can be disabled (useful for unit tests). +* ThinkingSphinx::Test has a clear method and no-index option for starting for real-time setups. +* Allow disabling of distributed indices. + +### Changed + +* Include full statements when query execution errors are raised (uglier, but more useful when debugging). +* Connection error messages now mention Sphinx, instead of just MySQL. +* Raise an exception when a referenced column does not exist. +* Capistrano tasks use thinking_sphinx_rails_env (defaults to standard environment) (Robert Coleman). +* Alias group and count columns for easier referencing in other clauses. +* Log real-time index updates (Demian Ferreiro). +* All indices now respond to a public attributes method. + +### Fixed + +* Don't apply attribute-only updates to real-time indices. +* Don't instantiate blank strings (via inheritance type columns) as constants. +* Don't presume all indices for a model have delta pairs, even if one does. +* Always use connection options for connection information. +* respond_to? works reliably with masks (Konstantin Burnaev). +* Avoid null values in MVA query/ranged-query sources. +* Don't send unicode null characters to real-time Sphinx indices. +* :populate option is now respected for single-model searches. +* :thinking_sphinx_roles is now used consistently in Capistrano v3 tasks. +* Only expand log directory if it exists. +* Handle JDBC connection errors appropriately (Adam Hutchison). +* Fixing wildcarding of Unicode strings. +* Improved handling of association searches with real-time indices, including via has_many :though associations (Rob Anderton). + +## 3.1.0 - 2014-01-11 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.1.0) + +### Added + +* Support for Capistrano v3 (Alexander Tipugin). +* JRuby support (with Sphinx 2.1 or newer). +* Support for Sphinx 2.2.x's HAVING and GROUP N BY SphinxQL options. +* Adding max_predicted_time search option (Sphinx 2.2.x). +* Wildcard/starring can be applied directly to strings using ThinkingSphinx::Query.wildcard('pancakes'), and escaping via ThinkingSphinx::Query.escape('pancakes'). +* Capistrano recipe now includes tasks for realtime indices. +* :group option within :sql options in a search call is passed through to the underlying ActiveRecord relation (Siarhei Hanchuk). +* Persistent connections can be disabled if you wish. +* Track what's being indexed, and don't double-up while indexing is running. Single indices (e.g. deltas) can be processed while a full index is happening, though. +* Pass through :delta_options to delta processors (Timo Virkalla). +* All delta records can have their core pairs marked as deleted after a suspended delta (use ThinkingSphinx::Deltas.suspend_and_update instead of ThinkingSphinx::Deltas.suspend). +* Set custom database settings within the index definition, using the set_database method. A more sane approach with multiple databases. + +### Changed + +* Updating Riddle requirement to >= 1.5.10. +* Extracting join generation into its own gem: Joiner. +* Geodist calculation is now prepended to the SELECT statement, so it can be referred to by other dynamic attributes. +* Auto-wildcard/starring (via :star => true) now treats escaped characters as word separators. +* Capistrano recipe no longer automatically adds thinking_sphinx:index and thinking_sphinx:start to be run after deploy:cold. +* UTF-8 forced encoding is now disabled by default (in line with Sphinx 2.1.x). +* Sphinx functions are now the default, instead of the legacy special variables (in line with Sphinx 2.1.x). +* Rails 3.1 is no longer supported. +* MRI 1.9.2 is no longer supported. +* Insist on at least * for SphinxQL SELECT statements. +* Reset the delta column to true after core indexing is completed, instead of before, and don't filter out delta records from the core source. +* Provide a distributed index per model that covers both core and delta indices. + +### Fixed + +* Indices will be detected in Rails engines upon configuration. +* Destroy callbacks are ignored for non-persisted objects. +* Blank STI values are converted to the parent class in Sphinx index data (Jonathan Greenberg). +* Track indices on parent STI models when marking documents as deleted. +* Separate per_page/max_matches values are respected in facet searches (Timo Virkkala). +* Don't split function calls when casting timestamps (Timo Virkalla). + +## 3.0.6 - 2013-10-20 + +[Release Notes](https://github.com/pat/thinking-sphinx/releases/tag/v3.0.6) + +### Added + +* Raise an error if no indices match the search criteria (Bryan Ricker). +* skip_time_zone setting is now available per environment via config/thinking_sphinx.yml to avoid the sql_query_pre time zone command. +* Added new search options in Sphinx 2.1.x. +* Added ability to disable UTF-8 forced encoding, now that Sphinx 2.1.2 returns UTF-8 strings by default. This will be disabled by default in Thinking Sphinx 3.1.0. +* Added ability to switch between Sphinx special variables and the equivalent functions. Sphinx 2.1.x requires the latter, and that behaviour will become the default in Sphinx 3.1.0. +* Adding search_for_ids on scoped search calls. +* MySQL users can enable a minimal GROUP BY statement, to speed up queries: set_property :minimal_group_by? => true. + +### Changed + +* Updating Riddle dependency to be >= 1.5.9. +* Separated directory preparation from data generation for real-time index (re)generation tasks. +* Have tests index UTF-8 characters where appropriate (Pedro Cunha). +* Always use DISTINCT in group concatenation. +* Sphinx connection failures now have their own class, ThinkingSphinx::ConnectionError, instead of the standard Mysql2::Error. +* Don't clobber custom :select options for facet searches (Timo Virkkala). +* Automatically load Riddle's Sphinx 2.0.5 compatability changes. +* Realtime fields and attributes now accept symbols as well as column objects, and fields can be sortable (with a _sort prefix for the matching attribute). +* Insist on the log directory existing, to ensure correct behaviour for symlinked paths. (Michael Pearson). +* Rake's silent mode is respected for indexing (@endoscient). + +### Fixed + +* Cast every column to a timestamp for timestamp attributes with multiple columns. +* Don't use Sphinx ordering if SQL order option is supplied to a search. +* Custom middleware and mask options now function correctly with model-scoped searches. +* Suspended deltas now no longer update core indices as well. +* Use alphabetical ordering for index paths consistently (@grin). +* Convert very small floats to fixed format for geo-searches. + +## 3.0.5 - 2013-08-26 + +### Added + +* Allow scoping of real-time index models. + +### Changed + +* Updating Riddle dependency to be >= 1.5.8. +* Real-time index population presentation and logic are now separated. +* Using the connection pool for update callbacks, excerpts, deletions. +* Don't add the sphinx_internal_class_name unless STI models are indexed. +* Use Mysql2's reconnect option and have it turned on by default. +* Improved auto-starring with escaped characters. + +### Fixed + +* Respect existing sql_query_range/sql_query_info settings. +* Don't add select clauses or joins to sql_query if they're for query/ranged-query properties. +* Set database timezones as part of the indexing process. +* Chaining scopes with just options works again. + +## 3.0.4 - 2013-07-09 + +### Added + +* ts:regenerate rake task for rebuilding Sphinx when realtime indices are involved. +* ts:clear task removes all Sphinx index and binlog files. +* Facet search calls now respect the limit option (which otherwise defaults to max_matches) (Demian Ferreiro). +* Excerpts words can be overwritten with the words option (@groe). +* The :facets option can be used in facet searches to limit which facets are queried. +* A separate role can be set for Sphinx actions with Capistrano (Andrey Chernih). +* Facet searches can now be called from Sphinx scopes. + +### Changed + +* Updating Riddle dependency to be >= 1.5.7. +* Glaze now responds to respond_to? (@groe). +* Deleted ActiveRecord objects are deleted in realtime indices as well. +* Realtime callbacks are no longer automatically added, but they're now more flexible (for association situations). +* Cleaning and refactoring so Code Climate ranks this as A-level code (Philip Arndt, Shevaun Coker, Garrett Heinlen). +* Exceptions raised when communicating with Sphinx are now mentioned in the logs when queries are retried (instead of STDOUT). +* Excerpts now use just the query and standard conditions, instead of parsing Sphinx's keyword metadata (which had model names in it). +* Get database connection details from ActiveRecord::Base, not each model, as this is where changes are reflected. +* Default Sphinx scopes are applied to new facet searches. + +### Fixed + +* Empty queries with the star option set to true are handled gracefully. +* Excerpts are now wildcard-friendly. +* Facet searches now use max_matches value (with a default of 1000) to ensure as many results as possible are returned. +* The settings cache is now cleared when the configuration singleton is reset (Pedro Cunha). +* Escaped @'s in queries are considered part of each word, instead of word separators. +* Internal class name conditions are ignored with auto-starred queries. +* RDoc doesn't like constant hierarchies split over multiple lines. + +## 3.0.3 - 2013-05-07 + +### Added + +* INDEX_ONLY environment flag is passed through when invoked through Capistrano (Demian Ferreiro). +* use_64_bit option returns as cast_to_timestamp instead (Denis Abushaev). +* Collection of hooks (lambdas) that get called before indexing. Useful for delta libraries. + +### Changed + +* Updating Riddle dependency to be >= 1.5.6 +* Delta jobs get common classes to allow third-party delta behaviours to leverage Thinking Sphinx. +* Raise ThinkingSphinx::MixedScopesError if a search is called through an ActiveRecord scope. +* GroupEnumeratorsMask is now a default mask, as masks need to be in place before search results are populated/the middleware is called (and previously it was being added within a middleware call). +* The current_page method is now a part of ThinkingSphinx::Search, as it is used when populating results. + +### Fixed + +* Update to association handling for Rails/ActiveRecord 4.0.0.rc1. +* Cast and concatenate multi-column attributes correctly. +* Don't load fields or attributes when building a real-time index - otherwise the index is translated before it has a chance to be built. +* Default search panes are cloned for each search. +* Index-level settings (via set_property) are now applied consistently after global settings (in thinking_sphinx.yml). +* All string values returned from Sphinx are now properly converted to UTF8. +* The default search masks are now cloned for each search, instead of referring to the constant (and potentially modifying it often). + +## 3.0.2 - 2013-03-23 + +### Added + +* Ruby 2.0 support. +* Rails 4.0.0 beta1 support. +* Indexes defined in app/indices in engines are now loaded (Antonio Tapiador del Dujo). +* Query errors are classified as such, instead of getting the base SphinxError. + +### Changed + +* per_page now accepts an optional paging limit, to match WillPaginate's behaviour. If none is supplied, it just returns the page size. +* Strings and regular expressions in ThinkingSphinx::Search::Query are now treated as UTF-8. +* Setting a custom framework will rebuild the core configuration around its provided settings (path and environment). +* Search masks don't rely on respond_to?, and so Object/Kernel methods are passed through to the underlying array instead. +* Empty search conditions are now ignored, instead of being appended with no value (Nicholas Klick). +* Custom conditions are no longer added to the sql_query_range value, as they may involve associations. + +### Fixed + +* :utf8? option within index definitions is now supported, and defaults to true if the database configuration's encoding is set to 'utf8'. +* indices_location and configuration_file values in thinking_sphinx.yml will be applied to the configuration. +* Primary keys that are not 'id' now work correctly. +* Search options specified in index definitions and thinking_sphinx.yml are now used in search requests (eg: max_matches, field_weights). +* Custom association conditions are no longer presumed to be an array. +* Capistrano tasks use the correct ts rake task prefix (David Celis). + +## 3.0.1 - 2013-02-04 + +### Added + +* Provide Capistrano deployment tasks (David Celis). +* Allow specifying of Sphinx version. Is only useful for Flying Sphinx purposes at this point - has no impact on Riddle or Sphinx. +* Support new JDBC configuration style (when JDBC can be used) (Kyle Stevens). +* Mysql2::Errors are wrapped as ThinkingSphinx::SphinxErrors, with subclasses of SyntaxError and ParseError used appropriately. Syntax and parse errors do not prompt a retry on a new connection. +* Polymorphic associations can be used within index definitions when the appropriate classes are set out. +* Allow custom strings for SQL joins in index definitions. +* indexer and searchd settings are added to the appropriate objects from config/thinking_sphinx.yml (@ygelfand). + +### Changed + +* Use connection pool for search queries. If a query fails, it will be retried on a new connection before raising if necessary. +* Glaze always passes methods through to the underlying ActiveRecord::Base object if they don't exist on any of the panes. + +### Fixed + +* Referring to associations via polymorphic associations in an index definition now works. +* Don't override foreign keys for polymorphic association replacements. +* Quote namespaced model names in class field condition. +* New lines are maintained and escaped in custom source queries. +* Subclasses of indexed models fire delta callbacks properly. +* Thinking Sphinx can be loaded via thinking/sphinx, to satisfy Bundler. +* New lines are maintained and escaped in sql_query values. + +## 3.0.0 - 2013-01-02 + +### Added + +* Initial realtime index support, including the ts:generate task for building index datasets. Sphinx 2.0.6 is required. +* SphinxQL connection pooling via the Innertube gem. + +### Changed + +* Updating Riddle dependency to 1.5.4. +* UTF-8 is now the default charset again (as it was in earlier Thinking Sphinx versions). +* Removing ts:version rake task. + +### Fixed + +* Respect source options as well as underlying settings via the set_property method in index definitions. +* Load real-time index definitions when listing fields, attributes, and/or conditions. + +## 3.0.0.rc - 2012-12-22 + +### Added + +* Source type support (query and ranged query) for both attributes and fields. Custom SQL strings can be supplied as well. +* Wordcount attributes and fields now supported. +* Support for Sinatra and other non-Rails frameworks. +* A sphinx scope can be defined as the default. +* An index can have multiple sources, by using define_source within the index definition. +* sanitize_sql is available within an index definition. +* Providing :prefixes => true or :infixes => true as an option when declaring a field means just the noted fields have infixes/prefixes applied. +* ThinkingSphinx::Search#query_time returns the time Sphinx took to make the query. +* Namespaced model support. +* Default settings for index definition arguments can be set in config/thinking_sphinx.yml. +* A custom Riddle/Sphinx controller can be supplied. Useful for Flying Sphinx to have an API layer over Sphinx commands, without needing custom gems for different Thinking Sphinx/Flying Sphinx combinations. + +### Fixed + +* Correctly escape nulls in inheritance column (Darcy Laycock). +* Use ThinkingSphinx::Configuration#render_to_file instead of ThinkingSphinx::Configuration#build in test helpers (Darcy Laycock). +* Suppressing delta output in test helpers now works (Darcy Laycock). + +## 3.0.0.pre - 2012-10-06 + +First pre-release of v3. Not quite feature complete, but the important stuff is certainly covered. See the README for more the finer details. diff --git a/Gemfile b/Gemfile index 851e7be82..212c9e950 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,17 @@ +# frozen_string_literal: true + source 'https://rubygems.org' gemspec -gem 'mysql2', '~> 0.3.12b4', :platform => :ruby -gem 'pg', '~> 0.16.0', :platform => :ruby +gem 'mysql2', '~> 0.5.0', :platform => :ruby +gem 'pg', '~> 0.18.4', :platform => :ruby + +gem 'activerecord', '< 7' if RUBY_VERSION.to_f <= 2.4 -gem 'activerecord-jdbcmysql-adapter', '~> 1.3.4', :platform => :jruby -gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3.4', :platform => :jruby +if RUBY_PLATFORM == 'java' + gem 'jdbc-mysql', '5.1.35', :platform => :jruby + gem 'activerecord-jdbcmysql-adapter', '>= 1.3.23', :platform => :jruby + gem 'activerecord-jdbcpostgresql-adapter', '>= 1.3.23', :platform => :jruby + gem 'activerecord', '>= 3.2.22' +end diff --git a/HISTORY b/HISTORY deleted file mode 100644 index e1d552b9e..000000000 --- a/HISTORY +++ /dev/null @@ -1,235 +0,0 @@ -2015-01-21: 3.1.3 -* [CHANGE] Log excerpt SphinxQL queries just like the search queries. -* [CHANGE] Load Railtie if Rails::Railtie is defined, instead of just Rails (Andrew Cone). -* [CHANGE] Convert raw Sphinx results to an array when querying (Bryan Ricker). -* [FIX] Generate de-polymorphised associations properly for Rails 4.2 -* [FIX] Use reflect_on_association instead of reflections, to stick to the public ActiveRecord::Base API. -* [FIX] Don't load ActiveRecord early - fixes a warning in Rails 4.2. -* [FEATURE] Allow for custom offset references with the :offset_as option - thus one model across many schemas with Apartment can be treated differently. -* [FEATURE] Allow for custom IndexSet classes. -* [FIX] Don't double-up on STI filtering, already handled by Rails. -* [CHANGE] Add bigint support for real-time indices, and use bigints for the sphinx_internal_id attribute (mapped to model primary keys) (Chance Downs). - -2014-11-04: 3.1.2 -* [CHANGE] regenerate task now only deletes index files for real-time indices. -* [CHANGE] Raise an exception when a populated search query is modified (as it can't be requeried). -* [FEATURE] Allow for custom paths for index files using :path option in the ThinkingSphinx::Index.define call. -* [FIX] Ensure indexing guard files are removed when an exception is raised (Bobby Uhlenbrock). -* [FIX] Don't update real-time indices for objects that are not persisted (Chance Downs). -* [FEATURE] Allow the binlog path to be an empty string (Bobby Uhlenbrock). -* [FIX] Use STI base class for polymorphic association replacements. -* [FIX] Convert database setting keys to symbols for consistency with Rails (@dimko). -* [FIX] Field weights and other search options are now respected from set_property. -* [CHANGE] Log indices that aren't processed due to guard files existing. -* [FEATURE] Add status task to report on whether Sphinx is running. -* [FIX] Models with more than one index have correct facet counts (using Sphinx 2.1.x or newer). -* [FEATURE] Real-time index callbacks can take a block for dynamic scoping. -* [FIX] Some association fixes for Rails 4.1. -* [CHANGE] Paginate records by 1000 results at a time when flagging as deleted. -* [CHANGE] Default the Capistrano TS Rails environment to use rails_env, and then fall back to stage. -* [CHANGE] rebuild task uses clear between stopping the daemon and indexing. -* [FIX] Clear connections when raising connection errors. -* [FEATURE] Allow casting of document ids pre-offset as bigints (via big_documents_id option). - -2014-04-22: 3.1.1 -* [CHANGE] Include full statements when query execution errors are raised (uglier, but more useful when debugging). -* [FEATURE] Allow for common section in generated Sphinx configuration files for Sphinx 2.2.x (disabled by default, though) (Trevor Smith). -* [FEATURE] Basic support for HABTM associations and MVAs with query/ranged-query sources. -* [CHANGE] Connection error messages now mention Sphinx, instead of just MySQL. -* [FIX] Don't apply attribute-only updates to real-time indices. -* [FIX] Don't instantiate blank strings (via inheritance type columns) as constants. -* [FIX] Don't presume all indices for a model have delta pairs, even if one does. -* [CHANGE] Raise an exception when a referenced column does not exist. -* [CHANGE] Capistrano tasks use thinking_sphinx_rails_env (defaults to standard environment) (Robert Coleman). -* [FIX] Always use connection options for connection information. -* [FIX] respond_to? works reliably with masks (Konstantin Burnaev). -* [FEATURE] Real-time indices callbacks can be disabled (useful for unit tests). -* [FEATURE] ThinkingSphinx::Test has a clear method and no-index option for starting for real-time setups. -* [FIX] Avoid null values in MVA query/ranged-query sources. -* [CHANGE] Alias group and count columns for easier referencing in other clauses. -* [FEATURE] Allow disabling of distributed indices. -* [FIX] Don't send unicode null characters to real-time Sphinx indices. -* [FIX] :populate option is now respected for single-model searches. -* [FIX] :thinking_sphinx_roles is now used consistently in Capistrano v3 tasks. -* [CHANGE] Log real-time index updates (Demian Ferreiro). -* [FIX] Only expand log directory if it exists. -* [FIX] Handle JDBC connection errors appropriately (Adam Hutchison). -* [FIX] Fixing wildcarding of Unicode strings. -* [CHANGE] All indices now respond to a public attributes method. -* [FIX] Improved handling of association searches with real-time indices, including via has_many :though associations (Rob Anderton). - -2014-01-11: 3.1.0 -* [CHANGE] Updating Riddle requirement to >= 1.5.10. -* [CHANGE] Extracting join generation into its own gem: Joiner. -* [FEATURE] Support for Capistrano v3 (Alexander Tipugin). -* [FEATURE] JRuby support (with Sphinx 2.1 or newer). -* [CHANGE] Geodist calculation is now prepended to the SELECT statement, so it can be referred to by other dynamic attributes. -* [FIX] Indices will be detected in Rails engines upon configuration. -* [FEATURE] Support for Sphinx 2.2.x's HAVING and GROUP N BY SphinxQL options. -* [FEATURE] Adding max_predicted_time search option (Sphinx 2.2.x). -* [FEATURE] Wildcard/starring can be applied directly to strings using ThinkingSphinx::Query.wildcard('pancakes'), and escaping via ThinkingSphinx::Query.escape('pancakes'). -* [CHANGE] Auto-wildcard/starring (via :star => true) now treats escaped characters as word separators. -* [FEATURE] Capistrano recipe now includes tasks for realtime indices. -* [CHANGE] Capistrano recipe no longer automatically adds thinking_sphinx:index and thinking_sphinx:start to be run after deploy:cold. -* [CHANGE] UTF-8 forced encoding is now disabled by default (in line with Sphinx 2.1.x). -* [CHANGE] Sphinx functions are now the default, instead of the legacy special variables (in line with Sphinx 2.1.x). -* [CHANGE] Rails 3.1 is no longer supported. -* [CHANGE] MRI 1.9.2 is no longer supported. -* [FIX] Destroy callbacks are ignored for non-persisted objects. -* [FEATURE] :group option within :sql options in a search call is passed through to the underlying ActiveRecord relation (Siarhei Hanchuk). -* [FIX] Blank STI values are converted to the parent class in Sphinx index data (Jonathan Greenberg). -* [CHANGE] Insist on at least * for SphinxQL SELECT statements. -* [FIX] Track indices on parent STI models when marking documents as deleted. -* [FEATURE] Persistent connections can be disabled if you wish. -* [FIX] Separate per_page/max_matches values are respected in facet searches (Timo Virkkala). -* [FIX] Don't split function calls when casting timestamps (Timo Virkalla). -* [FEATURE] Track what's being indexed, and don't double-up while indexing is running. Single indices (e.g. deltas) can be processed while a full index is happening, though. -* [FEATURE] Pass through :delta_options to delta processors (Timo Virkalla). -* [FEATURE] All delta records can have their core pairs marked as deleted after a suspended delta (use ThinkingSphinx::Deltas.suspend_and_update instead of ThinkingSphinx::Deltas.suspend). -* [CHANGE] Reset the delta column to true after core indexing is completed, instead of before, and don't filter out delta records from the core source. -* [FEATURE] Set custom database settings within the index definition, using the set_database method. A more sane approach with multiple databases. -* [CHANGE] Provide a distributed index per model that covers both core and delta indices. - -2013-10-20: 3.0.6 -* [FEATURE] Raise an error if no indices match the search criteria (Bryan Ricker). -* [FEATURE] skip_time_zone setting is now available per environment via config/thinking_sphinx.yml to avoid the sql_query_pre time zone command. -* [CHANGE] Updating Riddle dependency to be >= 1.5.9. -* [FEATURE] Added new search options in Sphinx 2.1.x. -* [FEATURE] Added ability to disable UTF-8 forced encoding, now that Sphinx 2.1.2 returns UTF-8 strings by default. This will be disabled by default in Thinking Sphinx 3.1.0. -* [FEATURE] Added ability to switch between Sphinx special variables and the equivalent functions. Sphinx 2.1.x requires the latter, and that behaviour will become the default in Sphinx 3.1.0. -* [FIX] Cast every column to a timestamp for timestamp attributes with multiple columns. -* [CHANGE] Separated directory preparation from data generation for real-time index (re)generation tasks. -* [CHANGE] Have tests index UTF-8 characters where appropriate (Pedro Cunha). -* [FIX] Don't use Sphinx ordering if SQL order option is supplied to a search. -* [CHANGE] Always use DISTINCT in group concatenation. -* [CHANGE] Sphinx connection failures now have their own class, ThinkingSphinx::ConnectionError, instead of the standard Mysql2::Error. -* [FIX] Custom middleware and mask options now function correctly with model-scoped searches. -* [FEATURE] Adding search_for_ids on scoped search calls. -* [CHANGE] Don't clobber custom :select options for facet searches (Timo Virkkala). -* [CHANGE] Automatically load Riddle's Sphinx 2.0.5 compatability changes. -* [FIX] Suspended deltas now no longer update core indices as well. -* [CHANGE] Realtime fields and attributes now accept symbols as well as column objects, and fields can be sortable (with a _sort prefix for the matching attribute). -* [FEATURE] MySQL users can enable a minimal GROUP BY statement, to speed up queries: set_property :minimal_group_by? => true. -* [CHANGE] Insist on the log directory existing, to ensure correct behaviour for symlinked paths. (Michael Pearson). -* [FIX] Use alphabetical ordering for index paths consistently (@grin). -* [FIX] Convert very small floats to fixed format for geo-searches. -* [CHANGE] Rake's silent mode is respected for indexing (@endoscient). - -2013-08-26: 3.0.5 -* [CHANGE] Updating Riddle dependency to be >= 1.5.8. -* [FEATURE] Allow scoping of real-time index models. -* [CHANGE] Real-time index population presentation and logic are now separated. -* [CHANGE] Using the connection pool for update callbacks, excerpts, deletions. -* [FIX] Respect existing sql_query_range/sql_query_info settings. -* [CHANGE] Don't add the sphinx_internal_class_name unless STI models are indexed. -* [FIX] Don't add select clauses or joins to sql_query if they're for query/ranged-query properties. -* [CHANGE] Use Mysql2's reconnect option and have it turned on by default. -* [FIX] Set database timezones as part of the indexing process. -* [CHANGE] Improved auto-starring with escaped characters. -* [FIX] Chaining scopes with just options works again. - -2013-07-09: 3.0.4 -* [CHANGE] Updating Riddle dependency to be >= 1.5.7. -* [FEATURE] ts:regenerate rake task for rebuilding Sphinx when realtime indices are involved. -* [FEATURE] ts:clear task removes all Sphinx index and binlog files. -* [CHANGE] Glaze now responds to respond_to? (@groe). -* [FEATURE] Facet search calls now respect the limit option (which otherwise defaults to max_matches) (Demian Ferreiro). -* [FEATURE] Excerpts words can be overwritten with the words option (@groe). -* [FIX] Empty queries with the star option set to true are handled gracefully. -* [CHANGE] Deleted ActiveRecord objects are deleted in realtime indices as well. -* [CHANGE] Realtime callbacks are no longer automatically added, but they're now more flexible (for association situations). -* [CHANGE] Cleaning and refactoring so Code Climate ranks this as A-level code (Philip Arndt, Shevaun Coker, Garrett Heinlen). -* [FIX] Excerpts are now wildcard-friendly. -* [FIX] Facet searches now use max_matches value (with a default of 1000) to ensure as many results as possible are returned. -* [CHANGE] Exceptions raised when communicating with Sphinx are now mentioned in the logs when queries are retried (instead of STDOUT). -* [CHANGE] Excerpts now use just the query and standard conditions, instead of parsing Sphinx's keyword metadata (which had model names in it). -* [FIX] The settings cache is now cleared when the configuration singleton is reset (Pedro Cunha). -* [FEATURE] The :facets option can be used in facet searches to limit which facets are queried. -* [FIX] Escaped @'s in queries are considered part of each word, instead of word separators. -* [FIX] Internal class name conditions are ignored with auto-starred queries. -* [FEATURE] A separate role can be set for Sphinx actions with Capistrano (Andrey Chernih). -* [FIX] RDoc doesn't like constant hierarchies split over multiple lines. -* [CHANGE] Get database connection details from ActiveRecord::Base, not each model, as this is where changes are reflected. -* [CHANGE] Default Sphinx scopes are applied to new facet searches. -* [FEATURE] Facet searches can now be called from Sphinx scopes. - -2013-05-07: 3.0.3 -* [CHANGE] Updating Riddle dependency to be >= 1.5.6 -* [FEATURE] INDEX_ONLY environment flag is passed through when invoked through Capistrano (Demian Ferreiro). -* [FEATURE] use_64_bit option returns as cast_to_timestamp instead (Denis Abushaev). -* [FIX] Update to association handling for Rails/ActiveRecord 4.0.0.rc1. -* [CHANGE] Delta jobs get common classes to allow third-party delta behaviours to leverage Thinking Sphinx. -* [FEATURE] Collection of hooks (lambdas) that get called before indexing. Useful for delta libraries. -* [FIX] Cast and concatenate multi-column attributes correctly. -* [FIX] Don't load fields or attributes when building a real-time index - otherwise the index is translated before it has a chance to be built. -* [CHANGE] Raise ThinkingSphinx::MixedScopesError if a search is called through an ActiveRecord scope. -* [FIX] Default search panes are cloned for each search. -* [FIX] Index-level settings (via set_property) are now applied consistently after global settings (in thinking_sphinx.yml). -* [FIX] All string values returned from Sphinx are now properly converted to UTF8. -* [CHANGE] GroupEnumeratorsMask is now a default mask, as masks need to be in place before search results are populated/the middleware is called (and previously it was being added within a middleware call). -* [FIX] The default search masks are now cloned for each search, instead of referring to the constant (and potentially modifying it often). -* [CHANGE] The current_page method is now a part of ThinkingSphinx::Search, as it is used when populating results. - -2013-03-23: 3.0.2 -* [CHANGE] per_page now accepts an optional paging limit, to match WillPaginate's behaviour. If none is supplied, it just returns the page size. -* [FEATURE] Ruby 2.0 support. -* [FEATURE] Rails 4.0.0 beta1 support. -* [FIX] :utf8? option within index definitions is now supported, and defaults to true if the database configuration's encoding is set to 'utf8'. -* [FIX] indices_location and configuration_file values in thinking_sphinx.yml will be applied to the configuration. -* [CHANGE] Strings and regular expressions in ThinkingSphinx::Search::Query are now treated as UTF-8. -* [FIX] Primary keys that are not 'id' now work correctly. -* [CHANGE] Setting a custom framework will rebuild the core configuration around its provided settings (path and environment). -* [CHANGE] Search masks don't rely on respond_to?, and so Object/Kernel methods are passed through to the underlying array instead. -* [FIX] Search options specified in index definitions and thinking_sphinx.yml are now used in search requests (eg: max_matches, field_weights). -* [FEATURE] Indexes defined in app/indices in engines are now loaded (Antonio Tapiador del Dujo). -* [FIX] Custom association conditions are no longer presumed to be an array. -* [CHANGE] Empty search conditions are now ignored, instead of being appended with no value (Nicholas Klick). -* [CHANGE] Custom conditions are no longer added to the sql_query_range value, as they may involve associations. -* [FIX] Capistrano tasks use the correct ts rake task prefix (David Celis). -* [FEATURE] Query errors are classified as such, instead of getting the base SphinxError. - -2013-02-04: 3.0.1 -* [FEATURE] Provide Capistrano deployment tasks (David Celis). -* [FEATURE] Allow specifying of Sphinx version. Is only useful for Flying Sphinx purposes at this point - has no impact on Riddle or Sphinx. -* [FEATURE] Support new JDBC configuration style (when JDBC can be used) (Kyle Stevens). -* [FIX] Referring to associations via polymorphic associations in an index definition now works. -* [FEATURE] Mysql2::Errors are wrapped as ThinkingSphinx::SphinxErrors, with subclasses of SyntaxError and ParseError used appropriately. Syntax and parse errors do not prompt a retry on a new connection. -* [CHANGE] Use connection pool for search queries. If a query fails, it will be retried on a new connection before raising if necessary. -* [CHANGE] Glaze always passes methods through to the underlying ActiveRecord::Base object if they don't exist on any of the panes. -* [FIX] Don't override foreign keys for polymorphic association replacements. -* [FIX] Quote namespaced model names in class field condition. -* [FEATURE] Polymorphic associations can be used within index definitions when the appropriate classes are set out. -* [FEATURE] Allow custom strings for SQL joins in index definitions. -* [FIX] New lines are maintained and escaped in custom source queries. -* [FIX] Subclasses of indexed models fire delta callbacks properly. -* [FIX] Thinking Sphinx can be loaded via thinking/sphinx, to satisfy Bundler. -* [FEATURE] indexer and searchd settings are added to the appropriate objects from config/thinking_sphinx.yml (@ygelfand). -* [FIX] New lines are maintained and escaped in sql_query values. - -2013-01-02: 3.0.0 -* [CHANGE] Updating Riddle dependency to 1.5.4. -* [FIX] Respect source options as well as underlying settings via the set_property method in index definitions. -* [FIX] Load real-time index definitions when listing fields, attributes, and/or conditions. -* [CHANGE] UTF-8 is now the default charset again (as it was in earlier Thinking Sphinx versions). -* [FEATURE] Initial realtime index support, including the ts:generate task for building index datasets. Sphinx 2.0.6 is required. -* [CHANGE] Removing ts:version rake task. -* [FEATURE] SphinxQL connection pooling via the Innertube gem. - -2012-12-22: 3.0.0.rc -* [FEATURE] Source type support (query and ranged query) for both attributes and fields. Custom SQL strings can be supplied as well. -* [FEATURE] Wordcount attributes and fields now supported. -* [FEATURE] Support for Sinatra and other non-Rails frameworks. -* [FEATURE] A sphinx scope can be defined as the default. -* [FEATURE] An index can have multiple sources, by using define_source within the index definition. -* [FEATURE] sanitize_sql is available within an index definition. -* [FEATURE] Providing :prefixes => true or :infixes => true as an option when declaring a field means just the noted fields have infixes/prefixes applied. -* [FEATURE] ThinkingSphinx::Search#query_time returns the time Sphinx took to make the query. -* [FEATURE] Namespaced model support. -* [FEATURE] Default settings for index definition arguments can be set in config/thinking_sphinx.yml. -* [FIX] Correctly escape nulls in inheritance column (Darcy Laycock). -* [FIX] Use ThinkingSphinx::Configuration#render_to_file instead of ThinkingSphinx::Configuration#build in test helpers (Darcy Laycock). -* [FIX] Suppressing delta output in test helpers now works (Darcy Laycock). -* [FEATURE] A custom Riddle/Sphinx controller can be supplied. Useful for Flying Sphinx to have an API layer over Sphinx commands, without needing custom gems for different Thinking Sphinx/Flying Sphinx combinations. - -2012-10-06: 3.0.0.pre -* First pre-release. Not quite feature complete, but the important stuff is certainly covered. See the README for more the finer details. diff --git a/Procfile.support b/Procfile.support new file mode 100644 index 000000000..2bb81ac45 --- /dev/null +++ b/Procfile.support @@ -0,0 +1,2 @@ +postgres: postgres -D data/postgres -p ${POSTGRES_PORT:-5432} +mysql: $(brew --prefix mysql@5.7)/bin/mysqld --datadir=$(PWD)/data/mysql --port ${MYSQL_PORT:-3306} --socket=mysql.sock diff --git a/README.textile b/README.textile index e2a8c16bd..b24088cb0 100644 --- a/README.textile +++ b/README.textile @@ -1,56 +1,58 @@ 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.3. +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.6.0. h2. Upgrading -Please refer to the release notes for any changes you need to make when 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. -* "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 -* "v3.1.1":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.1 -* "v3.1.0":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.0 -* "v3.0.6":https://github.com/pat/thinking-sphinx/releases/tag/v3.0.6 - -If you're upgrading from pre-v3, then the documentation has "pretty extensive notes":http://pat.github.io/thinking-sphinx/upgrading.html on what's changed. +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.13', :platform => :ruby
-gem 'jdbc-mysql', '~> 5.1.28', :platform => :jruby
-gem 'thinking-sphinx', '~> 3.1.3'
+gem 'mysql2', '~> 0.4', :platform => :ruby
+gem 'jdbc-mysql', '~> 5.1.35', :platform => :jruby
+gem 'thinking-sphinx', '~> 5.5'
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":http://pat.github.io/thinking-sphinx/installing_sphinx.html.
+You'll also need to install Sphinx - this is covered in "the extended documentation":https://freelancing-gods.com/thinking-sphinx/installing_sphinx.html.
h2. Usage
-Begin by reading the "quick-start guide":http://pat.github.io/thinking-sphinx/quickstart.html, and beyond that, "the documentation":http://pat.github.io/thinking-sphinx/ should serve you pretty well.
+Begin by reading the "quick-start guide":https://freelancing-gods.com/thinking-sphinx/quickstart.html, and beyond that, "the documentation":https://freelancing-gods.com/thinking-sphinx/ should serve you pretty well.
+
+h2. Requirements
-h3. Extending with Middleware, Glazes and Panes
+The current release of Thinking Sphinx works with the following versions of its dependencies:
-These are covered in "a blog post":http://freelancing-gods.com/posts/rewriting_thinking_sphinx_middleware_glazes_and_panes.
+|_. Library |_. Minimum |_. Tested Against |
+| Ruby | v2.4 | v2.4, v2.5, v2.6, v2.7, v3.0, v3.1, v3.2 |
+| Sphinx | v2.2.11 | v2.2.11, v3.4.1 |
+| Manticore | v2.8 | v4.0, v6.0 |
+| ActiveRecord | v4.2 | v4.2..v7.0 |
-h2. Requirements
+It _might_ work with older versions of Ruby, but it's highly recommended to update to a supported release.
-h3. Sphinx
+It should also work with JRuby, but the test environment for that in CI has been unreliable, hence that's not actively tested against at the moment.
-Thinking Sphinx v3 is currently built for Sphinx 2.0.5 or newer, and releases since v3.1.0 expect Sphinx 2.1.2 or newer by default.
+h3. Sphinx or Manticore
+
+If you're using Sphinx, v2.2.11 is recommended even though it's quite old, as it works well with PostgreSQL databases (but if you're using MySQL - or real-time indices - then v3.3.1 should also be fine).
+
+If you're opting for Manticore instead, v2.8 or newer works, but v4 or newer is recommended as that's what is actively tested against. The v4.2 and 5.0 releases had bugs with facet searching, but that's been fixed in Manticore v6.0.
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.
-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 (v1.9.3 or newer) or JRuby (1.7.9 or newer). I'm open to patches to improve Rubinius support (if required - it may work with it right now).
-
-JRuby is only supported as of Thinking Sphinx v3.1.0, and requires Sphinx 2.1.2 or newer.
+You'll need either the standard Ruby (v2.4 or newer) or JRuby (9.1 or newer).
h3. Database Versions
@@ -58,6 +60,8 @@ MySQL 5.x and Postgres 8.4 or better are supported.
h2. Contributing
+Please note that this project has a "Contributor Code of Conduct":http://contributor-covenant.org/version/1/0/0/. By participating in this project you agree to abide by its terms.
+
To contribute, clone this repository and have a good look through the specs - you'll notice the distinction between acceptance tests that actually use Sphinx and go through the full stack, and unit tests (everything else) which use liberal test doubles to ensure they're only testing the behaviour of the class in question. I've found this leads to far better code design.
All development is done on the @develop@ branch; please base any pull requests off of that branch. Please write the tests and then the code to get them passing, and send through a pull request.
@@ -77,4 +81,4 @@ You can then run the unit tests with @rake spec:unit@, the acceptance tests with
h2. Licence
-Copyright (c) 2007-2015, 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-2024, 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/Rakefile b/Rakefile
index 4c9988551..7df04a89b 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'bundler'
require 'appraisal'
diff --git a/bin/console b/bin/console
new file mode 100755
index 000000000..4999689b1
--- /dev/null
+++ b/bin/console
@@ -0,0 +1,15 @@
+#! /usr/bin/env ruby
+# frozen_string_literal: true
+
+require "bundler/setup"
+require "thinking_sphinx"
+
+# You can add fixtures and/or initialization code here to make experimenting
+# with your gem easier. You can also use a different console, if you like.
+
+# (If you use this, don't forget to add pry to your Gemfile!)
+# require "pry"
+# Pry.start
+
+require "irb"
+IRB.start(__FILE__)
diff --git a/bin/loadsphinx b/bin/loadsphinx
new file mode 100755
index 000000000..946e54e2a
--- /dev/null
+++ b/bin/loadsphinx
@@ -0,0 +1,99 @@
+#!/usr/bin/env bash
+
+version=$1
+engine=$2
+
+set -e
+
+load_sphinx () {
+ distro="xenial"
+
+ case $version in
+ 2.1.9)
+ url="http://sphinxsearch.com/files/sphinxsearch_2.1.9-release-0ubuntu11~trusty_amd64.deb"
+ format="deb"
+ distro="trusty";;
+ 2.2.11)
+ url="http://sphinxsearch.com/files/sphinxsearch_2.2.11-release-1~jessie_amd64.deb"
+ format="deb"
+ distro="trusty";;
+ 3.0.3)
+ url="http://sphinxsearch.com/files/sphinx-3.0.3-facc3fb-linux-amd64.tar.gz"
+ format="gz";;
+ 3.1.1)
+ url="http://sphinxsearch.com/files/sphinx-3.1.1-612d99f-linux-amd64.tar.gz"
+ format="gz";;
+ 3.2.1)
+ url="http://sphinxsearch.com/files/sphinx-3.2.1-f152e0b-linux-amd64.tar.gz"
+ format="gz";;
+ 3.3.1)
+ url="http://sphinxsearch.com/files/sphinx-3.3.1-b72d67b-linux-amd64.tar.gz"
+ format="gz";;
+ 3.4.1)
+ url="http://sphinxsearch.com/files/sphinx-3.4.1-efbcc65-linux-amd64.tar.gz"
+ format="gz";;
+ *)
+ echo "No Sphinx version $version available"
+ exit 1;;
+ esac
+
+ if [ "$distro" == "trusty" ]; then
+ curl --location http://launchpadlibrarian.net/247512886/libmysqlclient18_5.6.28-1ubuntu3_amd64.deb -o libmysql.deb
+ sudo apt-get install ./libmysql.deb
+ fi
+
+ if [ "$format" == "deb" ]; then
+ curl --location $url -o sphinx.deb
+ sudo apt-get install libodbc1
+ sudo dpkg -i ./sphinx.deb
+ sudo apt-get install -f
+ else
+ curl $url -o sphinx.tar.gz
+ tar -zxvf sphinx.tar.gz
+ sudo mv sphinx-$version/bin/* /usr/local/bin/.
+ fi
+}
+
+load_manticore () {
+ url="https://github.com/manticoresoftware/manticore/releases/download/$version/manticore_$version.deb"
+
+ case $version in
+ 2.6.4)
+ url="https://github.com/manticoresoftware/manticoresearch/releases/download/2.6.4/manticore_2.6.4-180503-37308c3-release-stemmer.xenial_amd64-bin.deb";;
+ 2.7.5)
+ 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-4e81114d-release-stemmer.stretch_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";;
+ 3.5.4)
+ url="https://repo.manticoresearch.com/repository/manticoresearch_focal/dists/focal/main/binary-amd64/manticore_3.5.4-210107-f70faec5_amd64.deb";;
+ 4.0.2)
+ url="https://repo.manticoresearch.com/repository/manticoresearch_focal/dists/focal/main/binary-amd64/manticore_4.0.2-210921-af497f245_amd64.deb";;
+ 4.2.0)
+ url="https://repo.manticoresearch.com/repository/manticoresearch_focal/dists/focal/main/binary-amd64/manticore_4.2.0-211223-15e927b28_amd64.deb";;
+ 6.0.0)
+ url="skipped";;
+ *)
+ echo "No Manticore version $version available"
+ exit 1;;
+ esac
+
+ if [ "$version" == "6.0.0" ]; then
+ curl --location https://repo.manticoresearch.com/manticore-repo.noarch.deb -o repo.deb
+ sudo dpkg -i repo.deb
+ sudo apt update
+ sudo apt install manticore
+ else
+ sudo apt-get install default-libmysqlclient-dev
+ curl --location $url -o manticore.deb
+ sudo dpkg -i ./manticore.deb
+ sudo apt-get install -f
+ fi
+}
+
+if [ "$engine" == "sphinx" ]; then
+ load_sphinx
+else
+ load_manticore
+fi
diff --git a/gemfiles/.gitignore b/gemfiles/.gitignore
deleted file mode 100644
index 33905cb38..000000000
--- a/gemfiles/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.gemfile.lock
\ No newline at end of file
diff --git a/gemfiles/rails_3_2.gemfile b/gemfiles/rails_3_2.gemfile
deleted file mode 100644
index 6e8bfa3bf..000000000
--- a/gemfiles/rails_3_2.gemfile
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file was generated by Appraisal
-
-source "https://rubygems.org"
-
-gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
-gem "pg", "~> 0.16.0", :platform=>:ruby
-gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform=>:jruby
-gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform=>:jruby
-gem "rails", "~> 3.2.21"
-
-gemspec :path=>"../"
\ No newline at end of file
diff --git a/gemfiles/rails_4_0.gemfile b/gemfiles/rails_4_0.gemfile
deleted file mode 100644
index 7b8dfd189..000000000
--- a/gemfiles/rails_4_0.gemfile
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file was generated by Appraisal
-
-source "https://rubygems.org"
-
-gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
-gem "pg", "~> 0.16.0", :platform=>:ruby
-gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform=>:jruby
-gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform=>:jruby
-gem "rails", "~> 4.0.12"
-
-gemspec :path=>"../"
\ No newline at end of file
diff --git a/gemfiles/rails_4_1.gemfile b/gemfiles/rails_4_1.gemfile
deleted file mode 100644
index f79b9af5d..000000000
--- a/gemfiles/rails_4_1.gemfile
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file was generated by Appraisal
-
-source "https://rubygems.org"
-
-gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
-gem "pg", "~> 0.16.0", :platform=>:ruby
-gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform=>:jruby
-gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform=>:jruby
-gem "rails", "~> 4.1.8"
-
-gemspec :path=>"../"
\ No newline at end of file
diff --git a/gemfiles/rails_4_2.gemfile b/gemfiles/rails_4_2.gemfile
deleted file mode 100644
index 7bf8eb808..000000000
--- a/gemfiles/rails_4_2.gemfile
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file was generated by Appraisal
-
-source "https://rubygems.org"
-
-gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
-gem "pg", "~> 0.16.0", :platform=>:ruby
-gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform=>:jruby
-gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform=>:jruby
-gem "rails", "~> 4.2.0"
-
-gemspec :path=>"../"
\ No newline at end of file
diff --git a/lib/thinking-sphinx.rb b/lib/thinking-sphinx.rb
index 861ec024f..60f264cb4 100644
--- a/lib/thinking-sphinx.rb
+++ b/lib/thinking-sphinx.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
require 'thinking_sphinx'
diff --git a/lib/thinking/sphinx.rb b/lib/thinking/sphinx.rb
index 861ec024f..60f264cb4 100644
--- a/lib/thinking/sphinx.rb
+++ b/lib/thinking/sphinx.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
require 'thinking_sphinx'
diff --git a/lib/thinking_sphinx.rb b/lib/thinking_sphinx.rb
index c219fca57..89de63a2a 100644
--- a/lib/thinking_sphinx.rb
+++ b/lib/thinking_sphinx.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
if RUBY_PLATFORM == 'java'
require 'java'
require 'jdbc/mysql'
@@ -16,7 +18,7 @@
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 = {})
@@ -32,22 +34,45 @@ def self.search_for_ids(query = '', options = {})
ThinkingSphinx::Search::Merger.new(search).merge! nil, :ids_only => true
end
+ def self.none
+ ThinkingSphinx::Search.new nil, :none => true
+ end
+
def self.before_index_hooks
@before_index_hooks
end
@before_index_hooks = []
+ def self.output
+ @output
+ end
+
+ @output = STDOUT
+
+ def self.rake_interface
+ @rake_interface ||= ThinkingSphinx::RakeInterface
+ end
+
+ def self.rake_interface=(interface)
+ @rake_interface = interface
+ end
+
+ module Hooks; end
+ module IndexingStrategies; end
module Subscribers; end
end
# Core
+require 'thinking_sphinx/attribute_types'
require 'thinking_sphinx/batched_search'
require 'thinking_sphinx/callbacks'
require 'thinking_sphinx/core'
+require 'thinking_sphinx/with_output'
+require 'thinking_sphinx/commander'
+require 'thinking_sphinx/commands'
require 'thinking_sphinx/configuration'
require 'thinking_sphinx/connection'
-require 'thinking_sphinx/controller'
require 'thinking_sphinx/deletion'
require 'thinking_sphinx/errors'
require 'thinking_sphinx/excerpter'
@@ -56,25 +81,31 @@ module Subscribers; end
require 'thinking_sphinx/float_formatter'
require 'thinking_sphinx/frameworks'
require 'thinking_sphinx/guard'
+require 'thinking_sphinx/hooks/guard_presence'
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/interfaces'
require 'thinking_sphinx/masks'
require 'thinking_sphinx/middlewares'
require 'thinking_sphinx/panes'
+require 'thinking_sphinx/processor'
require 'thinking_sphinx/query'
require 'thinking_sphinx/rake_interface'
require 'thinking_sphinx/scopes'
require 'thinking_sphinx/search'
-require 'thinking_sphinx/sphinxql'
+require 'thinking_sphinx/settings'
require 'thinking_sphinx/subscribers/populator_subscriber'
require 'thinking_sphinx/test'
require 'thinking_sphinx/utf8'
require 'thinking_sphinx/wildcard'
# Extended
-require 'thinking_sphinx/active_record'
require 'thinking_sphinx/deltas'
require 'thinking_sphinx/distributed'
require 'thinking_sphinx/logger'
require 'thinking_sphinx/real_time'
require 'thinking_sphinx/railtie' if defined?(Rails::Railtie)
+
+ThinkingSphinx.before_index_hooks << ThinkingSphinx::Hooks::GuardPresence
diff --git a/lib/thinking_sphinx/active_record.rb b/lib/thinking_sphinx/active_record.rb
index c78f668ed..4baca3c11 100644
--- a/lib/thinking_sphinx/active_record.rb
+++ b/lib/thinking_sphinx/active_record.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'active_record'
require 'joiner'
module ThinkingSphinx::ActiveRecord
module Callbacks; end
+ module Depolymorph; end
end
require 'thinking_sphinx/active_record/property'
@@ -14,7 +17,6 @@ module Callbacks; end
require 'thinking_sphinx/active_record/column_sql_presenter'
require 'thinking_sphinx/active_record/database_adapters'
require 'thinking_sphinx/active_record/field'
-require 'thinking_sphinx/active_record/filter_reflection'
require 'thinking_sphinx/active_record/index'
require 'thinking_sphinx/active_record/interpreter'
require 'thinking_sphinx/active_record/join_association'
@@ -23,9 +25,20 @@ module Callbacks; end
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'
+require 'thinking_sphinx/active_record/callbacks/association_delta_callbacks'
require 'thinking_sphinx/active_record/callbacks/delete_callbacks'
require 'thinking_sphinx/active_record/callbacks/delta_callbacks'
require 'thinking_sphinx/active_record/callbacks/update_callbacks'
+
+require 'thinking_sphinx/active_record/depolymorph/base_reflection'
+require 'thinking_sphinx/active_record/depolymorph/association_reflection'
+require 'thinking_sphinx/active_record/depolymorph/conditions_reflection'
+require 'thinking_sphinx/active_record/depolymorph/overridden_reflection'
+require 'thinking_sphinx/active_record/depolymorph/scoped_reflection'
+require 'thinking_sphinx/active_record/filter_reflection'
+
+ActiveRecord::Base.include ThinkingSphinx::ActiveRecord::Base
diff --git a/lib/thinking_sphinx/active_record/association.rb b/lib/thinking_sphinx/active_record/association.rb
index 927a73b69..de3b6f833 100644
--- a/lib/thinking_sphinx/active_record/association.rb
+++ b/lib/thinking_sphinx/active_record/association.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Association
def initialize(column)
@column = column
diff --git a/lib/thinking_sphinx/active_record/association_proxy.rb b/lib/thinking_sphinx/active_record/association_proxy.rb
index f61d94733..da3e68ce6 100644
--- a/lib/thinking_sphinx/active_record/association_proxy.rb
+++ b/lib/thinking_sphinx/active_record/association_proxy.rb
@@ -1,6 +1,6 @@
-module ThinkingSphinx::ActiveRecord::AssociationProxy
- extend ActiveSupport::Concern
+# frozen_string_literal: true
+module ThinkingSphinx::ActiveRecord::AssociationProxy
def search(query = nil, options = {})
perform_search super(*normalise_search_arguments(query, options))
end
@@ -10,6 +10,7 @@ def search_for_ids(query = nil, options = {})
end
private
+
def normalise_search_arguments(query, options)
query, options = nil, query if query.is_a?(Hash)
options[:ignore_scopes] = true
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 6a165f287..158549706 100644
--- a/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb
+++ b/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::AssociationProxy::AttributeFinder
def initialize(association)
@association = association
@@ -29,7 +31,7 @@ def indices
@indices ||= begin
configuration.preload_indices
configuration.indices_for_references(
- *@association.klass.name.underscore.to_sym
+ *configuration.index_set_class.reference_name(@association.klass)
).reject &:distributed?
end
end
diff --git a/lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb b/lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb
index eccfd3937..ada0667b0 100644
--- a/lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb
+++ b/lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::AssociationProxy::AttributeMatcher
def initialize(attribute, foreign_key)
@attribute, @foreign_key = attribute, foreign_key.to_s
diff --git a/lib/thinking_sphinx/active_record/attribute.rb b/lib/thinking_sphinx/active_record/attribute.rb
index 6b8569efc..7198475d4 100644
--- a/lib/thinking_sphinx/active_record/attribute.rb
+++ b/lib/thinking_sphinx/active_record/attribute.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Attribute <
ThinkingSphinx::ActiveRecord::Property
diff --git a/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb b/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb
index d253f111c..28b87fef0 100644
--- a/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb
+++ b/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb
@@ -1,13 +1,16 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Attribute::SphinxPresenter
SPHINX_TYPES = {
:integer => :uint,
:boolean => :bool,
- :timestamp => :timestamp,
+ :timestamp => :uint,
:float => :float,
:string => :string,
:bigint => :bigint,
:ordinal => :str2ordinal,
- :wordcount => :str2wordcount
+ :wordcount => :str2wordcount,
+ :json => :json
}
def initialize(attribute, source)
diff --git a/lib/thinking_sphinx/active_record/attribute/type.rb b/lib/thinking_sphinx/active_record/attribute/type.rb
index f85a620a1..df89fbcc7 100644
--- a/lib/thinking_sphinx/active_record/attribute/type.rb
+++ b/lib/thinking_sphinx/active_record/attribute/type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Attribute::Type
UPDATEABLE_TYPES = [:integer, :timestamp, :boolean, :float]
@@ -41,7 +43,7 @@ def associations
end
def big_integer?
- database_column.type == :integer && database_column.sql_type[/bigint/i]
+ type_symbol == :integer && database_column.sql_type[/bigint/i]
end
def column_name
@@ -72,19 +74,33 @@ def single_column_reference?
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?
- case database_column.type
+ case type_symbol
when :datetime, :date
:timestamp
when :text
:string
when :decimal
:float
+ when :integer, :boolean, :timestamp, :float, :string, :bigint, :json
+ type_symbol
else
- database_column.type
+ raise ThinkingSphinx::UnknownAttributeType,
+ <<-ERROR
+Unable to determine an equivalent Sphinx attribute type from #{database_column.type.class.name} for attribute #{attribute.name}. You may want to manually set the type.
+
+e.g.
+ has my_column, :type => :integer
+ ERROR
end
end
+
+ def type_symbol
+ return database_column.type if database_column.type.is_a?(Symbol)
+
+ database_column.type.class.name.demodulize.downcase.to_sym
+ end
end
diff --git a/lib/thinking_sphinx/active_record/attribute/values.rb b/lib/thinking_sphinx/active_record/attribute/values.rb
index 1b1fc5be0..5058b1f20 100644
--- a/lib/thinking_sphinx/active_record/attribute/values.rb
+++ b/lib/thinking_sphinx/active_record/attribute/values.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Attribute::Values
def initialize(attribute)
@attribute = attribute
diff --git a/lib/thinking_sphinx/active_record/base.rb b/lib/thinking_sphinx/active_record/base.rb
index 282214180..a093e0628 100644
--- a/lib/thinking_sphinx/active_record/base.rb
+++ b/lib/thinking_sphinx/active_record/base.rb
@@ -1,35 +1,67 @@
+# frozen_string_literal: true
+
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
+ # Avoid method collisions for public Thinking Sphinx methods added to all
+ # ActiveRecord models. The `sphinx_`-prefixed versions will always exist,
+ # and the non-prefixed versions will be added if a method of that name
+ # doesn't already exist.
+ #
+ # If a method is overwritten later by something else, that's also fine - the
+ # prefixed versions will still be there.
+ class_module = ThinkingSphinx::ActiveRecord::Base::ClassMethods
+ class_module.public_instance_methods.each do |method_name|
+ short_method = method_name.to_s.delete_prefix("sphinx_").to_sym
+ next if methods.include?(short_method)
+
+ define_singleton_method(short_method, method(method_name))
+ end
+
+ if ActiveRecord::VERSION::STRING.to_i >= 5
+ [
+ ::ActiveRecord::Reflection::HasManyReflection,
+ ::ActiveRecord::Reflection::HasAndBelongsToManyReflection
+ ].each do |reflection_class|
+ reflection_class.include DefaultReflectionAssociations
+ end
+ else
+ ::ActiveRecord::Associations::CollectionProxy.include(
+ ThinkingSphinx::ActiveRecord::AssociationProxy
+ )
+ end
+ end
- ::ActiveRecord::Associations::CollectionProxy.send :include,
- ThinkingSphinx::ActiveRecord::AssociationProxy
+ module DefaultReflectionAssociations
+ def extensions
+ super + [ThinkingSphinx::ActiveRecord::AssociationProxy]
+ end
end
module ClassMethods
- def facets(query = nil, options = {})
+ def sphinx_facets(query = nil, options = {})
merge_search ThinkingSphinx.facets, query, options
end
- def search(query = nil, options = {})
+ def sphinx_search(query = nil, options = {})
merge_search ThinkingSphinx.search, query, options
end
- def search_count(query = nil, options = {})
- search(query, options).total_entries
+ def sphinx_search_count(query = nil, options = {})
+ search_for_ids(query, options).total_entries
end
- def search_for_ids(query = nil, options = {})
+ def sphinx_search_for_ids(query = nil, options = {})
ThinkingSphinx::Search::Merger.new(
search(query, options)
).merge! nil, :ids_only => true
end
+ def sphinx_search_none
+ merge_search ThinkingSphinx.search, nil, none: true
+ end
+
private
def default_sphinx_scope?
diff --git a/lib/thinking_sphinx/active_record/callbacks/association_delta_callbacks.rb b/lib/thinking_sphinx/active_record/callbacks/association_delta_callbacks.rb
new file mode 100644
index 000000000..f51f3ae24
--- /dev/null
+++ b/lib/thinking_sphinx/active_record/callbacks/association_delta_callbacks.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::ActiveRecord::Callbacks::AssociationDeltaCallbacks
+ def initialize(path)
+ @path = path
+ end
+
+ def after_commit(instance)
+ Array(objects_for(instance)).each do |object|
+ object.update :delta => true unless object.frozen?
+ end
+ end
+
+ private
+
+ attr_reader :path
+
+ def objects_for(instance)
+ path.inject(instance) { |object, method| object.send method }
+ 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 8d3c961e8..1f8e75612 100644
--- a/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb
+++ b/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb
@@ -1,21 +1,27 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
ThinkingSphinx::Callbacks
- callbacks :after_destroy
+ callbacks :after_commit, :after_destroy, :after_rollback
+
+ def after_commit
+ delete_from_sphinx
+ end
def after_destroy
- return if instance.new_record?
+ delete_from_sphinx
+ end
- indices.each { |index|
- ThinkingSphinx::Deletion.perform index, instance.id
- }
+ def after_rollback
+ delete_from_sphinx
end
private
- def indices
- ThinkingSphinx::Configuration.instance.index_set_class.new(
- :classes => [instance.class]
- ).to_a
+ def delete_from_sphinx
+ return if ThinkingSphinx::Callbacks.suspended?
+
+ ThinkingSphinx::Processor.new(instance: instance).delete
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 1789b6bc2..6cdf4422f 100644
--- a/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb
+++ b/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb
@@ -1,12 +1,12 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks <
ThinkingSphinx::Callbacks
callbacks :after_commit, :before_save
def after_commit
- return unless delta_indices? && processors.any? { |processor|
- processor.toggled?(instance)
- } && !ThinkingSphinx::Deltas.suspended?
+ return unless !suspended? && delta_indices? && toggled?
delta_indices.each do |index|
index.delta_processor.index index
@@ -18,7 +18,8 @@ def after_commit
end
def before_save
- return unless delta_indices?
+ return unless !ThinkingSphinx::Callbacks.suspended? && delta_indices? &&
+ new_or_changed?
processors.each { |processor| processor.toggle instance }
end
@@ -42,10 +43,24 @@ def delta_indices?
end
def indices
- @indices ||= config.index_set_class.new :classes => [instance.class]
+ @indices ||= config.index_set_class.new(
+ :instances => [instance], :classes => [instance.class]
+ ).select { |index| index.type == "plain" }
+ end
+
+ def new_or_changed?
+ instance.new_record? || instance.changed?
end
def processors
delta_indices.collect &:delta_processor
end
+
+ def suspended?
+ ThinkingSphinx::Callbacks.suspended? || ThinkingSphinx::Deltas.suspended?
+ end
+
+ def toggled?
+ processors.any? { |processor| processor.toggled?(instance) }
+ 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 16120bb71..ee9add052 100644
--- a/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb
+++ b/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb
@@ -1,10 +1,18 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks <
ThinkingSphinx::Callbacks
+ if ActiveRecord::Base.instance_methods.grep(/saved_changes/).any?
+ CHANGED_ATTRIBUTES = lambda { |instance| instance.saved_changes.keys }
+ else
+ CHANGED_ATTRIBUTES = lambda { |instance| instance.changed }
+ end
+
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?
@@ -15,7 +23,7 @@ def after_update
def attributes_hash_for(index)
updateable_attributes_for(index).inject({}) do |hash, attribute|
- if instance.changed.include?(attribute.columns.first.__name.to_s)
+ if changed_attributes.include?(attribute.columns.first.__name.to_s)
hash[attribute.name] = attribute.value_for(instance)
end
@@ -23,6 +31,10 @@ def attributes_hash_for(index)
end
end
+ def changed_attributes
+ @changed_attributes ||= CHANGED_ATTRIBUTES.call instance
+ end
+
def configuration
ThinkingSphinx::Configuration.instance
end
@@ -35,7 +47,7 @@ def indices
end
def reference
- instance.class.name.underscore.to_sym
+ configuration.index_set_class.reference_name(instance.class)
end
def update(index)
@@ -43,7 +55,9 @@ def update(index)
return if attributes.empty?
sphinxql = Riddle::Query.update(
- index.name, index.document_id_for_key(instance.id), attributes
+ index.name,
+ index.document_id_for_key(instance.public_send(index.primary_key)),
+ attributes
)
ThinkingSphinx::Connection.take do |connection|
connection.execute(sphinxql)
diff --git a/lib/thinking_sphinx/active_record/column.rb b/lib/thinking_sphinx/active_record/column.rb
index 598e92c4e..99bbe48d0 100644
--- a/lib/thinking_sphinx/active_record/column.rb
+++ b/lib/thinking_sphinx/active_record/column.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Column
def initialize(*stack)
@stack = stack
diff --git a/lib/thinking_sphinx/active_record/column_sql_presenter.rb b/lib/thinking_sphinx/active_record/column_sql_presenter.rb
index c0b47f54c..0339bed37 100644
--- a/lib/thinking_sphinx/active_record/column_sql_presenter.rb
+++ b/lib/thinking_sphinx/active_record/column_sql_presenter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::ColumnSQLPresenter
def initialize(model, column, adapter, associations)
@model, @column, @adapter, @associations = model, column, adapter, associations
@@ -13,7 +15,9 @@ def with_table
return __name if string?
return nil unless exists?
- "#{associations.alias_for(__stack)}.#{adapter.quote __name}"
+ quoted_table = escape_table? ? escape_table(table) : table
+
+ "#{quoted_table}.#{adapter.quote __name}"
end
private
@@ -22,6 +26,14 @@ def with_table
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
+
def exists?
path.model.column_names.include?(column.__name.to_s)
rescue Joiner::AssociationNotFound
@@ -31,4 +43,12 @@ def exists?
def path
Joiner::Path.new model, column.__stack
end
+
+ def table
+ associations.alias_for __stack
+ end
+
+ def version
+ ActiveRecord::VERSION
+ end
end
diff --git a/lib/thinking_sphinx/active_record/database_adapters.rb b/lib/thinking_sphinx/active_record/database_adapters.rb
index 2a444586f..4d1c79458 100644
--- a/lib/thinking_sphinx/active_record/database_adapters.rb
+++ b/lib/thinking_sphinx/active_record/database_adapters.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::ActiveRecord::DatabaseAdapters
class << self
attr_accessor :default
@@ -12,7 +14,7 @@ def adapter_for(model)
when :postgresql
PostgreSQLAdapter
else
- raise "Invalid Database Adapter '#{adapter}': Thinking Sphinx only supports MySQL and PostgreSQL."
+ raise ThinkingSphinx::InvalidDatabaseAdapter, "Invalid adapter '#{adapter}': Thinking Sphinx only supports MySQL and PostgreSQL."
end
klass.new model
diff --git a/lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb b/lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb
index eb3460996..7c3240c09 100644
--- a/lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb
+++ b/lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::DatabaseAdapters::AbstractAdapter
def initialize(model)
@model = model
diff --git a/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb b/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb
index 943fbaffc..14d1f8f69 100644
--- a/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb
+++ b/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter <
ThinkingSphinx::ActiveRecord::DatabaseAdapters::AbstractAdapter
@@ -38,6 +40,12 @@ def time_zone_query_pre
end
def utf8_query_pre
- ['SET NAMES utf8']
+ ["SET NAMES #{settings['mysql_encoding']}"]
+ end
+
+ private
+
+ def settings
+ ThinkingSphinx::Configuration.instance.settings
end
end
diff --git a/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb b/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb
index 3acc963c3..eec4327ae 100644
--- a/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb
+++ b/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter <
ThinkingSphinx::ActiveRecord::DatabaseAdapters::AbstractAdapter
diff --git a/lib/thinking_sphinx/active_record/depolymorph/association_reflection.rb b/lib/thinking_sphinx/active_record/depolymorph/association_reflection.rb
new file mode 100644
index 000000000..96e556bcb
--- /dev/null
+++ b/lib/thinking_sphinx/active_record/depolymorph/association_reflection.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+# This custom association approach is only available in Rails 4.1-5.1. This
+# behaviour is superseded by OverriddenReflection for Rails 5.2, and was
+# preceded by ScopedReflection for Rails 4.0.
+class ThinkingSphinx::ActiveRecord::Depolymorph::AssociationReflection <
+ ThinkingSphinx::ActiveRecord::Depolymorph::BaseReflection
+
+ # Since Rails 4.2, the macro argument has been removed. The underlying
+ # behaviour remains the same, though.
+ def call
+ if explicit_macro?
+ klass.new name, nil, options, reflection.active_record
+ else
+ klass.new reflection.macro, name, nil, options, reflection.active_record
+ end
+ end
+
+ private
+
+ def explicit_macro?
+ ActiveRecord::Reflection::MacroReflection.instance_method(:initialize).
+ arity == 4
+ end
+
+ def options
+ super
+
+ @options[:sphinx_internal_filtered] = true
+ @options
+ end
+end
diff --git a/lib/thinking_sphinx/active_record/depolymorph/base_reflection.rb b/lib/thinking_sphinx/active_record/depolymorph/base_reflection.rb
new file mode 100644
index 000000000..c6f389301
--- /dev/null
+++ b/lib/thinking_sphinx/active_record/depolymorph/base_reflection.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::ActiveRecord::Depolymorph::BaseReflection
+ def initialize(reflection, name, class_name)
+ @reflection = reflection
+ @name = name
+ @class_name = class_name
+
+ @options = reflection.options.clone
+ end
+
+ def call
+ # Should be implemented by subclasses.
+ end
+
+ private
+
+ attr_reader :reflection, :name, :class_name
+
+ def klass
+ reflection.class
+ end
+
+ def options
+ @options.delete :polymorphic
+ @options[:class_name] = class_name
+ @options[:foreign_key] ||= "#{reflection.name}_id"
+ @options[:foreign_type] = reflection.foreign_type
+
+ @options
+ end
+end
diff --git a/lib/thinking_sphinx/active_record/depolymorph/conditions_reflection.rb b/lib/thinking_sphinx/active_record/depolymorph/conditions_reflection.rb
new file mode 100644
index 000000000..e88e140ab
--- /dev/null
+++ b/lib/thinking_sphinx/active_record/depolymorph/conditions_reflection.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+# The conditions approach is only available in Rails 3. This behaviour is
+# superseded by ScopedReflection for Rails 4.0.
+class ThinkingSphinx::ActiveRecord::Depolymorph::ConditionsReflection <
+ ThinkingSphinx::ActiveRecord::Depolymorph::BaseReflection
+
+ def call
+ klass.new reflection.macro, name, options, active_record
+ end
+
+ private
+
+ delegate :foreign_type, :active_record, :to => :reflection
+
+ def condition
+ "::ts_join_alias::.#{quoted_foreign_type} = '#{class_name}'"
+ end
+
+ def options
+ super
+
+ case @options[:conditions]
+ when nil
+ @options[:conditions] = condition
+ when Array
+ @options[:conditions] << condition
+ when Hash
+ @options[:conditions].merge! foreign_type => @options[:class_name]
+ else
+ @options[:conditions] = "#{@options[:conditions]} AND #{condition}"
+ end
+
+ @options
+ end
+
+ def quoted_foreign_type
+ active_record.connection.quote_column_name foreign_type
+ end
+end
diff --git a/lib/thinking_sphinx/active_record/depolymorph/overridden_reflection.rb b/lib/thinking_sphinx/active_record/depolymorph/overridden_reflection.rb
new file mode 100644
index 000000000..0b40f1637
--- /dev/null
+++ b/lib/thinking_sphinx/active_record/depolymorph/overridden_reflection.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+# This overriding approach is only available in Rails 5.2+. This behaviour
+# was preceded by AssociationReflection for Rails 4.1-5.1.
+class ThinkingSphinx::ActiveRecord::Depolymorph::OverriddenReflection <
+ ThinkingSphinx::ActiveRecord::Depolymorph::BaseReflection
+
+ module BuildJoinConstraint
+ def build_join_constraint(table, foreign_table)
+ super.and(
+ foreign_table[options[:foreign_type]].eq(
+ options[:class_name].constantize.base_class.name
+ )
+ )
+ end
+ end
+
+ module JoinScope
+ def join_scope(table, foreign_table, foreign_klass)
+ super.where(
+ foreign_table[options[:foreign_type]].eq(
+ options[:class_name].constantize.base_class.name
+ )
+ )
+ end
+ end
+
+ def self.overridden_classes
+ @overridden_classes ||= {}
+ end
+
+ def call
+ klass.new name, nil, options, reflection.active_record
+ end
+
+ private
+
+ def klass
+ self.class.overridden_classes[reflection.class] ||= begin
+ subclass = Class.new reflection.class
+ subclass.include extension(reflection)
+ subclass
+ end
+ end
+
+ def extension(reflection)
+ reflection.respond_to?(:build_join_constraint) ?
+ BuildJoinConstraint : JoinScope
+ end
+end
diff --git a/lib/thinking_sphinx/active_record/depolymorph/scoped_reflection.rb b/lib/thinking_sphinx/active_record/depolymorph/scoped_reflection.rb
new file mode 100644
index 000000000..e99a2089f
--- /dev/null
+++ b/lib/thinking_sphinx/active_record/depolymorph/scoped_reflection.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+# This scoped approach is only available in Rails 4.0. This behaviour is
+# superseded by AssociationReflection for Rails 4.1, and was preceded by
+# ConditionsReflection for Rails 3.2.
+class ThinkingSphinx::ActiveRecord::Depolymorph::ScopedReflection <
+ ThinkingSphinx::ActiveRecord::Depolymorph::BaseReflection
+
+ def call
+ klass.new reflection.macro, name, scope, options,
+ reflection.active_record
+ end
+
+ private
+
+ def scope
+ lambda { |association|
+ reflection = association.reflection
+ klass = reflection.class_name.constantize
+ where(
+ association.parent.aliased_table_name.to_sym =>
+ {reflection.foreign_type => klass.base_class.name}
+ )
+ }
+ end
+end
diff --git a/lib/thinking_sphinx/active_record/field.rb b/lib/thinking_sphinx/active_record/field.rb
index 86f62acb0..fefbee6c0 100644
--- a/lib/thinking_sphinx/active_record/field.rb
+++ b/lib/thinking_sphinx/active_record/field.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Field <
ThinkingSphinx::ActiveRecord::Property
include ThinkingSphinx::Core::Field
diff --git a/lib/thinking_sphinx/active_record/filter_reflection.rb b/lib/thinking_sphinx/active_record/filter_reflection.rb
index d2dff4845..816520b0e 100644
--- a/lib/thinking_sphinx/active_record/filter_reflection.rb
+++ b/lib/thinking_sphinx/active_record/filter_reflection.rb
@@ -1,75 +1,18 @@
-class ThinkingSphinx::ActiveRecord::FilterReflection
- attr_reader :reflection, :class_name
-
- delegate :foreign_type, :active_record, :to => :reflection
-
- def self.call(reflection, name, class_name)
- filter = new(reflection, class_name)
- klass = reflection.class
-
- if defined?(ActiveRecord::Reflection::MacroReflection)
- klass.new name, filter.scope, filter.options, reflection.active_record
- elsif reflection.respond_to?(:scope)
- klass.new reflection.macro, name, filter.scope, filter.options,
- reflection.active_record
- else
- klass.new reflection.macro, name, filter.options,
- reflection.active_record
- end
- end
-
- def initialize(reflection, class_name)
- @reflection, @class_name = reflection, class_name
- @options = reflection.options.clone
- end
-
- def options
- @options.delete :polymorphic
- @options[:class_name] = class_name
- @options[:foreign_key] ||= "#{reflection.name}_id"
- @options[:foreign_type] = reflection.foreign_type
-
- if reflection.respond_to?(:scope)
- @options[:sphinx_internal_filtered] = true
- return @options
- end
+# frozen_string_literal: true
- case @options[:conditions]
- when nil
- @options[:conditions] = condition
- when Array
- @options[:conditions] << condition
- when Hash
- @options[:conditions].merge!(reflection.foreign_type => @options[:class_name])
- else
- @options[:conditions] << " AND #{condition}"
- end
-
- @options
- end
-
- def scope
- if ::Joiner::Joins.instance_methods.include?(:join_association_class)
- return nil
- end
-
- lambda { |association|
- reflection = association.reflection
- klass = reflection.class_name.constantize
- where(
- association.parent.aliased_table_name.to_sym =>
- {reflection.foreign_type => klass.base_class.name}
- )
- }
- end
-
- private
-
- def condition
- "::ts_join_alias::.#{quoted_foreign_type} = '#{class_name}'"
+class ThinkingSphinx::ActiveRecord::FilterReflection
+ ReflectionGenerator = case ActiveRecord::VERSION::STRING.to_f
+ when 5.2..7.1
+ ThinkingSphinx::ActiveRecord::Depolymorph::OverriddenReflection
+ when 4.1..5.1
+ ThinkingSphinx::ActiveRecord::Depolymorph::AssociationReflection
+ when 4.0
+ ThinkingSphinx::ActiveRecord::Depolymorph::ScopedReflection
+ when 3.2
+ ThinkingSphinx::ActiveRecord::Depolymorph::ConditionsReflection
end
- def quoted_foreign_type
- active_record.connection.quote_column_name foreign_type
+ def self.call(reflection, name, class_name)
+ ReflectionGenerator.new(reflection, name, class_name).call
end
end
diff --git a/lib/thinking_sphinx/active_record/index.rb b/lib/thinking_sphinx/active_record/index.rb
index eac534bef..a6b659254 100644
--- a/lib/thinking_sphinx/active_record/index.rb
+++ b/lib/thinking_sphinx/active_record/index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Index < Riddle::Configuration::Index
include ThinkingSphinx::Core::Index
@@ -28,6 +30,10 @@ def facets
@facets ||= sources.collect(&:facets).flatten
end
+ def fields
+ sources.collect(&:fields).flatten
+ end
+
def sources
interpret_definition!
super
@@ -44,10 +50,6 @@ def adapter
adapter_for(model)
end
- def fields
- sources.collect(&:fields).flatten
- end
-
def interpreter
ThinkingSphinx::ActiveRecord::Interpreter
end
@@ -63,7 +65,7 @@ def source_options
:delta? => @options[:delta?],
:delta_processor => @options[:delta_processor],
:delta_options => @options[:delta_options],
- :primary_key => @options[:primary_key] || model.primary_key || :id
+ :primary_key => primary_key
}
end
end
diff --git a/lib/thinking_sphinx/active_record/interpreter.rb b/lib/thinking_sphinx/active_record/interpreter.rb
index 25652d344..a6e736d98 100644
--- a/lib/thinking_sphinx/active_record/interpreter.rb
+++ b/lib/thinking_sphinx/active_record/interpreter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Interpreter <
::ThinkingSphinx::Core::Interpreter
@@ -11,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)
@@ -39,10 +41,10 @@ def sanitize_sql(*arguments)
end
def set_database(hash_or_key)
- configuration = hash_or_key.is_a?(::Hash) ? hash_or_key.symbolize_keys :
+ configuration = hash_or_key.is_a?(::Hash) ? hash_or_key :
::ActiveRecord::Base.configurations[hash_or_key.to_s]
- __source.set_database_settings configuration
+ __source.set_database_settings configuration.symbolize_keys
end
def set_property(properties)
diff --git a/lib/thinking_sphinx/active_record/join_association.rb b/lib/thinking_sphinx/active_record/join_association.rb
index b95127b1b..d4cb5dc7b 100644
--- a/lib/thinking_sphinx/active_record/join_association.rb
+++ b/lib/thinking_sphinx/active_record/join_association.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::JoinAssociation <
::ActiveRecord::Associations::JoinDependency::JoinAssociation
@@ -5,7 +7,9 @@ def build_constraint(klass, table, key, foreign_table, foreign_key)
constraint = super
constraint = constraint.and(
- foreign_table[reflection.options[:foreign_type]].eq(base_klass.name)
+ foreign_table[reflection.options[:foreign_type]].eq(
+ base_klass.base_class.name
+ )
) if reflection.options[:sphinx_internal_filtered]
constraint
diff --git a/lib/thinking_sphinx/active_record/log_subscriber.rb b/lib/thinking_sphinx/active_record/log_subscriber.rb
index c03de51c7..7187b6ce3 100644
--- a/lib/thinking_sphinx/active_record/log_subscriber.rb
+++ b/lib/thinking_sphinx/active_record/log_subscriber.rb
@@ -1,18 +1,37 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::LogSubscriber < ActiveSupport::LogSubscriber
def guard(event)
- identifier = color 'Sphinx', GREEN, true
+ identifier = colored_text "Sphinx"
warn " #{identifier} #{event.payload[:guard]}"
end
def message(event)
- identifier = color 'Sphinx', GREEN, true
+ identifier = colored_text "Sphinx"
debug " #{identifier} #{event.payload[:message]}"
end
def query(event)
- identifier = color('Sphinx Query (%.1fms)' % event.duration, GREEN, true)
+ identifier = colored_text("Sphinx Query (%.1fms)" % event.duration)
debug " #{identifier} #{event.payload[:query]}"
end
+
+ def caution(event)
+ identifier = colored_text "Sphinx"
+ warn " #{identifier} #{event.payload[:caution]}"
+ end
+
+ private
+
+ if Rails.gem_version >= Gem::Version.new("7.1.0")
+ def colored_text(text)
+ color text, GREEN, bold: true
+ end
+ else
+ def colored_text(text)
+ color text, GREEN, true
+ end
+ end
end
ThinkingSphinx::ActiveRecord::LogSubscriber.attach_to :thinking_sphinx
diff --git a/lib/thinking_sphinx/active_record/polymorpher.rb b/lib/thinking_sphinx/active_record/polymorpher.rb
index 92c061c6c..f03e7788e 100644
--- a/lib/thinking_sphinx/active_record/polymorpher.rb
+++ b/lib/thinking_sphinx/active_record/polymorpher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Polymorpher
def initialize(source, column, class_names)
@source, @column, @class_names = source, column, class_names
diff --git a/lib/thinking_sphinx/active_record/property.rb b/lib/thinking_sphinx/active_record/property.rb
index c2c4a8fb4..27ef3c8c3 100644
--- a/lib/thinking_sphinx/active_record/property.rb
+++ b/lib/thinking_sphinx/active_record/property.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::Property
include ThinkingSphinx::Core::Property
diff --git a/lib/thinking_sphinx/active_record/property_query.rb b/lib/thinking_sphinx/active_record/property_query.rb
index df2ca96c8..677677545 100644
--- a/lib/thinking_sphinx/active_record/property_query.rb
+++ b/lib/thinking_sphinx/active_record/property_query.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::PropertyQuery
def initialize(property, source, type = nil)
@property, @source, @type = property, source, type
@@ -25,6 +27,7 @@ def to_s
attr_reader :property, :source, :type
delegate :unscoped, :to => :base_association_class, :prefix => true
+ delegate :sql, :to => Arel
def base_association
reflections.first
@@ -133,7 +136,7 @@ def to_sql
relation = relation.joins(joins) if joins.present?
relation = relation.where("#{quoted_foreign_key} BETWEEN $start AND $end") if ranged?
relation = relation.where("#{quoted_foreign_key} IS NOT NULL")
- relation = relation.order("#{quoted_foreign_key} ASC") if type.nil?
+ relation = relation.order(sql("#{quoted_foreign_key} ASC")) if type.nil?
relation.to_sql
end
diff --git a/lib/thinking_sphinx/active_record/property_sql_presenter.rb b/lib/thinking_sphinx/active_record/property_sql_presenter.rb
index c846be8b8..ea122c065 100644
--- a/lib/thinking_sphinx/active_record/property_sql_presenter.rb
+++ b/lib/thinking_sphinx/active_record/property_sql_presenter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::PropertySQLPresenter
attr_reader :property, :adapter, :associations
diff --git a/lib/thinking_sphinx/active_record/simple_many_query.rb b/lib/thinking_sphinx/active_record/simple_many_query.rb
index 943694bde..6e868454b 100644
--- a/lib/thinking_sphinx/active_record/simple_many_query.rb
+++ b/lib/thinking_sphinx/active_record/simple_many_query.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::SimpleManyQuery <
ThinkingSphinx::ActiveRecord::PropertyQuery
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 000000000..60f9f8ce5
--- /dev/null
+++ b/lib/thinking_sphinx/active_record/source_joins.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+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? or column_included_in_queries?(column)
+
+ 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
+
+ def source_query_properties
+ source.properties.select { |field| field.source_type == :query }
+ end
+
+ # Use "first" here instead of a more intuitive flatten because flatten
+ # will also ask each column to become an Array and that will start
+ # to retrieve data.
+ def column_included_in_queries?(column)
+ source_query_properties.collect(&:columns).collect(&:first).include?(column)
+ end
+end
diff --git a/lib/thinking_sphinx/active_record/sql_builder.rb b/lib/thinking_sphinx/active_record/sql_builder.rb
index f611bb477..875baaf8a 100644
--- a/lib/thinking_sphinx/active_record/sql_builder.rb
+++ b/lib/thinking_sphinx/active_record/sql_builder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module ActiveRecord
class SQLBuilder
@@ -16,20 +18,10 @@ def sql_query_range
statement.to_query_range_relation.to_sql
end
- def sql_query_info
- statement.to_query_info_relation.to_sql
- end
-
def sql_query_pre
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
@@ -53,18 +45,9 @@ def relation
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)
@@ -96,10 +79,6 @@ def document_id
"#{column} AS #{quoted_alias}"
end
- def reversed_document_id
- "($id - #{source.offset}) / #{config.indices.count}"
- end
-
def range_condition
condition = []
condition << "#{quoted_primary_key} BETWEEN $start AND $end" unless source.disable_range?
diff --git a/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb b/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb
index 397c73bba..a7ad895b2 100644
--- a/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb
+++ b/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module ActiveRecord
class SQLBuilder::ClauseBuilder
diff --git a/lib/thinking_sphinx/active_record/sql_builder/query.rb b/lib/thinking_sphinx/active_record/sql_builder/query.rb
index 579352692..5de6bd7fd 100644
--- a/lib/thinking_sphinx/active_record/sql_builder/query.rb
+++ b/lib/thinking_sphinx/active_record/sql_builder/query.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module ActiveRecord
class SQLBuilder::Query
@@ -18,10 +20,17 @@ def to_query
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]
@@ -38,6 +47,10 @@ def scope_by_utf8
self.scope += utf8_query_pre if source.options[:utf8?]
end
+ def source
+ report.source
+ end
+
def method_missing(*args, &block)
report.send *args, &block
end
diff --git a/lib/thinking_sphinx/active_record/sql_builder/statement.rb b/lib/thinking_sphinx/active_record/sql_builder/statement.rb
index 0c468cfbd..00caceb3b 100644
--- a/lib/thinking_sphinx/active_record/sql_builder/statement.rb
+++ b/lib/thinking_sphinx/active_record/sql_builder/statement.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'thinking_sphinx/active_record/sql_builder/clause_builder'
module ThinkingSphinx
@@ -20,12 +22,6 @@ def to_query_range_relation
scope
end
- def to_query_info_relation
- filter_by_query_info
-
- scope
- end
-
def to_query_pre
filter_by_query_pre
@@ -49,10 +45,6 @@ def filter_by_query_range
)
end
- def filter_by_query_info
- @scope = scope.where("#{quoted_primary_key} = #{reversed_document_id}")
- end
-
def filter_by_scopes
scope_by_select
scope_by_where_clause
@@ -140,10 +132,16 @@ def group_clause
builder.compose(
presenters_to_group(field_presenters),
presenters_to_group(attribute_presenters)
- ) unless source.options[:minimal_group_by?]
+ ) unless minimal_group_by?
builder.compose(groupings).separated
end
+
+ def minimal_group_by?
+ source.options[:minimal_group_by?] ||
+ config.settings['minimal_group_by?'] ||
+ config.settings['minimal_group_by']
+ end
end
end
end
diff --git a/lib/thinking_sphinx/active_record/sql_source.rb b/lib/thinking_sphinx/active_record/sql_source.rb
index 6ac56227a..b732439d7 100644
--- a/lib/thinking_sphinx/active_record/sql_source.rb
+++ b/lib/thinking_sphinx/active_record/sql_source.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module ActiveRecord
class SQLSource < Riddle::Configuration::SQLSource
include ThinkingSphinx::Core::Settings
- attr_reader :model, :database_settings, :options
+
+ attr_reader :model, :options
attr_accessor :fields, :attributes, :associations, :conditions,
:groupings, :polymorphs
@@ -12,9 +15,8 @@ class SQLSource < Riddle::Configuration::SQLSource
def initialize(model, options = {})
@model = model
- @database_settings = model.connection.instance_variable_get(:@config).clone
@options = {
- :utf8? => (@database_settings[:encoding] == 'utf8')
+ :utf8? => (database_settings[:encoding].to_s[/^utf8/])
}.merge options
@fields = []
@@ -37,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
@@ -61,6 +75,10 @@ def primary_key
options[:primary_key]
end
+ def properties
+ fields + attributes
+ end
+
def render
prepare_for_render unless @prepared
@@ -74,6 +92,9 @@ def set_database_settings(settings)
@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
@@ -83,7 +104,7 @@ def type
when DatabaseAdapters::PostgreSQLAdapter
'pgsql'
else
- raise "Unknown Adapter Type: #{adapter.class.name}"
+ raise UnknownDatabaseAdapter, "Provided type: #{adapter.class.name}"
end
end
@@ -118,15 +139,23 @@ def build_sql_fields
def build_sql_query
@sql_query = builder.sql_query
@sql_query_range ||= builder.sql_query_range
- @sql_query_info ||= builder.sql_query_info
@sql_query_pre += builder.sql_query_pre
- @sql_query_post_index += builder.sql_query_post_index
end
def config
ThinkingSphinx::Configuration.instance
end
+ def database_settings
+ @database_settings ||= begin
+ if model.connection.respond_to?(:config)
+ model.connection.config.clone
+ else
+ model.connection.instance_variable_get(:@config).clone
+ end
+ end
+ end
+
def prepare_for_render
polymorphs.each &:morph!
append_presenter_to_attribute_array
@@ -137,10 +166,6 @@ def prepare_for_render
@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 13f4adb86..722d731bb 100644
--- a/lib/thinking_sphinx/active_record/sql_source/template.rb
+++ b/lib/thinking_sphinx/active_record/sql_source/template.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::ActiveRecord::SQLSource::Template
attr_reader :source
@@ -16,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)
)
@@ -48,6 +50,6 @@ def model
end
def primary_key
- source.model.primary_key.to_sym
+ source.options[:primary_key].to_sym
end
end
diff --git a/lib/thinking_sphinx/attribute_types.rb b/lib/thinking_sphinx/attribute_types.rb
new file mode 100644
index 000000000..9bff5952f
--- /dev/null
+++ b/lib/thinking_sphinx/attribute_types.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::AttributeTypes
+ def self.call
+ @call ||= new.call
+ end
+
+ def self.reset
+ @call = nil
+ end
+
+ def call
+ return {} unless File.exist?(configuration_file)
+
+ realtime_indices.each { |index|
+ map_types_with_prefix index, :rt,
+ [:uint, :bigint, :float, :timestamp, :string, :bool, :json]
+
+ index.rt_attr_multi.each { |name| attributes[name] << :uint }
+ index.rt_attr_multi_64.each { |name| attributes[name] << :bigint }
+ }
+
+ plain_sources.each { |source|
+ map_types_with_prefix source, :sql,
+ [:uint, :bigint, :float, :timestamp, :string, :bool, :json]
+
+ source.sql_attr_str2ordinal { |name| attributes[name] << :uint }
+ source.sql_attr_str2wordcount { |name| attributes[name] << :uint }
+ source.sql_attr_multi.each { |setting|
+ type, name, *ignored = setting.split(/\s+/)
+ attributes[name] << type.to_sym
+ }
+ }
+
+ attributes.values.each &:uniq!
+ attributes
+ end
+
+ private
+
+ def attributes
+ @attributes ||= Hash.new { |hash, key| hash[key] = [] }
+ end
+
+ def configuration
+ @configuration ||= Riddle::Configuration.parse!(
+ File.read(configuration_file)
+ )
+ end
+
+ def configuration_file
+ ThinkingSphinx::Configuration.instance.configuration_file
+ end
+
+ def map_types_with_prefix(object, prefix, types)
+ types.each do |type|
+ object.public_send("#{prefix}_attr_#{type}").each do |name|
+ attributes[name] << type
+ end
+ end
+ end
+
+ def plain_sources
+ configuration.indices.select { |index|
+ index.type == 'plain' || index.type.nil?
+ }.collect(&:sources).flatten
+ end
+
+ def realtime_indices
+ configuration.indices.select { |index| index.type == 'rt' }
+ end
+end
diff --git a/lib/thinking_sphinx/batched_search.rb b/lib/thinking_sphinx/batched_search.rb
index f771b680d..628752de3 100644
--- a/lib/thinking_sphinx/batched_search.rb
+++ b/lib/thinking_sphinx/batched_search.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::BatchedSearch
attr_accessor :searches
diff --git a/lib/thinking_sphinx/callbacks.rb b/lib/thinking_sphinx/callbacks.rb
index 0cf2d648a..b8d2d789e 100644
--- a/lib/thinking_sphinx/callbacks.rb
+++ b/lib/thinking_sphinx/callbacks.rb
@@ -1,6 +1,15 @@
+# frozen_string_literal: true
+
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|
@@ -9,7 +18,27 @@ def self.callbacks(*methods)
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
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..ba5894698
--- /dev/null
+++ b/lib/thinking_sphinx/callbacks/appender.rb
@@ -0,0 +1,63 @@
+# 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
+ add_core_callbacks
+ add_delta_callbacks if behaviours.include?(:deltas)
+ add_real_time_callbacks if behaviours.include?(:real_time)
+ add_update_callbacks if behaviours.include?(:updates)
+ end
+
+ private
+
+ attr_reader :model, :reference, :options, :block
+
+ def add_core_callbacks
+ model.after_commit(
+ ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks,
+ on: :destroy
+ )
+ end
+
+ def add_delta_callbacks
+ if path.empty?
+ model.before_save ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks
+ model.after_commit ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks
+ else
+ model.after_commit(
+ ThinkingSphinx::ActiveRecord::Callbacks::AssociationDeltaCallbacks
+ .new(path)
+ )
+ end
+ end
+
+ def add_real_time_callbacks
+ model.after_commit(
+ ThinkingSphinx::RealTime.callback_for(reference, path, &block),
+ on: [:create, :update]
+ )
+ end
+
+ def add_update_callbacks
+ model.after_update ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks
+ end
+
+ def behaviours
+ options[:behaviours] || []
+ end
+
+ def path
+ options[:path] || []
+ end
+end
diff --git a/lib/thinking_sphinx/capistrano.rb b/lib/thinking_sphinx/capistrano.rb
index c976e45cd..0ad84aa21 100644
--- a/lib/thinking_sphinx/capistrano.rb
+++ b/lib/thinking_sphinx/capistrano.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
if defined?(Capistrano::VERSION)
if Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
recipe_version = 3
diff --git a/lib/thinking_sphinx/capistrano/v2.rb b/lib/thinking_sphinx/capistrano/v2.rb
index d319ed868..0098e69cc 100644
--- a/lib/thinking_sphinx/capistrano/v2.rb
+++ b/lib/thinking_sphinx/capistrano/v2.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
Capistrano::Configuration.instance(:must_exist).load do
_cset(:thinking_sphinx_roles) { :db }
_cset(:thinking_sphinx_options) { {:roles => fetch(:thinking_sphinx_roles)} }
diff --git a/lib/thinking_sphinx/capistrano/v3.rb b/lib/thinking_sphinx/capistrano/v3.rb
index 1f8919bbd..ddb28771e 100644
--- a/lib/thinking_sphinx/capistrano/v3.rb
+++ b/lib/thinking_sphinx/capistrano/v3.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
namespace :load do
task :defaults do
set :thinking_sphinx_roles, :db
diff --git a/lib/thinking_sphinx/commander.rb b/lib/thinking_sphinx/commander.rb
new file mode 100644
index 000000000..57a14c734
--- /dev/null
+++ b/lib/thinking_sphinx/commander.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commander
+ def self.call(command, configuration, options, stream = STDOUT)
+ raise ThinkingSphinx::UnknownCommand unless registry.keys.include?(command)
+
+ registry[command].call configuration, options, stream
+ end
+
+ def self.registry
+ @registry ||= {
+ :clear_real_time => ThinkingSphinx::Commands::ClearRealTime,
+ :clear_sql => ThinkingSphinx::Commands::ClearSQL,
+ :configure => ThinkingSphinx::Commands::Configure,
+ :index_sql => ThinkingSphinx::Commands::IndexSQL,
+ :index_real_time => ThinkingSphinx::Commands::IndexRealTime,
+ :merge => ThinkingSphinx::Commands::Merge,
+ :merge_and_update => ThinkingSphinx::Commands::MergeAndUpdate,
+ :prepare => ThinkingSphinx::Commands::Prepare,
+ :rotate => ThinkingSphinx::Commands::Rotate,
+ :running => ThinkingSphinx::Commands::Running,
+ :start_attached => ThinkingSphinx::Commands::StartAttached,
+ :start_detached => ThinkingSphinx::Commands::StartDetached,
+ :stop => ThinkingSphinx::Commands::Stop
+ }
+ end
+end
diff --git a/lib/thinking_sphinx/commands.rb b/lib/thinking_sphinx/commands.rb
new file mode 100644
index 000000000..47b6cd50a
--- /dev/null
+++ b/lib/thinking_sphinx/commands.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module ThinkingSphinx::Commands
+ #
+end
+
+require 'thinking_sphinx/commands/base'
+require 'thinking_sphinx/commands/clear_real_time'
+require 'thinking_sphinx/commands/clear_sql'
+require 'thinking_sphinx/commands/configure'
+require 'thinking_sphinx/commands/index_sql'
+require 'thinking_sphinx/commands/index_real_time'
+require 'thinking_sphinx/commands/merge'
+require 'thinking_sphinx/commands/merge_and_update'
+require 'thinking_sphinx/commands/prepare'
+require 'thinking_sphinx/commands/rotate'
+require 'thinking_sphinx/commands/running'
+require 'thinking_sphinx/commands/start_attached'
+require 'thinking_sphinx/commands/start_detached'
+require 'thinking_sphinx/commands/stop'
diff --git a/lib/thinking_sphinx/commands/base.rb b/lib/thinking_sphinx/commands/base.rb
new file mode 100644
index 000000000..4a34cf975
--- /dev/null
+++ b/lib/thinking_sphinx/commands/base.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::Base
+ include ThinkingSphinx::WithOutput
+
+ def self.call(configuration, options, stream = STDOUT)
+ new(configuration, options, stream).call_with_handling
+ end
+
+ def call_with_handling
+ call
+ rescue Riddle::CommandFailedError => error
+ handle_failure error.command_result
+ end
+
+ private
+
+ delegate :controller, :to => :configuration
+
+ def command(command, extra_options = {})
+ ThinkingSphinx::Commander.call(
+ command, configuration, options.merge(extra_options), stream
+ )
+ end
+
+ def command_output(output)
+ return "See above\n" if output.nil?
+
+ "\n\t" + output.gsub("\n", "\n\t")
+ end
+
+ def handle_failure(result)
+ stream.puts <<-TXT
+
+The Sphinx #{type} command failed:
+ Command: #{result.command}
+ Status: #{result.status}
+ Output: #{command_output result.output}
+There may be more information about the failure in #{configuration.searchd.log}.
+ TXT
+ exit(result.status || 1)
+ end
+
+ def log(message)
+ return if options[:silent]
+
+ stream.puts message
+ end
+
+ def skip_directories?
+ configuration.settings['skip_directory_creation']
+ end
+end
diff --git a/lib/thinking_sphinx/commands/clear_real_time.rb b/lib/thinking_sphinx/commands/clear_real_time.rb
new file mode 100644
index 000000000..9312e6caf
--- /dev/null
+++ b/lib/thinking_sphinx/commands/clear_real_time.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::ClearRealTime < ThinkingSphinx::Commands::Base
+ def call
+ options[:indices].each do |index|
+ index.render
+ Dir["#{index.path}.*"].each { |path| FileUtils.rm path }
+ end
+
+ FileUtils.rm_rf(binlog_path) if File.exist?(binlog_path)
+ end
+
+ private
+
+ def binlog_path
+ configuration.searchd.binlog_path
+ end
+
+ def type
+ 'clear_realtime'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/clear_sql.rb b/lib/thinking_sphinx/commands/clear_sql.rb
new file mode 100644
index 000000000..512c97d04
--- /dev/null
+++ b/lib/thinking_sphinx/commands/clear_sql.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::ClearSQL < ThinkingSphinx::Commands::Base
+ def call
+ options[:indices].each do |index|
+ index.render
+ Dir["#{index.path}.*"].each { |path| FileUtils.rm path }
+ end
+
+ FileUtils.rm_rf Dir["#{configuration.indices_location}/ts-*.tmp"]
+ end
+
+ private
+
+ def type
+ 'clear_sql'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/configure.rb b/lib/thinking_sphinx/commands/configure.rb
new file mode 100644
index 000000000..e0a298ffc
--- /dev/null
+++ b/lib/thinking_sphinx/commands/configure.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::Configure < ThinkingSphinx::Commands::Base
+ def call
+ log "Generating configuration to #{configuration.configuration_file}"
+
+ configuration.render_to_file
+ end
+
+ private
+
+ def type
+ 'configure'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/index_real_time.rb b/lib/thinking_sphinx/commands/index_real_time.rb
new file mode 100644
index 000000000..cfc2a7680
--- /dev/null
+++ b/lib/thinking_sphinx/commands/index_real_time.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::IndexRealTime < ThinkingSphinx::Commands::Base
+ def call
+ ThinkingSphinx::RealTime.processor.call options[:indices] do
+ command :rotate
+ end
+ end
+
+ private
+
+ def type
+ 'indexing'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/index_sql.rb b/lib/thinking_sphinx/commands/index_sql.rb
new file mode 100644
index 000000000..681660a81
--- /dev/null
+++ b/lib/thinking_sphinx/commands/index_sql.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::IndexSQL < ThinkingSphinx::Commands::Base
+ def call
+ if indices.empty?
+ ThinkingSphinx.before_index_hooks.each { |hook| hook.call }
+ end
+
+ configuration.indexing_strategy.call(indices) do |index_names|
+ configuration.guarding_strategy.call(index_names) do |names|
+ controller.index *names, :verbose => options[:verbose]
+ end
+ end
+ end
+
+ private
+
+ def indices
+ options[:indices] || []
+ end
+
+ def type
+ 'indexing'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/merge.rb b/lib/thinking_sphinx/commands/merge.rb
new file mode 100644
index 000000000..c78bd6e0f
--- /dev/null
+++ b/lib/thinking_sphinx/commands/merge.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::Merge < ThinkingSphinx::Commands::Base
+ def call
+ return unless indices_exist?
+
+ controller.merge(
+ options[:core_index].name,
+ options[:delta_index].name,
+ :filters => options[:filters],
+ :verbose => options[:verbose]
+ )
+ end
+
+ private
+
+ delegate :controller, :to => :configuration
+
+ def indices_exist?
+ File.exist?("#{options[:core_index].path}.spi") &&
+ File.exist?("#{options[:delta_index].path}.spi")
+ end
+
+ def type
+ 'merging'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/merge_and_update.rb b/lib/thinking_sphinx/commands/merge_and_update.rb
new file mode 100644
index 000000000..b3876b94b
--- /dev/null
+++ b/lib/thinking_sphinx/commands/merge_and_update.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::MergeAndUpdate < ThinkingSphinx::Commands::Base
+ def call
+ configuration.preload_indices
+ configuration.render
+
+ index_pairs.each do |(core_index, delta_index)|
+ command :merge,
+ :core_index => core_index,
+ :delta_index => delta_index,
+ :filters => {:sphinx_deleted => 0}
+
+ core_index.model.where(:delta => true).update_all(:delta => false)
+ end
+ end
+
+ private
+
+ delegate :controller, :to => :configuration
+
+ def core_indices
+ indices.select { |index| !index.delta? }.select do |index|
+ name_filters.empty? ||
+ name_filters.include?(index.name.gsub(/_core$/, ''))
+ end
+ end
+
+ def delta_for(core_index)
+ name = core_index.name.gsub(/_core$/, "_delta")
+ indices.detect { |index| index.name == name }
+ end
+
+ def index_pairs
+ core_indices.collect { |core_index|
+ [core_index, delta_for(core_index)]
+ }
+ end
+
+ def indices
+ @indices ||= configuration.indices.select { |index|
+ index.type == "plain" && index.options[:delta_processor]
+ }
+ end
+
+ def indices_exist?(*indices)
+ indices.all? { |index| File.exist?("#{index.path}.spi") }
+ end
+
+ def name_filters
+ @name_filters ||= options[:index_names] || []
+ end
+
+ def type
+ 'merging_and_updating'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/prepare.rb b/lib/thinking_sphinx/commands/prepare.rb
new file mode 100644
index 000000000..8dd7f69e3
--- /dev/null
+++ b/lib/thinking_sphinx/commands/prepare.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::Prepare < ThinkingSphinx::Commands::Base
+ def call
+ return if skip_directories?
+
+ FileUtils.mkdir_p configuration.indices_location
+ end
+
+ private
+
+ def type
+ 'prepare'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/rotate.rb b/lib/thinking_sphinx/commands/rotate.rb
new file mode 100644
index 000000000..461393044
--- /dev/null
+++ b/lib/thinking_sphinx/commands/rotate.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::Rotate < ThinkingSphinx::Commands::Base
+ def call
+ controller.rotate
+ end
+
+ private
+
+ def type
+ 'rotate'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/running.rb b/lib/thinking_sphinx/commands/running.rb
new file mode 100644
index 000000000..f3b71b29f
--- /dev/null
+++ b/lib/thinking_sphinx/commands/running.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::Running < ThinkingSphinx::Commands::Base
+ def call
+ return true if configuration.settings['skip_running_check']
+
+ controller.running?
+ end
+
+ private
+
+ def type
+ 'running'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/start_attached.rb b/lib/thinking_sphinx/commands/start_attached.rb
new file mode 100644
index 000000000..21df8ac1e
--- /dev/null
+++ b/lib/thinking_sphinx/commands/start_attached.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::StartAttached < ThinkingSphinx::Commands::Base
+ def call
+ FileUtils.mkdir_p configuration.indices_location unless skip_directories?
+
+ unless pid = fork
+ controller.start :verbose => options[:verbose], :nodetach => true
+ end
+
+ Signal.trap('TERM') { Process.kill(:TERM, pid) }
+ Signal.trap('INT') { Process.kill(:TERM, pid) }
+
+ Process.wait(pid)
+ end
+
+ private
+
+ def type
+ 'start'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/start_detached.rb b/lib/thinking_sphinx/commands/start_detached.rb
new file mode 100644
index 000000000..451d89adb
--- /dev/null
+++ b/lib/thinking_sphinx/commands/start_detached.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::StartDetached < ThinkingSphinx::Commands::Base
+ def call
+ FileUtils.mkdir_p configuration.indices_location unless skip_directories?
+
+ result = controller.start :verbose => options[:verbose]
+
+ if command :running
+ log "Started searchd successfully (pid: #{controller.pid})."
+ else
+ handle_failure result
+ end
+ end
+
+ private
+
+ def type
+ 'start'
+ end
+end
diff --git a/lib/thinking_sphinx/commands/stop.rb b/lib/thinking_sphinx/commands/stop.rb
new file mode 100644
index 000000000..368120a1d
--- /dev/null
+++ b/lib/thinking_sphinx/commands/stop.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Commands::Stop < ThinkingSphinx::Commands::Base
+ def call
+ unless command :running
+ log 'searchd is not currently running.'
+ return
+ end
+
+ pid = controller.pid
+ until !command :running do
+ controller.stop options
+ sleep(0.5)
+ end
+
+ log "Stopped searchd daemon (pid: #{pid})."
+ end
+
+ private
+
+ def type
+ 'stop'
+ end
+end
diff --git a/lib/thinking_sphinx/configuration.rb b/lib/thinking_sphinx/configuration.rb
index 1dfdb16c1..38704353f 100644
--- a/lib/thinking_sphinx/configuration.rb
+++ b/lib/thinking_sphinx/configuration.rb
@@ -1,16 +1,22 @@
+# frozen_string_literal: true
+
require 'pathname'
class ThinkingSphinx::Configuration < Riddle::Configuration
- attr_accessor :configuration_file, :indices_location, :version
+ attr_accessor :configuration_file, :indices_location, :version, :batch_size
attr_reader :index_paths
- attr_writer :controller, :index_set_class
+ attr_writer :controller, :index_set_class, :indexing_strategy,
+ :guarding_strategy
delegate :environment, :to => :framework
+ @@mutex = defined?(ActiveSupport::Concurrency::LoadInterlockAwareMonitor) ?
+ ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new : Mutex.new
+
def initialize
super
- setup
+ reset
end
def self.instance
@@ -27,7 +33,7 @@ def bin_path
def controller
@controller ||= begin
- rc = ThinkingSphinx::Controller.new self, configuration_file
+ rc = Riddle::Controller.new self, configuration_file
rc.bin_path = bin_path.gsub(/([^\/])$/, '\1/') if bin_path.present?
rc
end
@@ -39,14 +45,14 @@ def framework
def framework=(framework)
@framework = framework
- setup
+ reset
framework
end
def engine_index_paths
return [] unless defined?(Rails)
- engine_indice_paths.flatten.compact
+ engine_indice_paths.flatten.compact.sort
end
def engine_indice_paths
@@ -56,10 +62,18 @@ def engine_indice_paths
end
end
+ def guarding_strategy
+ @guarding_strategy ||= ThinkingSphinx::Guard::Files
+ end
+
def index_set_class
@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,105 +83,105 @@ def next_offset(reference)
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 { |file| preload_index file }
end
- end
- if settings['distributed_indices'].nil? || settings['distributed_indices']
- ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
+ normalise
+ verify
+
+ @preloaded_indices = true
end
+ end
- @preloaded_indices = true
+ def preload_index(file)
+ if ActiveRecord::VERSION::MAJOR <= 5
+ ActiveSupport::Dependencies.require_or_load file
+ else
+ load file
+ end
end
def render
preload_indices
- ThinkingSphinx::Configuration::ConsistentIds.new(indices).reconcile
- ThinkingSphinx::Configuration::MinimumFields.new(indices).reconcile
-
super
end
def render_to_file
- FileUtils.mkdir_p searchd.binlog_path unless searchd.binlog_path.blank?
+ unless settings['skip_directory_creation'] || searchd.binlog_path.blank?
+ FileUtils.mkdir_p searchd.binlog_path
+ end
open(configuration_file, 'w') { |file| file.write render }
end
def settings
- @settings ||= File.exists?(settings_file) ? settings_to_hash : {}
+ @settings ||= ThinkingSphinx::Settings.call self
end
- private
-
- def configure_searchd
- configure_searchd_log_files
+ def setup
+ @configuration_file = settings['configuration_file']
+ @index_paths = engine_index_paths +
+ [Pathname.new(framework.root).join('app', 'indices').to_s]
+ @indices_location = settings['indices_location']
+ @version = settings['version'] || '2.2.11'
+ @batch_size = settings['batch_size'] || 1000
- searchd.binlog_path = tmp_path.join('binlog', environment).to_s
- searchd.address = settings['address'].presence || Defaults::ADDRESS
- searchd.mysql41 = settings['mysql41'] || settings['port'] || Defaults::PORT
- searchd.workers = 'threads'
- searchd.mysql_version_string = '5.5.21' if RUBY_PLATFORM == 'java'
- end
+ if settings['common_sphinx_configuration']
+ common.common_sphinx_configuration = true
+ indexer.common_sphinx_configuration = true
+ end
- def configure_searchd_log_files
- searchd.pid_file = log_root.join("#{environment}.sphinx.pid").to_s
- searchd.log = log_root.join("#{environment}.searchd.log").to_s
- searchd.query_log = log_root.join("#{environment}.searchd.query.log").to_s
- end
+ configure_searchd
- def log_root
- real_path 'log'
- end
+ apply_sphinx_settings!
- def framework_root
- Pathname.new(framework.root)
+ @offsets = {}
end
- def real_path(*arguments)
- path = framework_root.join(*arguments)
- path.exist? ? path.realpath : path
- end
+ private
- def settings_to_hash
- contents = YAML.load(ERB.new(File.read(settings_file)).result)
- contents && contents[environment] || {}
- end
+ def apply_sphinx_settings!
+ sphinx_sections.each do |object|
+ settings.each do |key, value|
+ next unless object.class.settings.include?(key.to_sym)
- def settings_file
- framework_root.join 'config', 'thinking_sphinx.yml'
+ object.send("#{key}=", value)
+ end
+ end
end
- def setup
- @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'
+ def configure_searchd
+ searchd.socket = "#{settings["socket"]}:mysql41" if socket?
- if settings['common_sphinx_configuration']
- common.common_sphinx_configuration = true
- indexer.common_sphinx_configuration = true
+ if tcp?
+ searchd.address = settings['address'].presence || Defaults::ADDRESS
+ searchd.mysql41 = settings['mysql41'] || settings['port'] || Defaults::PORT
end
- configure_searchd
+ searchd.mysql_version_string = '5.5.21' if RUBY_PLATFORM == 'java'
+ end
- apply_sphinx_settings!
+ def normalise
+ if settings['distributed_indices'].nil? || settings['distributed_indices']
+ ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
+ end
- @offsets = {}
+ ThinkingSphinx::Configuration::ConsistentIds.new(indices).reconcile
+ ThinkingSphinx::Configuration::MinimumFields.new(indices).reconcile
+ end
+
+ def reset
+ @settings = nil
+ setup
end
- def tmp_path
- real_path 'tmp'
+ def socket?
+ settings["socket"].present?
end
def sphinx_sections
@@ -176,18 +190,20 @@ def sphinx_sections
sections
end
- def apply_sphinx_settings!
- sphinx_sections.each do |object|
- settings.each do |key, value|
- next unless object.class.settings.include?(key.to_sym)
+ def tcp?
+ settings["socket"].nil? ||
+ settings["address"].present? ||
+ settings["mysql41"].present? ||
+ settings["port"].present?
+ end
- object.send("#{key}=", value)
- end
- end
+ def verify
+ ThinkingSphinx::Configuration::DuplicateNames.new(indices).reconcile
end
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/consistent_ids.rb b/lib/thinking_sphinx/configuration/consistent_ids.rb
index 61fd90447..afd7420a0 100644
--- a/lib/thinking_sphinx/configuration/consistent_ids.rb
+++ b/lib/thinking_sphinx/configuration/consistent_ids.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Configuration::ConsistentIds
def initialize(indices)
@indices = indices
diff --git a/lib/thinking_sphinx/configuration/defaults.rb b/lib/thinking_sphinx/configuration/defaults.rb
index a495a6c30..b57dd9c30 100644
--- a/lib/thinking_sphinx/configuration/defaults.rb
+++ b/lib/thinking_sphinx/configuration/defaults.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Configuration::Defaults
ADDRESS = '127.0.0.1'
PORT = 9306
diff --git a/lib/thinking_sphinx/configuration/distributed_indices.rb b/lib/thinking_sphinx/configuration/distributed_indices.rb
index 13ea73f50..b4e4b08f2 100644
--- a/lib/thinking_sphinx/configuration/distributed_indices.rb
+++ b/lib/thinking_sphinx/configuration/distributed_indices.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Configuration::DistributedIndices
def initialize(indices)
@indices = indices
@@ -19,7 +21,7 @@ def append(index)
def distributed_index(reference, indices)
index = ThinkingSphinx::Distributed::Index.new reference
- index.local_indices += indices.collect &:name
+ index.local_index_objects = indices
index
end
diff --git a/lib/thinking_sphinx/configuration/duplicate_names.rb b/lib/thinking_sphinx/configuration/duplicate_names.rb
new file mode 100644
index 000000000..8741d93b8
--- /dev/null
+++ b/lib/thinking_sphinx/configuration/duplicate_names.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+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/configuration/minimum_fields.rb b/lib/thinking_sphinx/configuration/minimum_fields.rb
index 3563260bc..b96824caf 100644
--- a/lib/thinking_sphinx/configuration/minimum_fields.rb
+++ b/lib/thinking_sphinx/configuration/minimum_fields.rb
@@ -1,13 +1,13 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Configuration::MinimumFields
def initialize(indices)
@indices = indices
end
def reconcile
- return unless no_inheritance_columns?
-
- sources.each do |source|
- source.fields.delete_if do |field|
+ field_collections.each do |collection|
+ collection.fields.delete_if do |field|
field.name == 'sphinx_internal_class_name'
end
end
@@ -17,15 +17,20 @@ def reconcile
attr_reader :indices
- def no_inheritance_columns?
- indices.select { |index|
- index.model.column_names.include?(index.model.inheritance_column)
- }.empty?
+ def field_collections
+ indices_without_inheritance_of_type('plain').collect(&:sources).flatten +
+ indices_without_inheritance_of_type('rt')
+ end
+
+ def inheritance_columns?(index)
+ index.model.table_exists? && index.model.column_names.include?(index.model.inheritance_column)
+ end
+
+ def indices_without_inheritance_of_type(type)
+ indices_without_inheritance.select { |index| index.type == type }
end
- def sources
- @sources ||= @indices.select { |index|
- index.respond_to?(:sources)
- }.collect(&:sources).flatten
+ def indices_without_inheritance
+ indices.reject(&method(:inheritance_columns?))
end
end
diff --git a/lib/thinking_sphinx/connection.rb b/lib/thinking_sphinx/connection.rb
index 76d8f0d50..b3e954174 100644
--- a/lib/thinking_sphinx/connection.rb
+++ b/lib/thinking_sphinx/connection.rb
@@ -1,22 +1,25 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Connection
MAXIMUM_RETRIES = 3
def self.new
configuration = ThinkingSphinx::Configuration.instance
- # If you use localhost, MySQL insists on a socket connection, but Sphinx
- # requires a TCP connection. Using 127.0.0.1 fixes that.
- address = configuration.searchd.address || '127.0.0.1'
- address = '127.0.0.1' if address == 'localhost'
options = {
- :host => address,
+ :host => configuration.searchd.address,
:port => configuration.searchd.mysql41,
+ :socket => configuration.searchd.socket,
:reconnect => true
}.merge(configuration.settings['connection_options'] || {})
connection_class.new options
end
+ def self.clear
+ @pool = nil
+ end
+
def self.connection_class
return ThinkingSphinx::Connection::JRuby if RUBY_PLATFORM == 'java'
@@ -26,7 +29,7 @@ def self.connection_class
def self.pool
@pool ||= Innertube::Pool.new(
Proc.new { ThinkingSphinx::Connection.new },
- Proc.new { |connection| connection.close }
+ Proc.new { |connection| connection.close! }
)
end
@@ -63,112 +66,8 @@ def self.persistent=(persist)
end
@persistent = true
-
- class Client
- def close
- client.close unless ThinkingSphinx::Connection.persistent?
- end
-
- def execute(statement)
- query(statement).first
- end
-
- def query_all(*statements)
- query *statements
- end
-
- private
-
- def close_and_clear
- client.close
- @client = nil
- end
-
- def query(*statements)
- results_for *statements
- rescue => error
- message = "#{error.message} - #{statements.join('; ')}"
- wrapper = ThinkingSphinx::QueryExecutionError.new message
- wrapper.statement = statements.join('; ')
- raise wrapper
- ensure
- close_and_clear unless ThinkingSphinx::Connection.persistent?
- end
- end
-
- class MRI < Client
- def initialize(options)
- @options = options
- end
-
- def base_error
- Mysql2::Error
- end
-
- private
-
- attr_reader :options
-
- def client
- @client ||= Mysql2::Client.new({
- :flags => Mysql2::Client::MULTI_STATEMENTS
- }.merge(options))
- rescue base_error => error
- raise ThinkingSphinx::SphinxError.new_from_mysql error
- end
-
- def results_for(*statements)
- results = [client.query(statements.join('; '))]
- results << client.store_result while client.next_result
- results
- end
- end
-
- class JRuby < Client
- attr_reader :address, :options
-
- def initialize(options)
- @address = "jdbc:mysql://#{options[:host]}:#{options[:port]}/?allowMultiQueries=true"
- @options = options
- end
-
- def base_error
- Java::JavaSql::SQLException
- end
-
- private
-
- def client
- @client ||= java.sql.DriverManager.getConnection address,
- options[:username], options[:password]
- rescue base_error => error
- raise ThinkingSphinx::SphinxError.new_from_mysql error
- end
-
- def results_for(*statements)
- statement = client.createStatement
- statement.execute statements.join('; ')
-
- results = [set_to_array(statement.getResultSet)]
- results << set_to_array(statement.getResultSet) while statement.getMoreResults
- results.compact
- end
-
- def set_to_array(set)
- return nil if set.nil?
-
- meta = set.meta_data
- rows = []
-
- while set.next
- rows << (1..meta.column_count).inject({}) do |row, index|
- name = meta.column_name index
- row[name] = set.get_object(index)
- row
- end
- end
-
- rows
- end
- end
end
+
+require 'thinking_sphinx/connection/client'
+require 'thinking_sphinx/connection/jruby'
+require 'thinking_sphinx/connection/mri'
diff --git a/lib/thinking_sphinx/connection/client.rb b/lib/thinking_sphinx/connection/client.rb
new file mode 100644
index 000000000..03313cf96
--- /dev/null
+++ b/lib/thinking_sphinx/connection/client.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Connection::Client
+ def initialize(options)
+ if options[:socket].present?
+ options[:socket] = options[:socket].remove /:mysql41$/
+
+ options.delete :host
+ options.delete :port
+ else
+ options.delete :socket
+
+ # If you use localhost, MySQL insists on a socket connection, but in this
+ # situation we want a TCP connection. Using 127.0.0.1 fixes that.
+ if options[:host].nil? || options[:host] == "localhost"
+ options[:host] = "127.0.0.1"
+ end
+ end
+
+ @options = options
+ end
+
+ def close
+ close! unless ThinkingSphinx::Connection.persistent?
+ end
+
+ def close!
+ client.close
+ end
+
+ def execute(statement)
+ check_and_perform(statement).first
+ end
+
+ def query_all(*statements)
+ check_and_perform statements.join('; ')
+ end
+
+ private
+
+ def check(statements)
+ if statements.length > maximum_statement_length
+ exception = ThinkingSphinx::QueryLengthError.new
+ exception.statement = statements
+ raise exception
+ end
+ end
+
+ def check_and_perform(statements)
+ check statements
+ perform statements
+ end
+
+ def close_and_clear
+ client.close
+ @client = nil
+ end
+
+ def maximum_statement_length
+ @maximum_statement_length ||= ThinkingSphinx::Configuration.instance.
+ settings['maximum_statement_length']
+ end
+
+ def perform(statements)
+ results_for statements
+ rescue => error
+ message = "#{error.message} - #{statements}"
+ wrapper = ThinkingSphinx::QueryExecutionError.new message
+ wrapper.statement = statements
+ raise wrapper
+ ensure
+ close_and_clear unless ThinkingSphinx::Connection.persistent?
+ end
+end
diff --git a/lib/thinking_sphinx/connection/jruby.rb b/lib/thinking_sphinx/connection/jruby.rb
new file mode 100644
index 000000000..6e0295cc5
--- /dev/null
+++ b/lib/thinking_sphinx/connection/jruby.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Connection::JRuby < ThinkingSphinx::Connection::Client
+ attr_reader :address, :options
+
+ def initialize(options)
+ options.delete :socket
+
+ super
+
+ @address = "jdbc:mysql://#{@options[:host]}:#{@options[:port]}/?allowMultiQueries=true"
+ end
+
+ def base_error
+ Java::JavaSql::SQLException
+ end
+
+ private
+
+ def client
+ @client ||= Java::ComMysqlJdbc::Driver.new.connect address, properties
+ rescue base_error => error
+ raise ThinkingSphinx::SphinxError.new_from_mysql error
+ end
+
+ def properties
+ object = Java::JavaUtil::Properties.new
+ object.setProperty "user", options[:username] if options[:username]
+ object.setProperty "password", options[:password] if options[:password]
+ object
+ end
+
+ def results_for(statements)
+ statement = client.createStatement
+ statement.execute statements
+
+ results = [set_to_array(statement.getResultSet)]
+ results << set_to_array(statement.getResultSet) while statement.getMoreResults
+ results.compact
+ end
+
+ def set_to_array(set)
+ return nil if set.nil?
+
+ meta = set.getMetaData
+ rows = []
+
+ while set.next
+ rows << (1..meta.getColumnCount).inject({}) do |row, index|
+ name = meta.getColumnName index
+ row[name] = set.getObject(index)
+ row
+ end
+ end
+
+ rows
+ end
+end
diff --git a/lib/thinking_sphinx/connection/mri.rb b/lib/thinking_sphinx/connection/mri.rb
new file mode 100644
index 000000000..323327f3c
--- /dev/null
+++ b/lib/thinking_sphinx/connection/mri.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Connection::MRI < ThinkingSphinx::Connection::Client
+ def base_error
+ Mysql2::Error
+ end
+
+ private
+
+ attr_reader :options
+
+ def client
+ @client ||= Mysql2::Client.new({
+ :flags => Mysql2::Client::MULTI_STATEMENTS,
+ :connect_timeout => 5
+ }.merge(options))
+ rescue base_error => error
+ raise ThinkingSphinx::SphinxError.new_from_mysql error
+ end
+
+ def results_for(statements)
+ results = [client.query(statements)]
+ results << client.store_result while client.next_result
+ results
+ end
+end
diff --git a/lib/thinking_sphinx/controller.rb b/lib/thinking_sphinx/controller.rb
deleted file mode 100644
index add386c47..000000000
--- a/lib/thinking_sphinx/controller.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class ThinkingSphinx::Controller < Riddle::Controller
- def index(*indices)
- options = indices.extract_options!
- indices << '--all' if indices.empty?
-
- ThinkingSphinx::Guard::Files.call(indices) do |names|
- super(*(names + [options]))
- end
- end
-end
diff --git a/lib/thinking_sphinx/core.rb b/lib/thinking_sphinx/core.rb
index 63ed190b2..24325a5da 100644
--- a/lib/thinking_sphinx/core.rb
+++ b/lib/thinking_sphinx/core.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Core
#
end
diff --git a/lib/thinking_sphinx/core/field.rb b/lib/thinking_sphinx/core/field.rb
index dfd426704..b7d7c5cd2 100644
--- a/lib/thinking_sphinx/core/field.rb
+++ b/lib/thinking_sphinx/core/field.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Core::Field
def infixing?
options[:infixes]
diff --git a/lib/thinking_sphinx/core/index.rb b/lib/thinking_sphinx/core/index.rb
index 0a9d1ad22..469062cba 100644
--- a/lib/thinking_sphinx/core/index.rb
+++ b/lib/thinking_sphinx/core/index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Core::Index
extend ActiveSupport::Concern
include ThinkingSphinx::Core::Settings
@@ -9,8 +11,6 @@ module ThinkingSphinx::Core::Index
def initialize(reference, options = {})
@reference = reference.to_sym
- @docinfo = :extern
- @charset_type = 'utf-8'
@options = options
@offset = config.next_offset(options[:offset_as] || reference)
@type = 'plain'
@@ -26,11 +26,22 @@ def distributed?
false
end
+ def document_id_for_instance(instance)
+ document_id_for_key instance.public_send(primary_key)
+ end
+
def document_id_for_key(key)
+ return nil if key.nil?
+
key * config.indices.count + offset
end
def interpret_definition!
+ table_exists = model.table_exists?
+ unless table_exists
+ Rails.logger.info "No table exists for #{model}. Index can not be created"
+ return
+ end
return if @interpreted_definition
apply_defaults!
@@ -48,6 +59,11 @@ def options
@options
end
+ def primary_key
+ @primary_key ||= @options[:primary_key] ||
+ config.settings['primary_key'] || model.primary_key || :id
+ end
+
def render
pre_render
set_path
@@ -85,7 +101,10 @@ def pre_render
end
def set_path
- FileUtils.mkdir_p path_prefix
+ unless config.settings['skip_directory_creation']
+ FileUtils.mkdir_p path_prefix
+ end
+
@path = File.join path_prefix, name
end
end
diff --git a/lib/thinking_sphinx/core/interpreter.rb b/lib/thinking_sphinx/core/interpreter.rb
index 33f637188..3982f95d7 100644
--- a/lib/thinking_sphinx/core/interpreter.rb
+++ b/lib/thinking_sphinx/core/interpreter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Core::Interpreter < BasicObject
def self.translate!(index, block)
new(index, block).translate!
diff --git a/lib/thinking_sphinx/core/property.rb b/lib/thinking_sphinx/core/property.rb
index 0ee722d4a..4012364d6 100644
--- a/lib/thinking_sphinx/core/property.rb
+++ b/lib/thinking_sphinx/core/property.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Core::Property
def facet?
options[:facet]
diff --git a/lib/thinking_sphinx/core/settings.rb b/lib/thinking_sphinx/core/settings.rb
index 86e9d06c5..df759530b 100644
--- a/lib/thinking_sphinx/core/settings.rb
+++ b/lib/thinking_sphinx/core/settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Core::Settings
private
def apply_defaults!(defaults = self.class.settings)
diff --git a/lib/thinking_sphinx/deletion.rb b/lib/thinking_sphinx/deletion.rb
index 61fc97cfd..02ae111bc 100644
--- a/lib/thinking_sphinx/deletion.rb
+++ b/lib/thinking_sphinx/deletion.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Deletion
delegate :name, :to => :index
@@ -20,31 +22,49 @@ 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)
- ThinkingSphinx::Connection.take do |connection|
- connection.execute statement
- end
- end
+ statement = statement.gsub(/\s*\n\s*/, ' ').strip
- class RealtimeDeletion < ThinkingSphinx::Deletion
- def perform
- execute Riddle::Query::Delete.new(name, document_ids_for_keys).to_sql
+ ThinkingSphinx::Logger.log :query, statement do
+ ThinkingSphinx::Connection.take do |connection|
+ connection.execute statement
+ end
end
end
class PlainDeletion < ThinkingSphinx::Deletion
def perform
- document_ids_for_keys.each_slice(1000) do |document_ids|
+ ids.each_slice(1000) do |some_ids|
execute <<-SQL
UPDATE #{name}
SET sphinx_deleted = 1
-WHERE id IN (#{document_ids.join(', ')})
+WHERE sphinx_internal_id IN (#{some_ids.join(', ')})
+ SQL
+ end
+ end
+ end
+
+ class RealtimeDeletion < ThinkingSphinx::Deletion
+ def perform
+ return unless callbacks_enabled?
+
+ ids.each_slice(1000) do |some_ids|
+ execute <<-SQL
+DELETE FROM #{name}
+WHERE sphinx_internal_id IN (#{some_ids.join(', ')})
SQL
end
end
+
+ private
+
+ def callbacks_enabled?
+ setting = configuration.settings['real_time_callbacks']
+ setting.nil? || setting
+ end
+
+ def configuration
+ ThinkingSphinx::Configuration.instance
+ end
end
end
diff --git a/lib/thinking_sphinx/deltas.rb b/lib/thinking_sphinx/deltas.rb
index 3c5bf682a..198c604a3 100644
--- a/lib/thinking_sphinx/deltas.rb
+++ b/lib/thinking_sphinx/deltas.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Deltas
def self.config
ThinkingSphinx::Configuration.instance
diff --git a/lib/thinking_sphinx/deltas/default_delta.rb b/lib/thinking_sphinx/deltas/default_delta.rb
index 08884e8bf..c2e06bba5 100644
--- a/lib/thinking_sphinx/deltas/default_delta.rb
+++ b/lib/thinking_sphinx/deltas/default_delta.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Deltas::DefaultDelta
attr_reader :adapter, :options
@@ -13,7 +15,7 @@ def clause(delta_source = false)
def delete(index, instance)
ThinkingSphinx::Deltas::DeleteJob.new(
- index.name, index.document_id_for_key(instance.id)
+ index.name, index.document_id_for_instance(instance)
).perform
end
diff --git a/lib/thinking_sphinx/deltas/delete_job.rb b/lib/thinking_sphinx/deltas/delete_job.rb
index 4c8cb3a09..90378435b 100644
--- a/lib/thinking_sphinx/deltas/delete_job.rb
+++ b/lib/thinking_sphinx/deltas/delete_job.rb
@@ -1,15 +1,27 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Deltas::DeleteJob
def initialize(index_name, document_id)
@index_name, @document_id = index_name, document_id
end
def perform
- ThinkingSphinx::Connection.take do |connection|
- connection.execute Riddle::Query.update(
- @index_name, @document_id, :sphinx_deleted => true
- )
+ return if @document_id.nil?
+
+ ThinkingSphinx::Logger.log :query, statement do
+ ThinkingSphinx::Connection.take do |connection|
+ connection.execute statement
+ end
end
rescue ThinkingSphinx::ConnectionError => error
# This isn't vital, so don't raise the error.
end
+
+ private
+
+ def statement
+ @statement ||= Riddle::Query.update(
+ @index_name, @document_id, :sphinx_deleted => true
+ )
+ end
end
diff --git a/lib/thinking_sphinx/deltas/index_job.rb b/lib/thinking_sphinx/deltas/index_job.rb
index 7e698aaae..f0d1e720d 100644
--- a/lib/thinking_sphinx/deltas/index_job.rb
+++ b/lib/thinking_sphinx/deltas/index_job.rb
@@ -1,16 +1,28 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Deltas::IndexJob
def initialize(index_name)
@index_name = index_name
end
def perform
- configuration.controller.index @index_name,
- :verbose => !configuration.settings['quiet_deltas']
+ ThinkingSphinx::Commander.call(
+ :index_sql, configuration,
+ :indices => [index_name],
+ :verbose => !quiet_deltas?
+ )
end
private
+ attr_reader :index_name
+
def configuration
@configuration ||= ThinkingSphinx::Configuration.instance
end
+
+ def quiet_deltas?
+ configuration.settings['quiet_deltas'].nil? ||
+ configuration.settings['quiet_deltas']
+ end
end
diff --git a/lib/thinking_sphinx/distributed.rb b/lib/thinking_sphinx/distributed.rb
index 9a072286d..48b845518 100644
--- a/lib/thinking_sphinx/distributed.rb
+++ b/lib/thinking_sphinx/distributed.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Distributed
#
end
diff --git a/lib/thinking_sphinx/distributed/index.rb b/lib/thinking_sphinx/distributed/index.rb
index 7720923c1..26142ec0b 100644
--- a/lib/thinking_sphinx/distributed/index.rb
+++ b/lib/thinking_sphinx/distributed/index.rb
@@ -1,11 +1,14 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Distributed::Index <
Riddle::Configuration::DistributedIndex
- attr_reader :reference, :options
+ attr_reader :reference, :options, :local_index_objects
def initialize(reference)
- @reference = reference
- @options = {}
+ @reference = reference
+ @options = {}
+ @local_index_objects = []
super reference.to_s.gsub('/', '_')
end
@@ -18,7 +21,26 @@ def distributed?
true
end
+ def facets
+ local_index_objects.collect(&:facets).flatten
+ end
+
+ def local_index_objects=(indices)
+ self.local_indices = indices.collect(&:name)
+ @local_index_objects = indices
+ end
+
def model
@model ||= reference.to_s.camelize.constantize
end
+
+ def primary_key
+ @primary_key ||= configuration.settings['primary_key'] || :id
+ end
+
+ private
+
+ def configuration
+ ThinkingSphinx::Configuration.instance
+ end
end
diff --git a/lib/thinking_sphinx/errors.rb b/lib/thinking_sphinx/errors.rb
index 2d37686ff..35d069a7d 100644
--- a/lib/thinking_sphinx/errors.rb
+++ b/lib/thinking_sphinx/errors.rb
@@ -1,18 +1,24 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::SphinxError < StandardError
attr_accessor :statement
def self.new_from_mysql(error)
case error.message
- when /parse error/
+ when /parse error/, /query is non-computable/
replacement = ThinkingSphinx::ParseError.new(error.message)
when /syntax error/
replacement = ThinkingSphinx::SyntaxError.new(error.message)
- when /query error/
+ when /query error/, /unknown column/
replacement = ThinkingSphinx::QueryError.new(error.message)
- when /Can't connect to MySQL server/, /Communications link failure/
+ when /Can't connect to( MySQL)? server/,
+ /Communications link failure/,
+ /Lost connection to( MySQL)? server/
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
@@ -29,12 +35,28 @@ class ThinkingSphinx::ConnectionError < ThinkingSphinx::SphinxError
class ThinkingSphinx::QueryError < ThinkingSphinx::SphinxError
end
+class ThinkingSphinx::QueryLengthError < ThinkingSphinx::SphinxError
+ def message
+ <<-MESSAGE
+The supplied SphinxQL statement is #{statement.length} characters long. The maximum allowed length is #{ThinkingSphinx::Configuration.instance.settings['maximum_statement_length']}.
+
+If this error has been raised during real-time index population, it's probably due to overly large batches of records being processed at once. The default is 1000, but you can lower it on a per-environment basis in config/thinking_sphinx.yml:
+
+ development:
+ batch_size: 500
+ MESSAGE
+ end
+end
+
class ThinkingSphinx::SyntaxError < ThinkingSphinx::QueryError
end
class ThinkingSphinx::ParseError < ThinkingSphinx::QueryError
end
+class ThinkingSphinx::OutOfBoundsError < ThinkingSphinx::QueryError
+end
+
class ThinkingSphinx::QueryExecutionError < StandardError
attr_accessor :statement
end
@@ -50,3 +72,25 @@ class ThinkingSphinx::MissingColumnError < StandardError
class ThinkingSphinx::PopulatedResultsError < StandardError
end
+
+class ThinkingSphinx::DuplicateNameError < StandardError
+end
+
+class ThinkingSphinx::InvalidDatabaseAdapter < StandardError
+end
+
+class ThinkingSphinx::SphinxAlreadyRunning < StandardError
+end
+
+class ThinkingSphinx::UnknownDatabaseAdapter < StandardError
+end
+
+class ThinkingSphinx::UnknownAttributeType < StandardError
+end
+
+class ThinkingSphinx::TranscriptionError < StandardError
+ attr_accessor :inner_exception, :instance, :property
+end
+
+class ThinkingSphinx::UnknownCommand < StandardError
+end
diff --git a/lib/thinking_sphinx/excerpter.rb b/lib/thinking_sphinx/excerpter.rb
index 65fc66505..7ec275aa5 100644
--- a/lib/thinking_sphinx/excerpter.rb
+++ b/lib/thinking_sphinx/excerpter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Excerpter
DefaultOptions = {
:before_match => '',
diff --git a/lib/thinking_sphinx/facet.rb b/lib/thinking_sphinx/facet.rb
index 6df636f48..650a83a02 100644
--- a/lib/thinking_sphinx/facet.rb
+++ b/lib/thinking_sphinx/facet.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Facet
attr_reader :name
@@ -11,7 +13,7 @@ def filter_type
def results_from(raw)
raw.inject({}) { |hash, row|
- hash[row[group_column]] = row[ThinkingSphinx::SphinxQL.count[:column]]
+ hash[row[group_column]] = row["sphinx_internal_count"]
hash
}
end
@@ -19,8 +21,7 @@ def results_from(raw)
private
def group_column
- @properties.any?(&:multi?) ?
- ThinkingSphinx::SphinxQL.group_by[:column] : name
+ @properties.any?(&:multi?) ? "sphinx_internal_group" : name
end
def use_field?
diff --git a/lib/thinking_sphinx/facet_search.rb b/lib/thinking_sphinx/facet_search.rb
index bb1170fa2..9f82dd70b 100644
--- a/lib/thinking_sphinx/facet_search.rb
+++ b/lib/thinking_sphinx/facet_search.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::FacetSearch
include Enumerable
@@ -112,8 +114,8 @@ def limit
def options_for(facet)
options.merge(
:select => [(options[:select] || '*'),
- "#{ThinkingSphinx::SphinxQL.group_by[:select]}",
- "#{ThinkingSphinx::SphinxQL.count[:select]}"
+ "groupby() AS sphinx_internal_group",
+ "id AS sphinx_document_id, count(DISTINCT sphinx_document_id) AS sphinx_internal_count"
].join(', '),
:group_by => facet.name,
:indices => index_names_for(facet),
diff --git a/lib/thinking_sphinx/float_formatter.rb b/lib/thinking_sphinx/float_formatter.rb
index 68b0a3d72..58e5c9bef 100644
--- a/lib/thinking_sphinx/float_formatter.rb
+++ b/lib/thinking_sphinx/float_formatter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::FloatFormatter
PATTERN = /(\d+)e\-(\d+)$/
diff --git a/lib/thinking_sphinx/frameworks.rb b/lib/thinking_sphinx/frameworks.rb
index b30d730a3..14656ae3f 100644
--- a/lib/thinking_sphinx/frameworks.rb
+++ b/lib/thinking_sphinx/frameworks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Frameworks
def self.current
defined?(::Rails) ? ThinkingSphinx::Frameworks::Rails.new :
diff --git a/lib/thinking_sphinx/frameworks/plain.rb b/lib/thinking_sphinx/frameworks/plain.rb
index dff18a227..371fe85f7 100644
--- a/lib/thinking_sphinx/frameworks/plain.rb
+++ b/lib/thinking_sphinx/frameworks/plain.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Frameworks::Plain
attr_accessor :environment, :root
diff --git a/lib/thinking_sphinx/frameworks/rails.rb b/lib/thinking_sphinx/frameworks/rails.rb
index af17c3b6f..7ef4b53ea 100644
--- a/lib/thinking_sphinx/frameworks/rails.rb
+++ b/lib/thinking_sphinx/frameworks/rails.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Frameworks::Rails
def environment
Rails.env
diff --git a/lib/thinking_sphinx/guard.rb b/lib/thinking_sphinx/guard.rb
index 83e8df204..2e2840d7a 100644
--- a/lib/thinking_sphinx/guard.rb
+++ b/lib/thinking_sphinx/guard.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Guard
#
end
require 'thinking_sphinx/guard/file'
require 'thinking_sphinx/guard/files'
+require 'thinking_sphinx/guard/none'
diff --git a/lib/thinking_sphinx/guard/file.rb b/lib/thinking_sphinx/guard/file.rb
index a1509571c..a4b5e5623 100644
--- a/lib/thinking_sphinx/guard/file.rb
+++ b/lib/thinking_sphinx/guard/file.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Guard::File
attr_reader :name
@@ -10,7 +12,7 @@ def lock
end
def locked?
- File.exists? path
+ File.exist? path
end
def path
@@ -21,6 +23,6 @@ def path
end
def unlock
- FileUtils.rm path
+ FileUtils.rm(path) if locked?
end
end
diff --git a/lib/thinking_sphinx/guard/files.rb b/lib/thinking_sphinx/guard/files.rb
index 49e9c1be4..233a646a6 100644
--- a/lib/thinking_sphinx/guard/files.rb
+++ b/lib/thinking_sphinx/guard/files.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Guard::Files
def self.call(names, &block)
new(names).call(&block)
diff --git a/lib/thinking_sphinx/guard/none.rb b/lib/thinking_sphinx/guard/none.rb
new file mode 100644
index 000000000..3da65334e
--- /dev/null
+++ b/lib/thinking_sphinx/guard/none.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Guard::None
+ def self.call(names, &block)
+ block.call names
+ end
+end
diff --git a/lib/thinking_sphinx/hooks/guard_presence.rb b/lib/thinking_sphinx/hooks/guard_presence.rb
new file mode 100644
index 000000000..72954d08d
--- /dev/null
+++ b/lib/thinking_sphinx/hooks/guard_presence.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Hooks::GuardPresence
+ def self.call(configuration = nil, stream = STDERR)
+ new(configuration, stream).call
+ end
+
+ def initialize(configuration = nil, stream = STDERR)
+ @configuration = configuration || ThinkingSphinx::Configuration.instance
+ @stream = stream
+ end
+
+ def call
+ return if files.empty?
+
+ stream.puts "WARNING: The following indexing guard files exist:"
+ files.each do |file|
+ stream.puts " * #{file}"
+ end
+ stream.puts <<-TXT
+These files indicate indexing is already happening. If that is not the case,
+these files should be deleted to ensure all indices can be processed.
+
+ TXT
+ end
+
+ private
+
+ attr_reader :configuration, :stream
+
+ def files
+ @files ||= Dir["#{configuration.indices_location}/ts-*.tmp"]
+ end
+end
diff --git a/lib/thinking_sphinx/index.rb b/lib/thinking_sphinx/index.rb
index af3c27482..c94189346 100644
--- a/lib/thinking_sphinx/index.rb
+++ b/lib/thinking_sphinx/index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Index
attr_reader :reference, :options, :block
diff --git a/lib/thinking_sphinx/index_set.rb b/lib/thinking_sphinx/index_set.rb
index 185f49bef..2e87a1643 100644
--- a/lib/thinking_sphinx/index_set.rb
+++ b/lib/thinking_sphinx/index_set.rb
@@ -1,6 +1,13 @@
+# frozen_string_literal: true
+
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
def initialize(options = {}, configuration = nil)
@@ -27,15 +34,15 @@ 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
- @classes_and_ancestors ||= classes.collect { |model|
+ @classes_and_ancestors ||= mti_classes + sti_classes.collect { |model|
model.ancestors.take_while { |klass|
klass != ActiveRecord::Base
}.select { |klass|
@@ -61,13 +68,29 @@ 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)
+ }
+ end
+
def references
options[:references] || classes_and_ancestors.collect { |klass|
- klass.name.underscore.to_sym
+ self.class.reference_name(klass)
}
end
def references_specified?
options[:references] && options[:references].any?
end
+
+ def sti_classes
+ classes.select { |klass|
+ klass.column_names.include?(klass.inheritance_column)
+ }
+ end
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 000000000..d2abe3960
--- /dev/null
+++ b/lib/thinking_sphinx/indexing_strategies/all_at_once.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+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 000000000..32f3083cc
--- /dev/null
+++ b/lib/thinking_sphinx/indexing_strategies/one_at_a_time.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+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/interfaces.rb b/lib/thinking_sphinx/interfaces.rb
new file mode 100644
index 000000000..37019df45
--- /dev/null
+++ b/lib/thinking_sphinx/interfaces.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module ThinkingSphinx::Interfaces
+ #
+end
+
+require 'thinking_sphinx/interfaces/base'
+require 'thinking_sphinx/interfaces/daemon'
+require 'thinking_sphinx/interfaces/real_time'
+require 'thinking_sphinx/interfaces/sql'
diff --git a/lib/thinking_sphinx/interfaces/base.rb b/lib/thinking_sphinx/interfaces/base.rb
new file mode 100644
index 000000000..8a72773e2
--- /dev/null
+++ b/lib/thinking_sphinx/interfaces/base.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Interfaces::Base
+ include ThinkingSphinx::WithOutput
+
+ private
+
+ def command(command, extra_options = {})
+ ThinkingSphinx::Commander.call(
+ command, configuration, options.merge(extra_options), stream
+ )
+ end
+end
diff --git a/lib/thinking_sphinx/interfaces/daemon.rb b/lib/thinking_sphinx/interfaces/daemon.rb
new file mode 100644
index 000000000..57acaaa3c
--- /dev/null
+++ b/lib/thinking_sphinx/interfaces/daemon.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Interfaces::Daemon < ThinkingSphinx::Interfaces::Base
+ def start
+ if command :running
+ raise ThinkingSphinx::SphinxAlreadyRunning, 'searchd is already running'
+ end
+
+ command(options[:nodetach] ? :start_attached : :start_detached)
+ end
+
+ def status
+ if command :running
+ stream.puts "The Sphinx daemon searchd is currently running."
+ else
+ stream.puts "The Sphinx daemon searchd is not currently running."
+ end
+ end
+
+ def stop
+ command :stop
+ end
+
+ private
+
+ delegate :controller, :to => :configuration
+end
diff --git a/lib/thinking_sphinx/interfaces/real_time.rb b/lib/thinking_sphinx/interfaces/real_time.rb
new file mode 100644
index 000000000..c35a335b4
--- /dev/null
+++ b/lib/thinking_sphinx/interfaces/real_time.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Interfaces::RealTime < ThinkingSphinx::Interfaces::Base
+ def initialize(configuration, options, stream = STDOUT)
+ super
+
+ configuration.preload_indices
+
+ command :prepare
+ end
+
+ def clear
+ command :clear_real_time, :indices => indices
+ end
+
+ def index
+ return if indices.empty?
+ if !command :running
+ stream.puts <<-TXT
+The Sphinx daemon is not currently running. Real-time indices can only be
+populated by sending commands to a running daemon.
+ TXT
+ return
+ end
+
+ command :index_real_time, :indices => indices
+ end
+
+ private
+
+ def index_names
+ @index_names ||= options[:index_names] || []
+ end
+
+ def indices
+ @indices ||= begin
+ indices = configuration.indices.select { |index| index.type == 'rt' }
+
+ if index_names.any?
+ indices.select! { |index| index_names.include? index.name }
+ end
+
+ indices
+ end
+ end
+end
diff --git a/lib/thinking_sphinx/interfaces/sql.rb b/lib/thinking_sphinx/interfaces/sql.rb
new file mode 100644
index 000000000..b682f5db3
--- /dev/null
+++ b/lib/thinking_sphinx/interfaces/sql.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Interfaces::SQL < ThinkingSphinx::Interfaces::Base
+ def initialize(configuration, options, stream = STDOUT)
+ super
+
+ configuration.preload_indices
+
+ command :prepare
+ end
+
+ def clear
+ command :clear_sql, :indices => (filtered? ? filtered_indices : indices)
+ end
+
+ def index(reconfigure = true, verbose = nil)
+ stream.puts <<-TXT unless verbose.nil?
+The verbose argument to the index method is now deprecated, and can instead be
+managed by the :verbose option passed in when initialising RakeInterface. That
+option is set automatically when invoked by rake, via rake's --silent and/or
+--quiet arguments.
+ TXT
+ return if indices.empty?
+
+ command :configure if reconfigure
+ command :index_sql,
+ :indices => (filtered? ? filtered_indices.collect(&:name) : nil)
+ end
+
+ def merge
+ command :merge_and_update
+ end
+
+ private
+
+ def filtered?
+ index_names.any?
+ end
+
+ def filtered_indices
+ indices.select { |index| index_names.include? index.name }
+ end
+
+ def index_names
+ @index_names ||= options[:index_names] || []
+ end
+
+ def indices
+ @indices ||= configuration.indices.select do |index|
+ index.type == 'plain' || index.type.blank?
+ end
+ end
+end
diff --git a/lib/thinking_sphinx/logger.rb b/lib/thinking_sphinx/logger.rb
index ce1243dde..8722bb826 100644
--- a/lib/thinking_sphinx/logger.rb
+++ b/lib/thinking_sphinx/logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Logger
def self.log(notification, message, &block)
ActiveSupport::Notifications.instrument(
diff --git a/lib/thinking_sphinx/masks.rb b/lib/thinking_sphinx/masks.rb
index c7c265434..89510cc88 100644
--- a/lib/thinking_sphinx/masks.rb
+++ b/lib/thinking_sphinx/masks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Masks
#
end
diff --git a/lib/thinking_sphinx/masks/group_enumerators_mask.rb b/lib/thinking_sphinx/masks/group_enumerators_mask.rb
index 602024b02..8b6753259 100644
--- a/lib/thinking_sphinx/masks/group_enumerators_mask.rb
+++ b/lib/thinking_sphinx/masks/group_enumerators_mask.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Masks::GroupEnumeratorsMask
def initialize(search)
@search = search
@@ -9,20 +11,20 @@ def can_handle?(method)
def each_with_count(&block)
@search.raw.each_with_index do |row, index|
- yield @search[index], row[ThinkingSphinx::SphinxQL.count[:column]]
+ yield @search[index], row["sphinx_internal_count"]
end
end
def each_with_group(&block)
@search.raw.each_with_index do |row, index|
- yield @search[index], row[ThinkingSphinx::SphinxQL.group_by[:column]]
+ yield @search[index], row["sphinx_internal_group"]
end
end
def each_with_group_and_count(&block)
@search.raw.each_with_index do |row, index|
- yield @search[index], row[ThinkingSphinx::SphinxQL.group_by[:column]],
- row[ThinkingSphinx::SphinxQL.count[:column]]
+ yield @search[index], row["sphinx_internal_group"],
+ row["sphinx_internal_count"]
end
end
end
diff --git a/lib/thinking_sphinx/masks/pagination_mask.rb b/lib/thinking_sphinx/masks/pagination_mask.rb
index c215e3d75..344d2d373 100644
--- a/lib/thinking_sphinx/masks/pagination_mask.rb
+++ b/lib/thinking_sphinx/masks/pagination_mask.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Masks::PaginationMask
def initialize(search)
@search = search
@@ -37,6 +39,8 @@ def previous_page
search.current_page == 1 ? nil : search.current_page - 1
end
+ alias_method :prev_page, :previous_page
+
def total_entries
search.meta['total_found'].to_i
end
diff --git a/lib/thinking_sphinx/masks/scopes_mask.rb b/lib/thinking_sphinx/masks/scopes_mask.rb
index 04e9a6be7..2dd773d8b 100644
--- a/lib/thinking_sphinx/masks/scopes_mask.rb
+++ b/lib/thinking_sphinx/masks/scopes_mask.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Masks::ScopesMask
def initialize(search)
@search = search
@@ -24,6 +26,12 @@ def search_for_ids(query = nil, options = {})
search query, options.merge(:ids_only => true)
end
+ def none
+ ThinkingSphinx::Search::Merger.new(@search).merge! nil, :none => true
+ end
+
+ alias_method :search_none, :none
+
private
def apply_scope(scope, *args)
diff --git a/lib/thinking_sphinx/masks/weight_enumerator_mask.rb b/lib/thinking_sphinx/masks/weight_enumerator_mask.rb
index 2998c86f2..e3aa8680e 100644
--- a/lib/thinking_sphinx/masks/weight_enumerator_mask.rb
+++ b/lib/thinking_sphinx/masks/weight_enumerator_mask.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Masks::WeightEnumeratorMask
def initialize(search)
@search = search
@@ -9,7 +11,7 @@ def can_handle?(method)
def each_with_weight(&block)
@search.raw.each_with_index do |row, index|
- yield @search[index], row[ThinkingSphinx::SphinxQL.weight[:column]]
+ yield @search[index], row["weight()"]
end
end
end
diff --git a/lib/thinking_sphinx/middlewares.rb b/lib/thinking_sphinx/middlewares.rb
index 5fddee3f4..958a750cd 100644
--- a/lib/thinking_sphinx/middlewares.rb
+++ b/lib/thinking_sphinx/middlewares.rb
@@ -1,7 +1,11 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Middlewares; end
-%w[middleware active_record_translator geographer glazier ids_only inquirer
- sphinxql stale_id_checker stale_id_filter utf8].each do |middleware|
+%w[
+ middleware active_record_translator geographer glazier ids_only inquirer
+ sphinxql stale_id_checker stale_id_filter valid_options
+].each do |middleware|
require "thinking_sphinx/middlewares/#{middleware}"
end
@@ -10,7 +14,7 @@ def self.use(builder, middlewares)
middlewares.each { |m| builder.use m }
end
- BASE_MIDDLEWARES = [SphinxQL, Geographer, Inquirer]
+ BASE_MIDDLEWARES = [ValidOptions, SphinxQL, Geographer, Inquirer]
DEFAULT = ::Middleware::Builder.new do
use StaleIdFilter
diff --git a/lib/thinking_sphinx/middlewares/active_record_translator.rb b/lib/thinking_sphinx/middlewares/active_record_translator.rb
index 56e4a3c69..1986dba72 100644
--- a/lib/thinking_sphinx/middlewares/active_record_translator.rb
+++ b/lib/thinking_sphinx/middlewares/active_record_translator.rb
@@ -1,6 +1,11 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Middlewares::ActiveRecordTranslator <
ThinkingSphinx::Middlewares::Middleware
+ NO_MODEL = Struct.new(:primary_key).new(:id).freeze
+ NO_INDEX = Struct.new(:primary_key).new(:id).freeze
+
def call(contexts)
contexts.each do |context|
Inner.new(context).call
@@ -36,12 +41,25 @@ def ids_for_model(model_name)
}.compact
end
+ def index_for(model)
+ return NO_INDEX unless context[:indices]
+
+ context[:indices].detect { |index| index.model == model } || NO_INDEX
+ end
+
def model_names
@model_names ||= context[:results].collect { |row|
row['sphinx_internal_class']
}.uniq
end
+ def primary_key_for(model)
+ model = NO_MODEL unless model.respond_to?(:primary_key)
+
+ @primary_keys ||= {}
+ @primary_keys[model] ||= index_for(model).primary_key
+ end
+
def reset_memos
@model_names = nil
@results_for_models = nil
@@ -49,27 +67,32 @@ def reset_memos
def result_for(row)
results_for_models[row['sphinx_internal_class']].detect { |record|
- record.id == row['sphinx_internal_id']
+ record.public_send(
+ primary_key_for(record.class)
+ ) == row['sphinx_internal_id']
}
end
def results_for_models
@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)
+
+ model_sql_options = sql_options[name] || sql_options
+
+ hash[name] = model_relation_with_sql_options(model.unscoped, model_sql_options).where(
+ primary_key_for(model) => ids_for_model(name)
)
hash
end
end
- def model_relation_with_sql_options(relation)
- relation = relation.includes sql_options[:include] if sql_options[:include]
- relation = relation.joins sql_options[:joins] if sql_options[:joins]
- relation = relation.order sql_options[:order] if sql_options[:order]
- relation = relation.select sql_options[:select] if sql_options[:select]
- relation = relation.group sql_options[:group] if sql_options[:group]
+ def model_relation_with_sql_options(relation, model_sql_options)
+ relation = relation.includes model_sql_options[:include] if model_sql_options[:include]
+ relation = relation.joins model_sql_options[:joins] if model_sql_options[:joins]
+ relation = relation.order model_sql_options[:order] if model_sql_options[:order]
+ relation = relation.select model_sql_options[:select] if model_sql_options[:select]
+ relation = relation.group model_sql_options[:group] if model_sql_options[:group]
relation
end
diff --git a/lib/thinking_sphinx/middlewares/geographer.rb b/lib/thinking_sphinx/middlewares/geographer.rb
index 767f97e3d..d9e5e8cdc 100644
--- a/lib/thinking_sphinx/middlewares/geographer.rb
+++ b/lib/thinking_sphinx/middlewares/geographer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'active_support/core_ext/module/delegation'
class ThinkingSphinx::Middlewares::Geographer <
diff --git a/lib/thinking_sphinx/middlewares/glazier.rb b/lib/thinking_sphinx/middlewares/glazier.rb
index f322ebc0e..4363811bc 100644
--- a/lib/thinking_sphinx/middlewares/glazier.rb
+++ b/lib/thinking_sphinx/middlewares/glazier.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Middlewares::Glazier <
ThinkingSphinx::Middlewares::Middleware
@@ -14,6 +16,7 @@ def call(contexts)
class Inner
def initialize(context)
@context = context
+ @indices = {}
end
def call
@@ -29,10 +32,20 @@ def call
attr_reader :context
+ def indices_for(model)
+ @indices[model] ||= context[:indices].select do |index|
+ index.model == model
+ end
+ end
+
def row_for(result)
+ ids = indices_for(result.class).collect do |index|
+ result.send index.primary_key
+ end
+
context[:raw].detect { |row|
row['sphinx_internal_class'] == result.class.name &&
- row['sphinx_internal_id'] == result.id
+ ids.include?(row['sphinx_internal_id'])
}
end
end
diff --git a/lib/thinking_sphinx/middlewares/ids_only.rb b/lib/thinking_sphinx/middlewares/ids_only.rb
index 0885af42f..f03f6a30b 100644
--- a/lib/thinking_sphinx/middlewares/ids_only.rb
+++ b/lib/thinking_sphinx/middlewares/ids_only.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Middlewares::IdsOnly <
ThinkingSphinx::Middlewares::Middleware
diff --git a/lib/thinking_sphinx/middlewares/inquirer.rb b/lib/thinking_sphinx/middlewares/inquirer.rb
index 75bd45dad..9c0151294 100644
--- a/lib/thinking_sphinx/middlewares/inquirer.rb
+++ b/lib/thinking_sphinx/middlewares/inquirer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Middlewares::Inquirer <
ThinkingSphinx::Middlewares::Middleware
@@ -45,7 +47,7 @@ def initialize(context)
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/middleware.rb b/lib/thinking_sphinx/middlewares/middleware.rb
index 204714433..c5cdd96d5 100644
--- a/lib/thinking_sphinx/middlewares/middleware.rb
+++ b/lib/thinking_sphinx/middlewares/middleware.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Middlewares::Middleware
def initialize(app)
@app = app
diff --git a/lib/thinking_sphinx/middlewares/sphinxql.rb b/lib/thinking_sphinx/middlewares/sphinxql.rb
index 21115e924..69e01fd96 100644
--- a/lib/thinking_sphinx/middlewares/sphinxql.rb
+++ b/lib/thinking_sphinx/middlewares/sphinxql.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Middlewares::SphinxQL <
ThinkingSphinx::Middlewares::Middleware
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|
@@ -80,19 +82,6 @@ def descendants_from_tables
end.flatten
end
- def indices_match_classes?
- indices.collect(&:reference).uniq.sort == classes.collect { |klass|
- klass.name.underscore.to_sym
- }.sort
- end
-
- def inheritance_column_select(klass)
- <<-SQL
-SELECT DISTINCT #{klass.inheritance_column}
-FROM #{klass.table_name}
-SQL
- end
-
def exclusive_filters
@exclusive_filters ||= (options[:without] || {}).tap do |without|
without[:sphinx_internal_id] = options[:without_ids] if options[:without_ids].present?
@@ -142,6 +131,19 @@ def indices
end
end
+ def indices_match_classes?
+ indices.collect(&:reference).uniq.sort == classes.collect { |klass|
+ configuration.index_set_class.reference_name(klass)
+ }.sort
+ end
+
+ def inheritance_column_select(klass)
+ <<-SQL
+SELECT DISTINCT #{klass.inheritance_column}
+FROM #{klass.table_name}
+SQL
+ end
+
def order_clause
order_by = options[:order]
order_by = "#{order_by} ASC" if order_by.is_a? Symbol
@@ -159,8 +161,8 @@ def select_options
def values
options[:select] ||= ['*',
- "#{ThinkingSphinx::SphinxQL.group_by[:select]}",
- "#{ThinkingSphinx::SphinxQL.count[:select]}"
+ "groupby() AS sphinx_internal_group",
+ "id AS sphinx_document_id, count(DISTINCT sphinx_document_id) AS sphinx_internal_count"
].join(', ') if group_attribute.present?
options[:select]
end
diff --git a/lib/thinking_sphinx/middlewares/stale_id_checker.rb b/lib/thinking_sphinx/middlewares/stale_id_checker.rb
index aec680572..e58d94d8d 100644
--- a/lib/thinking_sphinx/middlewares/stale_id_checker.rb
+++ b/lib/thinking_sphinx/middlewares/stale_id_checker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Middlewares::StaleIdChecker <
ThinkingSphinx::Middlewares::Middleware
@@ -33,7 +35,7 @@ def expected_ids
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 8c5c2ce45..431528361 100644
--- a/lib/thinking_sphinx/middlewares/stale_id_filter.rb
+++ b/lib/thinking_sphinx/middlewares/stale_id_filter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Middlewares::StaleIdFilter <
ThinkingSphinx::Middlewares::Middleware
@@ -11,7 +13,7 @@ def call(contexts)
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 +22,7 @@ def call(contexts)
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/middlewares/utf8.rb b/lib/thinking_sphinx/middlewares/utf8.rb
deleted file mode 100644
index 086e86aa6..000000000
--- a/lib/thinking_sphinx/middlewares/utf8.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-class ThinkingSphinx::Middlewares::UTF8 <
- ThinkingSphinx::Middlewares::Middleware
-
- def call(contexts)
- contexts.each do |context|
- context[:results].each { |row| update_row row }
- update_row context[:meta]
- end unless encoded?
-
- app.call contexts
- end
-
- private
-
- def encoded?
- ThinkingSphinx::Configuration.instance.settings['utf8'].nil? ||
- ThinkingSphinx::Configuration.instance.settings['utf8']
- end
-
- def update_row(row)
- row.each do |key, value|
- next unless value.is_a?(String)
-
- row[key] = ThinkingSphinx::UTF8.encode value
- end
- end
-end
diff --git a/lib/thinking_sphinx/middlewares/valid_options.rb b/lib/thinking_sphinx/middlewares/valid_options.rb
new file mode 100644
index 000000000..b666fccd1
--- /dev/null
+++ b/lib/thinking_sphinx/middlewares/valid_options.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Middlewares::ValidOptions <
+ ThinkingSphinx::Middlewares::Middleware
+
+ def call(contexts)
+ contexts.each { |context| check_options context.search.options }
+
+ app.call contexts
+ end
+
+ private
+
+ def check_options(options)
+ unknown = invalid_keys options.keys
+ return if unknown.empty?
+
+ ThinkingSphinx::Logger.log :caution,
+ "Unexpected search options: #{unknown.inspect}"
+ end
+
+ def invalid_keys(keys)
+ keys - ThinkingSphinx::Search.valid_options
+ end
+end
diff --git a/lib/thinking_sphinx/panes.rb b/lib/thinking_sphinx/panes.rb
index 94ba474e5..66f59b239 100644
--- a/lib/thinking_sphinx/panes.rb
+++ b/lib/thinking_sphinx/panes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Panes
#
end
diff --git a/lib/thinking_sphinx/panes/attributes_pane.rb b/lib/thinking_sphinx/panes/attributes_pane.rb
index ab6e9a592..ad4ca8814 100644
--- a/lib/thinking_sphinx/panes/attributes_pane.rb
+++ b/lib/thinking_sphinx/panes/attributes_pane.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Panes::AttributesPane
def initialize(context, object, raw)
@raw = raw
diff --git a/lib/thinking_sphinx/panes/distance_pane.rb b/lib/thinking_sphinx/panes/distance_pane.rb
index e8c03f7ed..31fe1b0e8 100644
--- a/lib/thinking_sphinx/panes/distance_pane.rb
+++ b/lib/thinking_sphinx/panes/distance_pane.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Panes::DistancePane
def initialize(context, object, raw)
@raw = raw
diff --git a/lib/thinking_sphinx/panes/excerpts_pane.rb b/lib/thinking_sphinx/panes/excerpts_pane.rb
index bde976e86..c1806d95b 100644
--- a/lib/thinking_sphinx/panes/excerpts_pane.rb
+++ b/lib/thinking_sphinx/panes/excerpts_pane.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Panes::ExcerptsPane
def initialize(context, object, raw)
@context, @object = context, object
diff --git a/lib/thinking_sphinx/panes/weight_pane.rb b/lib/thinking_sphinx/panes/weight_pane.rb
index 221d36492..6c3397b58 100644
--- a/lib/thinking_sphinx/panes/weight_pane.rb
+++ b/lib/thinking_sphinx/panes/weight_pane.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Panes::WeightPane
def initialize(context, object, raw)
@raw = raw
end
def weight
- @raw[ThinkingSphinx::SphinxQL.weight[:column]]
+ @raw["weight()"]
end
end
diff --git a/lib/thinking_sphinx/processor.rb b/lib/thinking_sphinx/processor.rb
new file mode 100644
index 000000000..db440ccf9
--- /dev/null
+++ b/lib/thinking_sphinx/processor.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::Processor
+ # @param instance [ActiveRecord::Base] an ActiveRecord object
+ # @param model [Class] the ActiveRecord model of the instance
+ # @param id [Integer] the instance indices primary key (might be different from model primary key)
+ def initialize(instance: nil, model: nil, id: nil)
+ raise ArgumentError if instance.nil? && (model.nil? || id.nil?)
+
+ @instance = instance
+ @model = model || instance.class
+ @id = id
+ end
+
+ def delete
+ return if instance&.new_record?
+
+ indices.each { |index| perform_deletion(index) }
+ end
+
+ # Will insert instance into all matching indices
+ def upsert
+ real_time_indices.each do |index|
+ found = loaded_instance(index)
+ ThinkingSphinx::RealTime::Transcriber.new(index).copy found if found
+ end
+ end
+
+ # Will upsert or delete instance into all matching indices based on index scope
+ def sync
+ real_time_indices.each do |index|
+ found = find_in(index)
+
+ if found
+ ThinkingSphinx::RealTime::Transcriber.new(index).copy found
+ else
+ ThinkingSphinx::Deletion.perform(index, index_id(index))
+ end
+ end
+ end
+
+ private
+
+ attr_reader :instance, :model, :id
+
+ def indices
+ ThinkingSphinx::Configuration.instance.index_set_class.new(
+ :instances => [instance].compact, :classes => [model]
+ ).to_a
+ end
+
+ def find_in(index)
+ index.scope.find_by(index.primary_key => index_id(index))
+ end
+
+ def loaded_instance(index)
+ instance || find_in(index)
+ end
+
+ def real_time_indices
+ indices.select { |index| index.is_a? ThinkingSphinx::RealTime::Index }
+ end
+
+ def perform_deletion(index)
+ ThinkingSphinx::Deletion.perform(index, index_id(index))
+ end
+
+ def index_id(index)
+ id || instance.public_send(index.primary_key)
+ end
+end
diff --git a/lib/thinking_sphinx/query.rb b/lib/thinking_sphinx/query.rb
index f21196f79..21fcc0992 100644
--- a/lib/thinking_sphinx/query.rb
+++ b/lib/thinking_sphinx/query.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Query
def self.escape(query)
Riddle::Query.escape query
diff --git a/lib/thinking_sphinx/railtie.rb b/lib/thinking_sphinx/railtie.rb
index 1d8a17aa8..cac3ba898 100644
--- a/lib/thinking_sphinx/railtie.rb
+++ b/lib/thinking_sphinx/railtie.rb
@@ -1,11 +1,38 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Railtie < Rails::Railtie
+ config.to_prepare do
+ ThinkingSphinx::Configuration.reset
+ end
+
+ config.after_initialize do
+ require 'thinking_sphinx/active_record'
+ end
+
initializer 'thinking_sphinx.initialisation' do
- if defined?(ActiveRecord::Base)
- ActiveRecord::Base.send :include, ThinkingSphinx::ActiveRecord::Base
+ ActiveSupport.on_load(:active_record) do
+ require 'thinking_sphinx/active_record'
end
+
+ if zeitwerk?
+ ActiveSupport::Dependencies.autoload_paths.delete(
+ Rails.root.join("app", "indices").to_s
+ )
+ end
+
+ Rails.application.config.eager_load_paths -=
+ ThinkingSphinx::Configuration.instance.index_paths
+ Rails.application.config.eager_load_paths.freeze
end
rake_tasks do
load File.expand_path('../tasks.rb', __FILE__)
end
+
+ def zeitwerk?
+ return true if ActiveSupport::VERSION::MAJOR >= 7
+ return false if ActiveSupport::VERSION::MAJOR <= 5
+
+ Rails.application.config.autoloader == :zeitwerk
+ end
end
diff --git a/lib/thinking_sphinx/rake_interface.rb b/lib/thinking_sphinx/rake_interface.rb
index 1f020aa15..95a69dbba 100644
--- a/lib/thinking_sphinx/rake_interface.rb
+++ b/lib/thinking_sphinx/rake_interface.rb
@@ -1,87 +1,32 @@
-class ThinkingSphinx::RakeInterface
- def clear_all
- [
- configuration.indices_location,
- configuration.searchd.binlog_path
- ].each do |path|
- FileUtils.rm_r(path) if File.exists?(path)
- end
- end
+# frozen_string_literal: true
- def clear_real_time
- indices = configuration.indices.select { |index| index.type == 'rt' }
- indices.each do |index|
- index.render
- Dir["#{index.path}.*"].each { |path| FileUtils.rm path }
- end
+class ThinkingSphinx::RakeInterface
+ DEFAULT_OPTIONS = {:verbose => true}
- path = configuration.searchd.binlog_path
- FileUtils.rm_r(path) if File.exists?(path)
+ def initialize(options = {})
+ @options = DEFAULT_OPTIONS.merge options
+ @options[:verbose] = false if @options[:silent]
end
def configure
- puts "Generating configuration to #{configuration.configuration_file}"
- configuration.render_to_file
- end
-
- def generate
- indices = configuration.indices.select { |index| index.type == 'rt' }
- indices.each do |index|
- ThinkingSphinx::RealTime::Populator.populate index
- end
- end
-
- def index(reconfigure = true, verbose = true)
- configure if reconfigure
- FileUtils.mkdir_p configuration.indices_location
- ThinkingSphinx.before_index_hooks.each { |hook| hook.call }
- controller.index :verbose => verbose
+ ThinkingSphinx::Commander.call :configure, configuration, options
end
- def prepare
- configuration.preload_indices
- configuration.render
-
- FileUtils.mkdir_p configuration.indices_location
+ def daemon
+ @daemon ||= ThinkingSphinx::Interfaces::Daemon.new configuration, options
end
- def start
- 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})."
- else
- puts "Failed to start searchd. Check the log files for more information."
- end
+ def rt
+ @rt ||= ThinkingSphinx::Interfaces::RealTime.new configuration, options
end
- def status
- if controller.running?
- puts "The Sphinx daemon searchd is currently running."
- else
- puts "The Sphinx daemon searchd is not currently running."
- end
- end
-
- def stop
- unless controller.running?
- puts 'searchd is not currently running.' and return
- end
-
- pid = controller.pid
- until controller.stop do
- sleep(0.5)
- end
-
- puts "Stopped searchd daemon (pid: #{pid})."
+ def sql
+ @sql ||= ThinkingSphinx::Interfaces::SQL.new configuration, options
end
private
- delegate :controller, :to => :configuration
+ attr_reader :options
def configuration
ThinkingSphinx::Configuration.instance
diff --git a/lib/thinking_sphinx/real_time.rb b/lib/thinking_sphinx/real_time.rb
index 95e016f23..3c6c5570c 100644
--- a/lib/thinking_sphinx/real_time.rb
+++ b/lib/thinking_sphinx/real_time.rb
@@ -1,10 +1,28 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::RealTime
module Callbacks
#
end
def self.callback_for(reference, path = [], &block)
- Callbacks::RealTimeCallbacks.new reference, path, &block
+ Callbacks::RealTimeCallbacks.new reference.to_sym, path, &block
+ end
+
+ def self.populator
+ @populator ||= ThinkingSphinx::RealTime::Populator
+ end
+
+ def self.populator=(value)
+ @populator = value
+ end
+
+ def self.processor
+ @processor ||= ThinkingSphinx::RealTime::Processor
+ end
+
+ def self.processor=(value)
+ @processor = value
end
end
@@ -14,6 +32,9 @@ def self.callback_for(reference, path = [], &block)
require 'thinking_sphinx/real_time/index'
require 'thinking_sphinx/real_time/interpreter'
require 'thinking_sphinx/real_time/populator'
+require 'thinking_sphinx/real_time/processor'
+require 'thinking_sphinx/real_time/transcribe_instance'
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 adc970c9c..1a64bb582 100644
--- a/lib/thinking_sphinx/real_time/attribute.rb
+++ b/lib/thinking_sphinx/real_time/attribute.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property
def multi?
@options[:multi]
@@ -8,7 +10,9 @@ def type
end
def translate(object)
- super || default_value
+ output = super || default_value
+
+ json? ? output.to_json : output
end
private
@@ -16,4 +20,8 @@ def translate(object)
def default_value
type == :string ? '' : 0
end
+
+ def json?
+ type == :json
+ end
end
diff --git a/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb b/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb
index 8030cba0e..b88a26006 100644
--- a/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb
+++ b/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb
@@ -1,16 +1,16 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks
def initialize(reference, path = [], &block)
@reference, @path, @block = reference, path, block
end
- def after_save(instance)
- return unless real_time_indices? && callbacks_enabled?
+ def after_commit(instance)
+ persist_changes instance
+ end
- real_time_indices.each do |index|
- objects_for(instance).each do |object|
- ThinkingSphinx::RealTime::Transcriber.new(index).copy object
- end
- end
+ def after_save(instance)
+ persist_changes instance
end
private
@@ -40,6 +40,16 @@ def objects_for(instance)
Array results
end
+ def persist_changes(instance)
+ return unless real_time_indices? && callbacks_enabled?
+
+ real_time_indices.each do |index|
+ objects_for(instance).each do |object|
+ ThinkingSphinx::RealTime::Transcriber.new(index).copy object
+ end
+ end
+ end
+
def real_time_indices?
real_time_indices.any?
end
diff --git a/lib/thinking_sphinx/real_time/field.rb b/lib/thinking_sphinx/real_time/field.rb
index 684f99443..e2bf30b89 100644
--- a/lib/thinking_sphinx/real_time/field.rb
+++ b/lib/thinking_sphinx/real_time/field.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::RealTime::Field < ThinkingSphinx::RealTime::Property
include ThinkingSphinx::Core::Field
diff --git a/lib/thinking_sphinx/real_time/index.rb b/lib/thinking_sphinx/real_time/index.rb
index 43ea17dfa..f5db245e8 100644
--- a/lib/thinking_sphinx/real_time/index.rb
+++ b/lib/thinking_sphinx/real_time/index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
include ThinkingSphinx::Core::Index
@@ -8,16 +10,20 @@ def initialize(reference, options = {})
@attributes = []
@conditions = []
- Template.new(self).apply
-
super reference, options
+
+ Template.new(self).apply
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
@@ -59,16 +65,16 @@ def append_unique_attribute(collection, attribute)
def collection_for(attribute)
case attribute.type
- when :integer, :boolean
+ when :integer, :boolean, :timestamp
attribute.multi? ? @rt_attr_multi : @rt_attr_uint
when :string
@rt_attr_string
- when :timestamp
- @rt_attr_timestamp
when :float
@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/index/template.rb b/lib/thinking_sphinx/real_time/index/template.rb
index 89d0de79b..c8db1067d 100644
--- a/lib/thinking_sphinx/real_time/index/template.rb
+++ b/lib/thinking_sphinx/real_time/index/template.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::RealTime::Index::Template
attr_reader :index
@@ -8,9 +10,13 @@ def initialize(index)
def apply
add_field class_column, :sphinx_internal_class_name
- add_attribute :id, :sphinx_internal_id, :bigint
+ add_attribute primary_key, :sphinx_internal_id, :bigint
add_attribute class_column, :sphinx_internal_class, :string, :facet => true
add_attribute 0, :sphinx_deleted, :integer
+
+ if tidying?
+ add_attribute -> (_) { Time.current.to_i }, :sphinx_updated_at, :timestamp
+ end
end
private
@@ -31,4 +37,16 @@ def add_field(column, name)
def class_column
[:class, :name]
end
+
+ def config
+ ThinkingSphinx::Configuration.instance
+ end
+
+ def primary_key
+ index.primary_key.to_sym
+ end
+
+ def tidying?
+ config.settings["real_time_tidy"]
+ end
end
diff --git a/lib/thinking_sphinx/real_time/interpreter.rb b/lib/thinking_sphinx/real_time/interpreter.rb
index bc11b12a8..ba9c11a1d 100644
--- a/lib/thinking_sphinx/real_time/interpreter.rb
+++ b/lib/thinking_sphinx/real_time/interpreter.rb
@@ -1,18 +1,22 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::RealTime::Interpreter <
::ThinkingSphinx::Core::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
@@ -37,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
@@ -45,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/populator.rb b/lib/thinking_sphinx/real_time/populator.rb
index e1b8d24e7..b739445ea 100644
--- a/lib/thinking_sphinx/real_time/populator.rb
+++ b/lib/thinking_sphinx/real_time/populator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::RealTime::Populator
def self.populate(index)
new(index).populate
@@ -5,28 +7,28 @@ def self.populate(index)
def initialize(index)
@index = index
+ @started_at = Time.current
end
- def populate(&block)
+ def populate
instrument 'start_populating'
- remove_files
-
- scope.find_each do |instance|
- transcriber.copy instance
- instrument 'populated', :instance => instance
+ scope.find_in_batches(:batch_size => batch_size) do |instances|
+ transcriber.copy *instances
+ instrument 'populated', :instances => instances
end
- controller.rotate
+ transcriber.clear_before(started_at) if configuration.settings["real_time_tidy"]
+
instrument 'finish_populating'
end
private
- attr_reader :index
+ attr_reader :index, :started_at
- delegate :controller, :to => :configuration
- delegate :scope, :to => :index
+ delegate :controller, :batch_size, :to => :configuration
+ delegate :scope, :to => :index
def configuration
ThinkingSphinx::Configuration.instance
@@ -38,10 +40,6 @@ def instrument(message, options = {})
)
end
- def remove_files
- Dir["#{index.path}*"].each { |file| FileUtils.rm file }
- end
-
def transcriber
@transcriber ||= ThinkingSphinx::RealTime::Transcriber.new index
end
diff --git a/lib/thinking_sphinx/real_time/processor.rb b/lib/thinking_sphinx/real_time/processor.rb
new file mode 100644
index 000000000..52c157739
--- /dev/null
+++ b/lib/thinking_sphinx/real_time/processor.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::RealTime::Processor
+ def self.call(indices, &block)
+ new(indices).call(&block)
+ end
+
+ def initialize(indices)
+ @indices = indices
+ end
+
+ def call(&block)
+ subscribe_to_progress
+
+ indices.each do |index|
+ ThinkingSphinx::RealTime.populator.populate index
+
+ block.call
+ end
+ end
+
+ private
+
+ attr_reader :indices
+
+ def command
+ ThinkingSphinx::Commander.call(
+ command, configuration, options, stream
+ )
+ end
+
+ def subscribe_to_progress
+ ThinkingSphinx::Subscribers::PopulatorSubscriber.
+ attach_to 'thinking_sphinx.real_time'
+ end
+end
diff --git a/lib/thinking_sphinx/real_time/property.rb b/lib/thinking_sphinx/real_time/property.rb
index b2be10dd5..7f913b015 100644
--- a/lib/thinking_sphinx/real_time/property.rb
+++ b/lib/thinking_sphinx/real_time/property.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::RealTime::Property
include ThinkingSphinx::Core::Property
@@ -14,10 +16,6 @@ def name
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/transcribe_instance.rb b/lib/thinking_sphinx/real_time/transcribe_instance.rb
new file mode 100644
index 000000000..ba29da794
--- /dev/null
+++ b/lib/thinking_sphinx/real_time/transcribe_instance.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class ThinkingSphinx::RealTime::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|
+ begin
+ instance_values << property.translate(instance)
+ rescue StandardError => error
+ raise_wrapper error, property
+ end
+ end
+ end
+
+ private
+
+ attr_reader :instance, :index, :properties
+
+ def document_id
+ index.document_id_for_key instance.public_send(index.primary_key)
+ end
+
+ def raise_wrapper(error, property)
+ wrapper = ThinkingSphinx::TranscriptionError.new
+ wrapper.inner_exception = error
+ wrapper.instance = instance
+ wrapper.property = property
+
+ raise wrapper
+ end
+end
diff --git a/lib/thinking_sphinx/real_time/transcriber.rb b/lib/thinking_sphinx/real_time/transcriber.rb
index c11e79d24..4562bb3c5 100644
--- a/lib/thinking_sphinx/real_time/transcriber.rb
+++ b/lib/thinking_sphinx/real_time/transcriber.rb
@@ -1,31 +1,36 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::RealTime::Transcriber
def initialize(index)
@index = index
end
- def copy(instance)
- return unless instance.persisted? && copy?(instance)
+ def clear_before(time)
+ execute <<~SQL.strip
+ DELETE FROM #{@index.name} WHERE sphinx_updated_at < #{time.to_i}
+ SQL
+ end
- 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
+ def copy(*instances)
+ items = instances.select { |instance|
+ instance.persisted? && copy?(instance)
+ }
+ return unless items.present?
- 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
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 +43,47 @@ def copy?(instance)
end
}
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)
+ )
+ end
+
+ 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/lib/thinking_sphinx/real_time/translator.rb b/lib/thinking_sphinx/real_time/translator.rb
new file mode 100644
index 000000000..f723c762d
--- /dev/null
+++ b/lib/thinking_sphinx/real_time/translator.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+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.call(object) if name.is_a?(Proc)
+ return name unless name.is_a?(Symbol)
+ return result unless result.is_a?(String)
+
+ result.gsub("\u0000", '').force_encoding "UTF-8"
+ 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/scopes.rb b/lib/thinking_sphinx/scopes.rb
index 7f415ddf2..e71c82fcc 100644
--- a/lib/thinking_sphinx/scopes.rb
+++ b/lib/thinking_sphinx/scopes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx::Scopes
extend ActiveSupport::Concern
@@ -24,5 +26,9 @@ def method_missing(method, *args, &block)
query, options = sphinx_scopes[method].call(*args)
search query, (options || {})
end
+
+ def respond_to_missing?(method, include_private = false)
+ super || sphinx_scopes.keys.include?(method)
+ end
end
end
diff --git a/lib/thinking_sphinx/search.rb b/lib/thinking_sphinx/search.rb
index 3743b898d..97b1799c9 100644
--- a/lib/thinking_sphinx/search.rb
+++ b/lib/thinking_sphinx/search.rb
@@ -1,11 +1,23 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Search < Array
CORE_METHODS = %w( == class class_eval extend frozen? id instance_eval
- instance_of? instance_values instance_variable_defined?
+ instance_exec instance_of? instance_values instance_variable_defined?
instance_variable_get instance_variable_set instance_variables is_a?
kind_of? member? method methods nil? object_id respond_to?
respond_to_missing? send should should_not type )
SAFE_METHODS = %w( partition private_methods protected_methods public_methods
send class )
+ KNOWN_OPTIONS = (
+ [
+ :classes, :conditions, :excerpts, :geo, :group_by, :ids_only,
+ :ignore_scopes, :indices, :limit, :masks, :max_matches, :middleware,
+ :none, :offset, :order, :order_group_by, :page, :per_page, :populate,
+ :retry_stale, :select, :skip_sti, :sql, :star, :with, :with_all, :without,
+ :without_ids
+ ] +
+ ThinkingSphinx::Middlewares::SphinxQL::SELECT_OPTIONS
+ ).uniq
DEFAULT_MASKS = [
ThinkingSphinx::Masks::PaginationMask,
ThinkingSphinx::Masks::ScopesMask,
@@ -21,6 +33,12 @@ class ThinkingSphinx::Search < Array
attr_reader :options
attr_accessor :query
+ def self.valid_options
+ @valid_options
+ end
+
+ @valid_options = KNOWN_OPTIONS.dup
+
def initialize(query = nil, options = {})
query, options = nil, query if query.is_a?(Hash)
@query, @options = query, options
@@ -38,6 +56,16 @@ def current_page
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
@@ -64,7 +92,7 @@ def per_page(value = nil)
def populate
return self if @populated
- middleware.call [context]
+ middleware.call [context] unless options[:none]
@populated = true
self
diff --git a/lib/thinking_sphinx/search/batch_inquirer.rb b/lib/thinking_sphinx/search/batch_inquirer.rb
index dc6da1879..9ca03cb4e 100644
--- a/lib/thinking_sphinx/search/batch_inquirer.rb
+++ b/lib/thinking_sphinx/search/batch_inquirer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Search::BatchInquirer
def initialize(&block)
@queries = []
diff --git a/lib/thinking_sphinx/search/context.rb b/lib/thinking_sphinx/search/context.rb
index 85a3df22f..b1f051b3a 100644
--- a/lib/thinking_sphinx/search/context.rb
+++ b/lib/thinking_sphinx/search/context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Search::Context
attr_reader :search, :configuration
@@ -5,6 +7,7 @@ def initialize(search, configuration = nil)
@search = search
@configuration = configuration || ThinkingSphinx::Configuration.instance
@memory = {
+ :raw => [],
:results => [],
:panes => ThinkingSphinx::Configuration::Defaults::PANES.clone
}
@@ -17,4 +20,12 @@ def [](key)
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/glaze.rb b/lib/thinking_sphinx/search/glaze.rb
index bc8167416..182c231c4 100644
--- a/lib/thinking_sphinx/search/glaze.rb
+++ b/lib/thinking_sphinx/search/glaze.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Search::Glaze < BasicObject
def initialize(context, object, raw = {}, pane_classes = [])
@object, @raw = object, raw
diff --git a/lib/thinking_sphinx/search/merger.rb b/lib/thinking_sphinx/search/merger.rb
index 4592c411e..bff5e010c 100644
--- a/lib/thinking_sphinx/search/merger.rb
+++ b/lib/thinking_sphinx/search/merger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Search::Merger
attr_reader :search
diff --git a/lib/thinking_sphinx/search/query.rb b/lib/thinking_sphinx/search/query.rb
index a385c31a9..5654442bf 100644
--- a/lib/thinking_sphinx/search/query.rb
+++ b/lib/thinking_sphinx/search/query.rb
@@ -1,4 +1,6 @@
# encoding: utf-8
+# frozen_string_literal: true
+
class ThinkingSphinx::Search::Query
attr_reader :keywords, :conditions, :star
@@ -10,12 +12,18 @@ def to_s
(star_keyword(keywords || '') + ' ' + conditions.keys.collect { |key|
next if conditions[key].blank?
- "@#{key} #{star_keyword conditions[key], key}"
+ "#{expand_key key} #{star_keyword conditions[key], key}"
}.join(' ')).strip
end
private
+ def expand_key(key)
+ return "@#{key}" unless key.is_a?(Array)
+
+ "@(#{key.join(',')})"
+ end
+
def star_keyword(keyword, key = nil)
return keyword.to_s unless star
return keyword.to_s if key.to_s == 'sphinx_internal_class_name'
diff --git a/lib/thinking_sphinx/search/stale_ids_exception.rb b/lib/thinking_sphinx/search/stale_ids_exception.rb
index 928afa33e..484bea0dd 100644
--- a/lib/thinking_sphinx/search/stale_ids_exception.rb
+++ b/lib/thinking_sphinx/search/stale_ids_exception.rb
@@ -1,11 +1,15 @@
+# frozen_string_literal: true
+
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
- "Record IDs found by Sphinx but not by ActiveRecord : #{ids.join(', ')}"
+ "Record IDs found by Sphinx but not by ActiveRecord : #{ids.join(', ')}\n" \
+ "https://freelancing-gods.com/thinking-sphinx/v5/common_issues.html#record-ids"
end
end
diff --git a/lib/thinking_sphinx/settings.rb b/lib/thinking_sphinx/settings.rb
new file mode 100644
index 000000000..621cb3bda
--- /dev/null
+++ b/lib/thinking_sphinx/settings.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require "pathname"
+
+class ThinkingSphinx::Settings
+ ALWAYS_ABSOLUTE = %w[ socket ]
+ FILE_KEYS = %w[
+ indices_location configuration_file bin_path log query_log pid_file
+ binlog_path snippets_file_prefix sphinxql_state path stopwords wordforms
+ exceptions global_idf rlp_context rlp_root rlp_environment plugin_dir
+ lemmatizer_base mysql_ssl_cert mysql_ssl_key mysql_ssl_ca
+ ].freeze
+ DEFAULTS = {
+ "configuration_file" => "config/ENVIRONMENT.sphinx.conf",
+ "indices_location" => "db/sphinx/ENVIRONMENT",
+ "pid_file" => "log/ENVIRONMENT.sphinx.pid",
+ "log" => "log/ENVIRONMENT.searchd.log",
+ "query_log" => "log/ENVIRONMENT.searchd.query.log",
+ "binlog_path" => "tmp/binlog/ENVIRONMENT",
+ "workers" => "threads",
+ "mysql_encoding" => "utf8",
+ "maximum_statement_length" => (2 ** 23) - 5,
+ "real_time_tidy" => false,
+ "cutoff" => 0
+ }.freeze
+ YAML_SAFE_LOAD = YAML.method(:safe_load).parameters.any? do |parameter|
+ parameter == [:key, :aliases]
+ end
+
+ def self.call(configuration)
+ new(configuration).call
+ end
+
+ def initialize(configuration)
+ @configuration = configuration
+ end
+
+ def call
+ return defaults unless File.exist? file
+
+ merged.inject({}) do |hash, (key, value)|
+ if absolute_key?(key)
+ hash[key] = absolute value
+ else
+ hash[key] = value
+ end
+ hash
+ end
+ end
+
+ private
+
+ attr_reader :configuration
+
+ delegate :framework, :to => :configuration
+
+ def absolute(relative)
+ return relative if relative.nil?
+
+ real_path File.absolute_path(relative, framework.root)
+ end
+
+ def absolute_key?(key)
+ return true if ALWAYS_ABSOLUTE.include?(key)
+
+ merged["absolute_paths"] && file_keys.include?(key)
+ end
+
+ def defaults
+ DEFAULTS.inject({}) do |hash, (key, value)|
+ if value.is_a?(String)
+ value = value.gsub("ENVIRONMENT", framework.environment)
+ end
+
+ if FILE_KEYS.include?(key)
+ hash[key] = absolute value
+ else
+ hash[key] = value
+ end
+
+ hash
+ end
+ end
+
+ def file
+ @file ||= Pathname.new(framework.root).join "config", "thinking_sphinx.yml"
+ end
+
+ def file_keys
+ @file_keys ||= FILE_KEYS + (original["file_keys"] || [])
+ end
+
+ def join(first, last)
+ return first if last.nil?
+
+ File.join first, last
+ end
+
+ def merged
+ @merged ||= defaults.merge original
+ end
+
+ def original
+ yaml_contents && yaml_contents[framework.environment] || {}
+ end
+
+ def real_path(base, nonexistent = nil)
+ if File.exist?(base)
+ join File.realpath(base), nonexistent
+ else
+ components = File.split base
+ real_path components.first, join(components.last, nonexistent)
+ end
+ end
+
+ def yaml_contents
+ @yaml_contents ||= begin
+ input = File.read file
+ input = ERB.new(input).result if defined?(ERB)
+
+ if YAML_SAFE_LOAD
+ YAML.safe_load(input, aliases: true)
+ else
+ YAML.load(input)
+ end
+ end
+ end
+end
diff --git a/lib/thinking_sphinx/sinatra.rb b/lib/thinking_sphinx/sinatra.rb
index 3dba3c9ef..fe382c7ac 100644
--- a/lib/thinking_sphinx/sinatra.rb
+++ b/lib/thinking_sphinx/sinatra.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
require 'thinking_sphinx'
ActiveSupport.on_load :active_record do
- include ThinkingSphinx::ActiveRecord::Base
+ require 'thinking_sphinx/active_record'
end
diff --git a/lib/thinking_sphinx/sphinxql.rb b/lib/thinking_sphinx/sphinxql.rb
deleted file mode 100644
index fe84e3011..000000000
--- a/lib/thinking_sphinx/sphinxql.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module ThinkingSphinx::SphinxQL
- mattr_accessor :weight, :group_by, :count
-
- def self.functions!
- self.weight = {:select => 'weight()', :column => 'weight()'}
- self.group_by = {
- :select => 'groupby() AS sphinx_internal_group',
- :column => 'sphinx_internal_group'
- }
- self.count = {
- :select => 'id AS sphinx_document_id, count(DISTINCT sphinx_document_id) AS sphinx_internal_count',
- :column => 'sphinx_internal_count'
- }
- end
-
- def self.variables!
- self.weight = {:select => '@weight', :column => '@weight'}
- self.group_by = {:select => '@groupby', :column => '@groupby'}
- self.count = {:select => '@count', :column => '@count'}
- end
-
- self.functions!
-end
diff --git a/lib/thinking_sphinx/subscribers/populator_subscriber.rb b/lib/thinking_sphinx/subscribers/populator_subscriber.rb
index 6fa806183..f113e373f 100644
--- a/lib/thinking_sphinx/subscribers/populator_subscriber.rb
+++ b/lib/thinking_sphinx/subscribers/populator_subscriber.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Subscribers::PopulatorSubscriber
def self.attach_to(namespace)
subscriber = new
@@ -16,19 +18,31 @@ def call(message, *args)
ActiveSupport::Notifications::Event.new(message, *args)
end
+ def error(event)
+ error = event.payload[:error].inner_exception
+ instance = event.payload[:error].instance
+
+ puts <<-MESSAGE
+
+Error transcribing #{instance.class} #{instance.id}:
+#{error.message}
+ MESSAGE
+ end
+
def start_populating(event)
puts "Generating index files for #{event.payload[:index].name}"
end
def populated(event)
- print '.'
+ print '.' * event.payload[:instances].length
end
def finish_populating(event)
print "\n"
end
-end
-ThinkingSphinx::Subscribers::PopulatorSubscriber.attach_to(
- 'thinking_sphinx.real_time'
-)
+ private
+
+ delegate :output, :to => ThinkingSphinx
+ delegate :puts, :print, :to => :output
+end
diff --git a/lib/thinking_sphinx/tasks.rb b/lib/thinking_sphinx/tasks.rb
index 1a59d9e66..2038eb710 100644
--- a/lib/thinking_sphinx/tasks.rb
+++ b/lib/thinking_sphinx/tasks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
namespace :ts do
desc 'Generate the Sphinx configuration file'
task :configure => :environment do
@@ -5,54 +7,79 @@
end
desc 'Generate the Sphinx configuration file and process all indices'
- task :index => :environment do
- interface.index(
- ENV['INDEX_ONLY'] != 'true',
- !Rake.application.options.silent
- )
- end
+ task :index => ['ts:sql:index', 'ts:rt:index']
desc 'Clear out Sphinx files'
- task :clear => :environment do
- interface.clear_all
- end
+ task :clear => ['ts:sql:clear', 'ts:rt:clear']
- desc 'Clear out real-time index files'
- task :clear_rt => :environment do
- interface.clear_real_time
- end
+ desc "Merge all delta indices into their respective core indices"
+ task :merge => ["ts:sql:merge"]
- desc 'Generate fresh index files for real-time indices'
- task :generate => :environment do
- interface.prepare
- interface.generate
- end
-
- desc 'Stop Sphinx, index and then restart Sphinx'
- task :rebuild => [:stop, :clear, :index, :start]
-
- desc 'Stop Sphinx, clear files, reconfigure, start Sphinx, generate files'
- task :regenerate => [:stop, :clear_rt, :configure, :start, :generate]
+ desc 'Delete and regenerate Sphinx files, restart the daemon'
+ task :rebuild => [
+ :stop, :clear, :configure, 'ts:sql:index', :start, 'ts:rt:index'
+ ]
desc 'Restart the Sphinx daemon'
task :restart => [:stop, :start]
desc 'Start the Sphinx daemon'
task :start => :environment do
- interface.start
+ interface.daemon.start
end
desc 'Stop the Sphinx daemon'
task :stop => :environment do
- interface.stop
+ interface.daemon.stop
end
desc 'Determine whether Sphinx is running'
task :status => :environment do
- interface.status
+ interface.daemon.status
+ end
+
+ namespace :sql do
+ desc 'Delete SQL-backed Sphinx files'
+ task :clear => :environment do
+ interface.sql.clear
+ end
+
+ desc 'Generate fresh index files for SQL-backed indices'
+ task :index => :environment do
+ interface.sql.index(ENV['INDEX_ONLY'] != 'true')
+ end
+
+ task :merge => :environment do
+ interface.sql.merge
+ end
+
+ desc 'Delete and regenerate SQL-backed Sphinx files, restart the daemon'
+ task :rebuild => ['ts:stop', 'ts:sql:clear', 'ts:sql:index', 'ts:start']
+ end
+
+ namespace :rt do
+ desc 'Delete real-time Sphinx files'
+ task :clear => :environment do
+ interface.rt.clear
+ end
+
+ desc 'Generate fresh index files for real-time indices'
+ task :index => :environment do
+ interface.rt.index
+ end
+
+ desc 'Delete and regenerate real-time Sphinx files, restart the daemon'
+ task :rebuild => [
+ 'ts:stop', 'ts:rt:clear', 'ts:configure', 'ts:start', 'ts:rt:index'
+ ]
end
def interface
- @interface ||= ThinkingSphinx::RakeInterface.new
+ @interface ||= ThinkingSphinx.rake_interface.new(
+ :verbose => Rake::FileUtilsExt.verbose_flag,
+ :silent => Rake.application.options.silent,
+ :nodetach => (ENV['NODETACH'] == 'true'),
+ :index_names => ENV.fetch('INDEX_FILTER', '').split(',')
+ )
end
end
diff --git a/lib/thinking_sphinx/test.rb b/lib/thinking_sphinx/test.rb
index 0fd150e78..8e4abdcc4 100644
--- a/lib/thinking_sphinx/test.rb
+++ b/lib/thinking_sphinx/test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Test
def self.init(suppress_delta_output = true)
FileUtils.mkdir_p config.indices_location
@@ -40,7 +42,7 @@ def self.clear
config.indices_location,
config.searchd.binlog_path
].each do |path|
- FileUtils.rm_r(path) if File.exists?(path)
+ FileUtils.rm_rf(path) if File.exist?(path)
end
end
diff --git a/lib/thinking_sphinx/utf8.rb b/lib/thinking_sphinx/utf8.rb
index 08f157016..6a6c4e260 100644
--- a/lib/thinking_sphinx/utf8.rb
+++ b/lib/thinking_sphinx/utf8.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::UTF8
attr_reader :string
diff --git a/lib/thinking_sphinx/wildcard.rb b/lib/thinking_sphinx/wildcard.rb
index 57e6b87a2..2027e7ef9 100644
--- a/lib/thinking_sphinx/wildcard.rb
+++ b/lib/thinking_sphinx/wildcard.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ThinkingSphinx::Wildcard
DEFAULT_TOKEN = /\p{Word}+/
@@ -14,7 +16,7 @@ def call
query.gsub(extended_pattern) do
pre, proper, post = $`, $&, $'
# E.g. "@foo", "/2", "~3", but not as part of a token pattern
- is_operator = pre == '@' ||
+ is_operator = pre.match(%r{@$}) ||
pre.match(%r{([^\\]+|\A)[~/]\Z}) ||
pre.match(%r{(\W|^)@\([^\)]*$})
# E.g. "foo bar", with quotes
diff --git a/lib/thinking_sphinx/with_output.rb b/lib/thinking_sphinx/with_output.rb
new file mode 100644
index 000000000..b253b9ce6
--- /dev/null
+++ b/lib/thinking_sphinx/with_output.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module ThinkingSphinx::WithOutput
+ def initialize(configuration, options = {}, stream = STDOUT)
+ @configuration = configuration
+ @options = options
+ @stream = stream
+ end
+
+ private
+
+ attr_reader :configuration, :options, :stream
+end
diff --git a/spec/acceptance/association_scoping_spec.rb b/spec/acceptance/association_scoping_spec.rb
index 1579bbfce..3b78861e8 100644
--- a/spec/acceptance/association_scoping_spec.rb
+++ b/spec/acceptance/association_scoping_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Scoping association search calls by foreign keys', :live => true do
@@ -9,7 +11,7 @@
dublin = Article.create :title => 'Guide to Dublin', :user => paul
index
- pat.articles.search('Guide').to_a.should == [melbourne]
+ expect(pat.articles.search('Guide').to_a).to eq([melbourne])
end
it "limits id-only results to those matching the foreign key" do
@@ -19,7 +21,7 @@
dublin = Article.create :title => 'Guide to Dublin', :user => paul
index
- pat.articles.search_for_ids('Guide').to_a.should == [melbourne.id]
+ expect(pat.articles.search_for_ids('Guide').to_a).to eq([melbourne.id])
end
end
@@ -31,7 +33,7 @@
audi = Manufacturer.create :name => 'Audi'
r_eight = Car.create :name => 'R8 Spyder', :manufacturer => audi
- porsche.cars.search('Spyder').to_a.should == [spyder]
+ expect(porsche.cars.search('Spyder').to_a).to eq([spyder])
end
it "limits id-only results to those matching the foreign key" do
@@ -41,7 +43,7 @@
audi = Manufacturer.create :name => 'Audi'
r_eight = Car.create :name => 'R8 Spyder', :manufacturer => audi
- porsche.cars.search_for_ids('Spyder').to_a.should == [spyder.id]
+ expect(porsche.cars.search_for_ids('Spyder').to_a).to eq([spyder.id])
end
end
@@ -57,7 +59,7 @@
pancakes.categories << flat
waffles.categories << food
- flat.products.search('Low').to_a.should == [pancakes]
+ expect(flat.products.search('Low').to_a).to eq([pancakes])
end
end
end
diff --git a/spec/acceptance/attribute_access_spec.rb b/spec/acceptance/attribute_access_spec.rb
index ae0c82ca5..10d4ef775 100644
--- a/spec/acceptance/attribute_access_spec.rb
+++ b/spec/acceptance/attribute_access_spec.rb
@@ -1,41 +1,58 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Accessing attributes directly via search results', :live => true do
it "allows access to attribute values" do
- Book.create! :title => 'American Gods', :year => 2001
+ Book.create! :title => 'American Gods', :publishing_year => 2001
index
search = Book.search('gods')
search.context[:panes] << ThinkingSphinx::Panes::AttributesPane
- search.first.sphinx_attributes['year'].should == 2001
+ expect(search.first.sphinx_attributes['publishing_year']).to eq(2001)
end
it "provides direct access to the search weight/relevance scores" do
- Book.create! :title => 'American Gods', :year => 2001
+ Book.create! :title => 'American Gods', :publishing_year => 2001
index
- search = Book.search 'gods',
- :select => "*, #{ThinkingSphinx::SphinxQL.weight[:select]}"
+ search = Book.search 'gods', :select => "*, weight()"
+ search.context[:panes] << ThinkingSphinx::Panes::WeightPane
+
+ if ENV["SPHINX_ENGINE"] == "sphinx" && ENV["SPHINX_VERSION"].to_f > 3.3
+ expect(search.first.weight).to eq(20_000.0)
+ else
+ expect(search.first.weight).to eq(2500)
+ end
+ end
+
+ it "provides direct access to the weight with alternative primary keys" do
+ album = Album.create! :name => 'Sing to the Moon', :artist => 'Laura Mvula'
+
+ search = Album.search 'sing', :select => "*, weight()"
search.context[:panes] << ThinkingSphinx::Panes::WeightPane
- search.first.weight.should == 2500
+ expect(search.first.weight).to be >= 1000
end
it "can enumerate with the weight" do
- gods = Book.create! :title => 'American Gods', :year => 2001
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
index
- search = Book.search 'gods',
- :select => "*, #{ThinkingSphinx::SphinxQL.weight[:select]}"
+ search = Book.search 'gods', :select => "*, weight()"
search.masks << ThinkingSphinx::Masks::WeightEnumeratorMask
- expectations = [[gods, 2500]]
+ if ENV["SPHINX_ENGINE"] == "sphinx" && ENV["SPHINX_VERSION"].to_f > 3.3
+ expectations = [[gods, 20_000.0]]
+ else
+ expectations = [[gods, 2500]]
+ end
search.each_with_weight do |result, weight|
expectation = expectations.shift
- result.should == expectation.first
- weight.should == expectation.last
+ expect(result).to eq(expectation.first)
+ expect(weight).to eq(expectation.last)
end
end
end
diff --git a/spec/acceptance/attribute_updates_spec.rb b/spec/acceptance/attribute_updates_spec.rb
index 1576e7706..830c25b47 100644
--- a/spec/acceptance/attribute_updates_spec.rb
+++ b/spec/acceptance/attribute_updates_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Update attributes automatically where possible', :live => true do
@@ -5,12 +7,12 @@
article = Article.create :title => 'Pancakes', :published => false
index
- Article.search('pancakes', :with => {:published => true}).should be_empty
+ expect(Article.search('pancakes', :with => {:published => true})).to be_empty
article.published = true
article.save
- Article.search('pancakes', :with => {:published => true}).to_a
- .should == [article]
+ expect(Article.search('pancakes', :with => {:published => true}).to_a)
+ .to eq([article])
end
end
diff --git a/spec/acceptance/batch_searching_spec.rb b/spec/acceptance/batch_searching_spec.rb
index 731cc1f0e..e5a72acfe 100644
--- a/spec/acceptance/batch_searching_spec.rb
+++ b/spec/acceptance/batch_searching_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Executing multiple searches in one Sphinx call', :live => true do
@@ -12,10 +14,10 @@
batch.populate
- batch.searches.first.should include(pancakes)
- batch.searches.first.should_not include(waffles)
+ expect(batch.searches.first).to include(pancakes)
+ expect(batch.searches.first).not_to include(waffles)
- batch.searches.last.should include(waffles)
- batch.searches.last.should_not include(pancakes)
+ expect(batch.searches.last).to include(waffles)
+ expect(batch.searches.last).not_to include(pancakes)
end
end
diff --git a/spec/acceptance/big_integers_spec.rb b/spec/acceptance/big_integers_spec.rb
index 2106aaa98..3f097a515 100644
--- a/spec/acceptance/big_integers_spec.rb
+++ b/spec/acceptance/big_integers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe '64 bit integer support' do
@@ -21,17 +23,17 @@
[small_index, large_index, real_time_index]
).reconcile
- large_index.sources.first.attributes.detect { |attribute|
+ expect(large_index.sources.first.attributes.detect { |attribute|
attribute.name == 'sphinx_internal_id'
- }.type.should == :bigint
+ }.type).to eq(:bigint)
- small_index.sources.first.attributes.detect { |attribute|
+ expect(small_index.sources.first.attributes.detect { |attribute|
attribute.name == 'sphinx_internal_id'
- }.type.should == :bigint
+ }.type).to eq(:bigint)
- real_time_index.attributes.detect { |attribute|
+ expect(real_time_index.attributes.detect { |attribute|
attribute.name == 'sphinx_internal_id'
- }.type.should == :bigint
+ }.type).to eq(:bigint)
end
end
@@ -50,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_attribute :id, 980190962
+ product.update :id => 980190962
expect(
Product.search('widget', :indices => ['product_core']).to_a
).to eq([product])
diff --git a/spec/acceptance/excerpts_spec.rb b/spec/acceptance/excerpts_spec.rb
index abc721fc0..10aa5b982 100644
--- a/spec/acceptance/excerpts_spec.rb
+++ b/spec/acceptance/excerpts_spec.rb
@@ -1,29 +1,30 @@
# encoding: utf-8
+# frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Accessing excerpts for methods on a search result', :live => true do
it "returns excerpts for a given method" do
- Book.create! :title => 'American Gods', :year => 2001
+ Book.create! :title => 'American Gods', :publishing_year => 2001
index
search = Book.search('gods')
search.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
- search.first.excerpts.title.
- should == 'American Gods'
+ expect(search.first.excerpts.title).
+ to eq('American Gods')
end
it "handles UTF-8 text for excerpts" do
- Book.create! :title => 'Война и миръ', :year => 1869
+ Book.create! :title => 'Война и миръ', :publishing_year => 1869
index
search = Book.search 'миръ'
search.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
- search.first.excerpts.title.
- should == 'Война и миръ'
- end
+ expect(search.first.excerpts.title).
+ to eq('Война и миръ')
+ end if ENV['SPHINX_VERSION'].try :[], /2.2.\d/
it "does not include class names in excerpts" do
Book.create! :title => 'The Graveyard Book'
@@ -32,8 +33,8 @@
search = Book.search('graveyard')
search.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
- search.first.excerpts.title.
- should == 'The Graveyard Book'
+ expect(search.first.excerpts.title).
+ to eq('The Graveyard Book')
end
it "respects the star option with queries" do
@@ -43,7 +44,7 @@
search = Article.search('thin', :star => true)
search.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
- search.first.excerpts.title.
- should == 'Something'
+ expect(search.first.excerpts.title).
+ to eq('Something')
end
end
diff --git a/spec/acceptance/facets_spec.rb b/spec/acceptance/facets_spec.rb
index 5c49467c5..e2102833c 100644
--- a/spec/acceptance/facets_spec.rb
+++ b/spec/acceptance/facets_spec.rb
@@ -1,4 +1,5 @@
# encoding: utf-8
+# frozen_string_literal: true
require 'acceptance/spec_helper'
@@ -16,9 +17,9 @@
Tee.create! :colour => green
index
- Tee.facets.to_hash[:colour_id].should == {
+ expect(Tee.facets.to_hash[:colour_id]).to eq({
blue.id => 2, red.id => 1, green.id => 3
- }
+ })
end
it "provides facet breakdowns across classes" do
@@ -28,11 +29,9 @@
Article.create!
index
- article_count = ENV['SPHINX_VERSION'].try(:[], /2.0.\d/) ? 2 : 1
-
- ThinkingSphinx.facets.to_hash[:class].should == {
- 'Tee' => 2, 'City' => 1, 'Article' => article_count
- }
+ expect(ThinkingSphinx.facets.to_hash[:class]).to eq({
+ 'Tee' => 2, 'City' => 1, 'Article' => 1
+ })
end
it "handles field facets" do
@@ -42,9 +41,9 @@
Book.create! :title => '1Q84', :author => '村上 春樹'
index
- Book.facets.to_hash[:author].should == {
+ expect(Book.facets.to_hash[:author]).to eq({
'Neil Gaiman' => 2, 'Terry Pratchett' => 1, '村上 春樹' => 1
- }
+ })
end
it "handles MVA facets" do
@@ -62,9 +61,9 @@
:tag => pancakes
index
- User.facets.to_hash[:tag_ids].should == {
+ expect(User.facets.to_hash[:tag_ids]).to eq({
pancakes.id => 2, waffles.id => 1
- }
+ })
end
it "can filter on integer facet results" do
@@ -76,7 +75,7 @@
r1 = Tee.create! :colour => red
index
- Tee.facets.for(:colour_id => blue.id).to_a.should == [b1, b2]
+ expect(Tee.facets.for(:colour_id => blue.id).to_a).to eq([b1, b2])
end
it "can filter on MVA facet results" do
@@ -91,7 +90,7 @@
Tagging.create! :article => Article.create!(:user => u2), :tag => pancakes
index
- User.facets.for(:tag_ids => waffles.id).to_a.should == [u1]
+ expect(User.facets.for(:tag_ids => waffles.id).to_a).to eq([u1])
end
it "can filter on string facet results" do
@@ -100,7 +99,7 @@
snuff = Book.create! :title => 'Snuff', :author => 'Terry Pratchett'
index
- Book.facets.for(:author => 'Neil Gaiman').to_a.should == [gods, boys]
+ expect(Book.facets.for(:author => 'Neil Gaiman').to_a).to eq([gods, boys])
end
it "allows enumeration" do
@@ -119,10 +118,24 @@
[:class, {'Tee' => 3}]
]
Tee.facets.each do |facet, hash|
- facet.should == expectations[calls].first
- hash.should == expectations[calls].last
+ expect(facet).to eq(expectations[calls].first)
+ expect(hash).to eq(expectations[calls].last)
calls += 1
end
end
+
+ it "can be called on distributed indices" do
+ blue = Colour.create! :name => 'blue'
+ green = Colour.create! :name => 'green'
+
+ Tee.create! :colour => blue
+ Tee.create! :colour => blue
+ Tee.create! :colour => green
+ index
+
+ expect(Tee.facets(:indices => ["tee"]).to_hash[:colour_id]).to eq({
+ blue.id => 2, green.id => 1
+ })
+ end
end
diff --git a/spec/acceptance/geosearching_spec.rb b/spec/acceptance/geosearching_spec.rb
index a0438f5aa..9f68c2ce9 100644
--- a/spec/acceptance/geosearching_spec.rb
+++ b/spec/acceptance/geosearching_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Searching by latitude and longitude', :live => true do
@@ -7,8 +9,8 @@
bri = City.create :name => 'Brisbane', :lat => -0.4794031, :lng => 2.670838
index
- City.search(:geo => [-0.616241, 2.602712], :order => 'geodist ASC').
- to_a.should == [syd, mel, bri]
+ expect(City.search(:geo => [-0.616241, 2.602712], :order => 'geodist ASC').
+ to_a).to eq([syd, mel, bri])
end
it "filters by distance" do
@@ -17,10 +19,10 @@
bri = City.create :name => 'Brisbane', :lat => -0.4794031, :lng => 2.670838
index
- City.search(
+ expect(City.search(
:geo => [-0.616241, 2.602712],
:with => {:geodist => 0.0..470_000.0}
- ).to_a.should == [mel, syd]
+ ).to_a).to eq([mel, syd])
end
it "provides the distance for each search result" do
@@ -30,16 +32,26 @@
index
cities = City.search(:geo => [-0.616241, 2.602712], :order => 'geodist ASC')
- if ENV['SPHINX_VERSION'].try :[], /2.2.\d/
+ if ENV.fetch('SPHINX_VERSION', '2.1.2').to_f > 2.1
expected = {:mysql => 249907.171875, :postgresql => 249912.03125}
else
expected = {:mysql => 250326.906250, :postgresql => 250331.234375}
end
- if ActiveRecord::Base.configurations['test']['adapter'][/postgres/]
- cities.first.geodist.should == expected[:postgresql]
+ adapter = nil
+
+ if ActiveRecord::VERSION::STRING.to_f > 6.0
+ adapter = ActiveRecord::Base.configurations.configs_for.first.adapter
+ elsif ActiveRecord::VERSION::STRING.to_f > 5.2
+ adapter = ActiveRecord::Base.configurations.configs_for.first.config["adapter"]
+ else
+ adapter = ActiveRecord::Base.configurations['test']['adapter']
+ end
+
+ if adapter[/postgres/]
+ expect(cities.first.geodist).to be_within(0.01).of(expected[:postgresql])
else # mysql
- cities.first.geodist.should == expected[:mysql]
+ expect(cities.first.geodist).to be_within(0.01).of(expected[:mysql])
end
end
@@ -49,10 +61,10 @@
bri = City.create :name => 'Brisbane', :lat => -0.4794031, :lng => 2.670838
index
- City.search(
+ expect(City.search(
:geo => [-0.616241, 2.602712],
:with => {:geodist => 0.0..470_000.0},
:select => "*, geodist as custom_weight"
- ).to_a.should == [mel, syd]
+ ).to_a).to eq([mel, syd])
end
end
diff --git a/spec/acceptance/grouping_by_attributes_spec.rb b/spec/acceptance/grouping_by_attributes_spec.rb
index 831517a04..8481f9dca 100644
--- a/spec/acceptance/grouping_by_attributes_spec.rb
+++ b/spec/acceptance/grouping_by_attributes_spec.rb
@@ -1,77 +1,79 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Grouping search results by attributes', :live => true do
it "groups by the provided attribute" do
- snuff = Book.create! :title => 'Snuff', :year => 2011
- earth = Book.create! :title => 'The Long Earth', :year => 2012
- dodger = Book.create! :title => 'Dodger', :year => 2012
+ snuff = Book.create! :title => 'Snuff', :publishing_year => 2011
+ earth = Book.create! :title => 'The Long Earth', :publishing_year => 2012
+ dodger = Book.create! :title => 'Dodger', :publishing_year => 2012
index
- Book.search(:group_by => :year).to_a.should == [snuff, earth]
+ expect(Book.search(:group_by => :publishing_year).to_a).to eq([snuff, earth])
end
it "allows sorting within the group" do
- snuff = Book.create! :title => 'Snuff', :year => 2011
- earth = Book.create! :title => 'The Long Earth', :year => 2012
- dodger = Book.create! :title => 'Dodger', :year => 2012
+ snuff = Book.create! :title => 'Snuff', :publishing_year => 2011
+ earth = Book.create! :title => 'The Long Earth', :publishing_year => 2012
+ dodger = Book.create! :title => 'Dodger', :publishing_year => 2012
index
- Book.search(:group_by => :year, :order_group_by => 'title ASC').to_a.
- should == [snuff, dodger]
+ expect(Book.search(:group_by => :publishing_year, :order_group_by => 'title ASC').to_a).
+ to eq([snuff, dodger])
end
it "allows enumerating by count" do
- snuff = Book.create! :title => 'Snuff', :year => 2011
- earth = Book.create! :title => 'The Long Earth', :year => 2012
- dodger = Book.create! :title => 'Dodger', :year => 2012
+ snuff = Book.create! :title => 'Snuff', :publishing_year => 2011
+ earth = Book.create! :title => 'The Long Earth', :publishing_year => 2012
+ dodger = Book.create! :title => 'Dodger', :publishing_year => 2012
index
expectations = [[snuff, 1], [earth, 2]]
- Book.search(:group_by => :year).each_with_count do |book, count|
+ Book.search(:group_by => :publishing_year).each_with_count do |book, count|
expectation = expectations.shift
- book.should == expectation.first
- count.should == expectation.last
+ expect(book).to eq(expectation.first)
+ expect(count).to eq(expectation.last)
end
end
it "allows enumerating by group" do
- snuff = Book.create! :title => 'Snuff', :year => 2011
- earth = Book.create! :title => 'The Long Earth', :year => 2012
- dodger = Book.create! :title => 'Dodger', :year => 2012
+ snuff = Book.create! :title => 'Snuff', :publishing_year => 2011
+ earth = Book.create! :title => 'The Long Earth', :publishing_year => 2012
+ dodger = Book.create! :title => 'Dodger', :publishing_year => 2012
index
expectations = [[snuff, 2011], [earth, 2012]]
- Book.search(:group_by => :year).each_with_group do |book, group|
+ Book.search(:group_by => :publishing_year).each_with_group do |book, group|
expectation = expectations.shift
- book.should == expectation.first
- group.should == expectation.last
+ expect(book).to eq(expectation.first)
+ expect(group).to eq(expectation.last)
end
end
it "allows enumerating by group and count" do
- snuff = Book.create! :title => 'Snuff', :year => 2011
- earth = Book.create! :title => 'The Long Earth', :year => 2012
- dodger = Book.create! :title => 'Dodger', :year => 2012
+ snuff = Book.create! :title => 'Snuff', :publishing_year => 2011
+ earth = Book.create! :title => 'The Long Earth', :publishing_year => 2012
+ dodger = Book.create! :title => 'Dodger', :publishing_year => 2012
index
expectations = [[snuff, 2011, 1], [earth, 2012, 2]]
- search = Book.search(:group_by => :year)
+ search = Book.search(:group_by => :publishing_year)
search.each_with_group_and_count do |book, group, count|
expectation = expectations.shift
- book.should == expectation[0]
- group.should == expectation[1]
- count.should == expectation[2]
+ expect(book).to eq(expectation[0])
+ expect(group).to eq(expectation[1])
+ expect(count).to eq(expectation[2])
end
end
end
diff --git a/spec/acceptance/index_options_spec.rb b/spec/acceptance/index_options_spec.rb
index 1491453a9..1634f990e 100644
--- a/spec/acceptance/index_options_spec.rb
+++ b/spec/acceptance/index_options_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Index options' do
@@ -14,11 +16,11 @@
end
it "keeps #{type}_fields blank" do
- index.send("#{type}_fields").should be_nil
+ expect(index.send("#{type}_fields")).to be_nil
end
it "sets min_#{type}_len" do
- index.send("min_#{type}_len").should == 3
+ expect(index.send("min_#{type}_len")).to eq(3)
end
end
@@ -33,11 +35,11 @@
end
it "#{type}_fields should contain the field" do
- index.send("#{type}_fields").should == 'title'
+ expect(index.send("#{type}_fields")).to eq('title')
end
it "sets min_#{type}_len" do
- index.send("min_#{type}_len").should == 3
+ expect(index.send("min_#{type}_len")).to eq(3)
end
end
end
@@ -57,12 +59,12 @@
end
it "stores each source definition" do
- index.sources.length.should == 2
+ expect(index.sources.length).to eq(2)
end
it "treats each source as separate" do
- index.sources.first.fields.length.should == 2
- index.sources.last.fields.length.should == 3
+ expect(index.sources.first.fields.length).to eq(2)
+ expect(index.sources.last.fields.length).to eq(3)
end
end
@@ -77,11 +79,11 @@
end
it "declares wordcount fields" do
- index.sources.first.sql_field_str2wordcount.should == ['title']
+ expect(index.sources.first.sql_field_str2wordcount).to eq(['title'])
end
it "declares wordcount attributes" do
- index.sources.first.sql_attr_str2wordcount.should == ['content']
+ expect(index.sources.first.sql_attr_str2wordcount).to eq(['content'])
end
end
@@ -98,15 +100,15 @@
end
it "allows for core source settings" do
- index.sources.first.sql_range_step.should == 5
+ expect(index.sources.first.sql_range_step).to eq(5)
end
it "allows for source options" do
- index.sources.first.disable_range?.should be_true
+ expect(index.sources.first.disable_range?).to be_truthy
end
it "respects sql_query_pre values" do
- index.sources.first.sql_query_pre.should include("DO STUFF")
+ expect(index.sources.first.sql_query_pre).to include("DO STUFF")
end
end
@@ -130,23 +132,23 @@
end
it "prioritises index-level options over YAML options" do
- index.min_infix_len.should == 1
+ expect(index.min_infix_len).to eq(1)
end
it "prioritises index-level source options" do
- index.sources.first.sql_range_step.should == 20
+ expect(index.sources.first.sql_range_step).to eq(20)
end
it "keeps index-level options prioritised when rendered again" do
index.render
- index.min_infix_len.should == 1
+ expect(index.min_infix_len).to eq(1)
end
it "keeps index-level options prioritised when rendered again" do
index.render
- index.sources.first.sql_range_step.should == 20
+ expect(index.sources.first.sql_range_step).to eq(20)
end
end
end
diff --git a/spec/acceptance/indexing_spec.rb b/spec/acceptance/indexing_spec.rb
index 09dd96455..d2182ee5a 100644
--- a/spec/acceptance/indexing_spec.rb
+++ b/spec/acceptance/indexing_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Indexing', :live => true do
@@ -8,7 +10,7 @@
article = Article.create! :title => 'Pancakes'
index 'article_core'
- Article.search.should be_empty
+ expect(Article.search).to be_empty
FileUtils.rm path
end
@@ -20,7 +22,7 @@
article = Article.create! :title => 'Pancakes'
index 'article_core'
- Article.search.should_not be_empty
+ expect(Article.search).not_to be_empty
FileUtils.rm path
end
@@ -31,6 +33,6 @@
index 'article_core'
file = Rails.root.join('db/sphinx/test/ts-article_core.tmp')
- File.exist?(file).should be_false
+ expect(File.exist?(file)).to be_falsey
end
end
diff --git a/spec/acceptance/merging_spec.rb b/spec/acceptance/merging_spec.rb
new file mode 100644
index 000000000..7e69e8a16
--- /dev/null
+++ b/spec/acceptance/merging_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require "acceptance/spec_helper"
+
+describe "Merging deltas", :live => true do
+ it "merges in new records" do
+ guards = Book.create(
+ :title => "Guards! Guards!", :author => "Terry Pratchett"
+ )
+ sleep 0.25
+
+ expect(
+ Book.search("Terry Pratchett", :indices => ["book_delta"]).to_a
+ ).to eq([guards])
+ expect(
+ Book.search("Terry Pratchett", :indices => ["book_core"]).to_a
+ ).to be_empty
+
+ merge
+ guards.reload
+
+ expect(
+ Book.search("Terry Pratchett", :indices => ["book_core"]).to_a
+ ).to eq([guards])
+ expect(guards.delta).to eq(false)
+ end
+
+ it "merges in changed records" do
+ race = Book.create(
+ :title => "The Hate Space", :author => "Maxine Beneba Clarke"
+ )
+ index
+ expect(
+ Book.search("Space", :indices => ["book_core"]).to_a
+ ).to eq([race])
+
+ race.reload.update :title => "The Hate Race"
+ sleep 0.25
+ expect(
+ Book.search("Race", :indices => ["book_delta"]).to_a
+ ).to eq([race])
+ expect(
+ Book.search("Race", :indices => ["book_core"]).to_a
+ ).to be_empty
+
+ merge
+ race.reload
+
+ expect(
+ Book.search("Race", :indices => ["book_core"]).to_a
+ ).to eq([race])
+ expect(
+ Book.search("Race", :indices => ["book_delta"]).to_a
+ ).to eq([race])
+ expect(
+ Book.search("Space", :indices => ["book_core"]).to_a
+ ).to be_empty
+ expect(race.delta).to eq(false)
+ end
+
+ it "maintains existing records" do
+ race = Book.create(
+ :title => "The Hate Race", :author => "Maxine Beneba Clarke"
+ )
+ index
+
+ soil = Book.create(
+ :title => "Foreign Soil", :author => "Maxine Beneba Clarke"
+ )
+ sleep 0.25
+ expect(
+ Book.search("Soil", :indices => ["book_delta"]).to_a
+ ).to eq([soil])
+ expect(
+ Book.search("Soil", :indices => ["book_core"]).to_a
+ ).to be_empty
+ expect(
+ Book.search("Race", :indices => ["book_core"]).to_a
+ ).to eq([race])
+
+ merge
+
+ expect(
+ Book.search("Soil", :indices => ["book_core"]).to_a
+ ).to eq([soil])
+ expect(
+ Book.search("Race", :indices => ["book_core"]).to_a
+ ).to eq([race])
+ end
+end
diff --git a/spec/acceptance/paginating_search_results_spec.rb b/spec/acceptance/paginating_search_results_spec.rb
index 87f1fc5d6..df085f432 100644
--- a/spec/acceptance/paginating_search_results_spec.rb
+++ b/spec/acceptance/paginating_search_results_spec.rb
@@ -1,24 +1,42 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Paginating search results', :live => true do
it "tracks how many results there are in total" do
+ expect(Article.search.total_entries).to be_zero
+
21.times { |number| Article.create :title => "Article #{number}" }
index
- Article.search.total_entries.should == 21
+ if ENV["SPHINX_ENGINE"] == "manticore" && ENV["SPHINX_VERSION"].to_f >= 4.0
+ # I suspect this is a bug in Manticore?
+ expect(Article.search.total_entries).to eq(22)
+ else
+ expect(Article.search.total_entries).to eq(21)
+ end
end
it "paginates the result set by default" do
+ expect(Article.search.total_entries).to be_zero
+
21.times { |number| Article.create :title => "Article #{number}" }
index
- Article.search.length.should == 20
+ expect(Article.search.length).to eq(20)
end
it "tracks the number of pages" do
+ expect(Article.search.total_entries).to be_zero
+
21.times { |number| Article.create :title => "Article #{number}" }
index
- Article.search.total_pages.should == 2
+ if ENV["SPHINX_ENGINE"] == "manticore" && ENV["SPHINX_VERSION"].to_f >= 4.0
+ # I suspect this is a bug in Manticore?
+ expect(Article.search.total_pages).to eq(1)
+ else
+ expect(Article.search.total_pages).to eq(2)
+ end
end
end
diff --git a/spec/acceptance/real_time_updates_spec.rb b/spec/acceptance/real_time_updates_spec.rb
index 57d3d273a..8eca41b6c 100644
--- a/spec/acceptance/real_time_updates_spec.rb
+++ b/spec/acceptance/real_time_updates_spec.rb
@@ -1,17 +1,115 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Updates to records in real-time indices', :live => true do
it "handles fields with unicode nulls" do
product = Product.create! :name => "Widget \u0000"
- Product.search.first.should == product
- end
+ expect(Product.search.first).to eq(product)
+ end unless ENV['DATABASE'] == 'postgresql'
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])
+ end
+
+ it "handles inserts and updates for namespaced models" do
+ person = Admin::Person.create :name => 'Death'
+
+ expect(Admin::Person.search('Death').to_a).to eq([person])
+
+ person.update :name => 'Mort'
+
+ expect(Admin::Person.search('Death').to_a).to be_empty
+ expect(Admin::Person.search('Mort').to_a).to eq([person])
+ end
+
+ it "can use direct interface for upserting records" do
+ Admin::Person.connection.execute <<~SQL
+ INSERT INTO admin_people (name, created_at, updated_at)
+ VALUES ('Pat', now(), now());
+ SQL
+
+ expect(Admin::Person.search('Pat').to_a).to be_empty
+
+ instance = Admin::Person.find_by(:name => 'Pat')
+ ThinkingSphinx::Processor.new(instance: instance).upsert
+
+ expect(Admin::Person.search('Pat').to_a).to eq([instance])
+
+ Admin::Person.connection.execute <<~SQL
+ UPDATE admin_people SET name = 'Patrick' WHERE name = 'Pat';
+ SQL
+
+ expect(Admin::Person.search('Patrick').to_a).to be_empty
+
+ instance.reload
+ ThinkingSphinx::Processor.new(model: Admin::Person, id: instance.id).upsert
+
+ expect(Admin::Person.search('Patrick').to_a).to eq([instance])
+ end
+
+ it "can use direct interface for processing records outside scope" do
+ Article.connection.execute <<~SQL
+ INSERT INTO articles (title, published, created_at, updated_at)
+ VALUES ('Nice Title', TRUE, now(), now());
+ SQL
+
+ article = Article.last
+
+ ThinkingSphinx::Processor.new(model: article.class, id: article.id).sync
+
+ expect(ThinkingSphinx.search('Nice', :indices => ["published_articles_core"])).to include(article)
+
+ Article.connection.execute <<~SQL
+ UPDATE articles SET published = FALSE WHERE title = 'Nice Title';
+ SQL
+ ThinkingSphinx::Processor.new(model: article.class, id: article.id).sync
+
+ expect(ThinkingSphinx.search('Nice', :indices => ["published_articles_core"])).to be_empty
+ end
+
+ it "can use direct interface for processing deleted records" do
+ Article.connection.execute <<~SQL
+ INSERT INTO articles (title, published, created_at, updated_at)
+ VALUES ('Nice Title', TRUE, now(), now());
+ SQL
+
+ article = Article.last
+ ThinkingSphinx::Processor.new(:instance => article).sync
+
+ expect(ThinkingSphinx.search('Nice', :indices => ["published_articles_core"])).to include(article)
+
+ Article.connection.execute <<~SQL
+ DELETE FROM articles where title = 'Nice Title';
+ SQL
+
+ ThinkingSphinx::Processor.new(:instance => article).sync
+
+ expect(ThinkingSphinx.search('Nice', :indices => ["published_articles_core"])).to be_empty
+ end
+
+ it "syncs records in real-time index with alternate ids" do
+ Album.connection.execute <<~SQL
+ INSERT INTO albums (id, name, artist, integer_id)
+ VALUES ('#{("a".."z").to_a.sample}', 'Sing to the Moon', 'Laura Mvula', #{rand(10000)});
+ SQL
+
+ album = Album.last
+ ThinkingSphinx::Processor.new(:model => Album, id: album.integer_id).sync
+
+ expect(ThinkingSphinx.search('Laura', :indices => ["album_real_core"])).to include(album)
+
+ Article.connection.execute <<~SQL
+ DELETE FROM albums where id = '#{album.id}';
+ SQL
+
+ ThinkingSphinx::Processor.new(:instance => album).sync
- Product.search('blue fish', :indices => ['product_core']).to_a.
- should == [product]
+ expect(ThinkingSphinx.search('Laura', :indices => ["album_real_core"])).to be_empty
end
end
diff --git a/spec/acceptance/remove_deleted_records_spec.rb b/spec/acceptance/remove_deleted_records_spec.rb
index 1101afd50..b4146751c 100644
--- a/spec/acceptance/remove_deleted_records_spec.rb
+++ b/spec/acceptance/remove_deleted_records_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Hiding deleted records from search results', :live => true do
@@ -5,32 +7,62 @@
pancakes = Article.create! :title => 'Pancakes'
index
- Article.search('pancakes').should_not be_empty
+ expect(Article.search('pancakes')).not_to be_empty
pancakes.destroy
- Article.search('pancakes').should be_empty
+ expect(Article.search('pancakes')).to be_empty
end
it "will catch stale records deleted without callbacks being fired" do
pancakes = Article.create! :title => 'Pancakes'
index
- Article.search('pancakes').should_not be_empty
+ expect(Article.search('pancakes')).not_to be_empty
Article.connection.execute "DELETE FROM articles WHERE id = #{pancakes.id}"
- Article.search('pancakes').should be_empty
+ expect(Article.search('pancakes')).to be_empty
end
it "removes records from real-time index results" do
product = Product.create! :name => 'Shiny'
- Product.search('Shiny', :indices => ['product_core']).to_a.
- should == [product]
+ expect(Product.search('Shiny', :indices => ['product_core']).to_a).
+ to eq([product])
+
+ product.destroy
+
+ expect(Product.search_for_ids('Shiny', :indices => ['product_core'])).
+ to be_empty
+ end
+
+ it "removes records from real-time index results with alternate ids" do
+ album = Album.create! :name => 'Sing to the Moon', :artist => 'Laura Mvula'
+
+ expect(Album.search('Sing', :indices => ['album_real_core']).to_a).
+ to eq([album])
+
+ album.destroy
+
+ expect(Album.search_for_ids('Sing', :indices => ['album_real_core'])).
+ to 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'
+ expect(Product.search('Shiny', :indices => ['product_core']).to_a).
+ to eq([product])
+
+ ThinkingSphinx::Configuration.instance.
+ settings['real_time_callbacks'] = false
product.destroy
+ expect(Product.search_for_ids('Shiny', :indices => ['product_core'])).
+ not_to be_empty
- Product.search_for_ids('Shiny', :indices => ['product_core']).
- should be_empty
+ ThinkingSphinx::Configuration.instance.
+ settings['real_time_callbacks'] = original
end
it "deletes STI child classes from parent indices" do
@@ -40,4 +72,28 @@
expect(Bird.search_for_ids('duck')).to be_empty
end
+
+ it "can use a direct interface for processing records" do
+ pancakes = Article.create! :title => 'Pancakes'
+ index
+ expect(Article.search('pancakes')).not_to be_empty
+
+ Article.connection.execute "DELETE FROM articles WHERE id = #{pancakes.id}"
+ expect(Article.search_for_ids('pancakes')).not_to be_empty
+
+ ThinkingSphinx::Processor.new(instance: pancakes).delete
+ expect(Article.search_for_ids('pancakes')).to be_empty
+ end
+
+ it "can use a direct interface for processing records without an instance" do
+ pancakes = Article.create! :title => 'Pancakes'
+ index
+ expect(Article.search('pancakes')).not_to be_empty
+
+ Article.connection.execute "DELETE FROM articles WHERE id = #{pancakes.id}"
+ expect(Article.search_for_ids('pancakes')).not_to be_empty
+
+ ThinkingSphinx::Processor.new(model: Article, id: pancakes.id).delete
+ expect(Article.search_for_ids('pancakes')).to be_empty
+ end
end
diff --git a/spec/acceptance/search_counts_spec.rb b/spec/acceptance/search_counts_spec.rb
index 39798bb79..1fe7be3e8 100644
--- a/spec/acceptance/search_counts_spec.rb
+++ b/spec/acceptance/search_counts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Get search result counts', :live => true do
@@ -5,7 +7,7 @@
4.times { |i| Article.create :title => "Article #{i}" }
index
- Article.search_count.should == 4
+ expect(Article.search_count).to eq(4)
end
it "returns counts across all models" do
@@ -13,6 +15,6 @@
2.times { |i| Book.create :title => "Book #{i}" }
index
- ThinkingSphinx.count.should == 5
+ expect(ThinkingSphinx.count).to eq(5)
end
end
diff --git a/spec/acceptance/search_for_just_ids_spec.rb b/spec/acceptance/search_for_just_ids_spec.rb
index 8b9c69a6e..8862dbd30 100644
--- a/spec/acceptance/search_for_just_ids_spec.rb
+++ b/spec/acceptance/search_for_just_ids_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Searching for just instance Ids', :live => true do
@@ -6,7 +8,7 @@
waffles = Article.create! :title => 'Waffles'
index
- Article.search_for_ids('pancakes').to_a.should == [pancakes.id]
+ expect(Article.search_for_ids('pancakes').to_a).to eq([pancakes.id])
end
it "works across the global context" do
@@ -14,6 +16,6 @@
book = Book.create! :title => 'American Gods'
index
- ThinkingSphinx.search_for_ids.to_a.should =~ [article.id, book.id]
+ expect(ThinkingSphinx.search_for_ids.to_a).to match_array([article.id, book.id])
end
end
diff --git a/spec/acceptance/searching_across_models_spec.rb b/spec/acceptance/searching_across_models_spec.rb
index 626a3f87a..e471fcd71 100644
--- a/spec/acceptance/searching_across_models_spec.rb
+++ b/spec/acceptance/searching_across_models_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Searching across models', :live => true do
@@ -5,7 +7,7 @@
article = Article.create! :title => 'Pancakes'
index
- ThinkingSphinx.search.first.should == article
+ expect(ThinkingSphinx.search.first).to eq(article)
end
it "returns results matching the given query" do
@@ -14,8 +16,8 @@
index
articles = ThinkingSphinx.search 'pancakes'
- articles.should include(pancakes)
- articles.should_not include(waffles)
+ expect(articles).to include(pancakes)
+ expect(articles).not_to include(waffles)
end
it "handles results from different models" do
@@ -23,7 +25,7 @@
book = Book.create! :title => 'American Gods'
index
- ThinkingSphinx.search.to_a.should =~ [article, book]
+ expect(ThinkingSphinx.search.to_a).to match_array([article, book])
end
it "filters by multiple classes" do
@@ -32,7 +34,14 @@
user = User.create! :name => 'Pat'
index
- ThinkingSphinx.search(:classes => [User, Article]).to_a.
- should =~ [article, user]
+ expect(ThinkingSphinx.search(:classes => [User, Article]).to_a).
+ to match_array([article, user])
+ end
+
+ it "has a 'none' default scope" do
+ article = Article.create! :title => 'Pancakes'
+ index
+
+ expect(ThinkingSphinx.none).to be_empty
end
end
diff --git a/spec/acceptance/searching_across_schemas_spec.rb b/spec/acceptance/searching_across_schemas_spec.rb
index b50daed6b..68374b59b 100644
--- a/spec/acceptance/searching_across_schemas_spec.rb
+++ b/spec/acceptance/searching_across_schemas_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
multi_schema = MultiSchema.new
@@ -16,23 +18,23 @@
it 'can distinguish between objects with the same primary key' do
multi_schema.switch :public
jekyll = Product.create name: 'Doctor Jekyll'
- Product.search('Jekyll', :retry_stale => false).to_a.should == [jekyll]
- Product.search(:retry_stale => false).to_a.should == [jekyll]
+ expect(Product.search('Jekyll', :retry_stale => false).to_a).to eq([jekyll])
+ expect(Product.search(:retry_stale => false).to_a).to eq([jekyll])
multi_schema.switch :thinking_sphinx
hyde = Product.create name: 'Mister Hyde'
- Product.search('Jekyll', :retry_stale => false).to_a.should == []
- Product.search('Hyde', :retry_stale => false).to_a.should == [hyde]
- Product.search(:retry_stale => false).to_a.should == [hyde]
+ expect(Product.search('Jekyll', :retry_stale => false).to_a).to eq([])
+ expect(Product.search('Hyde', :retry_stale => false).to_a).to eq([hyde])
+ expect(Product.search(:retry_stale => false).to_a).to eq([hyde])
multi_schema.switch :public
- Product.search('Jekyll', :retry_stale => false).to_a.should == [jekyll]
- Product.search(:retry_stale => false).to_a.should == [jekyll]
- Product.search('Hyde', :retry_stale => false).to_a.should == []
+ expect(Product.search('Jekyll', :retry_stale => false).to_a).to eq([jekyll])
+ expect(Product.search(:retry_stale => false).to_a).to eq([jekyll])
+ expect(Product.search('Hyde', :retry_stale => false).to_a).to eq([])
- Product.search(
+ expect(Product.search(
:middleware => ThinkingSphinx::Middlewares::RAW_ONLY,
:indices => ['product_core', 'product_two_core']
- ).to_a.length.should == 2
+ ).to_a.length).to eq(2)
end
end if multi_schema.active?
diff --git a/spec/acceptance/searching_on_fields_spec.rb b/spec/acceptance/searching_on_fields_spec.rb
index b6e53e93f..a04ab1439 100644
--- a/spec/acceptance/searching_on_fields_spec.rb
+++ b/spec/acceptance/searching_on_fields_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Searching on fields', :live => true do
@@ -8,8 +10,8 @@
index
articles = Article.search :conditions => {:title => 'pancakes'}
- articles.should include(pancakes)
- articles.should_not include(waffles)
+ expect(articles).to include(pancakes)
+ expect(articles).not_to include(waffles)
end
it "limits results for a field from an association" do
@@ -17,7 +19,7 @@
pancakes = Article.create! :title => 'Pancakes', :user => user
index
- Article.search(:conditions => {:user => 'pat'}).first.should == pancakes
+ expect(Article.search(:conditions => {:user => 'pat'}).first).to eq(pancakes)
end
it "returns results with matches from grouped fields" do
@@ -26,23 +28,23 @@
waffles = Article.create! :title => 'Waffles', :user => user
index
- Article.search('waffles', :conditions => {:title => 'pancakes'}).to_a.
- should == [pancakes]
+ expect(Article.search('waffles', :conditions => {:title => 'pancakes'}).to_a).
+ to eq([pancakes])
end
it "returns results with matches from concatenated columns in a field" do
book = Book.create! :title => 'Night Watch', :author => 'Terry Pratchett'
index
- Book.search(:conditions => {:info => 'Night Pratchett'}).to_a.
- should == [book]
+ expect(Book.search(:conditions => {:info => 'Night Pratchett'}).to_a).
+ to eq([book])
end
it "handles NULLs in concatenated fields" do
book = Book.create! :title => 'Night Watch'
index
- Book.search(:conditions => {:info => 'Night Watch'}).to_a.should == [book]
+ expect(Book.search(:conditions => {:info => 'Night Watch'}).to_a).to eq([book])
end
it "returns results with matches from file fields" do
@@ -52,6 +54,6 @@
book = Book.create! :title => 'Accelerando', :blurb_file => file_path.to_s
index
- Book.search('cyberpunk').to_a.should == [book]
+ expect(Book.search('cyberpunk').to_a).to eq([book])
end
end
diff --git a/spec/acceptance/searching_with_filters_spec.rb b/spec/acceptance/searching_with_filters_spec.rb
index b9c3da02d..199cce20f 100644
--- a/spec/acceptance/searching_with_filters_spec.rb
+++ b/spec/acceptance/searching_with_filters_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Searching with filters', :live => true do
@@ -6,16 +8,16 @@
waffles = Article.create! :title => 'Waffles', :published => false
index
- Article.search(:with => {:published => true}).to_a.should == [pancakes]
+ expect(Article.search(:with => {:published => true}).to_a).to eq([pancakes])
end
it "limits results by an array of values" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
index
- Book.search(:with => {:year => [2001, 2005]}).to_a.should == [gods, boys]
+ expect(Book.search(:with => {:publishing_year => [2001, 2005]}).to_a).to match_array([gods, boys])
end
it "limits results by a ranged filter" do
@@ -28,8 +30,8 @@
grave.update_column :created_at, 1.day.ago
index
- Book.search(:with => {:created_at => 6.days.ago..2.days.ago}).to_a.
- should == [gods, boys]
+ expect(Book.search(:with => {:created_at => 6.days.ago..2.days.ago}).to_a).
+ to match_array([gods, boys])
end
it "limits results by exclusive filters on single values" do
@@ -37,16 +39,16 @@
waffles = Article.create! :title => 'Waffles', :published => false
index
- Article.search(:without => {:published => true}).to_a.should == [waffles]
+ expect(Article.search(:without => {:published => true}).to_a).to eq([waffles])
end
it "limits results by exclusive filters on arrays of values" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
index
- Book.search(:without => {:year => [2001, 2005]}).to_a.should == [grave]
+ expect(Book.search(:without => {:publishing_year => [2001, 2005]}).to_a).to eq([grave])
end
it "limits results by ranged filters on timestamp MVAs" do
@@ -64,9 +66,9 @@
index
- Article.search(
+ expect(Article.search(
:with => {:taggings_at => 1.days.ago..1.day.from_now}
- ).to_a.should == [pancakes]
+ ).to_a).to eq([pancakes])
end
it "takes into account local timezones for timestamps" do
@@ -84,9 +86,9 @@
index
- Article.search(
+ expect(Article.search(
:with => {:taggings_at => 2.minutes.ago..Time.zone.now}
- ).to_a.should == [pancakes]
+ ).to_a).to eq([pancakes])
end
it "limits results with MVAs having all of the given values" do
@@ -103,13 +105,13 @@
index
articles = Article.search :with_all => {:tag_ids => [food.id, flat.id]}
- articles.to_a.should == [pancakes]
+ expect(articles.to_a).to eq([pancakes])
end
it "limits results with MVAs that don't contain all the given values" do
# Matching results may have some of the given values, but cannot have all
# of them. Certainly an edge case.
- pending "SphinxQL doesn't yet support OR in its WHERE clause"
+ skip "SphinxQL doesn't yet support OR in its WHERE clause"
pancakes = Article.create :title => 'Pancakes'
waffles = Article.create :title => 'Waffles'
@@ -124,7 +126,7 @@
index
articles = Article.search :without_all => {:tag_ids => [food.id, flat.id]}
- articles.to_a.should == [waffles]
+ expect(articles.to_a).to eq([waffles])
end
it "limits results on real-time indices with multi-value integer attributes" do
@@ -139,6 +141,19 @@
waffles.categories << food
products = Product.search :with => {:category_ids => [flat.id]}
- products.to_a.should == [pancakes]
+ expect(products.to_a).to eq([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}
+ expect(products.to_a).to eq([pancakes])
+
+ products = Product.search :with => {"options.sugar" => 1}
+ expect(products.to_a).to match_array([pancakes, waffles])
+ end if JSONColumn.call
end
diff --git a/spec/acceptance/searching_with_sti_spec.rb b/spec/acceptance/searching_with_sti_spec.rb
index da4a98170..a37bfe0c7 100644
--- a/spec/acceptance/searching_with_sti_spec.rb
+++ b/spec/acceptance/searching_with_sti_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Searching across STI models', :live => true do
@@ -6,7 +8,7 @@
duck = Bird.create :name => 'Duck'
index
- Animal.search(:indices => ['animal_core']).to_a.should == [platypus, duck]
+ expect(Animal.search(:indices => ['animal_core']).to_a).to eq([platypus, duck])
end
it "limits results based on subclasses" do
@@ -14,7 +16,7 @@
duck = Bird.create :name => 'Duck'
index
- Bird.search(:indices => ['animal_core']).to_a.should == [duck]
+ expect(Bird.search(:indices => ['animal_core']).to_a).to eq([duck])
end
it "returns results for deeper subclasses when searching on their parents" do
@@ -23,7 +25,7 @@
emu = FlightlessBird.create :name => 'Emu'
index
- Bird.search(:indices => ['animal_core']).to_a.should == [duck, emu]
+ expect(Bird.search(:indices => ['animal_core']).to_a).to eq([duck, emu])
end
it "returns results for deeper subclasses" do
@@ -32,7 +34,7 @@
emu = FlightlessBird.create :name => 'Emu'
index
- FlightlessBird.search(:indices => ['animal_core']).to_a.should == [emu]
+ expect(FlightlessBird.search(:indices => ['animal_core']).to_a).to eq([emu])
end
it "filters out sibling subclasses" do
@@ -41,7 +43,7 @@
otter = Mammal.create :name => 'Otter'
index
- Bird.search(:indices => ['animal_core']).to_a.should == [duck]
+ expect(Bird.search(:indices => ['animal_core']).to_a).to eq([duck])
end
it "obeys :classes if supplied" do
@@ -50,18 +52,18 @@
emu = FlightlessBird.create :name => 'Emu'
index
- Bird.search(
+ expect(Bird.search(
:indices => ['animal_core'],
:skip_sti => true,
:classes => [Bird, FlightlessBird]
- ).to_a.should == [duck, emu]
+ ).to_a).to eq([duck, emu])
end
it 'finds root objects when type is blank' do
animal = Animal.create :name => 'Animal', type: ''
index
- Animal.search(:indices => ['animal_core']).to_a.should == [animal]
+ expect(Animal.search(:indices => ['animal_core']).to_a).to eq([animal])
end
it 'allows for indices on mid-hierarchy classes' do
@@ -69,6 +71,6 @@
emu = FlightlessBird.create :name => 'Emu'
index
- Bird.search(:indices => ['bird_core']).to_a.should == [duck, emu]
+ expect(Bird.search(:indices => ['bird_core']).to_a).to eq([duck, emu])
end
end
diff --git a/spec/acceptance/searching_within_a_model_spec.rb b/spec/acceptance/searching_within_a_model_spec.rb
index 3bfb0c199..45cb79b74 100644
--- a/spec/acceptance/searching_within_a_model_spec.rb
+++ b/spec/acceptance/searching_within_a_model_spec.rb
@@ -1,4 +1,6 @@
# encoding: UTF-8
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Searching within a model', :live => true do
@@ -6,7 +8,7 @@
article = Article.create! :title => 'Pancakes'
index
- Article.search.first.should == article
+ expect(Article.search.first).to eq(article)
end
it "returns results matching the given query" do
@@ -15,22 +17,22 @@
index
articles = Article.search 'pancakes'
- articles.should include(pancakes)
- articles.should_not include(waffles)
+ expect(articles).to include(pancakes)
+ expect(articles).not_to include(waffles)
end
it "handles unicode characters" do
istanbul = City.create! :name => 'İstanbul'
index
- City.search('İstanbul').to_a.should == [istanbul]
+ expect(City.search('İstanbul').to_a).to eq([istanbul])
end
it "will star provided queries on request" do
article = Article.create! :title => 'Pancakes'
index
- Article.search('cake', :star => true).first.should == article
+ expect(Article.search('cake', :star => true).first).to eq(article)
end
it "allows for searching on specific indices" do
@@ -38,7 +40,7 @@
index
articles = Article.search('pancake', :indices => ['stemmed_article_core'])
- articles.to_a.should == [article]
+ expect(articles.to_a).to eq([article])
end
it "allows for searching on distributed indices" do
@@ -46,38 +48,63 @@
index
articles = Article.search('pancake', :indices => ['article'])
- articles.to_a.should == [article]
+ expect(articles.to_a).to eq([article])
end
it "can search on namespaced models" do
person = Admin::Person.create :name => 'James Bond'
index
- Admin::Person.search('Bond').to_a.should == [person]
+ expect(Admin::Person.search('Bond').to_a).to eq([person])
end
it "raises an error if searching through an ActiveRecord scope" do
- lambda {
+ expect {
City.ordered.search
- }.should raise_error(ThinkingSphinx::MixedScopesError)
+ }.to raise_error(ThinkingSphinx::MixedScopesError)
end
it "does not raise an error when searching with a default ActiveRecord scope" do
- lambda {
+ expect {
User.search
- }.should_not raise_error(ThinkingSphinx::MixedScopesError)
+ }.not_to raise_error
end
it "raises an error when searching with default and applied AR scopes" do
- lambda {
+ expect {
User.recent.search
- }.should raise_error(ThinkingSphinx::MixedScopesError)
+ }.to raise_error(ThinkingSphinx::MixedScopesError)
end
it "raises an error if the model has no indices defined" do
- lambda {
+ expect {
Category.search.to_a
- }.should raise_error(ThinkingSphinx::NoIndicesError)
+ }.to raise_error(ThinkingSphinx::NoIndicesError)
+ end
+
+ it "handles models with alternative id columns" do
+ album = Album.create! :name => 'The Seldom Seen Kid', :artist => 'Elbow'
+ index
+
+ expect(Album.search(:indices => ['album_core', 'album_delta']).first).
+ to eq(album)
+
+ expect(Album.search(:indices => ['album_real_core']).first).
+ to eq(album)
+ end
+
+ it "is available via a sphinx-prefixed method" do
+ article = Article.create! :title => 'Pancakes'
+ index
+
+ expect(Article.sphinx_search.first).to eq(article)
+ end
+
+ it "has a 'none' default scope" do
+ article = Article.create! :title => 'Pancakes'
+ index
+
+ expect(Article.search_none).to be_empty
end
end
@@ -85,6 +112,6 @@
it "returns results" do
product = Product.create! :name => 'Widget'
- Product.search.first.should == product
+ expect(Product.search.first).to eq(product)
end
end
diff --git a/spec/acceptance/sorting_search_results_spec.rb b/spec/acceptance/sorting_search_results_spec.rb
index eecb74d7b..20f2799ac 100644
--- a/spec/acceptance/sorting_search_results_spec.rb
+++ b/spec/acceptance/sorting_search_results_spec.rb
@@ -1,48 +1,50 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Sorting search results', :live => true do
it "sorts by a given clause" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
index
- Book.search(:order => 'year ASC').to_a.should == [gods, boys, grave]
+ expect(Book.search(:order => 'publishing_year ASC').to_a).to eq([gods, boys, grave])
end
it "sorts by a given attribute in ascending order" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
index
- Book.search(:order => :year).to_a.should == [gods, boys, grave]
+ expect(Book.search(:order => :publishing_year).to_a).to eq([gods, boys, grave])
end
it "sorts by a given sortable field" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
index
- Book.search(:order => :title).to_a.should == [gods, boys, grave]
+ expect(Book.search(:order => :title).to_a).to eq([gods, boys, grave])
end
it "sorts by a given sortable field with real-time indices" do
widgets = Product.create! :name => 'Widgets'
gadgets = Product.create! :name => 'Gadgets'
- Product.search(:order => "name_sort ASC").to_a.should == [gadgets, widgets]
+ expect(Product.search(:order => "name_sort ASC").to_a).to eq([gadgets, widgets])
end
it "can sort with a provided expression" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
index
- Book.search(
- :select => '*, year MOD 2004 as mod_year', :order => 'mod_year ASC'
- ).to_a.should == [boys, grave, gods]
+ expect(Book.search(
+ :select => '*, publishing_year MOD 2004 as mod_year', :order => 'mod_year ASC'
+ ).to_a).to eq([boys, grave, gods])
end
end
diff --git a/spec/acceptance/spec_helper.rb b/spec/acceptance/spec_helper.rb
index 9ad705842..f877bf30f 100644
--- a/spec/acceptance/spec_helper.rb
+++ b/spec/acceptance/spec_helper.rb
@@ -1,17 +1,6 @@
+# frozen_string_literal: true
+
require 'spec_helper'
root = File.expand_path File.dirname(__FILE__)
Dir["#{root}/support/**/*.rb"].each { |file| require file }
-
-if ENV['SPHINX_VERSION'].try :[], /2.0.\d/
- ThinkingSphinx::SphinxQL.variables!
-
- ThinkingSphinx::Middlewares::DEFAULT.insert_after(
- ThinkingSphinx::Middlewares::Inquirer,
- ThinkingSphinx::Middlewares::UTF8
- )
- ThinkingSphinx::Middlewares::RAW_ONLY.insert_after(
- ThinkingSphinx::Middlewares::Inquirer,
- ThinkingSphinx::Middlewares::UTF8
- )
-end
diff --git a/spec/acceptance/specifying_sql_spec.rb b/spec/acceptance/specifying_sql_spec.rb
index 234bb546a..2d790a148 100644
--- a/spec/acceptance/specifying_sql_spec.rb
+++ b/spec/acceptance/specifying_sql_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'specifying SQL for index definitions' do
@@ -8,7 +10,7 @@
join user
}
index.render
- index.sources.first.sql_query.should match(/LEFT OUTER JOIN .users./)
+ expect(index.sources.first.sql_query).to match(/LEFT OUTER JOIN .users./)
end
it "handles deep joins" do
@@ -20,8 +22,8 @@
index.render
query = index.sources.first.sql_query
- query.should match(/LEFT OUTER JOIN .users./)
- query.should match(/LEFT OUTER JOIN .articles./)
+ expect(query).to match(/LEFT OUTER JOIN .users./)
+ expect(query).to match(/LEFT OUTER JOIN .articles./)
end
it "handles has-many :through joins" do
@@ -32,8 +34,8 @@
index.render
query = index.sources.first.sql_query
- query.should match(/LEFT OUTER JOIN .taggings./)
- query.should match(/LEFT OUTER JOIN .tags./)
+ expect(query).to match(/LEFT OUTER JOIN .taggings./)
+ expect(query).to match(/LEFT OUTER JOIN .tags./)
end
it "handles custom join SQL statements" do
@@ -45,7 +47,7 @@
index.render
query = index.sources.first.sql_query
- query.should match(/INNER JOIN foo ON foo.x = bar.y/)
+ expect(query).to match(/INNER JOIN foo ON foo.x = bar.y/)
end
it "handles GROUP BY clauses" do
@@ -57,7 +59,7 @@
index.render
query = index.sources.first.sql_query
- query.should match(/GROUP BY .articles.\..id., .?articles.?\..title., .?articles.?\..id., lat/)
+ expect(query).to match(/GROUP BY .articles.\..id., .?articles.?\..title., .?articles.?\..id., lat/)
end
it "handles WHERE clauses" do
@@ -69,7 +71,7 @@
index.render
query = index.sources.first.sql_query
- query.should match(/WHERE .+title != 'secret'.+ GROUP BY/)
+ expect(query).to match(/WHERE .+title != 'secret'.+ GROUP BY/)
end
it "handles manual MVA declarations" do
@@ -81,7 +83,7 @@
}
index.render
- index.sources.first.sql_attr_multi.should == ['uint tag_ids from field']
+ expect(index.sources.first.sql_attr_multi).to eq(['uint tag_ids from field'])
end
it "provides the sanitize_sql helper within the index definition block" do
@@ -93,7 +95,7 @@
index.render
query = index.sources.first.sql_query
- query.should match(/WHERE .+title != 'secret'.+ GROUP BY/)
+ expect(query).to match(/WHERE .+title != 'secret'.+ GROUP BY/)
end
it "escapes new lines in SQL snippets" do
@@ -111,7 +113,7 @@
index.render
query = index.sources.first.sql_query
- query.should match(/\\\n/)
+ expect(query).to match(/\\\n/)
end
it "joins each polymorphic relation" do
@@ -123,10 +125,10 @@
index.render
query = index.sources.first.sql_query
- query.should match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/)
- query.should match(/LEFT OUTER JOIN .books. ON .books.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Book'/)
- query.should match(/articles\..title., books\..title./)
- end
+ expect(query).to match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/)
+ expect(query).to match(/LEFT OUTER JOIN .books. ON .books.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Book'/)
+ expect(query).to match(/.articles.\..title., .books.\..title./)
+ end if ActiveRecord::VERSION::MAJOR > 3
it "concatenates references that have column" do
index = ThinkingSphinx::ActiveRecord::Index.new(:event)
@@ -137,10 +139,10 @@
index.render
query = index.sources.first.sql_query
- query.should match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/)
- query.should_not match(/articles\..title., users\..title./)
- query.should match(/articles\..title./)
- end
+ expect(query).to match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/)
+ expect(query).not_to match(/articles\..title., users\..title./)
+ expect(query).to match(/.articles.\..title./)
+ end if ActiveRecord::VERSION::MAJOR > 3
it "respects deeper associations through polymorphic joins" do
index = ThinkingSphinx::ActiveRecord::Index.new(:event)
@@ -151,13 +153,31 @@
index.render
query = index.sources.first.sql_query
- query.should match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/)
- query.should match(/LEFT OUTER JOIN .users. ON .users.\..id. = .articles.\..user_id./)
- query.should match(/users\..name./)
+ expect(query).to match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/)
+ expect(query).to match(/LEFT OUTER JOIN .users. ON .users.\..id. = .articles.\..user_id./)
+ expect(query).to match(/.users.\..name./)
end
-end
+
+ it "allows for STI mixed with polymorphic joins" do
+ index = ThinkingSphinx::ActiveRecord::Index.new(:event)
+ index.definition_block = Proc.new {
+ indexes eventable.name, :as => :name
+ polymorphs eventable, :to => %w(Bird Car)
+ }
+ index.render
+
+ query = index.sources.first.sql_query
+ expect(query).to match(/LEFT OUTER JOIN .animals. ON .animals.\..id. = .events.\..eventable_id. .* AND .events.\..eventable_type. = 'Animal'/)
+ expect(query).to match(/LEFT OUTER JOIN .cars. ON .cars.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Car'/)
+ expect(query).to match(/.animals.\..name., .cars.\..name./)
+ end
+end if ActiveRecord::VERSION::MAJOR > 3
describe 'separate queries for MVAs' do
+ def id_type
+ ActiveRecord::VERSION::STRING.to_f > 5.0 ? 'bigint' : 'uint'
+ end
+
let(:index) { ThinkingSphinx::ActiveRecord::Index.new(:article) }
let(:count) { ThinkingSphinx::Configuration.instance.indices.count }
let(:source) { index.sources.first }
@@ -174,8 +194,33 @@
}
declaration, query = attribute.split(/;\s+/)
- declaration.should == 'uint tag_ids from query'
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)$/)
+ expect(declaration).to eq("uint tag_ids from query")
+ expect(query).to match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)$/)
+ end
+
+ it "does not include attributes sourced via separate queries" do
+ index.definition_block = Proc.new {
+ indexes title
+ has taggings.tag_id, :as => :tag_ids, :source => :query
+ }
+ index.render
+
+ # We don't want it in the SELECT, JOIN or GROUP clauses. This should catch
+ # them all.
+ expect(source.sql_query).not_to include('taggings')
+ end
+
+ it "keeps the joins in for separately queried tables if they're used elsewhere" do
+ index.definition_block = Proc.new {
+ indexes taggings.tag.name, :as => :tag_names
+ has taggings.tag.created_at, :as => :tag_dates, :source => :query
+ }
+ index.render
+
+ expect(source.sql_query).to include('taggings')
+ expect(source.sql_query).to include('tags')
+ expect(source.sql_query).to_not match(/.tags.\..created_at./)
+ expect(source.sql_query).to match(/.tags.\..name./)
end
it "generates a SQL query with joins when appropriate for MVAs" do
@@ -190,8 +235,8 @@
}
declaration, query = attribute.split(/;\s+/)
- declaration.should == 'uint tag_ids from query'
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.taggings.\..article_id. IS NOT NULL\)\s?$/)
+ expect(declaration).to eq("#{id_type} tag_ids from query")
+ expect(query).to match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.taggings.\..article_id. IS NOT NULL\)\s?$/)
end
it "respects has_many :through joins for MVA queries" do
@@ -206,8 +251,8 @@
}
declaration, query = attribute.split(/;\s+/)
- declaration.should == 'uint tag_ids from query'
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.taggings.\..article_id. IS NOT NULL\)\s?$/)
+ expect(declaration).to eq("#{id_type} tag_ids from query")
+ expect(query).to match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.taggings.\..article_id. IS NOT NULL\)\s?$/)
end
it "can handle multiple joins for MVA queries" do
@@ -224,8 +269,8 @@
}
declaration, query = attribute.split(/;\s+/)
- declaration.should == 'uint tag_ids from query'
- query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.articles.\..user_id. IS NOT NULL\)\s?$/)
+ expect(declaration).to eq("#{id_type} tag_ids from query")
+ expect(query).to match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.articles.\..user_id. IS NOT NULL\)\s?$/)
end
it "can handle simple HABTM joins for MVA queries" do
@@ -242,9 +287,9 @@
}
declaration, query = attribute.split(/;\s+/)
- declaration.should == 'uint genre_ids from query'
- query.should match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .books_genres.\..genre_id. AS .genre_ids. FROM .books_genres.\s?$/)
- end
+ expect(declaration).to eq("#{id_type} genre_ids from query")
+ expect(query).to match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .books_genres.\..genre_id. AS .genre_ids. FROM .books_genres.\s?$/)
+ end if ActiveRecord::VERSION::MAJOR > 3
it "generates an appropriate range SQL queries for an MVA" do
index.definition_block = Proc.new {
@@ -258,9 +303,9 @@
}
declaration, query, range = attribute.split(/;\s+/)
- declaration.should == 'uint tag_ids from ranged-query'
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)$/)
- range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
+ expect(declaration).to eq("uint tag_ids from ranged-query")
+ expect(query).to match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)$/)
+ expect(range).to match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
end
it "generates a SQL query with joins when appropriate for MVAs" do
@@ -275,9 +320,9 @@
}
declaration, query, range = attribute.split(/;\s+/)
- declaration.should == 'uint tag_ids from ranged-query'
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)$/)
- range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
+ expect(declaration).to eq("#{id_type} tag_ids from ranged-query")
+ expect(query).to match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)$/)
+ expect(range).to match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
end
it "can handle ranged queries for simple HABTM joins for MVA queries" do
@@ -294,10 +339,10 @@
}
declaration, query, range = attribute.split(/;\s+/)
- declaration.should == 'uint genre_ids from ranged-query'
- query.should match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .books_genres.\..genre_id. AS .genre_ids. FROM .books_genres. WHERE \(.books_genres.\..book_id. BETWEEN \$start AND \$end\)$/)
- range.should match(/^SELECT MIN\(.books_genres.\..book_id.\), MAX\(.books_genres.\..book_id.\) FROM .books_genres.$/)
- end
+ expect(declaration).to eq("#{id_type} genre_ids from ranged-query")
+ expect(query).to match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .books_genres.\..genre_id. AS .genre_ids. FROM .books_genres. WHERE \(.books_genres.\..book_id. BETWEEN \$start AND \$end\)$/)
+ expect(range).to match(/^SELECT MIN\(.books_genres.\..book_id.\), MAX\(.books_genres.\..book_id.\) FROM .books_genres.$/)
+ end if ActiveRecord::VERSION::MAJOR > 3
it "respects custom SQL snippets as the query value" do
index.definition_block = Proc.new {
@@ -312,8 +357,8 @@
}
declaration, query = attribute.split(/;\s+/)
- declaration.should == 'uint tag_ids from query'
- query.should == 'My Custom SQL Query'
+ expect(declaration).to eq('uint tag_ids from query')
+ expect(query).to eq('My Custom SQL Query')
end
it "respects custom SQL snippets as the ranged query value" do
@@ -329,9 +374,9 @@
}
declaration, query, range = attribute.split(/;\s+/)
- declaration.should == 'uint tag_ids from ranged-query'
- query.should == 'My Custom SQL Query'
- range.should == 'And a Range'
+ expect(declaration).to eq('uint tag_ids from ranged-query')
+ expect(query).to eq('My Custom SQL Query')
+ expect(range).to eq('And a Range')
end
it "escapes new lines in custom SQL snippets" do
@@ -349,8 +394,8 @@
}
declaration, query = attribute.split(/;\s+/)
- declaration.should == 'uint tag_ids from query'
- query.should == "My Custom\\\nSQL Query"
+ expect(declaration).to eq('uint tag_ids from query')
+ expect(query).to eq("My Custom\\\nSQL Query")
end
end
@@ -368,8 +413,8 @@
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\s+/)
- declaration.should == 'tags from query'
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC\s?$/)
+ expect(declaration).to eq('tags from query')
+ expect(query).to match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC\s?$/)
end
it "respects has_many :through joins for MVF queries" do
@@ -381,8 +426,8 @@
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\s+/)
- declaration.should == 'tags from query'
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC\s?$/)
+ expect(declaration).to eq('tags from query')
+ expect(query).to match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC\s?$/)
end
it "can handle multiple joins for MVF queries" do
@@ -396,8 +441,8 @@
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\s+/)
- declaration.should == 'tags from query'
- query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.articles.\..user_id. IS NOT NULL\)\s? ORDER BY .articles.\..user_id. ASC\s?$/)
+ expect(declaration).to eq('tags from query')
+ expect(query).to match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.articles.\..user_id. IS NOT NULL\)\s? ORDER BY .articles.\..user_id. ASC\s?$/)
end
it "generates a SQL query with joins when appropriate for MVFs" do
@@ -409,9 +454,20 @@
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query, range = field.split(/;\s+/)
- declaration.should == 'tags from ranged-query'
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC$/)
- range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
+ expect(declaration).to eq('tags from ranged-query')
+ expect(query).to match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC$/)
+ expect(range).to match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
+ end
+
+ it "does not include fields sourced via separate queries" do
+ index.definition_block = Proc.new {
+ indexes taggings.tag.name, :as => :tags, :source => :query
+ }
+ index.render
+
+ # We don't want it in the SELECT, JOIN or GROUP clauses. This should catch
+ # them all.
+ expect(source.sql_query).not_to include('tags')
end
it "respects custom SQL snippets as the query value" do
@@ -423,8 +479,8 @@
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\s+/)
- declaration.should == 'tags from query'
- query.should == 'My Custom SQL Query'
+ expect(declaration).to eq('tags from query')
+ expect(query).to eq('My Custom SQL Query')
end
it "respects custom SQL snippets as the ranged query value" do
@@ -437,9 +493,9 @@
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query, range = field.split(/;\s+/)
- declaration.should == 'tags from ranged-query'
- query.should == 'My Custom SQL Query'
- range.should == 'And a Range'
+ expect(declaration).to eq('tags from ranged-query')
+ expect(query).to eq('My Custom SQL Query')
+ expect(range).to eq('And a Range')
end
it "escapes new lines in custom SQL snippets" do
@@ -454,7 +510,7 @@
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\s+/)
- declaration.should == 'tags from query'
- query.should == "My Custom\\\nSQL Query"
+ expect(declaration).to eq('tags from query')
+ expect(query).to eq("My Custom\\\nSQL Query")
end
end
diff --git a/spec/acceptance/sphinx_scopes_spec.rb b/spec/acceptance/sphinx_scopes_spec.rb
index 1141d73a3..c47d11ee0 100644
--- a/spec/acceptance/sphinx_scopes_spec.rb
+++ b/spec/acceptance/sphinx_scopes_spec.rb
@@ -1,41 +1,43 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Sphinx scopes', :live => true do
it "allows calling sphinx scopes from models" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
index
- Book.by_year(2009).to_a.should == [grave]
+ expect(Book.by_publishing_year(2009).to_a).to eq([grave])
end
it "allows scopes to return both query and options" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
index
- Book.by_query_and_year('Graveyard', 2009).to_a.should == [grave]
+ expect(Book.by_query_and_publishing_year('Graveyard', 2009).to_a).to eq([grave])
end
it "allows chaining of scopes" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
index
- Book.by_year(2001..2005).ordered.to_a.should == [boys, gods]
+ expect(Book.by_publishing_year(2001..2005).ordered.to_a).to eq([boys, gods])
end
it "allows chaining of scopes that include queries" do
- gods = Book.create! :title => 'American Gods', :year => 2001
- boys = Book.create! :title => 'Anansi Boys', :year => 2005
- grave = Book.create! :title => 'The Graveyard Book', :year => 2009
+ gods = Book.create! :title => 'American Gods', :publishing_year => 2001
+ boys = Book.create! :title => 'Anansi Boys', :publishing_year => 2005
+ grave = Book.create! :title => 'The Graveyard Book', :publishing_year => 2009
index
- Book.by_year(2001).by_query_and_year('Graveyard', 2009).to_a.
- should == [grave]
+ expect(Book.by_publishing_year(2001).by_query_and_publishing_year('Graveyard', 2009).to_a).
+ to eq([grave])
end
it "allows further search calls on scopes" do
@@ -43,7 +45,7 @@
pratchett = Book.create! :title => 'Small Gods'
index
- Book.by_query('Gods').search('Small').to_a.should == [pratchett]
+ expect(Book.by_query('Gods').search('Small').to_a).to eq([pratchett])
end
it "allows facet calls on scopes" do
@@ -52,9 +54,9 @@
Book.create! :title => 'Small Gods', :author => 'Terry Pratchett'
index
- Book.by_query('Gods').facets.to_hash[:author].should == {
+ expect(Book.by_query('Gods').facets.to_hash[:author]).to eq({
'Neil Gaiman' => 1, 'Terry Pratchett' => 1
- }
+ })
end
it "allows accessing counts on scopes" do
@@ -64,7 +66,7 @@
Book.create! :title => 'Night Watch'
index
- Book.by_query('gods').count.should == 2
+ expect(Book.by_query('gods').count).to eq(2)
end
it 'raises an exception when trying to modify a populated request' do
@@ -75,4 +77,11 @@
ThinkingSphinx::PopulatedResultsError
)
end
+
+ it "handles a chainable 'none' scope and returns nothing" do
+ Book.create! :title => 'Small Gods'
+ index
+
+ expect(Book.by_query('gods').none).to be_empty
+ end
end
diff --git a/spec/acceptance/sql_deltas_spec.rb b/spec/acceptance/sql_deltas_spec.rb
index bb7935d83..e1808aa00 100644
--- a/spec/acceptance/sql_deltas_spec.rb
+++ b/spec/acceptance/sql_deltas_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'SQL delta indexing', :live => true do
@@ -7,38 +9,50 @@
)
index
- Book.search('Terry Pratchett').to_a.should == [guards]
+ expect(Book.search('Terry Pratchett').to_a).to eq([guards])
men = Book.create(
:title => 'Men At Arms', :author => 'Terry Pratchett'
)
sleep 0.25
- Book.search('Terry Pratchett').to_a.should == [guards, men]
+ expect(Book.search('Terry Pratchett').to_a).to match_array([guards, men])
end
it "automatically indexes updated records" do
book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett'
index
- Book.search('Harry').to_a.should == [book]
+ 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
- Book.search('Terry').to_a.should == [book]
+ expect(Book.search('Terry').to_a).to eq([book])
end
it "does not match on old values" do
book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett'
index
- Book.search('Harry').to_a.should == [book]
+ expect(Book.search('Harry').to_a).to eq([book])
+
+ book.reload.update(:author => 'Terry Pratchett')
+ sleep 0.25
+
+ expect(Book.search('Harry')).to be_empty
+ end
+
+ it "does not match on old values with alternative ids" do
+ album = Album.create :name => 'Eternal Nightcap', :artist => 'The Whitloms'
+ index
+
+ expect(Album.search('Whitloms').to_a).to eq([album])
- book.reload.update_attributes(:author => 'Terry Pratchett')
+ album.reload.update(:artist => 'The Whitlams')
sleep 0.25
- Book.search('Harry').should be_empty
+ expect(Book.search('Whitloms')).to be_empty
end
it "automatically indexes new records of subclasses" do
@@ -47,6 +61,18 @@
)
sleep 0.25
- Book.search('Gaiman').to_a.should == [book]
+ expect(Book.search('Gaiman').to_a).to eq([book])
+ end
+
+ it "updates associated models" do
+ colour = Colour.create(:name => 'green')
+ sleep 0.25
+
+ expect(Colour.search('green').to_a).to eq([colour])
+
+ tee = colour.tees.create
+ sleep 0.25
+
+ expect(Colour.search(:with => {:tee_ids => tee.id}).to_a).to eq([colour])
end
end
diff --git a/spec/acceptance/support/database_cleaner.rb b/spec/acceptance/support/database_cleaner.rb
index 30646eb93..7c460d284 100644
--- a/spec/acceptance/support/database_cleaner.rb
+++ b/spec/acceptance/support/database_cleaner.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
- config.after(:each) do
+ config.after(:each) do |example|
if example.example_group_instance.class.metadata[:live]
DatabaseCleaner.clean
end
diff --git a/spec/acceptance/support/sphinx_controller.rb b/spec/acceptance/support/sphinx_controller.rb
index 806704c6b..9f5674391 100644
--- a/spec/acceptance/support/sphinx_controller.rb
+++ b/spec/acceptance/support/sphinx_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SphinxController
def initialize
config.searchd.mysql41 = 9307
@@ -10,14 +12,12 @@ def setup
ThinkingSphinx::Configuration.reset
- ActiveSupport::Dependencies.loaded.each do |path|
- $LOADED_FEATURES.delete "#{path}.rb"
- end
-
- ActiveSupport::Dependencies.clear
+ if Rails::VERSION::MAJOR < 7
+ ActiveSupport::Dependencies.loaded.each do |path|
+ $LOADED_FEATURES.delete "#{path}.rb"
+ end
- if ENV['SPHINX_VERSION'].try :[], /2.0.\d/
- ThinkingSphinx::Configuration.instance.settings['utf8'] = false
+ ActiveSupport::Dependencies.clear
end
config.searchd.mysql41 = 9307
@@ -28,14 +28,30 @@ def setup
def start
config.controller.start
+ rescue Riddle::CommandFailedError => error
+ puts <<-TXT
+
+The Sphinx start command failed:
+ Command: #{error.command_result.command}
+ Status: #{error.command_result.status}
+ Output: #{error.command_result.output}
+ TXT
+ raise error
end
def stop
- config.controller.stop
+ while config.controller.running? do
+ config.controller.stop
+ sleep(0.1)
+ end
end
def index(*indices)
- config.controller.index *indices
+ ThinkingSphinx::Commander.call :index_sql, config, :indices => indices
+ end
+
+ def merge
+ ThinkingSphinx::Commander.call(:merge_and_update, config, {})
end
private
diff --git a/spec/acceptance/support/sphinx_helpers.rb b/spec/acceptance/support/sphinx_helpers.rb
index 93f87d14c..87935accb 100644
--- a/spec/acceptance/support/sphinx_helpers.rb
+++ b/spec/acceptance/support/sphinx_helpers.rb
@@ -1,16 +1,27 @@
+# frozen_string_literal: true
+
module SphinxHelpers
def sphinx
@sphinx ||= SphinxController.new
end
def index(*indices)
- sleep 0.5 if ENV['TRAVIS']
+ sleep 0.5 if ENV['CI']
yield if block_given?
sphinx.index *indices
sleep 0.25
- sleep 0.5 if ENV['TRAVIS']
+ sleep 0.5 if ENV['CI']
+ end
+
+ def merge
+ sleep 0.5 if ENV['CI']
+ sleep 0.5
+
+ sphinx.merge
+ sleep 1.5
+ sleep 0.5 if ENV['CI']
end
end
@@ -27,4 +38,8 @@ def index(*indices)
config.after :all do |group|
sphinx.stop if group.class.metadata[:live]
end
+
+ config.after :suite do
+ SphinxController.new.stop
+ end
end
diff --git a/spec/acceptance/suspended_deltas_spec.rb b/spec/acceptance/suspended_deltas_spec.rb
index 5829961ab..b5c8c603b 100644
--- a/spec/acceptance/suspended_deltas_spec.rb
+++ b/spec/acceptance/suspended_deltas_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'acceptance/spec_helper'
describe 'Suspend deltas for a given action', :live => true do
@@ -5,50 +7,50 @@
book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett'
index
- Book.search('Harry').to_a.should == [book]
+ 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
- Book.search('Terry').to_a.should == []
+ expect(Book.search('Terry').to_a).to eq([])
end
sleep 0.25
- Book.search('Terry').to_a.should == [book]
+ expect(Book.search('Terry').to_a).to eq([book])
end
it "returns core records even though they are no longer valid" do
book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett'
index
- Book.search('Harry').to_a.should == [book]
+ 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
- Book.search('Terry').to_a.should == []
+ expect(Book.search('Terry').to_a).to eq([])
end
sleep 0.25
- Book.search('Harry').to_a.should == [book]
+ expect(Book.search('Harry').to_a).to eq([book])
end
it "marks core records as deleted" do
book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett'
index
- Book.search('Harry').to_a.should == [book]
+ 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
- Book.search('Terry').to_a.should == []
+ expect(Book.search('Terry').to_a).to eq([])
end
sleep 0.25
- Book.search('Harry').to_a.should be_empty
+ expect(Book.search('Harry').to_a).to be_empty
end
end
diff --git a/spec/fixtures/database.yml b/spec/fixtures/database.yml
index 10457672b..a65f6b0f0 100644
--- a/spec/fixtures/database.yml
+++ b/spec/fixtures/database.yml
@@ -1,4 +1,4 @@
username: root
-password:
+password: thinking_sphinx
host: localhost
database: thinking_sphinx
diff --git a/spec/internal/app/indices/admin_person_index.rb b/spec/internal/app/indices/admin_person_index.rb
index 94901e387..931448b0c 100644
--- a/spec/internal/app/indices/admin_person_index.rb
+++ b/spec/internal/app/indices/admin_person_index.rb
@@ -1,3 +1,9 @@
+# frozen_string_literal: true
+
ThinkingSphinx::Index.define 'admin/person', :with => :active_record do
indexes name
end
+
+ThinkingSphinx::Index.define 'admin/person', :with => :real_time, :name => 'admin_person_rt' do
+ indexes name
+end
diff --git a/spec/internal/app/indices/album_index.rb b/spec/internal/app/indices/album_index.rb
new file mode 100644
index 000000000..21f9aeb1f
--- /dev/null
+++ b/spec/internal/app/indices/album_index.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+ThinkingSphinx::Index.define :album, :with => :active_record, :primary_key => :integer_id, :delta => true do
+ indexes name, artist
+end
+
+ThinkingSphinx::Index.define :album, :with => :real_time, :primary_key => :integer_id, :name => :album_real do
+ indexes name, artist
+end
diff --git a/spec/internal/app/indices/animal_index.rb b/spec/internal/app/indices/animal_index.rb
index e5be81a94..64fd15ea0 100644
--- a/spec/internal/app/indices/animal_index.rb
+++ b/spec/internal/app/indices/animal_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
ThinkingSphinx::Index.define :animal, :with => :active_record do
indexes name
end
diff --git a/spec/internal/app/indices/article_index.rb b/spec/internal/app/indices/article_index.rb
index 6a655cc80..a0c456218 100644
--- a/spec/internal/app/indices/article_index.rb
+++ b/spec/internal/app/indices/article_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
ThinkingSphinx::Index.define :article, :with => :active_record do
indexes title, content
indexes user.name, :as => :user
@@ -5,10 +7,9 @@
has published, user_id
has taggings.tag_id, :as => :tag_ids, :source => :query
- has taggings.created_at, :as => :taggings_at
+ has taggings.created_at, :as => :taggings_at, :type => :timestamp
set_property :min_infix_len => 4
- set_property :enable_star => true
end
ThinkingSphinx::Index.define :article, :with => :active_record,
@@ -18,7 +19,13 @@
has published, user_id
has taggings.tag_id, :as => :tag_ids
- has taggings.created_at, :as => :taggings_at
+ has taggings.created_at, :as => :taggings_at, :type => :timestamp
set_property :morphology => 'stem_en'
end
+
+ThinkingSphinx::Index.define :article, :name => :published_articles, :with => :real_time do
+ indexes title, content
+
+ scope { Article.where :published => true }
+end
diff --git a/spec/internal/app/indices/bird_index.rb b/spec/internal/app/indices/bird_index.rb
index 6879ae1c3..c3bb7c276 100644
--- a/spec/internal/app/indices/bird_index.rb
+++ b/spec/internal/app/indices/bird_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FlightlessBird
ThinkingSphinx::Index.define :bird, :with => :active_record do
indexes name
diff --git a/spec/internal/app/indices/book_index.rb b/spec/internal/app/indices/book_index.rb
index 30baaee6b..d1c2dc5de 100644
--- a/spec/internal/app/indices/book_index.rb
+++ b/spec/internal/app/indices/book_index.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
ThinkingSphinx::Index.define :book, :with => :active_record, :delta => true do
indexes title, :sortable => true
indexes author, :facet => true
indexes [title, author], :as => :info
indexes blurb_file, :file => true
- has year, created_at
+ has publishing_year
+ has created_at, :type => :timestamp
end
diff --git a/spec/internal/app/indices/car_index.rb b/spec/internal/app/indices/car_index.rb
index f684c5c3a..6e0848a11 100644
--- a/spec/internal/app/indices/car_index.rb
+++ b/spec/internal/app/indices/car_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
ThinkingSphinx::Index.define :car, :with => :real_time do
indexes name, :sortable => true
diff --git a/spec/internal/app/indices/city_index.rb b/spec/internal/app/indices/city_index.rb
index e80ebcf81..12f8e2e47 100644
--- a/spec/internal/app/indices/city_index.rb
+++ b/spec/internal/app/indices/city_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
ThinkingSphinx::Index.define :city, :with => :active_record do
indexes name
has lat, lng
diff --git a/spec/internal/app/indices/colour_index.rb b/spec/internal/app/indices/colour_index.rb
new file mode 100644
index 000000000..d427cc858
--- /dev/null
+++ b/spec/internal/app/indices/colour_index.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+ThinkingSphinx::Index.define :colour, :with => :active_record, :delta => true do
+ indexes name
+
+ has tees.id, :as => :tee_ids
+end
diff --git a/spec/internal/app/indices/product_index.rb b/spec/internal/app/indices/product_index.rb
index 9cbef0d94..26fc95fe6 100644
--- a/spec/internal/app/indices/product_index.rb
+++ b/spec/internal/app/indices/product_index.rb
@@ -1,14 +1,15 @@
+# frozen_string_literal: true
+
multi_schema = MultiSchema.new
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?
- multi_schema.create 'thinking_sphinx'
-
ThinkingSphinx::Index.define(:product,
:name => :product_two, :offset_as => :product_two, :with => :real_time
) do
diff --git a/spec/internal/app/indices/tee_index.rb b/spec/internal/app/indices/tee_index.rb
index cbb228c6f..2777ed7e2 100644
--- a/spec/internal/app/indices/tee_index.rb
+++ b/spec/internal/app/indices/tee_index.rb
@@ -1,4 +1,6 @@
+# frozen_string_literal: true
+
ThinkingSphinx::Index.define :tee, :with => :active_record do
- index colour.name
+ indexes colour.name
has colour_id, :facet => true
end
diff --git a/spec/internal/app/indices/user_index.rb b/spec/internal/app/indices/user_index.rb
index 0af8840aa..719e8e9b5 100644
--- a/spec/internal/app/indices/user_index.rb
+++ b/spec/internal/app/indices/user_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
ThinkingSphinx::Index.define :user, :with => :active_record do
indexes name
diff --git a/spec/internal/app/models/admin/person.rb b/spec/internal/app/models/admin/person.rb
index 2c48d632e..eaa3935dc 100644
--- a/spec/internal/app/models/admin/person.rb
+++ b/spec/internal/app/models/admin/person.rb
@@ -1,3 +1,9 @@
+# frozen_string_literal: true
+
class Admin::Person < ActiveRecord::Base
self.table_name = 'admin_people'
+
+ 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
new file mode 100644
index 000000000..7b6e953e4
--- /dev/null
+++ b/spec/internal/app/models/album.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class Album < ActiveRecord::Base
+ self.primary_key = :id
+
+ before_validation :set_id, :on => :create
+ before_validation :set_integer_id, :on => :create
+
+ ThinkingSphinx::Callbacks.append(
+ self, :behaviours => [:sql, :real_time, :deltas]
+ )
+
+ validates :id, :presence => true, :uniqueness => true
+ validates :integer_id, :presence => true, :uniqueness => true
+
+ private
+
+ def set_id
+ self.id = (Album.maximum(:id) || "a").next
+ end
+
+ def set_integer_id
+ self.integer_id = (Album.maximum(:integer_id) || 0) + 1
+ end
+end
diff --git a/spec/internal/app/models/animal.rb b/spec/internal/app/models/animal.rb
index e7d24fa22..4431ef86b 100644
--- a/spec/internal/app/models/animal.rb
+++ b/spec/internal/app/models/animal.rb
@@ -1,2 +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 6664d400c..b2ebf186b 100644
--- a/spec/internal/app/models/article.rb
+++ b/spec/internal/app/models/article.rb
@@ -1,5 +1,9 @@
+# frozen_string_literal: true
+
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 1bda40b8e..a2d11ddc3 100644
--- a/spec/internal/app/models/bird.rb
+++ b/spec/internal/app/models/bird.rb
@@ -1,2 +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 6a5fe58f9..a2cd0639e 100644
--- a/spec/internal/app/models/book.rb
+++ b/spec/internal/app/models/book.rb
@@ -1,14 +1,18 @@
+# frozen_string_literal: true
+
class Book < ActiveRecord::Base
include ThinkingSphinx::Scopes
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}}
+ sphinx_scope(:by_publishing_year) do |year|
+ {:with => {:publishing_year => year}}
end
- sphinx_scope(:by_query_and_year) do |query, year|
- [query, {:with => {:year =>year}}]
+ sphinx_scope(:by_query_and_publishing_year) do |query, year|
+ [query, {:with => {:publishing_year =>year}}]
end
- sphinx_scope(:ordered) { {:order => 'year DESC'} }
+ sphinx_scope(:ordered) { {:order => 'publishing_year DESC'} }
end
diff --git a/spec/internal/app/models/car.rb b/spec/internal/app/models/car.rb
index 661fa506e..8651aca86 100644
--- a/spec/internal/app/models/car.rb
+++ b/spec/internal/app/models/car.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
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/categorisation.rb b/spec/internal/app/models/categorisation.rb
index 1af00b9c7..0553928ac 100644
--- a/spec/internal/app/models/categorisation.rb
+++ b/spec/internal/app/models/categorisation.rb
@@ -1,6 +1,15 @@
+# frozen_string_literal: true
+
class Categorisation < ActiveRecord::Base
belongs_to :category
belongs_to :product
- after_save ThinkingSphinx::RealTime.callback_for(:product, [:product])
+ after_commit :update_product
+
+ private
+
+ def update_product
+ product.reload
+ ThinkingSphinx::RealTime.callback_for(:product, [:product]).after_save self
+ end
end
diff --git a/spec/internal/app/models/category.rb b/spec/internal/app/models/category.rb
index cdb4ed032..45405bd8a 100644
--- a/spec/internal/app/models/category.rb
+++ b/spec/internal/app/models/category.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Category < ActiveRecord::Base
has_many :categorisations
has_many :products, :through => :categorisations
diff --git a/spec/internal/app/models/city.rb b/spec/internal/app/models/city.rb
index 1e39734cb..59c27041b 100644
--- a/spec/internal/app/models/city.rb
+++ b/spec/internal/app/models/city.rb
@@ -1,3 +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/colour.rb b/spec/internal/app/models/colour.rb
index d20d60595..34bc8ea42 100644
--- a/spec/internal/app/models/colour.rb
+++ b/spec/internal/app/models/colour.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+
class Colour < ActiveRecord::Base
has_many :tees
+
+ ThinkingSphinx::Callbacks.append(self, behaviours: [:sql, :deltas])
end
diff --git a/spec/internal/app/models/event.rb b/spec/internal/app/models/event.rb
index adabceef9..c22ce9a8d 100644
--- a/spec/internal/app/models/event.rb
+++ b/spec/internal/app/models/event.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Event < ActiveRecord::Base
belongs_to :eventable, :polymorphic => true
end
diff --git a/spec/internal/app/models/flightless_bird.rb b/spec/internal/app/models/flightless_bird.rb
index 345a2fcdc..f7c749a7e 100644
--- a/spec/internal/app/models/flightless_bird.rb
+++ b/spec/internal/app/models/flightless_bird.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class FlightlessBird < Bird
end
diff --git a/spec/internal/app/models/genre.rb b/spec/internal/app/models/genre.rb
index d9f9fc908..da4cd7fbe 100644
--- a/spec/internal/app/models/genre.rb
+++ b/spec/internal/app/models/genre.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Genre < ActiveRecord::Base
#
end
diff --git a/spec/internal/app/models/hardcover.rb b/spec/internal/app/models/hardcover.rb
index 88a04e3ed..45dc3c93a 100644
--- a/spec/internal/app/models/hardcover.rb
+++ b/spec/internal/app/models/hardcover.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Hardcover < Book
#
end
diff --git a/spec/internal/app/models/mammal.rb b/spec/internal/app/models/mammal.rb
index 6cd52f02e..1291a1c39 100644
--- a/spec/internal/app/models/mammal.rb
+++ b/spec/internal/app/models/mammal.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class Mammal < Animal
end
diff --git a/spec/internal/app/models/manufacturer.rb b/spec/internal/app/models/manufacturer.rb
index 6ddeac8e8..aed9e5eb8 100644
--- a/spec/internal/app/models/manufacturer.rb
+++ b/spec/internal/app/models/manufacturer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Manufacturer < ActiveRecord::Base
has_many :cars
end
diff --git a/spec/internal/app/models/product.rb b/spec/internal/app/models/product.rb
index d6cf201fc..2bc519f98 100644
--- a/spec/internal/app/models/product.rb
+++ b/spec/internal/app/models/product.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
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/tag.rb b/spec/internal/app/models/tag.rb
index 9574ef9a3..376642bf2 100644
--- a/spec/internal/app/models/tag.rb
+++ b/spec/internal/app/models/tag.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Tag < ActiveRecord::Base
has_many :taggings
has_many :articles, :through => :taggings
diff --git a/spec/internal/app/models/tagging.rb b/spec/internal/app/models/tagging.rb
index 7e6d0df12..fcfbc8c1b 100644
--- a/spec/internal/app/models/tagging.rb
+++ b/spec/internal/app/models/tagging.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :article
diff --git a/spec/internal/app/models/tee.rb b/spec/internal/app/models/tee.rb
index 1cd013b25..39e2ea928 100644
--- a/spec/internal/app/models/tee.rb
+++ b/spec/internal/app/models/tee.rb
@@ -1,3 +1,10 @@
+# frozen_string_literal: true
+
class Tee < ActiveRecord::Base
belongs_to :colour
+
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
+ ThinkingSphinx::Callbacks.append(
+ self, behaviours: [:sql, :deltas], :path => [:colour]
+ )
end
diff --git a/spec/internal/app/models/tweet.rb b/spec/internal/app/models/tweet.rb
index c4a20a64b..c6ea180a1 100644
--- a/spec/internal/app/models/tweet.rb
+++ b/spec/internal/app/models/tweet.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Tweet < ActiveRecord::Base
self.primary_key = :id
end
diff --git a/spec/internal/app/models/user.rb b/spec/internal/app/models/user.rb
index 87b3670f4..26053bc50 100644
--- a/spec/internal/app/models/user.rb
+++ b/spec/internal/app/models/user.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
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/internal/config/database.yml b/spec/internal/config/database.yml
index 76b44e8a7..222dba6e8 100644
--- a/spec/internal/config/database.yml
+++ b/spec/internal/config/database.yml
@@ -1,6 +1,17 @@
test:
adapter: <%= ENV['DATABASE'] || 'mysql2' %>
database: thinking_sphinx
- username: <%= ENV['DATABASE'] == 'postgresql' ? ENV['USER'] : 'root' %>
+ username: <%= ENV['DATABASE'] == 'postgresql' ? 'postgres' : 'root' %>
+<% if ENV["DATABASE_PASSWORD"] %>
+ password: <%= ENV["DATABASE_PASSWORD"] %>
+<% end %>
+<% if ENV["DATABASE_PORT"] %>
+ host: 127.0.0.1
+ port: <%= ENV["DATABASE_PORT"] %>
+<% elsif ENV["CI"] %>
+ password: thinking_sphinx
+ host: 127.0.0.1
+ port: <%= ENV['DATABASE'] == 'postgresql' ? 5432 : 3306 %>
+<% end %>
min_messages: warning
encoding: utf8
diff --git a/spec/internal/db/schema.rb b/spec/internal/db/schema.rb
index 62e8936d6..3aede6ba5 100644
--- a/spec/internal/db/schema.rb
+++ b/spec/internal/db/schema.rb
@@ -1,9 +1,19 @@
+# frozen_string_literal: true
+
ActiveRecord::Schema.define do
create_table(:admin_people, :force => true) do |t|
t.string :name
t.timestamps null: false
end
+ create_table(:albums, :force => true, :id => false) do |t|
+ t.string :id
+ t.integer :integer_id
+ t.string :name
+ t.string :artist
+ t.boolean :delta, :default => true, :null => false
+ end
+
create_table(:animals, :force => true) do |t|
t.string :name
t.string :type
@@ -29,7 +39,7 @@
create_table(:books, :force => true) do |t|
t.string :title
t.string :author
- t.integer :year
+ t.integer :publishing_year
t.string :blurb_file
t.boolean :delta, :default => true, :null => false
t.string :type, :default => 'Book', :null => false
@@ -58,6 +68,7 @@
create_table(:colours, :force => true) do |t|
t.string :name
+ t.boolean :delta, :null => false, :default => true
t.timestamps null: false
end
@@ -72,6 +83,7 @@
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 00ddd84e0..1d4b30962 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rubygems'
require 'bundler'
@@ -5,14 +7,22 @@
root = File.expand_path File.dirname(__FILE__)
require "#{root}/support/multi_schema"
+require "#{root}/support/json_column"
+require "#{root}/support/mysql"
require 'thinking_sphinx/railtie'
Combustion.initialize! :active_record
-Dir["#{root}/support/**/*.rb"].each { |file| require file }
+MultiSchema.new.create 'thinking_sphinx'
+
+require "#{root}/support/sphinx_yaml_helpers"
RSpec.configure do |config|
# enable filtering for examples
config.filter_run :wip => nil
config.run_all_when_everything_filtered = true
+
+ config.around :each, :live do |example|
+ example.run_with_retry :retry => 3
+ end
end
diff --git a/spec/support/json_column.rb b/spec/support/json_column.rb
new file mode 100644
index 000000000..7b9d6c037
--- /dev/null
+++ b/spec/support/json_column.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class JSONColumn
+ include ActiveRecord::ConnectionAdapters
+
+ def self.call
+ new.call
+ end
+
+ def call
+ ruby? && 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
+
+ def ruby?
+ RUBY_PLATFORM != 'java'
+ end
+end
diff --git a/spec/support/multi_schema.rb b/spec/support/multi_schema.rb
index d5cf6b6bb..69c653f1b 100644
--- a/spec/support/multi_schema.rb
+++ b/spec/support/multi_schema.rb
@@ -1,15 +1,19 @@
+# frozen_string_literal: true
+
class MultiSchema
def active?
ENV['DATABASE'] == 'postgresql'
end
def create(schema_name)
+ return unless active?
+
unless connection.schema_exists? schema_name
connection.execute %Q{CREATE SCHEMA "#{schema_name}"}
end
switch schema_name
- silence_stream(STDOUT) { load Rails.root.join('db', 'schema.rb') }
+ load Rails.root.join('db', 'schema.rb')
end
def current
diff --git a/spec/support/mysql.rb b/spec/support/mysql.rb
new file mode 100644
index 000000000..f44ec7e3b
--- /dev/null
+++ b/spec/support/mysql.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# New versions of MySQL don't allow NULL values for primary keys, but old
+# versions of Rails do. To use both at the same time, we need to update Rails'
+# default primary key type to no longer have a default NULL value.
+#
+class PatchAdapter
+ def call
+ return unless using_mysql? && using_rails_pre_4_1?
+
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::
+ NATIVE_DATABASE_TYPES[:primary_key] = "int(11) auto_increment PRIMARY KEY"
+ end
+
+ def using_mysql?
+ ENV.fetch('DATABASE', 'mysql2') == 'mysql2'
+ end
+
+ def using_rails_pre_4_1?
+ ActiveRecord::VERSION::STRING.to_f < 4.1
+ end
+end
+
+PatchAdapter.new.call
diff --git a/spec/support/sphinx_yaml_helpers.rb b/spec/support/sphinx_yaml_helpers.rb
index 9f1d14ec5..442fd5c68 100644
--- a/spec/support/sphinx_yaml_helpers.rb
+++ b/spec/support/sphinx_yaml_helpers.rb
@@ -1,6 +1,13 @@
+# frozen_string_literal: true
+
module SphinxYamlHelpers
def write_configuration(hash)
- File.stub :read => {'test' => hash}.to_yaml, :exists? => true
+ allow(File).to receive(:read).and_return({'test' => hash}.to_yaml)
+ allow(File).to receive(:exist?).and_wrap_original do |original, path|
+ next true if path.to_s == File.absolute_path("config/thinking_sphinx.yml", Rails.root.to_s)
+
+ original.call(path)
+ end
end
end
diff --git a/spec/thinking_sphinx/active_record/association_spec.rb b/spec/thinking_sphinx/active_record/association_spec.rb
index 7953d4594..b91a5afc1 100644
--- a/spec/thinking_sphinx/active_record/association_spec.rb
+++ b/spec/thinking_sphinx/active_record/association_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Association do
@@ -6,7 +8,7 @@
describe '#stack' do
it "returns the column's stack and name" do
- association.stack.should == [:users, :post]
+ expect(association.stack).to eq([:users, :post])
end
end
end
diff --git a/spec/thinking_sphinx/active_record/attribute/type_spec.rb b/spec/thinking_sphinx/active_record/attribute/type_spec.rb
index 6be290190..5e7aa8b70 100644
--- a/spec/thinking_sphinx/active_record/attribute/type_spec.rb
+++ b/spec/thinking_sphinx/active_record/attribute/type_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module ActiveRecord
class Attribute; end
@@ -23,52 +25,55 @@ class Attribute; end
before :each do
column.__stack << :foo
- model.stub :reflect_on_association => association
+ allow(model).to receive(:reflect_on_association).and_return(association)
end
it "returns true if there are has_many associations" do
- association.stub :macro => :has_many
+ allow(association).to receive(:macro).and_return(:has_many)
- type.should be_multi
+ expect(type).to be_multi
end
it "returns true if there are has_and_belongs_to_many associations" do
- association.stub :macro => :has_and_belongs_to_many
+ allow(association).to receive(:macro).and_return(:has_and_belongs_to_many)
- type.should be_multi
+ expect(type).to be_multi
end
it "returns false if there are no associations" do
column.__stack.clear
- type.should_not be_multi
+ expect(type).not_to be_multi
end
it "returns false if there are only belongs_to associations" do
- association.stub :macro => :belongs_to
+ allow(association).to receive(:macro).and_return(:belongs_to)
- type.should_not be_multi
+ expect(type).not_to be_multi
end
it "returns false if there are only has_one associations" do
- association.stub :macro => :has_one
+ allow(association).to receive(:macro).and_return(:has_one)
- type.should_not be_multi
+ expect(type).not_to be_multi
end
it "returns true if deeper associations have many" do
column.__stack << :bar
deep_association = double(:klass => double, :macro => :has_many)
- association.stub :macro => :belongs_to,
- :klass => double(:reflect_on_association => deep_association)
- type.should be_multi
+ allow(association).to receive(:macro).and_return(:belongs_to)
+ allow(association).to receive(:klass).and_return(
+ double(:reflect_on_association => deep_association)
+ )
+
+ expect(type).to be_multi
end
it "respects the provided setting" do
attribute.options[:multi] = true
- type.should be_multi
+ expect(type).to be_multi
end
end
@@ -76,73 +81,79 @@ class Attribute; end
it "returns the type option provided" do
attribute.options[:type] = :datetime
- type.type.should == :datetime
+ expect(type.type).to eq(:datetime)
end
it "detects integer types from the database" do
- db_column.stub!(:type => :integer, :sql_type => 'integer(11)')
+ allow(db_column).to receive_messages(:type => :integer, :sql_type => 'integer(11)')
- type.type.should == :integer
+ expect(type.type).to eq(:integer)
end
it "detects boolean types from the database" do
- db_column.stub!(:type => :boolean)
+ allow(db_column).to receive_messages(:type => :boolean)
- type.type.should == :boolean
+ expect(type.type).to eq(:boolean)
end
it "detects datetime types from the database as timestamps" do
- db_column.stub!(:type => :datetime)
+ allow(db_column).to receive_messages(:type => :datetime)
- type.type.should == :timestamp
+ expect(type.type).to eq(:timestamp)
end
it "detects date types from the database as timestamps" do
- db_column.stub!(:type => :date)
+ allow(db_column).to receive_messages(:type => :date)
- type.type.should == :timestamp
+ expect(type.type).to eq(:timestamp)
end
it "detects string types from the database" do
- db_column.stub!(:type => :string)
+ allow(db_column).to receive_messages(:type => :string)
- type.type.should == :string
+ expect(type.type).to eq(:string)
end
it "detects text types from the database as strings" do
- db_column.stub!(:type => :text)
+ allow(db_column).to receive_messages(:type => :text)
- type.type.should == :string
+ expect(type.type).to eq(:string)
end
it "detects float types from the database" do
- db_column.stub!(:type => :float)
+ allow(db_column).to receive_messages(:type => :float)
- type.type.should == :float
+ expect(type.type).to eq(:float)
end
it "detects decimal types from the database as floats" do
- db_column.stub!(:type => :decimal)
+ allow(db_column).to receive_messages(:type => :decimal)
- type.type.should == :float
+ expect(type.type).to eq(:float)
end
it "detects big ints as big ints" do
- db_column.stub :type => :bigint
+ allow(db_column).to receive_messages :type => :bigint
- type.type.should == :bigint
+ expect(type.type).to eq(:bigint)
end
it "detects large integers as big ints" do
- db_column.stub :type => :integer, :sql_type => 'bigint(20)'
+ allow(db_column).to receive_messages :type => :integer, :sql_type => 'bigint(20)'
+
+ expect(type.type).to eq(:bigint)
+ end
+
+ it "detects JSON" do
+ allow(db_column).to receive_messages :type => :json
- type.type.should == :bigint
+ expect(type.type).to eq(:json)
end
it "respects provided type setting" do
attribute.options[:type] = :timestamp
- type.type.should == :timestamp
+ expect(type.type).to eq(:timestamp)
end
it 'raises an error if the database column does not exist' do
diff --git a/spec/thinking_sphinx/active_record/base_spec.rb b/spec/thinking_sphinx/active_record/base_spec.rb
index db9f98767..2e5886be3 100644
--- a/spec/thinking_sphinx/active_record/base_spec.rb
+++ b/spec/thinking_sphinx/active_record/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Base do
@@ -16,34 +18,34 @@ def self.name; 'SubModel'; end
describe '.facets' do
it "returns a new search object" do
- model.facets.should be_a(ThinkingSphinx::FacetSearch)
+ expect(model.facets).to be_a(ThinkingSphinx::FacetSearch)
end
it "passes through arguments to the search object" do
- model.facets('pancakes').query.should == 'pancakes'
+ expect(model.facets('pancakes').query).to eq('pancakes')
end
it "scopes the search to a given model" do
- model.facets('pancakes').options[:classes].should == [model]
+ expect(model.facets('pancakes').options[:classes]).to eq([model])
end
it "merges the :classes option with the model" do
- model.facets('pancakes', :classes => [sub_model]).
- options[:classes].should == [sub_model, model]
+ expect(model.facets('pancakes', :classes => [sub_model]).
+ options[:classes]).to eq([sub_model, model])
end
it "applies the default scope if there is one" do
- model.stub :default_sphinx_scope => :default,
+ allow(model).to receive_messages :default_sphinx_scope => :default,
:sphinx_scopes => {:default => Proc.new { {:order => :created_at} }}
- model.facets.options[:order].should == :created_at
+ expect(model.facets.options[:order]).to eq(:created_at)
end
it "does not apply a default scope if one is not set" do
- model.stub :default_sphinx_scope => nil,
+ allow(model).to receive_messages :default_sphinx_scope => nil,
:default => {:order => :created_at}
- model.facets.options[:order].should be_nil
+ expect(model.facets.options[:order]).to be_nil
end
end
@@ -55,55 +57,55 @@ def self.name; 'SubModel'; end
end
it "returns a new search object" do
- model.search.should be_a(ThinkingSphinx::Search)
+ expect(model.search).to be_a(ThinkingSphinx::Search)
end
it "passes through arguments to the search object" do
- model.search('pancakes').query.should == 'pancakes'
+ expect(model.search('pancakes').query).to eq('pancakes')
end
it "scopes the search to a given model" do
- model.search('pancakes').options[:classes].should == [model]
+ expect(model.search('pancakes').options[:classes]).to eq([model])
end
it "passes through options to the search object" do
- model.search('pancakes', populate: true).
- options[:populate].should be_true
+ expect(model.search('pancakes', populate: true).
+ options[:populate]).to be_truthy
end
it "should automatically populate when :populate is set to true" do
- stack.should_receive(:call).and_return(true)
+ expect(stack).to receive(:call).and_return(true)
model.search('pancakes', populate: true)
end
it "merges the :classes option with the model" do
- model.search('pancakes', :classes => [sub_model]).
- options[:classes].should == [sub_model, model]
+ expect(model.search('pancakes', :classes => [sub_model]).
+ options[:classes]).to eq([sub_model, model])
end
it "respects provided middleware" do
- model.search(:middleware => ThinkingSphinx::Middlewares::RAW_ONLY).
- options[:middleware].should == ThinkingSphinx::Middlewares::RAW_ONLY
+ expect(model.search(:middleware => ThinkingSphinx::Middlewares::RAW_ONLY).
+ options[:middleware]).to eq(ThinkingSphinx::Middlewares::RAW_ONLY)
end
it "respects provided masks" do
- model.search(:masks => [ThinkingSphinx::Masks::PaginationMask]).
- masks.should == [ThinkingSphinx::Masks::PaginationMask]
+ expect(model.search(:masks => [ThinkingSphinx::Masks::PaginationMask]).
+ masks).to eq([ThinkingSphinx::Masks::PaginationMask])
end
it "applies the default scope if there is one" do
- model.stub :default_sphinx_scope => :default,
+ allow(model).to receive_messages :default_sphinx_scope => :default,
:sphinx_scopes => {:default => Proc.new { {:order => :created_at} }}
- model.search.options[:order].should == :created_at
+ expect(model.search.options[:order]).to eq(:created_at)
end
it "does not apply a default scope if one is not set" do
- model.stub :default_sphinx_scope => nil,
+ allow(model).to receive_messages :default_sphinx_scope => nil,
:default => {:order => :created_at}
- model.search.options[:order].should be_nil
+ expect(model.search.options[:order]).to be_nil
end
end
@@ -112,18 +114,18 @@ def self.name; 'SubModel'; end
:populated? => false) }
before :each do
- ThinkingSphinx.stub :search => search
- FileUtils.stub :mkdir_p => true
+ allow(ThinkingSphinx).to receive_messages :search => search
+ allow(FileUtils).to receive_messages :mkdir_p => true
end
it "returns the search object's total entries count" do
- model.search_count.should == search.total_entries
+ expect(model.search_count).to eq(search.total_entries)
end
it "scopes the search to a given model" do
model.search_count
- search.options[:classes].should == [model]
+ expect(search.options[:classes]).to eq([model])
end
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 dd5eafbe1..91aa896ec 100644
--- a/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb
+++ b/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks do
@@ -10,20 +12,20 @@
let(:callbacks) { double('callbacks', :after_destroy => nil) }
before :each do
- ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks.
- stub :new => callbacks
+ allow(ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks).
+ to receive_messages :new => callbacks
end
it "builds an object from the instance" do
- ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks.
- should_receive(:new).with(instance).and_return(callbacks)
+ expect(ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks).
+ to receive(:new).with(instance).and_return(callbacks)
ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks.
after_destroy(instance)
end
it "invokes after_destroy on the object" do
- callbacks.should_receive(:after_destroy)
+ expect(callbacks).to receive(:after_destroy)
ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks.
after_destroy(instance)
@@ -32,26 +34,95 @@
describe '#after_destroy' do
let(:index_set) { double 'index set', :to_a => [index] }
- let(:index) { double('index', :name => 'foo_core',
+ let(:index) { double('index', :name => 'foo_core', :primary_key => :id,
:document_id_for_key => 14, :type => 'plain', :distributed? => false) }
let(:instance) { double('instance', :id => 7, :new_record? => false) }
before :each do
- ThinkingSphinx::IndexSet.stub :new => index_set
+ allow(ThinkingSphinx::IndexSet).to receive_messages :new => index_set
end
it "performs the deletion for the index and instance" do
- ThinkingSphinx::Deletion.should_receive(:perform).with(index, 7)
+ expect(ThinkingSphinx::Deletion).to receive(:perform).with(index, 7)
callbacks.after_destroy
end
it "doesn't do anything if the instance is a new record" do
- instance.stub :new_record? => true
+ allow(instance).to receive_messages :new_record? => true
+
+ expect(ThinkingSphinx::Deletion).not_to receive(:perform)
+
+ callbacks.after_destroy
+ end
+
+ it 'does nothing if callbacks are suspended' do
+ ThinkingSphinx::Callbacks.suspend!
- ThinkingSphinx::Deletion.should_not_receive(:perform)
+ expect(ThinkingSphinx::Deletion).not_to receive(:perform)
callbacks.after_destroy
+
+ ThinkingSphinx::Callbacks.resume!
+ end
+ end
+
+ describe '.after_rollback' do
+ let(:callbacks) { double('callbacks', :after_rollback => nil) }
+
+ before :each do
+ allow(ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks).
+ to receive_messages :new => callbacks
+ end
+
+ it "builds an object from the instance" do
+ expect(ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks).
+ to receive(:new).with(instance).and_return(callbacks)
+
+ ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks.
+ after_rollback(instance)
+ end
+
+ it "invokes after_rollback on the object" do
+ expect(callbacks).to receive(:after_rollback)
+
+ ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks.
+ after_rollback(instance)
+ end
+ end
+
+ describe '#after_rollback' do
+ let(:index_set) { double 'index set', :to_a => [index] }
+ let(:index) { double('index', :name => 'foo_core', :primary_key => :id,
+ :document_id_for_key => 14, :type => 'plain', :distributed? => false) }
+ let(:instance) { double('instance', :id => 7, :new_record? => false) }
+
+ before :each do
+ allow(ThinkingSphinx::IndexSet).to receive_messages :new => index_set
+ end
+
+ it "performs the deletion for the index and instance" do
+ expect(ThinkingSphinx::Deletion).to receive(:perform).with(index, 7)
+
+ callbacks.after_rollback
+ end
+
+ it "doesn't do anything if the instance is a new record" do
+ allow(instance).to receive_messages :new_record? => true
+
+ expect(ThinkingSphinx::Deletion).not_to receive(:perform)
+
+ callbacks.after_rollback
+ end
+
+ it 'does nothing if callbacks are suspended' do
+ ThinkingSphinx::Callbacks.suspend!
+
+ expect(ThinkingSphinx::Deletion).not_to receive(:perform)
+
+ callbacks.after_rollback
+
+ ThinkingSphinx::Callbacks.resume!
end
end
end
diff --git a/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb b/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb
index d5f50f080..95ababee3 100644
--- a/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb
+++ b/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks do
@@ -11,7 +13,7 @@
}
before :each do
- ThinkingSphinx::Configuration.stub :instance => config
+ allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
[:after_commit, :before_save].each do |callback|
@@ -19,20 +21,20 @@
let(:callbacks) { double('callbacks', callback => nil) }
before :each do
- ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks.
- stub :new => callbacks
+ allow(ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks).
+ to receive_messages :new => callbacks
end
it "builds an object from the instance" do
- ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks.
- should_receive(:new).with(instance).and_return(callbacks)
+ expect(ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks).
+ to receive(:new).with(instance).and_return(callbacks)
ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks.
send(callback, instance)
end
it "invokes #{callback} on the object" do
- callbacks.should_receive(callback)
+ expect(callbacks).to receive(callback)
ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks.
send(callback, instance)
@@ -42,22 +44,23 @@
describe '#after_commit' do
let(:index) {
- double('index', :delta? => false, :delta_processor => processor)
+ double('index', :delta? => false, :delta_processor => processor,
+ :type => 'plain')
}
before :each do
- config.stub :index_set_class => double(:new => [index])
+ allow(config).to receive_messages :index_set_class => double(:new => [index])
end
context 'without delta indices' do
it "does not fire a delta index when no delta indices" do
- processor.should_not_receive(:index)
+ expect(processor).not_to receive(:index)
callbacks.after_commit
end
it "does not delete the instance from any index" do
- processor.should_not_receive(:delete)
+ expect(processor).not_to receive(:delete)
callbacks.after_commit
end
@@ -65,58 +68,58 @@
context 'with delta indices' do
let(:core_index) { double('index', :delta? => false, :name => 'foo_core',
- :delta_processor => processor) }
+ :delta_processor => processor, :type => 'plain') }
let(:delta_index) { double('index', :delta? => true, :name => 'foo_delta',
- :delta_processor => processor) }
+ :delta_processor => processor, :type => 'plain') }
before :each do
- ThinkingSphinx::Deltas.stub :suspended? => false
+ allow(ThinkingSphinx::Deltas).to receive_messages :suspended? => false
- config.stub :index_set_class => double(
+ allow(config).to receive_messages :index_set_class => double(
:new => [core_index, delta_index]
)
end
it "only indexes delta indices" do
- processor.should_receive(:index).with(delta_index)
+ expect(processor).to receive(:index).with(delta_index)
callbacks.after_commit
end
it "does not process delta indices when deltas are suspended" do
- ThinkingSphinx::Deltas.stub :suspended? => true
+ allow(ThinkingSphinx::Deltas).to receive_messages :suspended? => true
- processor.should_not_receive(:index)
+ expect(processor).not_to receive(:index)
callbacks.after_commit
end
it "deletes the instance from the core index" do
- processor.should_receive(:delete).with(core_index, instance)
+ expect(processor).to receive(:delete).with(core_index, instance)
callbacks.after_commit
end
it "does not index if model's delta flag is not true" do
- processor.stub :toggled? => false
+ allow(processor).to receive_messages :toggled? => false
- processor.should_not_receive(:index)
+ expect(processor).not_to receive(:index)
callbacks.after_commit
end
it "does not delete if model's delta flag is not true" do
- processor.stub :toggled? => false
+ allow(processor).to receive_messages :toggled? => false
- processor.should_not_receive(:delete)
+ expect(processor).not_to receive(:delete)
callbacks.after_commit
end
it "does not delete when deltas are suspended" do
- ThinkingSphinx::Deltas.stub :suspended? => true
+ allow(ThinkingSphinx::Deltas).to receive_messages :suspended? => true
- processor.should_not_receive(:delete)
+ expect(processor).not_to receive(:delete)
callbacks.after_commit
end
@@ -125,23 +128,47 @@
describe '#before_save' do
let(:index) {
- double('index', :delta? => true, :delta_processor => processor)
+ double('index', :delta? => true, :delta_processor => processor,
+ :type => 'plain')
}
before :each do
- config.stub :index_set_class => double(:new => [index])
+ allow(config).to receive_messages :index_set_class => double(:new => [index])
+ allow(instance).to receive_messages(
+ :changed? => true,
+ :new_record? => false
+ )
end
it "sets delta to true if there are delta indices" do
- processor.should_receive(:toggle).with(instance)
+ expect(processor).to receive(:toggle).with(instance)
callbacks.before_save
end
it "does not try to set delta to true if there are no delta indices" do
- index.stub :delta? => false
+ allow(index).to receive_messages :delta? => false
+
+ expect(processor).not_to receive(:toggle)
+
+ callbacks.before_save
+ end
+
+ it "does not try to set delta to true if the instance is unchanged" do
+ allow(instance).to receive_messages :changed? => false
+
+ expect(processor).not_to receive(:toggle)
+
+ callbacks.before_save
+ end
+
+ it "does set delta to true if the instance is unchanged but new" do
+ allow(instance).to receive_messages(
+ :changed? => false,
+ :new_record? => true
+ )
- processor.should_not_receive(:toggle)
+ expect(processor).to receive(:toggle)
callbacks.before_save
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 050c48878..9186683b1 100644
--- a/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb
+++ b/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module ActiveRecord
module Callbacks; end
@@ -17,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'}
+ :type => 'plain', :primary_key => :id}
let(:source) { double('source', :attributes => []) }
+ let(:set_class) { double(:reference_name => :article) }
before :each do
stub_const 'ThinkingSphinx::Configuration',
@@ -30,7 +33,7 @@ module Callbacks; end
stub_const 'ThinkingSphinx::Connection', double
stub_const 'Riddle::Query', double(:update => 'SphinxQL')
- ThinkingSphinx::Connection.stub(:take).and_yield(connection)
+ allow(ThinkingSphinx::Connection).to receive(:take).and_yield(connection)
source.attributes.replace([
double(:name => 'foo', :updateable? => true,
@@ -40,35 +43,49 @@ module Callbacks; end
double(:name => 'baz', :updateable? => false)
])
- instance.stub :changed => ['bar_column', 'baz'], :bar_column => 7
+ allow(instance).to receive_messages(
+ :changed => ['bar_column', 'baz'],
+ :bar_column => 7,
+ :saved_changes => {'bar_column' => [1, 2], 'baz' => [3, 4]}
+ )
end
it "does not send any updates to Sphinx if updates are disabled" do
configuration.settings['attribute_updates'] = false
- connection.should_not_receive(:execute)
+ expect(connection).not_to receive(:execute)
callbacks.after_update
end
it "builds an update query with only updateable attributes that have changed" do
- Riddle::Query.should_receive(:update).
- with('article_core', 3, 'bar' => 7).and_return('SphinxQL')
+ expect(Riddle::Query).to receive(:update).
+ with('article_core', 3, { 'bar' => 7 }).and_return('SphinxQL')
callbacks.after_update
end
it "sends the update query through to Sphinx" do
- connection.should_receive(:execute).with('SphinxQL')
+ expect(connection).to receive(:execute).with('SphinxQL')
callbacks.after_update
end
it "doesn't care if the update fails at Sphinx's end" do
- connection.stub(:execute).
+ allow(connection).to receive(:execute).
and_raise(ThinkingSphinx::ConnectionError.new(''))
- lambda { callbacks.after_update }.should_not raise_error
+ expect { callbacks.after_update }.not_to raise_error
+ end
+
+ it 'does nothing if callbacks are suspended' do
+ ThinkingSphinx::Callbacks.suspend!
+
+ expect(connection).not_to receive(:execute)
+
+ callbacks.after_update
+
+ ThinkingSphinx::Callbacks.resume!
end
end
end
diff --git a/spec/thinking_sphinx/active_record/column_spec.rb b/spec/thinking_sphinx/active_record/column_spec.rb
index 60765ddd4..af1f76bb9 100644
--- a/spec/thinking_sphinx/active_record/column_spec.rb
+++ b/spec/thinking_sphinx/active_record/column_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Column do
describe '#__name' do
it "returns the top item" do
column = ThinkingSphinx::ActiveRecord::Column.new(:content)
- column.__name.should == :content
+ expect(column.__name).to eq(:content)
end
end
@@ -14,27 +16,27 @@
it "returns itself when it's a string column" do
column = ThinkingSphinx::ActiveRecord::Column.new('foo')
- column.__replace(base, replacements).collect(&:__path).
- should == [['foo']]
+ expect(column.__replace(base, replacements).collect(&:__path)).
+ to eq([['foo']])
end
it "returns itself when the base of the stack does not match" do
column = ThinkingSphinx::ActiveRecord::Column.new(:b, :c)
- column.__replace(base, replacements).collect(&:__path).
- should == [[:b, :c]]
+ expect(column.__replace(base, replacements).collect(&:__path)).
+ to eq([[:b, :c]])
end
it "returns an array of new columns " do
column = ThinkingSphinx::ActiveRecord::Column.new(:a, :b, :e)
- column.__replace(base, replacements).collect(&:__path).
- should == [[:a, :c, :e], [:a, :d, :e]]
+ expect(column.__replace(base, replacements).collect(&:__path)).
+ to eq([[:a, :c, :e], [:a, :d, :e]])
end
end
describe '#__stack' do
it "returns all but the top item" do
column = ThinkingSphinx::ActiveRecord::Column.new(:users, :posts, :id)
- column.__stack.should == [:users, :posts]
+ expect(column.__stack).to eq([:users, :posts])
end
end
@@ -43,28 +45,28 @@
it "shifts the current name to the stack" do
column.email
- column.__stack.should == [:user]
+ expect(column.__stack).to eq([:user])
end
it "adds the new method call as the name" do
column.email
- column.__name.should == :email
+ expect(column.__name).to eq(:email)
end
it "returns itself" do
- column.email.should == column
+ expect(column.email).to eq(column)
end
end
describe '#string?' do
it "is true when the name is a string" do
column = ThinkingSphinx::ActiveRecord::Column.new('content')
- column.should be_a_string
+ expect(column).to be_a_string
end
it "is false when the name is a symbol" do
column = ThinkingSphinx::ActiveRecord::Column.new(:content)
- column.should_not be_a_string
+ expect(column).not_to be_a_string
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 000000000..7f27ce65a
--- /dev/null
+++ b/spec/thinking_sphinx/active_record/column_sql_presenter_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+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)
+ allow(adapter).to receive(:quote) { |arg| "`#{arg}`" }
+ end
+
+ context "when there's no explicit db name" do
+ before { allow(associations).to receive_messages(:alias_for => 'table_name') }
+
+ it 'returns quoted table and column names' do
+ expect(presenter.with_table).to eq('`table_name`.`column_name`')
+ end
+ end
+
+ context 'when an eplicit db name is provided' do
+ before { allow(associations).to receive_messages(:alias_for => 'db_name.table_name') }
+
+ it 'returns properly quoted table name with column name' do
+ expect(presenter.with_table).to eq('`db_name`.`table_name`.`column_name`')
+ end
+ end
+ end
+end
diff --git a/spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb b/spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb
index 5a3a45d2c..4b3820bc7 100644
--- a/spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb
+++ b/spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::DatabaseAdapters::AbstractAdapter do
@@ -9,23 +11,23 @@
describe '#quote' do
it "uses the model's connection to quote columns" do
- connection.should_receive(:quote_column_name).with('foo')
+ expect(connection).to receive(:quote_column_name).with('foo')
adapter.quote 'foo'
end
it "returns the quoted value" do
- connection.stub :quote_column_name => '"foo"'
+ allow(connection).to receive_messages :quote_column_name => '"foo"'
- adapter.quote('foo').should == '"foo"'
+ expect(adapter.quote('foo')).to eq('"foo"')
end
end
describe '#quoted_table_name' do
it "passes the method through to the model" do
- model.should_receive(:quoted_table_name).and_return('"articles"')
+ expect(model).to receive(:quoted_table_name).and_return('"articles"')
- adapter.quoted_table_name.should == '"articles"'
+ expect(adapter.quoted_table_name).to eq('"articles"')
end
end
end
diff --git a/spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb b/spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb
index 1b802b8ea..dc5bcf6f6 100644
--- a/spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb
+++ b/spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter do
@@ -7,50 +9,62 @@
let(:model) { double('model') }
it "returns 1 for true" do
- adapter.boolean_value(true).should == 1
+ expect(adapter.boolean_value(true)).to eq(1)
end
it "returns 0 for false" do
- adapter.boolean_value(false).should == 0
+ expect(adapter.boolean_value(false)).to eq(0)
end
describe '#cast_to_string' do
it "casts the clause to characters" do
- adapter.cast_to_string('foo').should == "CAST(foo AS char)"
+ expect(adapter.cast_to_string('foo')).to eq("CAST(foo AS char)")
end
end
describe '#cast_to_timestamp' do
it "converts to unix timestamps" do
- adapter.cast_to_timestamp('created_at').
- should == 'UNIX_TIMESTAMP(created_at)'
+ expect(adapter.cast_to_timestamp('created_at')).
+ to eq('UNIX_TIMESTAMP(created_at)')
end
end
describe '#concatenate' do
it "concatenates with the given separator" do
- adapter.concatenate('foo, bar, baz', ',').
- should == "CONCAT_WS(',', foo, bar, baz)"
+ expect(adapter.concatenate('foo, bar, baz', ',')).
+ to eq("CONCAT_WS(',', foo, bar, baz)")
end
end
describe '#convert_nulls' do
it "translates arguments to an IFNULL SQL call" do
- adapter.convert_nulls('id', 5).should == 'IFNULL(id, 5)'
+ expect(adapter.convert_nulls('id', 5)).to eq('IFNULL(id, 5)')
end
end
describe '#convert_blank' do
it "translates arguments to a COALESCE NULLIF SQL call" do
- adapter.convert_blank('id', 5).should == "COALESCE(NULLIF(id, ''), 5)"
+ expect(adapter.convert_blank('id', 5)).to eq("COALESCE(NULLIF(id, ''), 5)")
end
end
-
describe '#group_concatenate' do
it "group concatenates the clause with the given separator" do
- adapter.group_concatenate('foo', ',').
- should == "GROUP_CONCAT(DISTINCT foo SEPARATOR ',')"
+ expect(adapter.group_concatenate('foo', ',')).
+ to eq("GROUP_CONCAT(DISTINCT foo SEPARATOR ',')")
+ end
+ end
+
+ describe '#utf8_query_pre' do
+ it "defaults to using utf8" do
+ expect(adapter.utf8_query_pre).to eq(["SET NAMES utf8"])
+ end
+
+ it "allows custom values" do
+ ThinkingSphinx::Configuration.instance.settings['mysql_encoding'] =
+ 'utf8mb4'
+
+ expect(adapter.utf8_query_pre).to eq(["SET NAMES utf8mb4"])
end
end
end
diff --git a/spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb b/spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb
index 4d0b967c6..03a63bd15 100644
--- a/spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb
+++ b/spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter do
@@ -8,57 +10,57 @@
describe '#boolean_value' do
it "returns 'TRUE' for true" do
- adapter.boolean_value(true).should == 'TRUE'
+ expect(adapter.boolean_value(true)).to eq('TRUE')
end
it "returns 'FALSE' for false" do
- adapter.boolean_value(false).should == 'FALSE'
+ expect(adapter.boolean_value(false)).to eq('FALSE')
end
end
describe '#cast_to_string' do
it "casts the clause to characters" do
- adapter.cast_to_string('foo').should == 'foo::varchar'
+ expect(adapter.cast_to_string('foo')).to eq('foo::varchar')
end
end
describe '#cast_to_timestamp' do
it "converts to int unix timestamps" do
- adapter.cast_to_timestamp('created_at').
- should == 'extract(epoch from created_at)::int'
+ expect(adapter.cast_to_timestamp('created_at')).
+ to eq('extract(epoch from created_at)::int')
end
it "converts to bigint unix timestamps" do
ThinkingSphinx::Configuration.instance.settings['64bit_timestamps'] = true
- adapter.cast_to_timestamp('created_at').
- should == 'extract(epoch from created_at)::bigint'
+ expect(adapter.cast_to_timestamp('created_at')).
+ to eq('extract(epoch from created_at)::bigint')
end
end
describe '#concatenate' do
it "concatenates with the given separator" do
- adapter.concatenate('foo, bar, baz', ',').
- should == "COALESCE(foo, '') || ',' || COALESCE(bar, '') || ',' || COALESCE(baz, '')"
+ expect(adapter.concatenate('foo, bar, baz', ',')).
+ to eq("COALESCE(foo, '') || ',' || COALESCE(bar, '') || ',' || COALESCE(baz, '')")
end
end
describe '#convert_nulls' do
it "translates arguments to a COALESCE SQL call" do
- adapter.convert_nulls('id', 5).should == 'COALESCE(id, 5)'
+ expect(adapter.convert_nulls('id', 5)).to eq('COALESCE(id, 5)')
end
end
describe '#convert_blank' do
it "translates arguments to a COALESCE NULLIF SQL call" do
- adapter.convert_blank('id', 5).should == "COALESCE(NULLIF(id, ''), 5)"
+ expect(adapter.convert_blank('id', 5)).to eq("COALESCE(NULLIF(id, ''), 5)")
end
end
describe '#group_concatenate' do
it "group concatenates the clause with the given separator" do
- adapter.group_concatenate('foo', ',').
- should == "array_to_string(array_agg(DISTINCT foo), ',')"
+ expect(adapter.group_concatenate('foo', ',')).
+ to eq("array_to_string(array_agg(DISTINCT foo), ',')")
end
end
end
diff --git a/spec/thinking_sphinx/active_record/database_adapters_spec.rb b/spec/thinking_sphinx/active_record/database_adapters_spec.rb
index b9b411561..9b0544c58 100644
--- a/spec/thinking_sphinx/active_record/database_adapters_spec.rb
+++ b/spec/thinking_sphinx/active_record/database_adapters_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::DatabaseAdapters do
@@ -5,21 +7,21 @@
describe '.adapter_for' do
it "returns a MysqlAdapter object for :mysql" do
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- stub(:adapter_type_for => :mysql)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters).
+ to receive_messages(:adapter_type_for => :mysql)
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model).
- should be_a(
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model)).
+ to be_a(
ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter
)
end
it "returns a PostgreSQLAdapter object for :postgresql" do
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- stub(:adapter_type_for => :postgresql)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters).
+ to receive_messages(:adapter_type_for => :postgresql)
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model).
- should be_a(
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model)).
+ to be_a(
ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter
)
end
@@ -29,21 +31,21 @@
adapter_instance = double('adapter instance')
ThinkingSphinx::ActiveRecord::DatabaseAdapters.default = adapter_class
- adapter_class.stub!(:new => adapter_instance)
+ allow(adapter_class).to receive_messages(:new => adapter_instance)
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model).
- should == adapter_instance
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model)).
+ to eq(adapter_instance)
ThinkingSphinx::ActiveRecord::DatabaseAdapters.default = nil
end
it "raises an exception for other responses" do
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- stub(:adapter_type_for => :sqlite)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters).
+ to receive_messages(:adapter_type_for => :sqlite)
- lambda {
+ expect {
ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model)
- }.should raise_error
+ }.to raise_error(ThinkingSphinx::InvalidDatabaseAdapter)
end
end
@@ -53,74 +55,74 @@
let(:model) { double('model', :connection => connection) }
it "translates a normal MySQL adapter" do
- klass.stub(:name => 'ActiveRecord::ConnectionAdapters::MysqlAdapter')
+ allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::MysqlAdapter')
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- adapter_type_for(model).should == :mysql
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
+ adapter_type_for(model)).to eq(:mysql)
end
it "translates a MySQL2 adapter" do
- klass.stub(:name => 'ActiveRecord::ConnectionAdapters::Mysql2Adapter')
+ allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::Mysql2Adapter')
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- adapter_type_for(model).should == :mysql
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
+ adapter_type_for(model)).to eq(:mysql)
end
it "translates a normal PostgreSQL adapter" do
- klass.stub(:name => 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter')
+ allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter')
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- adapter_type_for(model).should == :postgresql
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
+ adapter_type_for(model)).to eq(:postgresql)
end
it "translates a JDBC MySQL adapter to MySQL" do
- klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
- connection.stub(:config => {:adapter => 'jdbcmysql'})
+ allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
+ allow(connection).to receive_messages(:config => {:adapter => 'jdbcmysql'})
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- adapter_type_for(model).should == :mysql
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
+ adapter_type_for(model)).to eq(:mysql)
end
it "translates a JDBC PostgreSQL adapter to PostgreSQL" do
- klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
- connection.stub(:config => {:adapter => 'jdbcpostgresql'})
+ allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
+ allow(connection).to receive_messages(:config => {:adapter => 'jdbcpostgresql'})
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- adapter_type_for(model).should == :postgresql
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
+ adapter_type_for(model)).to eq(:postgresql)
end
it "translates a JDBC adapter with MySQL connection string to MySQL" do
- klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
- connection.stub(:config => {:adapter => 'jdbc',
+ allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
+ allow(connection).to receive_messages(:config => {:adapter => 'jdbc',
:url => 'jdbc:mysql://127.0.0.1:3306/sphinx'})
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- adapter_type_for(model).should == :mysql
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
+ adapter_type_for(model)).to eq(:mysql)
end
it "translates a JDBC adapter with PostgresSQL connection string to PostgresSQL" do
- klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
- connection.stub(:config => {:adapter => 'jdbc',
+ allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
+ allow(connection).to receive_messages(:config => {:adapter => 'jdbc',
:url => 'jdbc:postgresql://127.0.0.1:3306/sphinx'})
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- adapter_type_for(model).should == :postgresql
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
+ adapter_type_for(model)).to eq(:postgresql)
end
it "returns other JDBC adapters without translation" do
- klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
- connection.stub(:config => {:adapter => 'jdbcmssql'})
+ allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
+ allow(connection).to receive_messages(:config => {:adapter => 'jdbcmssql'})
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- adapter_type_for(model).should == 'jdbcmssql'
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
+ adapter_type_for(model)).to eq('jdbcmssql')
end
it "returns other unknown adapters without translation" do
- klass.stub(:name => 'ActiveRecord::ConnectionAdapters::FooAdapter')
+ allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::FooAdapter')
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- adapter_type_for(model).
- should == 'ActiveRecord::ConnectionAdapters::FooAdapter'
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
+ adapter_type_for(model)).
+ to eq('ActiveRecord::ConnectionAdapters::FooAdapter')
end
end
end
diff --git a/spec/thinking_sphinx/active_record/field_spec.rb b/spec/thinking_sphinx/active_record/field_spec.rb
index 692da5e9b..8b6e20f99 100644
--- a/spec/thinking_sphinx/active_record/field_spec.rb
+++ b/spec/thinking_sphinx/active_record/field_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Field do
@@ -7,16 +9,16 @@
let(:model) { double('model') }
before :each do
- column.stub! :to_a => [column]
+ allow(column).to receive_messages :to_a => [column]
end
describe '#columns' do
it 'returns the provided Column object' do
- field.columns.should == [column]
+ expect(field.columns).to eq([column])
end
it 'translates symbols to Column objects' do
- ThinkingSphinx::ActiveRecord::Column.should_receive(:new).with(:title).
+ expect(ThinkingSphinx::ActiveRecord::Column).to receive(:new).with(:title).
and_return(column)
ThinkingSphinx::ActiveRecord::Field.new model, :title
@@ -25,25 +27,25 @@
describe '#file?' do
it "defaults to false" do
- field.should_not be_file
+ expect(field).not_to be_file
end
it "is true if file option is set" do
field = ThinkingSphinx::ActiveRecord::Field.new model, column,
:file => true
- field.should be_file
+ expect(field).to be_file
end
end
describe '#with_attribute?' do
it "defaults to false" do
- field.should_not be_with_attribute
+ expect(field).not_to be_with_attribute
end
it "is true if the field is sortable" do
field = ThinkingSphinx::ActiveRecord::Field.new model, column,
:sortable => true
- field.should be_with_attribute
+ expect(field).to be_with_attribute
end
end
end
diff --git a/spec/thinking_sphinx/active_record/filter_reflection_spec.rb b/spec/thinking_sphinx/active_record/filter_reflection_spec.rb
index 0bef35593..600edf7f9 100644
--- a/spec/thinking_sphinx/active_record/filter_reflection_spec.rb
+++ b/spec/thinking_sphinx/active_record/filter_reflection_spec.rb
@@ -1,35 +1,67 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::FilterReflection do
describe '.call' do
let(:reflection) { double('Reflection', :macro => :has_some,
:options => options, :active_record => double, :name => 'baz',
- :foreign_type => :foo_type, :class => reflection_klass) }
+ :foreign_type => :foo_type, :class => original_klass,
+ :build_join_constraint => nil) }
let(:options) { {:polymorphic => true} }
let(:filtered_reflection) { double 'filtered reflection' }
- let(:reflection_klass) { double :new => filtered_reflection }
+ let(:original_klass) { double }
+ let(:subclass) { double :include => true }
before :each do
- reflection.active_record.stub_chain(:connection, :quote_column_name).
+ allow(reflection.active_record).to receive_message_chain(:connection, :quote_column_name).
and_return('"foo_type"')
+
+ if ActiveRecord::VERSION::STRING.to_f < 5.2
+ allow(original_klass).to receive(:new).and_return(filtered_reflection)
+ else
+ allow(Class).to receive(:new).with(original_klass).and_return(subclass)
+ allow(subclass).to receive(:new).and_return(filtered_reflection)
+ end
+ end
+
+ class ArgumentsWrapper
+ attr_reader :macro, :name, :scope, :options, :parent
+
+ def initialize(*arguments)
+ if ActiveRecord::VERSION::STRING.to_f < 4.0
+ @macro, @name, @options, @parent = arguments
+ elsif ActiveRecord::VERSION::STRING.to_f < 4.2
+ @macro, @name, @scope, @options, @parent = arguments
+ else
+ @name, @scope, @options, @parent = arguments
+ end
+ end
+ end
+
+ def reflection_klass
+ ActiveRecord::VERSION::STRING.to_f < 5.2 ? original_klass : subclass
+ end
+
+ def expected_reflection_arguments
+ expect(reflection_klass).to receive(:new) do |*arguments|
+ yield ArgumentsWrapper.new(*arguments)
+ end
end
it "uses the existing reflection's macro" do
- reflection_klass.should_receive(:new).
- with(:has_some, anything, anything, anything)
+ expect(reflection_klass).to receive(:new) do |macro, *args|
+ expect(macro).to eq(:has_some)
+ end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
- end unless defined?(ActiveRecord::Reflection::MacroReflection)
+ end if ActiveRecord::VERSION::STRING.to_f < 4.2
it "uses the supplied name" do
- if defined?(ActiveRecord::Reflection::MacroReflection)
- reflection_klass.should_receive(:new).
- with('foo_bar', anything, anything, anything)
- else
- reflection_klass.should_receive(:new).
- with(anything, 'foo_bar', anything, anything)
+ expected_reflection_arguments do |wrapper|
+ expect(wrapper.name).to eq('foo_bar')
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
@@ -38,12 +70,8 @@
end
it "uses the existing reflection's parent" do
- if defined?(ActiveRecord::Reflection::MacroReflection)
- reflection_klass.should_receive(:new).
- with(anything, anything, anything, reflection.active_record)
- else
- reflection_klass.should_receive(:new).
- with(anything, anything, anything, reflection.active_record)
+ expected_reflection_arguments do |wrapper|
+ expect(wrapper.parent).to eq(reflection.active_record)
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
@@ -52,14 +80,8 @@
end
it "removes the polymorphic setting from the options" do
- if defined?(ActiveRecord::Reflection::MacroReflection)
- reflection_klass.should_receive(:new) do |name, scope, options, parent|
- options[:polymorphic].should be_nil
- end
- else
- reflection_klass.should_receive(:new) do |macro, name, options, parent|
- options[:polymorphic].should be_nil
- end
+ expected_reflection_arguments do |wrapper|
+ expect(wrapper.options[:polymorphic]).to be_nil
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
@@ -68,14 +90,8 @@
end
it "adds the class name option" do
- if defined?(ActiveRecord::Reflection::MacroReflection)
- reflection_klass.should_receive(:new) do |name, scope, options, parent|
- options[:class_name].should == 'Bar'
- end
- else
- reflection_klass.should_receive(:new) do |macro, name, options, parent|
- options[:class_name].should == 'Bar'
- end
+ expected_reflection_arguments do |wrapper|
+ expect(wrapper.options[:class_name]).to eq('Bar')
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
@@ -84,14 +100,8 @@
end
it "sets the foreign key if necessary" do
- if defined?(ActiveRecord::Reflection::MacroReflection)
- reflection_klass.should_receive(:new) do |name, scope, options, parent|
- options[:foreign_key].should == 'baz_id'
- end
- else
- reflection_klass.should_receive(:new) do |macro, name, options, parent|
- options[:foreign_key].should == 'baz_id'
- end
+ expected_reflection_arguments do |wrapper|
+ expect(wrapper.options[:foreign_key]).to eq('baz_id')
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
@@ -102,14 +112,8 @@
it "respects supplied foreign keys" do
options[:foreign_key] = 'qux_id'
- if defined?(ActiveRecord::Reflection::MacroReflection)
- reflection_klass.should_receive(:new) do |name, scope, options, parent|
- options[:foreign_key].should == 'qux_id'
- end
- else
- reflection_klass.should_receive(:new) do |macro, name, options, parent|
- options[:foreign_key].should == 'qux_id'
- end
+ expected_reflection_arguments do |wrapper|
+ expect(wrapper.options[:foreign_key]).to eq('qux_id')
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
@@ -117,56 +121,87 @@
)
end
- it "sets conditions if there are none" do
- reflection_klass.should_receive(:new) do |macro, name, options, parent|
- options[:conditions].should == "::ts_join_alias::.\"foo_type\" = 'Bar'"
+ if ActiveRecord::VERSION::STRING.to_f < 4.0
+ it "sets conditions if there are none" do
+ expect(reflection_klass).to receive(:new) do |macro, name, options, parent|
+ expect(options[:conditions]).to eq("::ts_join_alias::.\"foo_type\" = 'Bar'")
+ end
+
+ ThinkingSphinx::ActiveRecord::FilterReflection.call(
+ reflection, 'foo_bar', 'Bar'
+ )
end
- ThinkingSphinx::ActiveRecord::FilterReflection.call(
- reflection, 'foo_bar', 'Bar'
- )
- end unless defined?(ActiveRecord::Reflection::MacroReflection)
+ it "appends to the conditions array" do
+ options[:conditions] = ['existing']
- it "appends to the conditions array" do
- options[:conditions] = ['existing']
+ expect(reflection_klass).to receive(:new) do |macro, name, options, parent|
+ expect(options[:conditions]).to eq(['existing', "::ts_join_alias::.\"foo_type\" = 'Bar'"])
+ end
- reflection_klass.should_receive(:new) do |macro, name, options, parent|
- options[:conditions].should == ['existing', "::ts_join_alias::.\"foo_type\" = 'Bar'"]
+ ThinkingSphinx::ActiveRecord::FilterReflection.call(
+ reflection, 'foo_bar', 'Bar'
+ )
end
- ThinkingSphinx::ActiveRecord::FilterReflection.call(
- reflection, 'foo_bar', 'Bar'
- )
- end unless defined?(ActiveRecord::Reflection::MacroReflection)
+ it "extends the conditions hash" do
+ options[:conditions] = {:x => :y}
- it "extends the conditions hash" do
- options[:conditions] = {:x => :y}
+ expect(reflection_klass).to receive(:new) do |macro, name, options, parent|
+ expect(options[:conditions]).to eq({:x => :y, :foo_type => 'Bar'})
+ end
- reflection_klass.should_receive(:new) do |macro, name, options, parent|
- options[:conditions].should == {:x => :y, :foo_type => 'Bar'}
+ ThinkingSphinx::ActiveRecord::FilterReflection.call(
+ reflection, 'foo_bar', 'Bar'
+ )
end
+ it "appends to the conditions string" do
+ options[:conditions] = 'existing'
+
+ expect(reflection_klass).to receive(:new) do |macro, name, options, parent|
+ expect(options[:conditions]).to eq("existing AND ::ts_join_alias::.\"foo_type\" = 'Bar'")
+ end
+
+ ThinkingSphinx::ActiveRecord::FilterReflection.call(
+ reflection, 'foo_bar', 'Bar'
+ )
+ end
+ else
+ it "does not add a conditions option" do
+ expected_reflection_arguments do |wrapper|
+ expect(wrapper.options.keys).not_to include(:conditions)
+ end
+
+ ThinkingSphinx::ActiveRecord::FilterReflection.call(
+ reflection, 'foo_bar', 'Bar'
+ )
+ end
+ end
+
+ it "includes custom behaviour in the subclass" do
+ expect(subclass).to receive(:include).with(ThinkingSphinx::ActiveRecord::Depolymorph::OverriddenReflection::BuildJoinConstraint)
+
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
- end unless defined?(ActiveRecord::Reflection::MacroReflection)
+ end if ActiveRecord::VERSION::STRING.to_f > 5.1
- it "appends to the conditions string" do
- options[:conditions] = 'existing'
+ it "includes custom behaviour in the subclass" do
+ allow(reflection).to receive(:respond_to?).with(:build_join_constraint).
+ and_return(false)
- reflection_klass.should_receive(:new) do |macro, name, options, parent|
- options[:conditions].should == "existing AND ::ts_join_alias::.\"foo_type\" = 'Bar'"
- end
+ expect(subclass).to receive(:include).with(ThinkingSphinx::ActiveRecord::Depolymorph::OverriddenReflection::JoinScope)
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
- end unless defined?(ActiveRecord::Reflection::MacroReflection)
+ end if ActiveRecord::VERSION::STRING.to_f >= 6.0
it "returns the new reflection" do
- ThinkingSphinx::ActiveRecord::FilterReflection.call(
+ expect(ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
- ).should == filtered_reflection
+ )).to eq(filtered_reflection)
end
end
end
diff --git a/spec/thinking_sphinx/active_record/index_spec.rb b/spec/thinking_sphinx/active_record/index_spec.rb
index 759e81a79..3448f2eb0 100644
--- a/spec/thinking_sphinx/active_record/index_spec.rb
+++ b/spec/thinking_sphinx/active_record/index_spec.rb
@@ -1,46 +1,50 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Index do
let(:index) { ThinkingSphinx::ActiveRecord::Index.new :user }
- let(:indices_path) { double('indices path', :join => '') }
let(:config) { double('config', :settings => {},
- :indices_location => indices_path, :next_offset => 8) }
+ :indices_location => 'location', :next_offset => 8,
+ :index_set_class => index_set_class) }
+ let(:index_set_class) { double :reference_name => :user }
before :each do
- ThinkingSphinx::Configuration.stub :instance => config
+ allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
describe '#append_source' do
- let(:model) { double('model', :primary_key => :id) }
+ let(:model) { double('model', :primary_key => :id,
+ :table_exists? => true) }
let(:source) { double('source') }
before :each do
- ActiveSupport::Inflector.stub(:constantize => model)
- ThinkingSphinx::ActiveRecord::SQLSource.stub :new => source
- config.stub :next_offset => 17
+ allow(ActiveSupport::Inflector).to receive_messages(:constantize => model)
+ allow(ThinkingSphinx::ActiveRecord::SQLSource).to receive_messages :new => source
+ allow(config).to receive_messages :next_offset => 17
end
it "adds a source to the index" do
- index.sources.should_receive(:<<).with(source)
+ expect(index.sources).to receive(:<<).with(source)
index.append_source
end
it "creates the source with the index's offset" do
- ThinkingSphinx::ActiveRecord::SQLSource.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::SQLSource).to receive(:new).
with(model, hash_including(:offset => 17)).and_return(source)
index.append_source
end
it "returns the new source" do
- index.append_source.should == source
+ expect(index.append_source).to eq(source)
end
it "defaults to the model's primary key" do
- model.stub :primary_key => :sphinx_id
+ allow(model).to receive_messages :primary_key => :sphinx_id
- ThinkingSphinx::ActiveRecord::SQLSource.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::SQLSource).to receive(:new).
with(model, hash_including(:primary_key => :sphinx_id)).
and_return(source)
@@ -48,9 +52,9 @@
end
it "uses a custom column when set" do
- model.stub :primary_key => :sphinx_id
+ allow(model).to receive_messages :primary_key => :sphinx_id
- ThinkingSphinx::ActiveRecord::SQLSource.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::SQLSource).to receive(:new).
with(model, hash_including(:primary_key => :custom_sphinx_id)).
and_return(source)
@@ -60,9 +64,9 @@
end
it "defaults to id if no primary key is set" do
- model.stub :primary_key => nil
+ allow(model).to receive_messages :primary_key => nil
- ThinkingSphinx::ActiveRecord::SQLSource.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::SQLSource).to receive(:new).
with(model, hash_including(:primary_key => :id)).
and_return(source)
@@ -72,12 +76,12 @@
describe '#delta?' do
it "defaults to false" do
- index.should_not be_delta
+ expect(index).not_to be_delta
end
it "reflects the delta? option" do
index = ThinkingSphinx::ActiveRecord::Index.new :user, :delta? => true
- index.should be_delta
+ expect(index).to be_delta
end
end
@@ -88,16 +92,16 @@
index = ThinkingSphinx::ActiveRecord::Index.new :user,
:delta_processor => processor_class
- index.delta_processor.should == processor
+ expect(index.delta_processor).to eq(processor)
end
end
describe '#document_id_for_key' do
it "calculates the document id based on offset and number of indices" do
- config.stub_chain(:indices, :count).and_return(5)
- config.stub :next_offset => 7
+ allow(config).to receive_message_chain(:indices, :count).and_return(5)
+ allow(config).to receive_messages :next_offset => 7
- index.document_id_for_key(123).should == 622
+ expect(index.document_id_for_key(123)).to eq(622)
end
end
@@ -109,14 +113,14 @@
end
it "interprets the definition block" do
- ThinkingSphinx::ActiveRecord::Interpreter.should_receive(:translate!).
+ expect(ThinkingSphinx::ActiveRecord::Interpreter).to receive(:translate!).
with(index, block)
index.interpret_definition!
end
it "only interprets the definition block once" do
- ThinkingSphinx::ActiveRecord::Interpreter.should_receive(:translate!).
+ expect(ThinkingSphinx::ActiveRecord::Interpreter).to receive(:translate!).
once
index.interpret_definition!
@@ -128,13 +132,13 @@
let(:model) { double('model') }
it "translates symbol references to model class" do
- ActiveSupport::Inflector.stub(:constantize => model)
+ allow(ActiveSupport::Inflector).to receive_messages(:constantize => model)
- index.model.should == model
+ expect(index.model).to eq(model)
end
it "memoizes the result" do
- ActiveSupport::Inflector.should_receive(:constantize).with('User').once.
+ expect(ActiveSupport::Inflector).to receive(:constantize).with('User').once.
and_return(model)
index.model
@@ -145,7 +149,7 @@
describe '#morphology' do
context 'with a render' do
before :each do
- FileUtils.stub :mkdir_p => true
+ allow(FileUtils).to receive_messages :mkdir_p => true
end
it "defaults to nil" do
@@ -154,7 +158,7 @@
rescue Riddle::Configuration::ConfigurationError
end
- index.morphology.should be_nil
+ expect(index.morphology).to be_nil
end
it "reads from the settings file if provided" do
@@ -165,7 +169,7 @@
rescue Riddle::Configuration::ConfigurationError
end
- index.morphology.should == 'stem_en'
+ expect(index.morphology).to eq('stem_en')
end
end
end
@@ -173,26 +177,26 @@
describe '#name' do
it "uses the core suffix by default" do
index = ThinkingSphinx::ActiveRecord::Index.new :user
- index.name.should == 'user_core'
+ expect(index.name).to eq('user_core')
end
it "uses the delta suffix when delta? is true" do
index = ThinkingSphinx::ActiveRecord::Index.new :user, :delta? => true
- index.name.should == 'user_delta'
+ expect(index.name).to eq('user_delta')
end
end
describe '#offset' do
before :each do
- config.stub :next_offset => 4
+ allow(config).to receive_messages :next_offset => 4
end
it "uses the next offset value from the configuration" do
- index.offset.should == 4
+ expect(index.offset).to eq(4)
end
it "uses the reference to get a unique offset" do
- config.should_receive(:next_offset).with(:user).and_return(2)
+ expect(config).to receive(:next_offset).with(:user).and_return(2)
index.offset
end
@@ -200,11 +204,11 @@
describe '#render' do
before :each do
- FileUtils.stub :mkdir_p => true
+ allow(FileUtils).to receive_messages :mkdir_p => true
end
it "interprets the provided definition" do
- index.should_receive(:interpret_definition!).at_least(:once)
+ expect(index).to receive(:interpret_definition!).at_least(:once)
begin
index.render
diff --git a/spec/thinking_sphinx/active_record/interpreter_spec.rb b/spec/thinking_sphinx/active_record/interpreter_spec.rb
index 3b497ff74..68d61f19e 100644
--- a/spec/thinking_sphinx/active_record/interpreter_spec.rb
+++ b/spec/thinking_sphinx/active_record/interpreter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Interpreter do
@@ -13,23 +15,28 @@
let(:block) { Proc.new { } }
before :each do
- ThinkingSphinx::ActiveRecord::SQLSource.stub! :new => source
- source.stub :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
let(:instance) { double('interpreter', :translate! => true) }
it "creates a new interpreter instance with the given block and index" do
- ThinkingSphinx::ActiveRecord::Interpreter.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::Interpreter).to receive(:new).
with(index, block).and_return(instance)
ThinkingSphinx::ActiveRecord::Interpreter.translate! index, block
end
it "calls translate! on the instance" do
- ThinkingSphinx::ActiveRecord::Interpreter.stub!(:new => instance)
- instance.should_receive(:translate!)
+ allow(ThinkingSphinx::ActiveRecord::Interpreter).to receive_messages(:new => instance)
+ expect(instance).to receive(:translate!)
ThinkingSphinx::ActiveRecord::Interpreter.translate! index, block
end
@@ -37,13 +44,13 @@
describe '#group_by' do
it "adds a source to the index" do
- index.should_receive(:append_source).and_return(source)
+ expect(index).to receive(:append_source).and_return(source)
instance.group_by 'lat'
end
it "only adds a single source for the given context" do
- index.should_receive(:append_source).once.and_return(source)
+ expect(index).to receive(:append_source).once.and_return(source)
instance.group_by 'lat'
instance.group_by 'lng'
@@ -52,7 +59,7 @@
it "appends a new grouping statement to the source" do
instance.group_by 'lat'
- source.groupings.should include('lat')
+ expect(source.groupings).to include('lat')
end
end
@@ -61,48 +68,46 @@
let(:attribute) { double('attribute') }
before :each do
- ThinkingSphinx::ActiveRecord::Attribute.stub! :new => attribute
+ allow(ThinkingSphinx::ActiveRecord::Attribute).to receive_messages :new => attribute
end
it "adds a source to the index" do
- index.should_receive(:append_source).and_return(source)
+ expect(index).to receive(:append_source).and_return(source)
instance.has column
end
it "only adds a single source for the given context" do
- index.should_receive(:append_source).once.and_return(source)
+ expect(index).to receive(:append_source).once.and_return(source)
instance.has column
instance.has column
end
it "creates a new attribute with the provided column" do
- ThinkingSphinx::ActiveRecord::Attribute.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::Attribute).to receive(:new).
with(model, column, {}).and_return(attribute)
instance.has column
end
it "passes through options to the attribute" do
- ThinkingSphinx::ActiveRecord::Attribute.should_receive(:new).
- with(model, column, :as => :other_name).and_return(attribute)
+ expect(ThinkingSphinx::ActiveRecord::Attribute).to receive(:new).
+ with(model, column, { :as => :other_name }).and_return(attribute)
instance.has column, :as => :other_name
end
it "adds an attribute to the source" do
- instance.has column
+ expect(source).to receive(:add_attribute).with(attribute)
- source.attributes.should 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
- source.attributes.select { |saved_attribute|
- saved_attribute == attribute
- }.length.should == 2
+ instance.has column, column
end
end
@@ -111,48 +116,46 @@
let(:field) { double('field') }
before :each do
- ThinkingSphinx::ActiveRecord::Field.stub! :new => field
+ allow(ThinkingSphinx::ActiveRecord::Field).to receive_messages :new => field
end
it "adds a source to the index" do
- index.should_receive(:append_source).and_return(source)
+ expect(index).to receive(:append_source).and_return(source)
instance.indexes column
end
it "only adds a single source for the given context" do
- index.should_receive(:append_source).once.and_return(source)
+ expect(index).to receive(:append_source).once.and_return(source)
instance.indexes column
instance.indexes column
end
it "creates a new field with the provided column" do
- ThinkingSphinx::ActiveRecord::Field.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::Field).to receive(:new).
with(model, column, {}).and_return(field)
instance.indexes column
end
it "passes through options to the field" do
- ThinkingSphinx::ActiveRecord::Field.should_receive(:new).
- with(model, column, :as => :other_name).and_return(field)
+ expect(ThinkingSphinx::ActiveRecord::Field).to receive(:new).
+ with(model, column, { :as => :other_name }).and_return(field)
instance.indexes column, :as => :other_name
end
it "adds a field to the source" do
- instance.indexes column
+ expect(source).to receive(:add_field).with(field)
- source.fields.should 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
- source.fields.select { |saved_field|
- saved_field == field
- }.length.should == 2
+ instance.indexes column, column
end
end
@@ -161,24 +164,24 @@
let(:association) { double('association') }
before :each do
- ThinkingSphinx::ActiveRecord::Association.stub! :new => association
+ allow(ThinkingSphinx::ActiveRecord::Association).to receive_messages :new => association
end
it "adds a source to the index" do
- index.should_receive(:append_source).and_return(source)
+ expect(index).to receive(:append_source).and_return(source)
instance.join column
end
it "only adds a single source for the given context" do
- index.should_receive(:append_source).once.and_return(source)
+ expect(index).to receive(:append_source).once.and_return(source)
instance.join column
instance.join column
end
it "creates a new association with the provided column" do
- ThinkingSphinx::ActiveRecord::Association.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::Association).to receive(:new).
with(column).and_return(association)
instance.join column
@@ -187,15 +190,15 @@
it "adds an association to the source" do
instance.join column
- source.associations.should include(association)
+ expect(source.associations).to include(association)
end
it "adds multiple fields when passed multiple columns" do
instance.join column, column
- source.associations.select { |saved_assoc|
+ expect(source.associations.select { |saved_assoc|
saved_assoc == association
- }.length.should == 2
+ }.length).to eq(2)
end
end
@@ -203,15 +206,15 @@
let(:column) { double('column') }
before :each do
- ThinkingSphinx::ActiveRecord::Column.stub!(:new => column)
+ allow(ThinkingSphinx::ActiveRecord::Column).to receive_messages(:new => column)
end
it "returns a new column for the given method" do
- instance.id.should == column
+ expect(instance.id).to eq(column)
end
it "should initialise the column with the method name and arguments" do
- ThinkingSphinx::ActiveRecord::Column.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::Column).to receive(:new).
with(:users, :posts, :subject).and_return(column)
instance.users(:posts, :subject)
@@ -220,26 +223,26 @@
describe '#set_database' do
before :each do
- source.stub :set_database_settings => true
+ allow(source).to receive_messages :set_database_settings => true
stub_const 'ActiveRecord::Base',
- double(:configurations => {'other' => {:baz => :qux}})
+ double(:configurations => {'other' => {'baz' => 'qux'}})
end
it "sends through a hash if provided" do
- source.should_receive(:set_database_settings).with(:foo => :bar)
+ expect(source).to receive(:set_database_settings).with({ :foo => :bar })
instance.set_database :foo => :bar
end
it "finds the environment settings if given a string key" do
- source.should_receive(:set_database_settings).with(:baz => :qux)
+ expect(source).to receive(:set_database_settings).with({ :baz => 'qux' })
instance.set_database 'other'
end
it "finds the environment settings if given a symbol key" do
- source.should_receive(:set_database_settings).with(:baz => :qux)
+ expect(source).to receive(:set_database_settings).with({ :baz => 'qux' })
instance.set_database :other
end
@@ -247,19 +250,19 @@
describe '#set_property' do
before :each do
- index.class.stub :settings => [:morphology]
- source.class.stub :settings => [:mysql_ssl_cert]
+ allow(index.class).to receive_messages :settings => [:morphology]
+ allow(source.class).to receive_messages :settings => [:mysql_ssl_cert]
end
it 'saves other settings as index options' do
instance.set_property :field_weights => {:name => 10}
- index.options[:field_weights].should == {:name => 10}
+ expect(index.options[:field_weights]).to eq({:name => 10})
end
context 'index settings' do
it "sets the provided setting" do
- index.should_receive(:morphology=).with('stem_en')
+ expect(index).to receive(:morphology=).with('stem_en')
instance.set_property :morphology => 'stem_en'
end
@@ -267,24 +270,24 @@
context 'source settings' do
before :each do
- source.stub :mysql_ssl_cert= => true
+ allow(source).to receive_messages :mysql_ssl_cert= => true
end
it "adds a source to the index" do
- index.should_receive(:append_source).and_return(source)
+ expect(index).to receive(:append_source).and_return(source)
instance.set_property :mysql_ssl_cert => 'private.cert'
end
it "only adds a single source for the given context" do
- index.should_receive(:append_source).once.and_return(source)
+ expect(index).to receive(:append_source).once.and_return(source)
instance.set_property :mysql_ssl_cert => 'private.cert'
instance.set_property :mysql_ssl_cert => 'private.cert'
end
it "sets the provided setting" do
- source.should_receive(:mysql_ssl_cert=).with('private.cert')
+ expect(source).to receive(:mysql_ssl_cert=).with('private.cert')
instance.set_property :mysql_ssl_cert => 'private.cert'
end
@@ -298,20 +301,20 @@
}
interpreter = ThinkingSphinx::ActiveRecord::Interpreter.new index, block
- interpreter.translate!.
- should == interpreter.__id__
+ expect(interpreter.translate!).
+ to eq(interpreter.__id__)
end
end
describe '#where' do
it "adds a source to the index" do
- index.should_receive(:append_source).and_return(source)
+ expect(index).to receive(:append_source).and_return(source)
instance.where 'id > 100'
end
it "only adds a single source for the given context" do
- index.should_receive(:append_source).once.and_return(source)
+ expect(index).to receive(:append_source).once.and_return(source)
instance.where 'id > 100'
instance.where 'id < 150'
@@ -320,7 +323,7 @@
it "appends a new grouping statement to the source" do
instance.where 'id > 100'
- source.conditions.should include('id > 100')
+ expect(source.conditions).to include('id > 100')
end
end
end
diff --git a/spec/thinking_sphinx/active_record/polymorpher_spec.rb b/spec/thinking_sphinx/active_record/polymorpher_spec.rb
index 646c1c0ef..671c6e73d 100644
--- a/spec/thinking_sphinx/active_record/polymorpher_spec.rb
+++ b/spec/thinking_sphinx/active_record/polymorpher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Polymorpher do
@@ -23,29 +25,29 @@
let(:animal_reflection) { double 'Animal Reflection' }
before :each do
- ThinkingSphinx::ActiveRecord::FilterReflection.
- stub(:call).
+ allow(ThinkingSphinx::ActiveRecord::FilterReflection).
+ to receive(:call).
and_return(article_reflection, animal_reflection)
- model.stub(:reflect_on_association) do |name|
+ allow(model).to receive(:reflect_on_association) do |name|
name == :foo ? reflection : nil
end
if ActiveRecord::Reflection.respond_to?(:add_reflection)
- ActiveRecord::Reflection.stub :add_reflection
+ allow(ActiveRecord::Reflection).to receive :add_reflection
end
end
it "creates a new reflection for each class" do
- ThinkingSphinx::ActiveRecord::FilterReflection.
- unstub :call
+ allow(ThinkingSphinx::ActiveRecord::FilterReflection).
+ to receive(:call).and_call_original
- ThinkingSphinx::ActiveRecord::FilterReflection.
- should_receive(:call).
+ expect(ThinkingSphinx::ActiveRecord::FilterReflection).
+ to receive(:call).
with(reflection, :foo_article, 'Article').
and_return(article_reflection)
- ThinkingSphinx::ActiveRecord::FilterReflection.
- should_receive(:call).
+ expect(ThinkingSphinx::ActiveRecord::FilterReflection).
+ to receive(:call).
with(reflection, :foo_animal, 'Animal').
and_return(animal_reflection)
@@ -54,9 +56,9 @@
it "adds the new reflections to the end-of-stack model" do
if ActiveRecord::Reflection.respond_to?(:add_reflection)
- ActiveRecord::Reflection.should_receive(:add_reflection).
+ expect(ActiveRecord::Reflection).to receive(:add_reflection).
with(model, :foo_article, article_reflection)
- ActiveRecord::Reflection.should_receive(:add_reflection).
+ expect(ActiveRecord::Reflection).to receive(:add_reflection).
with(model, :foo_animal, animal_reflection)
polymorpher.morph!
@@ -69,14 +71,14 @@
end
it "rebases each field" do
- field.should_receive(:rebase).with([:a, :b, :foo],
+ expect(field).to receive(:rebase).with([:a, :b, :foo],
:to => [[:a, :b, :foo_article], [:a, :b, :foo_animal]])
polymorpher.morph!
end
it "rebases each attribute" do
- attribute.should_receive(:rebase).with([:a, :b, :foo],
+ expect(attribute).to receive(:rebase).with([:a, :b, :foo],
:to => [[:a, :b, :foo_article], [:a, :b, :foo_animal]])
polymorpher.morph!
diff --git a/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb b/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb
index 828fb3030..d5eb92067 100644
--- a/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb
+++ b/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::PropertySQLPresenter do
@@ -7,7 +9,7 @@
let(:path) { double :aggregate? => false, :model => model }
before :each do
- adapter.stub(:quote) { |column| column }
+ allow(adapter).to receive(:quote) { |column| column }
stub_const 'Joiner::Path', double(:new => path)
end
@@ -25,95 +27,95 @@
describe '#to_group' do
it "returns the column name as a string" do
- presenter.to_group.should == 'articles.title'
+ expect(presenter.to_group).to eq('articles.title')
end
it "gets the column's table alias from the associations object" do
- column.stub!(:__stack => [:users, :posts])
+ allow(column).to receive_messages(:__stack => [:users, :posts])
- associations.should_receive(:alias_for).with([:users, :posts]).
+ expect(associations).to receive(:alias_for).with([:users, :posts]).
and_return('posts')
presenter.to_group
end
it "returns nil if the property is an aggregate" do
- path.stub! :aggregate? => true
+ allow(path).to receive_messages :aggregate? => true
- presenter.to_group.should be_nil
+ expect(presenter.to_group).to be_nil
end
it "returns nil if the field is sourced via a separate query" do
- field.stub :source_type => 'query'
+ allow(field).to receive_messages :source_type => 'query'
- presenter.to_group.should be_nil
+ expect(presenter.to_group).to be_nil
end
end
describe '#to_select' do
it "returns the column name as a string" do
- presenter.to_select.should == 'articles.title AS title'
+ expect(presenter.to_select).to eq('articles.title AS title')
end
it "gets the column's table alias from the associations object" do
- column.stub!(:__stack => [:users, :posts])
+ allow(column).to receive_messages(:__stack => [:users, :posts])
- associations.should_receive(:alias_for).with([:users, :posts]).
+ expect(associations).to receive(:alias_for).with([:users, :posts]).
and_return('posts')
presenter.to_select
end
it "returns the column name with an alias when provided" do
- field.stub!(:name => :subject)
+ allow(field).to receive_messages(:name => :subject)
- presenter.to_select.should == 'articles.title AS subject'
+ expect(presenter.to_select).to eq('articles.title AS subject')
end
it "groups and concatenates aggregated columns" do
- adapter.stub :group_concatenate do |clause, separator|
+ allow(adapter).to receive :group_concatenate do |clause, separator|
"GROUP_CONCAT(#{clause} SEPARATOR '#{separator}')"
end
- path.stub! :aggregate? => true
+ allow(path).to receive_messages :aggregate? => true
- presenter.to_select.
- should == "GROUP_CONCAT(articles.title SEPARATOR ' ') AS title"
+ expect(presenter.to_select).
+ to eq("GROUP_CONCAT(articles.title SEPARATOR ' ') AS title")
end
it "concatenates multiple columns" do
- adapter.stub :concatenate do |clause, separator|
+ allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
- field.stub!(:columns => [column, column])
+ allow(field).to receive_messages(:columns => [column, column])
- presenter.to_select.
- should == "CONCAT_WS(' ', articles.title, articles.title) AS title"
+ expect(presenter.to_select).
+ to eq("CONCAT_WS(' ', articles.title, articles.title) AS title")
end
it "does not include columns that don't exist" do
- adapter.stub :concatenate do |clause, separator|
+ allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
- field.stub!(:columns => [column, double('column', :string? => false,
+ allow(field).to receive_messages(:columns => [column, double('column', :string? => false,
:__stack => [], :__name => 'body')])
- presenter.to_select.
- should == "CONCAT_WS(' ', articles.title) AS title"
+ expect(presenter.to_select).
+ to eq("CONCAT_WS(' ', articles.title) AS title")
end
it "returns nil for query sourced fields" do
- field.stub :source_type => :query
+ allow(field).to receive_messages :source_type => :query
- presenter.to_select.should be_nil
+ expect(presenter.to_select).to be_nil
end
it "returns nil for ranged query sourced fields" do
- field.stub :source_type => :ranged_query
+ allow(field).to receive_messages :source_type => :ranged_query
- presenter.to_select.should be_nil
+ expect(presenter.to_select).to be_nil
end
end
end
@@ -131,131 +133,131 @@
:__name => 'created_at') }
before :each do
- adapter.stub :cast_to_timestamp do |clause|
+ allow(adapter).to receive :cast_to_timestamp do |clause|
"UNIX_TIMESTAMP(#{clause})"
end
end
describe '#to_group' do
it "returns the column name as a string" do
- presenter.to_group.should == 'articles.created_at'
+ expect(presenter.to_group).to eq('articles.created_at')
end
it "gets the column's table alias from the associations object" do
- column.stub!(:__stack => [:users, :posts])
+ allow(column).to receive_messages(:__stack => [:users, :posts])
- associations.should_receive(:alias_for).with([:users, :posts]).
+ expect(associations).to receive(:alias_for).with([:users, :posts]).
and_return('posts')
presenter.to_group
end
it "returns nil if the column is a string" do
- column.stub!(:string? => true)
+ allow(column).to receive_messages(:string? => true)
- presenter.to_group.should be_nil
+ expect(presenter.to_group).to be_nil
end
it "returns nil if the property is an aggregate" do
- path.stub! :aggregate? => true
+ allow(path).to receive_messages :aggregate? => true
- presenter.to_group.should be_nil
+ expect(presenter.to_group).to be_nil
end
it "returns nil if the attribute is sourced via a separate query" do
- attribute.stub :source_type => 'query'
+ allow(attribute).to receive_messages :source_type => 'query'
- presenter.to_group.should be_nil
+ expect(presenter.to_group).to be_nil
end
end
describe '#to_select' do
it "returns the column name as a string" do
- presenter.to_select.should == 'articles.created_at AS created_at'
+ expect(presenter.to_select).to eq('articles.created_at AS created_at')
end
it "gets the column's table alias from the associations object" do
- column.stub!(:__stack => [:users, :posts])
+ allow(column).to receive_messages(:__stack => [:users, :posts])
- associations.should_receive(:alias_for).with([:users, :posts]).
+ expect(associations).to receive(:alias_for).with([:users, :posts]).
and_return('posts')
presenter.to_select
end
it "returns the column name with an alias when provided" do
- attribute.stub!(:name => :creation_timestamp)
+ allow(attribute).to receive_messages(:name => :creation_timestamp)
- presenter.to_select.
- should == 'articles.created_at AS creation_timestamp'
+ expect(presenter.to_select).
+ to eq('articles.created_at AS creation_timestamp')
end
it "ensures datetime attributes are converted to timestamps" do
- attribute.stub :type => :timestamp
+ allow(attribute).to receive_messages :type => :timestamp
- presenter.to_select.
- should == 'UNIX_TIMESTAMP(articles.created_at) AS created_at'
+ expect(presenter.to_select).
+ to eq('UNIX_TIMESTAMP(articles.created_at) AS created_at')
end
it "does not include columns that don't exist" do
- adapter.stub :concatenate do |clause, separator|
+ allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
- adapter.stub :cast_to_string do |clause|
+ allow(adapter).to receive :cast_to_string do |clause|
"CAST(#{clause} AS varchar)"
end
- attribute.stub!(:columns => [column, double('column',
+ allow(attribute).to receive_messages(:columns => [column, double('column',
:string? => false, :__stack => [], :__name => 'updated_at')])
- presenter.to_select.should == "CONCAT_WS(',', CAST(articles.created_at AS varchar)) AS created_at"
+ expect(presenter.to_select).to eq("CONCAT_WS(',', CAST(articles.created_at AS varchar)) AS created_at")
end
it "casts and concatenates multiple columns for attributes" do
- adapter.stub :concatenate do |clause, separator|
+ allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
- adapter.stub :cast_to_string do |clause|
+ allow(adapter).to receive :cast_to_string do |clause|
"CAST(#{clause} AS varchar)"
end
- attribute.stub!(:columns => [column, column])
+ allow(attribute).to receive_messages(:columns => [column, column])
- presenter.to_select.should == "CONCAT_WS(',', CAST(articles.created_at AS varchar), CAST(articles.created_at AS varchar)) AS created_at"
+ expect(presenter.to_select).to eq("CONCAT_WS(',', CAST(articles.created_at AS varchar), CAST(articles.created_at AS varchar)) AS created_at")
end
it "double-casts and concatenates multiple columns for timestamp attributes" do
- adapter.stub :concatenate do |clause, separator|
+ allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
- adapter.stub :cast_to_string do |clause|
+ allow(adapter).to receive :cast_to_string do |clause|
"CAST(#{clause} AS varchar)"
end
- attribute.stub :columns => [column, column], :type => :timestamp
+ allow(attribute).to receive_messages :columns => [column, column], :type => :timestamp
- presenter.to_select.should == "CONCAT_WS(',', CAST(UNIX_TIMESTAMP(articles.created_at) AS varchar), CAST(UNIX_TIMESTAMP(articles.created_at) AS varchar)) AS created_at"
+ expect(presenter.to_select).to eq("CONCAT_WS(',', CAST(UNIX_TIMESTAMP(articles.created_at) AS varchar), CAST(UNIX_TIMESTAMP(articles.created_at) AS varchar)) AS created_at")
end
it "does not split attribute clause for timestamp casting if it looks like a function call" do
- column.stub :__name => "COALESCE(articles.updated_at, articles.created_at)", :string? => true
+ allow(column).to receive_messages :__name => "COALESCE(articles.updated_at, articles.created_at)", :string? => true
- attribute.stub :name => 'mod_date', :columns => [column],
+ allow(attribute).to receive_messages :name => 'mod_date', :columns => [column],
:type => :timestamp
- presenter.to_select.should == "UNIX_TIMESTAMP(COALESCE(articles.updated_at, articles.created_at)) AS mod_date"
+ expect(presenter.to_select).to eq("UNIX_TIMESTAMP(COALESCE(articles.updated_at, articles.created_at)) AS mod_date")
end
it "returns nil for query sourced attributes" do
- attribute.stub :source_type => :query
+ allow(attribute).to receive_messages :source_type => :query
- presenter.to_select.should be_nil
+ expect(presenter.to_select).to be_nil
end
it "returns nil for ranged query sourced attributes" do
- attribute.stub :source_type => :ranged_query
+ allow(attribute).to receive_messages :source_type => :ranged_query
- presenter.to_select.should be_nil
+ expect(presenter.to_select).to be_nil
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 c7f853b0f..1c80f046a 100644
--- a/spec/thinking_sphinx/active_record/sql_builder_spec.rb
+++ b/spec/thinking_sphinx/active_record/sql_builder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::SQLBuilder do
@@ -5,7 +7,7 @@
: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,
@@ -22,24 +24,24 @@
let(:builder) { ThinkingSphinx::ActiveRecord::SQLBuilder.new source }
before :each do
- ThinkingSphinx::Configuration.stub! :instance => config
- ThinkingSphinx::ActiveRecord::PropertySQLPresenter.stub! :new => presenter
- Joiner::Joins.stub! :new => associations
- relation.stub! :select => relation, :where => relation, :group => relation,
+ allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
+ allow(ThinkingSphinx::ActiveRecord::PropertySQLPresenter).to receive_messages :new => presenter
+ allow(Joiner::Joins).to receive_messages :new => associations
+ allow(relation).to receive_messages :select => relation, :where => relation, :group => relation,
:order => relation, :joins => relation, :to_sql => ''
- connection.stub!(:quote_column_name) { |column| "`#{column}`"}
+ allow(connection).to receive(:quote_column_name) { |column| "`#{column}`"}
end
describe 'sql_query' do
before :each do
- source.stub! :type => 'mysql'
+ allow(source).to receive_messages :type => 'mysql'
end
it "adds source associations to the joins of the query" do
source.associations << double('association',
:stack => [:user, :posts], :string? => false)
- associations.should_receive(:add_join_to).with([:user, :posts])
+ expect(associations).to receive(:add_join_to).with([:user, :posts])
builder.sql_query
end
@@ -48,25 +50,25 @@
source.associations << double('association',
:to_s => 'my string', :string? => true)
- relation.should_receive(:joins).with(['my string']).and_return(relation)
+ expect(relation).to receive(:joins).with(['my string']).and_return(relation)
builder.sql_query
end
context 'MySQL adapter' do
before :each do
- source.stub! :type => 'mysql'
+ allow(source).to receive_messages :type => 'mysql'
end
it "returns the relation's query" do
- relation.stub! :to_sql => 'SELECT * FROM people'
+ allow(relation).to receive_messages :to_sql => 'SELECT * FROM people'
- builder.sql_query.should == 'SELECT * FROM people'
+ expect(builder.sql_query).to eq('SELECT * FROM people')
end
it "ensures results aren't from cache" do
- relation.should_receive(:select) do |string|
- string.should match(/^SQL_NO_CACHE /)
+ expect(relation).to receive(:select) do |string|
+ expect(string).to match(/^SQL_NO_CACHE /)
relation
end
@@ -74,8 +76,8 @@
end
it "adds the document id using the offset and index count" do
- relation.should_receive(:select) do |string|
- string.should match(/`users`.`id` \* 5 \+ 3 AS `id`/)
+ expect(relation).to receive(:select) do |string|
+ expect(string).to match(/`users`.`id` \* 5 \+ 3 AS `id`/)
relation
end
@@ -85,8 +87,8 @@
it "adds each field to the SELECT clause" do
source.fields << double('field')
- relation.should_receive(:select) do |string|
- string.should match(/`name` AS `name`/)
+ expect(relation).to receive(:select) do |string|
+ expect(string).to match(/`name` AS `name`/)
relation
end
@@ -95,10 +97,10 @@
it "adds each attribute to the SELECT clause" do
source.attributes << double('attribute')
- presenter.stub!(:to_select => '`created_at` AS `created_at`')
+ allow(presenter).to receive_messages(:to_select => '`created_at` AS `created_at`')
- relation.should_receive(:select) do |string|
- string.should match(/`created_at` AS `created_at`/)
+ expect(relation).to receive(:select) do |string|
+ expect(string).to match(/`created_at` AS `created_at`/)
relation
end
@@ -106,8 +108,8 @@
end
it "limits results to a set range" do
- relation.should_receive(:where) do |string|
- string.should match(/`users`.`id` BETWEEN \$start AND \$end/)
+ expect(relation).to receive(:where) do |string|
+ expect(string).to match(/`users`.`id` BETWEEN \$start AND \$end/)
relation
end
@@ -115,10 +117,10 @@
end
it "shouldn't limit results to a range if ranges are disabled" do
- source.stub! :disable_range? => true
+ allow(source).to receive_messages :disable_range? => true
- relation.should_receive(:where) do |string|
- string.should_not match(/`users`.`id` BETWEEN \$start AND \$end/)
+ expect(relation).to receive(:where) do |string|
+ expect(string).not_to match(/`users`.`id` BETWEEN \$start AND \$end/)
relation
end
@@ -128,8 +130,8 @@
it "adds source conditions" do
source.conditions << 'created_at > NOW()'
- relation.should_receive(:where) do |string|
- string.should match(/created_at > NOW()/)
+ expect(relation).to receive(:where) do |string|
+ expect(string).to match(/created_at > NOW()/)
relation
end
@@ -137,8 +139,8 @@
end
it "groups by the primary key" do
- relation.should_receive(:group) do |string|
- string.should match(/`users`.`id`/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/`users`.`id`/)
relation
end
@@ -148,8 +150,8 @@
it "groups each field" do
source.fields << double('field')
- relation.should_receive(:group) do |string|
- string.should match(/`name`/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/`name`/)
relation
end
@@ -158,10 +160,10 @@
it "groups each attribute" do
source.attributes << double('attribute')
- presenter.stub!(:to_group => '`created_at`')
+ allow(presenter).to receive_messages(:to_group => '`created_at`')
- relation.should_receive(:group) do |string|
- string.should match(/`created_at`/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/`created_at`/)
relation
end
@@ -171,8 +173,8 @@
it "groups by source groupings" do
source.groupings << '`latitude`'
- relation.should_receive(:group) do |string|
- string.should match(/`latitude`/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/`latitude`/)
relation
end
@@ -180,7 +182,7 @@
end
it "orders by NULL" do
- relation.should_receive(:order).with('NULL').and_return(relation)
+ expect(relation).to receive(:order).with('NULL').and_return(relation)
builder.sql_query
end
@@ -188,13 +190,13 @@
context 'STI model' do
before :each do
model.column_names << 'type'
- model.stub! :descends_from_active_record? => false
- model.stub! :store_full_sti_class => true
+ allow(model).to receive_messages :descends_from_active_record? => false
+ allow(model).to receive_messages :store_full_sti_class => true
end
it "groups by the inheritance column" do
- relation.should_receive(:group) do |string|
- string.should match(/`users`.`type`/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/`users`.`type`/)
relation
end
@@ -204,12 +206,12 @@
context 'with a custom inheritance column' do
before :each do
model.column_names << 'custom_type'
- model.stub :inheritance_column => 'custom_type'
+ allow(model).to receive_messages :inheritance_column => 'custom_type'
end
it "groups by the right column" do
- relation.should_receive(:group) do |string|
- string.should match(/`users`.`custom_type`/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/`users`.`custom_type`/)
relation
end
@@ -222,14 +224,14 @@
let(:processor) { double('processor') }
before :each do
- source.stub! :delta_processor => processor
- source.stub! :delta? => true
+ allow(source).to receive_messages :delta_processor => processor
+ allow(source).to receive_messages :delta? => true
end
it "filters by the provided clause" do
- processor.should_receive(:clause).with(true).and_return('`delta` = 1')
- relation.should_receive(:where) do |string|
- string.should match(/`delta` = 1/)
+ expect(processor).to receive(:clause).with(true).and_return('`delta` = 1')
+ expect(relation).to receive(:where) do |string|
+ expect(string).to match(/`delta` = 1/)
relation
end
@@ -243,20 +245,20 @@
:to_group => '"name"') }
before :each do
- source.stub! :type => 'pgsql'
- model.stub! :quoted_table_name => '"users"'
- connection.stub!(:quote_column_name) { |column| "\"#{column}\""}
+ allow(source).to receive_messages :type => 'pgsql'
+ allow(model).to receive_messages :quoted_table_name => '"users"'
+ allow(connection).to receive(:quote_column_name) { |column| "\"#{column}\""}
end
it "returns the relation's query" do
- relation.stub! :to_sql => 'SELECT * FROM people'
+ allow(relation).to receive_messages :to_sql => 'SELECT * FROM people'
- builder.sql_query.should == 'SELECT * FROM people'
+ expect(builder.sql_query).to eq('SELECT * FROM people')
end
it "adds the document id using the offset and index count" do
- relation.should_receive(:select) do |string|
- string.should match(/"users"."id" \* 5 \+ 3 AS "id"/)
+ expect(relation).to receive(:select) do |string|
+ expect(string).to match(/"users"."id" \* 5 \+ 3 AS "id"/)
relation
end
@@ -266,8 +268,8 @@
it "adds each field to the SELECT clause" do
source.fields << double('field')
- relation.should_receive(:select) do |string|
- string.should match(/"name" AS "name"/)
+ expect(relation).to receive(:select) do |string|
+ expect(string).to match(/"name" AS "name"/)
relation
end
@@ -276,10 +278,10 @@
it "adds each attribute to the SELECT clause" do
source.attributes << double('attribute')
- presenter.stub!(:to_select => '"created_at" AS "created_at"')
+ allow(presenter).to receive_messages(:to_select => '"created_at" AS "created_at"')
- relation.should_receive(:select) do |string|
- string.should match(/"created_at" AS "created_at"/)
+ expect(relation).to receive(:select) do |string|
+ expect(string).to match(/"created_at" AS "created_at"/)
relation
end
@@ -287,8 +289,8 @@
end
it "limits results to a set range" do
- relation.should_receive(:where) do |string|
- string.should match(/"users"."id" BETWEEN \$start AND \$end/)
+ expect(relation).to receive(:where) do |string|
+ expect(string).to match(/"users"."id" BETWEEN \$start AND \$end/)
relation
end
@@ -296,10 +298,10 @@
end
it "shouldn't limit results to a range if ranges are disabled" do
- source.stub! :disable_range? => true
+ allow(source).to receive_messages :disable_range? => true
- relation.should_receive(:where) do |string|
- string.should_not match(/"users"."id" BETWEEN \$start AND \$end/)
+ expect(relation).to receive(:where) do |string|
+ expect(string).not_to match(/"users"."id" BETWEEN \$start AND \$end/)
relation
end
@@ -309,8 +311,8 @@
it "adds source conditions" do
source.conditions << 'created_at > NOW()'
- relation.should_receive(:where) do |string|
- string.should match(/created_at > NOW()/)
+ expect(relation).to receive(:where) do |string|
+ expect(string).to match(/created_at > NOW()/)
relation
end
@@ -318,8 +320,8 @@
end
it "groups by the primary key" do
- relation.should_receive(:group) do |string|
- string.should match(/"users"."id"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"users"."id"/)
relation
end
@@ -329,8 +331,8 @@
it "groups each field" do
source.fields << double('field')
- relation.should_receive(:group) do |string|
- string.should match(/"name"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"name"/)
relation
end
@@ -339,10 +341,10 @@
it "groups each attribute" do
source.attributes << double('attribute')
- presenter.stub!(:to_group => '"created_at"')
+ allow(presenter).to receive_messages(:to_group => '"created_at"')
- relation.should_receive(:group) do |string|
- string.should match(/"created_at"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"created_at"/)
relation
end
@@ -352,8 +354,8 @@
it "groups by source groupings" do
source.groupings << '"latitude"'
- relation.should_receive(:group) do |string|
- string.should match(/"latitude"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"latitude"/)
relation
end
@@ -361,7 +363,7 @@
end
it "has no ORDER clause" do
- relation.should_not_receive(:order)
+ expect(relation).not_to receive(:order)
builder.sql_query
end
@@ -372,8 +374,57 @@
end
it "groups by the primary key" do
- relation.should_receive(:group) do |string|
- string.should match(/"users"."id"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"users"."id"/)
+ relation
+ end
+
+ builder.sql_query
+ end
+
+ it "does not group by fields" do
+ source.fields << double('field')
+
+ expect(relation).to receive(:group) do |string|
+ expect(string).not_to match(/"name"/)
+ relation
+ end
+
+ builder.sql_query
+ end
+
+ it "does not group by attributes" do
+ source.attributes << double('attribute')
+ allow(presenter).to receive_messages(:to_group => '"created_at"')
+
+ expect(relation).to receive(:group) do |string|
+ expect(string).not_to match(/"created_at"/)
+ relation
+ end
+
+ builder.sql_query
+ end
+
+ it "groups by source groupings" do
+ source.groupings << '"latitude"'
+
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"latitude"/)
+ relation
+ end
+
+ builder.sql_query
+ end
+ end
+
+ context 'group by shortcut in global configuration' do
+ before :each do
+ config.settings['minimal_group_by'] = true
+ end
+
+ it "groups by the primary key" do
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"users"."id"/)
relation
end
@@ -383,8 +434,8 @@
it "does not group by fields" do
source.fields << double('field')
- relation.should_receive(:group) do |string|
- string.should_not match(/"name"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).not_to match(/"name"/)
relation
end
@@ -393,10 +444,10 @@
it "does not group by attributes" do
source.attributes << double('attribute')
- presenter.stub!(:to_group => '"created_at"')
+ allow(presenter).to receive_messages(:to_group => '"created_at"')
- relation.should_receive(:group) do |string|
- string.should_not match(/"created_at"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).not_to match(/"created_at"/)
relation
end
@@ -406,8 +457,8 @@
it "groups by source groupings" do
source.groupings << '"latitude"'
- relation.should_receive(:group) do |string|
- string.should match(/"latitude"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"latitude"/)
relation
end
@@ -418,13 +469,13 @@
context 'STI model' do
before :each do
model.column_names << 'type'
- model.stub! :descends_from_active_record? => false
- model.stub! :store_full_sti_class => true
+ allow(model).to receive_messages :descends_from_active_record? => false
+ allow(model).to receive_messages :store_full_sti_class => true
end
it "groups by the inheritance column" do
- relation.should_receive(:group) do |string|
- string.should match(/"users"."type"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"users"."type"/)
relation
end
@@ -434,12 +485,12 @@
context 'with a custom inheritance column' do
before :each do
model.column_names << 'custom_type'
- model.stub :inheritance_column => 'custom_type'
+ allow(model).to receive_messages :inheritance_column => 'custom_type'
end
it "groups by the right column" do
- relation.should_receive(:group) do |string|
- string.should match(/"users"."custom_type"/)
+ expect(relation).to receive(:group) do |string|
+ expect(string).to match(/"users"."custom_type"/)
relation
end
@@ -452,14 +503,14 @@
let(:processor) { double('processor') }
before :each do
- source.stub! :delta_processor => processor
- source.stub! :delta? => true
+ allow(source).to receive_messages :delta_processor => processor
+ allow(source).to receive_messages :delta? => true
end
it "filters by the provided clause" do
- processor.should_receive(:clause).with(true).and_return('"delta" = 1')
- relation.should_receive(:where) do |string|
- string.should match(/"delta" = 1/)
+ expect(processor).to receive(:clause).with(true).and_return('"delta" = 1')
+ expect(relation).to receive(:where) do |string|
+ expect(string).to match(/"delta" = 1/)
relation
end
@@ -469,82 +520,56 @@
end
end
- describe 'sql_query_info' do
- it "filters on the reversed document id" do
- relation.should_receive(:where).
- with("`users`.`id` = ($id - #{source.offset}) / #{indices.count}").
- and_return(relation)
-
- builder.sql_query_info
- end
-
- it "returns the generated SQL query" do
- relation.stub(:to_sql).and_return('SELECT * FROM people WHERE id = $id')
-
- builder.sql_query_info.should == 'SELECT * FROM people WHERE id = $id'
- end
- end
-
- describe 'sql_query_post_index' do
+ describe 'sql_query_pre' 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')
+ before :each do
+ allow(source).to receive_messages :options => {}, :delta_processor => nil, :delta? => false
+ allow(adapter).to receive_messages :utf8_query_pre => ['SET UTF8']
end
- end
- describe 'sql_query_pre' 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
+ allow(source).to receive_messages :delta_processor => processor
- before :each do
- source.stub :options => {}, :delta_processor => nil, :delta? => false
- adapter.stub :utf8_query_pre => ['SET UTF8']
+ expect(builder.sql_query_pre).to 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')
+ expect(builder.sql_query_pre).not_to include('RESET DELTAS')
end
it "does not add a reset query if this is a delta source" do
- source.stub :delta_processor => processor
- source.stub :delta? => true
+ allow(source).to receive_messages :delta_processor => processor
+ allow(source).to receive_messages :delta? => true
- builder.sql_query_pre.should_not include('RESET DELTAS')
+ expect(builder.sql_query_pre).not_to include('RESET DELTAS')
end
it "sets the group_concat_max_len value if set" do
source.options[:group_concat_max_len] = 123
- builder.sql_query_pre.
- should include('SET SESSION group_concat_max_len = 123')
+ expect(builder.sql_query_pre).
+ to include('SET SESSION group_concat_max_len = 123')
end
it "does not set the group_concat_max_len if not provided" do
source.options[:group_concat_max_len] = nil
- builder.sql_query_pre.select { |sql|
+ expect(builder.sql_query_pre.select { |sql|
sql[/SET SESSION group_concat_max_len/]
- }.should be_empty
+ }).to be_empty
end
it "sets the connection to use UTF-8 if required" do
source.options[:utf8?] = true
- builder.sql_query_pre.should include('SET UTF8')
+ expect(builder.sql_query_pre).to include('SET UTF8')
end
it "does not set the connection to use UTF-8 if not required" do
source.options[:utf8?] = false
- builder.sql_query_pre.should_not include('SET UTF8')
+ expect(builder.sql_query_pre).not_to include('SET UTF8')
end
it "adds a time-zone query by default" do
@@ -560,26 +585,26 @@
describe 'sql_query_range' do
before :each do
- adapter.stub!(:convert_nulls) { |string, default|
+ allow(adapter).to receive(:convert_nulls) { |string, default|
"ISNULL(#{string}, #{default})"
}
end
it "returns the relation's query" do
- relation.stub! :to_sql => 'SELECT * FROM people'
+ allow(relation).to receive_messages :to_sql => 'SELECT * FROM people'
- builder.sql_query_range.should == 'SELECT * FROM people'
+ expect(builder.sql_query_range).to eq('SELECT * FROM people')
end
it "returns nil if ranges are disabled" do
- source.stub! :disable_range? => true
+ allow(source).to receive_messages :disable_range? => true
- builder.sql_query_range.should be_nil
+ expect(builder.sql_query_range).to be_nil
end
it "selects the minimum primary key value, allowing for nulls" do
- relation.should_receive(:select) do |string|
- string.should match(/ISNULL\(MIN\(`users`.`id`\), 1\)/)
+ expect(relation).to receive(:select) do |string|
+ expect(string).to match(/ISNULL\(MIN\(`users`.`id`\), 1\)/)
relation
end
@@ -587,8 +612,8 @@
end
it "selects the maximum primary key value, allowing for nulls" do
- relation.should_receive(:select) do |string|
- string.should match(/ISNULL\(MAX\(`users`.`id`\), 1\)/)
+ expect(relation).to receive(:select) do |string|
+ expect(string).to match(/ISNULL\(MAX\(`users`.`id`\), 1\)/)
relation
end
@@ -596,8 +621,8 @@
end
it "shouldn't limit results to a range" do
- relation.should_receive(:where) do |string|
- string.should_not match(/`users`.`id` BETWEEN \$start AND \$end/)
+ expect(relation).to receive(:where) do |string|
+ expect(string).not_to match(/`users`.`id` BETWEEN \$start AND \$end/)
relation
end
@@ -607,8 +632,8 @@
it "does not add source conditions" do
source.conditions << 'created_at > NOW()'
- relation.should_receive(:where) do |string|
- string.should_not match(/created_at > NOW()/)
+ expect(relation).to receive(:where) do |string|
+ expect(string).not_to match(/created_at > NOW()/)
relation
end
@@ -619,14 +644,14 @@
let(:processor) { double('processor') }
before :each do
- source.stub! :delta_processor => processor
- source.stub! :delta? => true
+ allow(source).to receive_messages :delta_processor => processor
+ allow(source).to receive_messages :delta? => true
end
it "filters by the provided clause" do
- processor.should_receive(:clause).with(true).and_return('`delta` = 1')
- relation.should_receive(:where) do |string|
- string.should match(/`delta` = 1/)
+ expect(processor).to receive(:clause).with(true).and_return('`delta` = 1')
+ expect(relation).to receive(:where) do |string|
+ expect(string).to match(/`delta` = 1/)
relation
end
diff --git a/spec/thinking_sphinx/active_record/sql_source_spec.rb b/spec/thinking_sphinx/active_record/sql_source_spec.rb
index b131033a6..800e4cd29 100644
--- a/spec/thinking_sphinx/active_record/sql_source_spec.rb
+++ b/spec/thinking_sphinx/active_record/sql_source_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::SQLSource do
@@ -9,42 +11,80 @@
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
- ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter.
- stub!(:=== => true)
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- stub!(:adapter_for => adapter)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter).
+ to receive_messages(:=== => true)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters).
+ to receive_messages(:adapter_for => adapter)
end
describe '#adapter' do
it "returns a database adapter for the model" do
- ThinkingSphinx::ActiveRecord::DatabaseAdapters.
- should_receive(:adapter_for).with(model).and_return(adapter)
+ expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters).
+ to receive(:adapter_for).with(model).and_return(adapter)
+
+ expect(source.adapter).to eq(adapter)
+ 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
- source.adapter.should == adapter
+ 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
- source.attributes.collect(&:name).should include('sphinx_internal_id')
+ expect(source.attributes.collect(&:name)).to include('sphinx_internal_id')
end
it "has the class name attribute by default" do
- source.attributes.collect(&:name).should include('sphinx_internal_class')
+ expect(source.attributes.collect(&:name)).to include('sphinx_internal_class')
end
it "has the internal deleted attribute by default" do
- source.attributes.collect(&:name).should include('sphinx_deleted')
+ expect(source.attributes.collect(&:name)).to include('sphinx_deleted')
end
it "marks the internal class attribute as a facet" do
- source.attributes.detect { |attribute|
+ expect(source.attributes.detect { |attribute|
attribute.name == 'sphinx_internal_class'
- }.options[:facet].should be_true
+ }.options[:facet]).to be_truthy
end
end
@@ -53,27 +93,29 @@
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
- processor_class.should_receive(:try).with(:new, adapter, {}).
+ expect(processor_class).to receive(:try).with(:new, adapter, {}).
and_return processor
source.delta_processor
end
it "returns the given processor" do
- source.delta_processor.should == processor
+ expect(source.delta_processor).to eq(processor)
end
it "passes given options to the processor" do
- processor_class.should_receive(:try).with(:new, adapter, {:opt_key => :opt_value})
+ expect(processor_class).to receive(:try).with(:new, adapter, {:opt_key => :opt_value})
source_with_options.delta_processor
end
end
@@ -81,77 +123,99 @@
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
+ expect(source).to be_a_delta
end
end
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
+ expect(source.disable_range?).to be_truthy
end
end
describe '#fields' do
it "has the internal class field by default" do
- source.fields.collect(&:name).
- should include('sphinx_internal_class_name')
+ expect(source.fields.collect(&:name)).
+ to include('sphinx_internal_class_name')
end
it "sets the sphinx class field to use a string of the class name" do
- source.fields.detect { |field|
+ expect(source.fields.detect { |field|
field.name == 'sphinx_internal_class_name'
- }.columns.first.__name.should == "'User'"
+ }.columns.first.__name).to eq("'User'")
end
it "uses the inheritance column if it exists for the sphinx class field" do
- adapter.stub :quoted_table_name => '"users"', :quote => '"type"'
- adapter.stub(:convert_blank) { |clause, default|
+ allow(adapter).to receive_messages :quoted_table_name => '"users"', :quote => '"type"'
+ allow(adapter).to receive(:convert_blank) { |clause, default|
"coalesce(nullif(#{clause}, ''), #{default})"
}
- model.stub :column_names => ['type'], :sti_name => 'User'
+ allow(model).to receive_messages :column_names => ['type'], :sti_name => 'User'
- source.fields.detect { |field|
+ expect(source.fields.detect { |field|
field.name == 'sphinx_internal_class_name'
- }.columns.first.__name.
- should == "coalesce(nullif(\"users\".\"type\", ''), 'User')"
+ }.columns.first.__name).
+ to eq("coalesce(nullif(\"users\".\"type\", ''), 'User')")
end
end
describe '#name' do
it "defaults to the model name downcased with the given position" do
- source.name.should == 'user_3'
+ expect(source.name).to eq('user_3')
end
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'
+ expect(source.name).to eq('people_2')
end
end
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
+ expect(source.offset).to eq(12)
end
end
describe '#options' do
it "defaults to having utf8? set to false" do
- source.options[:utf8?].should be_false
+ expect(source.options[:utf8?]).to be_falsey
end
it "sets utf8? to true if the database encoding is utf8" do
db_config[:encoding] = 'utf8'
- source.options[:utf8?].should be_true
+ expect(source.options[:utf8?]).to be_truthy
+ end
+
+ it "sets utf8? to true if the database encoding starts with utf8" do
+ db_config[:encoding] = 'utf8mb4'
+
+ expect(source.options[:utf8?]).to be_truthy
+ 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
@@ -164,50 +228,34 @@
let(:template) { double('template', :apply => true) }
before :each do
- ThinkingSphinx::ActiveRecord::SQLBuilder.stub! :new => builder
- ThinkingSphinx::ActiveRecord::Attribute::SphinxPresenter.stub :new => presenter
- ThinkingSphinx::ActiveRecord::SQLSource::Template.stub :new => template
- ThinkingSphinx::Configuration.stub :instance => config
+ allow(ThinkingSphinx::ActiveRecord::SQLBuilder).to receive_messages :new => builder
+ allow(ThinkingSphinx::ActiveRecord::Attribute::SphinxPresenter).to receive_messages :new => presenter
+ allow(ThinkingSphinx::ActiveRecord::SQLSource::Template).to receive_messages :new => template
+ allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
it "uses the builder's sql_query value" do
- builder.stub! :sql_query => 'select * from table'
+ allow(builder).to receive_messages :sql_query => 'select * from table'
source.render
- source.sql_query.should == 'select * from table'
+ expect(source.sql_query).to eq('select * from table')
end
it "uses the builder's sql_query_range value" do
- builder.stub! :sql_query_range => 'select 0, 10 from table'
+ allow(builder).to receive_messages :sql_query_range => 'select 0, 10 from table'
source.render
- source.sql_query_range.should == 'select 0, 10 from table'
- end
-
- it "uses the builder's sql_query_info value" do
- builder.stub! :sql_query_info => 'select * from table where id = ?'
-
- source.render
-
- source.sql_query_info.should == 'select * from table where id = ?'
+ expect(source.sql_query_range).to eq('select 0, 10 from table')
end
it "appends the builder's sql_query_pre value" do
- builder.stub! :sql_query_pre => ['Change Setting']
+ allow(builder).to receive_messages :sql_query_pre => ['Change Setting']
source.render
- 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')
+ expect(source.sql_query_pre).to eq(['Change Setting'])
end
it "adds fields with attributes to sql_field_string" do
@@ -216,7 +264,7 @@
source.render
- source.sql_field_string.should include('title')
+ expect(source.sql_field_string).to include('title')
end
it "adds any joined or file fields" do
@@ -225,7 +273,7 @@
source.render
- source.sql_file_field.should include('title')
+ expect(source.sql_file_field).to include('title')
end
it "adds wordcounted fields to sql_field_str2wordcount" do
@@ -234,11 +282,11 @@
source.render
- source.sql_field_str2wordcount.should include('title')
+ expect(source.sql_field_str2wordcount).to include('title')
end
it "adds any joined fields" do
- ThinkingSphinx::ActiveRecord::PropertyQuery.stub(
+ allow(ThinkingSphinx::ActiveRecord::PropertyQuery).to receive_messages(
:new => double(:to_s => 'query for title')
)
source.fields << double('field', :name => 'title',
@@ -247,90 +295,99 @@
source.render
- source.sql_joined_field.should include('query for title')
+ expect(source.sql_joined_field).to include('query for title')
end
it "adds integer attributes to sql_attr_uint" do
source.attributes << double('attribute')
- presenter.stub :declaration => 'count', :collection_type => :uint
+ allow(presenter).to receive_messages :declaration => 'count', :collection_type => :uint
source.render
- source.sql_attr_uint.should include('count')
+ expect(source.sql_attr_uint).to include('count')
end
it "adds boolean attributes to sql_attr_bool" do
source.attributes << double('attribute')
- presenter.stub :declaration => 'published', :collection_type => :bool
+ allow(presenter).to receive_messages :declaration => 'published', :collection_type => :bool
source.render
- source.sql_attr_bool.should include('published')
+ expect(source.sql_attr_bool).to include('published')
end
it "adds string attributes to sql_attr_string" do
source.attributes << double('attribute')
- presenter.stub :declaration => 'name', :collection_type => :string
+ allow(presenter).to receive_messages :declaration => 'name', :collection_type => :string
source.render
- source.sql_attr_string.should include('name')
+ expect(source.sql_attr_string).to include('name')
end
- it "adds timestamp attributes to sql_attr_timestamp" do
+ it "adds timestamp attributes to sql_attr_uint" do
source.attributes << double('attribute')
- presenter.stub :declaration => 'created_at',
- :collection_type => :timestamp
+ allow(presenter).to receive_messages :declaration => 'created_at',
+ :collection_type => :uint
source.render
- source.sql_attr_timestamp.should include('created_at')
+ expect(source.sql_attr_uint).to include('created_at')
end
it "adds float attributes to sql_attr_float" do
source.attributes << double('attribute')
- presenter.stub :declaration => 'rating', :collection_type => :float
+ allow(presenter).to receive_messages :declaration => 'rating', :collection_type => :float
source.render
- source.sql_attr_float.should include('rating')
+ expect(source.sql_attr_float).to include('rating')
end
it "adds bigint attributes to sql_attr_bigint" do
source.attributes << double('attribute')
- presenter.stub :declaration => 'super_id', :collection_type => :bigint
+ allow(presenter).to receive_messages :declaration => 'super_id', :collection_type => :bigint
source.render
- source.sql_attr_bigint.should include('super_id')
+ expect(source.sql_attr_bigint).to include('super_id')
end
it "adds ordinal strings to sql_attr_str2ordinal" do
source.attributes << double('attribute')
- presenter.stub :declaration => 'name', :collection_type => :str2ordinal
+ allow(presenter).to receive_messages :declaration => 'name', :collection_type => :str2ordinal
source.render
- source.sql_attr_str2ordinal.should include('name')
+ expect(source.sql_attr_str2ordinal).to include('name')
end
it "adds multi-value attributes to sql_attr_multi" do
source.attributes << double('attribute')
- presenter.stub :declaration => 'uint tag_ids from field',
+ allow(presenter).to receive_messages :declaration => 'uint tag_ids from field',
:collection_type => :multi
source.render
- source.sql_attr_multi.should include('uint tag_ids from field')
+ expect(source.sql_attr_multi).to include('uint tag_ids from field')
end
it "adds word count attributes to sql_attr_str2wordcount" do
source.attributes << double('attribute')
- presenter.stub :declaration => 'name', :collection_type => :str2wordcount
+ allow(presenter).to receive_messages :declaration => 'name', :collection_type => :str2wordcount
+
+ source.render
+
+ expect(source.sql_attr_str2wordcount).to include('name')
+ end
+
+ it "adds json attributes to sql_attr_json" do
+ source.attributes << double('attribute')
+ allow(presenter).to receive_messages :declaration => 'json', :collection_type => :json
source.render
- source.sql_attr_str2wordcount.should include('name')
+ expect(source.sql_attr_json).to include('json')
end
it "adds relevant settings from thinking_sphinx.yml" do
@@ -339,7 +396,7 @@
source.render
- source.mysql_ssl_cert.should == 'foo.cert'
+ expect(source.mysql_ssl_cert).to eq('foo.cert')
end
end
@@ -347,84 +404,104 @@
it "sets the sql_host setting from the model's database settings" do
source.set_database_settings :host => '12.34.56.78'
- source.sql_host.should == '12.34.56.78'
+ expect(source.sql_host).to eq('12.34.56.78')
end
it "defaults sql_host to localhost if the model has no host" do
source.set_database_settings :host => nil
- source.sql_host.should == 'localhost'
+ expect(source.sql_host).to eq('localhost')
end
it "sets the sql_user setting from the model's database settings" do
source.set_database_settings :username => 'pat'
- source.sql_user.should == 'pat'
+ expect(source.sql_user).to eq('pat')
end
it "uses the user setting if username is not set in the model" do
source.set_database_settings :username => nil, :user => 'pat'
- source.sql_user.should == 'pat'
+ expect(source.sql_user).to eq('pat')
end
it "sets the sql_pass setting from the model's database settings" do
source.set_database_settings :password => 'swordfish'
- source.sql_pass.should == 'swordfish'
+ expect(source.sql_pass).to eq('swordfish')
end
it "escapes hashes in the password for sql_pass" do
source.set_database_settings :password => 'sword#fish'
- source.sql_pass.should == 'sword\#fish'
+ expect(source.sql_pass).to eq('sword\#fish')
end
it "sets the sql_db setting from the model's database settings" do
source.set_database_settings :database => 'rails_app'
- source.sql_db.should == 'rails_app'
+ expect(source.sql_db).to eq('rails_app')
end
it "sets the sql_port setting from the model's database settings" do
source.set_database_settings :port => 5432
- source.sql_port.should == 5432
+ expect(source.sql_port).to eq(5432)
end
it "sets the sql_sock setting from the model's database settings" do
source.set_database_settings :socket => '/unix/socket'
- source.sql_sock.should == '/unix/socket'
+ expect(source.sql_sock).to eq('/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'
+
+ expect(source.mysql_ssl_cert).to 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'
+
+ expect(source.mysql_ssl_key).to 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'
+
+ expect(source.mysql_ssl_ca).to eq '/path/to/ca.pem'
end
end
describe '#type' do
it "is mysql when using the MySQL Adapter" do
- ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter.
- stub!(:=== => true)
- ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter.
- stub!(:=== => false)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter).
+ to receive_messages(:=== => true)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter).
+ to receive_messages(:=== => false)
- source.type.should == 'mysql'
+ expect(source.type).to eq('mysql')
end
it "is pgsql when using the PostgreSQL Adapter" do
- ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter.
- stub!(:=== => false)
- ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter.
- stub!(:=== => true)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter).
+ to receive_messages(:=== => false)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter).
+ to receive_messages(:=== => true)
- source.type.should == 'pgsql'
+ expect(source.type).to eq('pgsql')
end
it "raises an exception for any other adapter" do
- ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter.
- stub!(:=== => false)
- ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter.
- stub!(:=== => false)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter).
+ to receive_messages(:=== => false)
+ allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter).
+ to receive_messages(:=== => false)
- lambda { source.type }.should raise_error
+ expect { source.type }.to raise_error(
+ ThinkingSphinx::UnknownDatabaseAdapter
+ )
end
end
end
diff --git a/spec/thinking_sphinx/attribute_types_spec.rb b/spec/thinking_sphinx/attribute_types_spec.rb
new file mode 100644
index 000000000..7c0c0c63b
--- /dev/null
+++ b/spec/thinking_sphinx/attribute_types_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::AttributeTypes do
+ let(:configuration) {
+ double('configuration', :configuration_file => 'sphinx.conf')
+ }
+
+ before :each do
+ allow(ThinkingSphinx::Configuration).to receive(:instance).
+ and_return(configuration)
+
+ allow(File).to receive(:exist?).with('sphinx.conf').and_return(true)
+ allow(File).to receive(:read).with('sphinx.conf').and_return(<<-CONF)
+index plain_index
+{
+ source = plain_source
+}
+
+source plain_source
+{
+ type = mysql
+ sql_attr_uint = customer_id
+ sql_attr_float = price
+ sql_attr_multi = uint comment_ids from field
+}
+
+index rt_index
+{
+ type = rt
+ rt_attr_uint = user_id
+ rt_attr_multi = comment_ids
+}
+ CONF
+ end
+
+ it 'returns an empty hash if no configuration file exists' do
+ allow(File).to receive(:exist?).with('sphinx.conf').and_return(false)
+
+ expect(ThinkingSphinx::AttributeTypes.new.call).to eq({})
+ end
+
+ it 'returns all known attributes' do
+ expect(ThinkingSphinx::AttributeTypes.new.call).to eq({
+ 'customer_id' => [:uint],
+ 'price' => [:float],
+ 'comment_ids' => [:uint],
+ 'user_id' => [:uint]
+ })
+ end
+end
diff --git a/spec/thinking_sphinx/commands/clear_real_time_spec.rb b/spec/thinking_sphinx/commands/clear_real_time_spec.rb
new file mode 100644
index 000000000..df20c5840
--- /dev/null
+++ b/spec/thinking_sphinx/commands/clear_real_time_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::ClearRealTime do
+ let(:command) { ThinkingSphinx::Commands::ClearRealTime.new(
+ configuration, {:indices => [users_index, parts_index]}, stream
+ ) }
+ let(:configuration) { double 'configuration', :searchd => double(:binlog_path => '/path/to/binlog') }
+ let(:stream) { double :puts => nil }
+ let(:users_index) { double :path => '/path/to/my/index/users', :render => true }
+ let(:parts_index) { double :path => '/path/to/my/index/parts', :render => true }
+
+ before :each do
+ allow(Dir).to receive(:[]).with('/path/to/my/index/users.*').
+ and_return(['users.a', 'users.b'])
+ allow(Dir).to receive(:[]).with('/path/to/my/index/parts.*').
+ and_return(['parts.a', 'parts.b'])
+
+ allow(FileUtils).to receive_messages :rm_rf => true,
+ :rm => true
+ allow(File).to receive_messages :exist? => true
+ end
+
+ it 'finds each file for real-time indices' do
+ expect(Dir).to receive(:[]).with('/path/to/my/index/users.*').
+ and_return([])
+
+ command.call
+ end
+
+ it "removes the directory for the binlog files" do
+ expect(FileUtils).to receive(:rm_rf).with('/path/to/binlog')
+
+ command.call
+ end
+
+ it "removes each file for real-time indices" do
+ expect(FileUtils).to receive(:rm).with('users.a')
+ expect(FileUtils).to receive(:rm).with('users.b')
+ expect(FileUtils).to receive(:rm).with('parts.a')
+ expect(FileUtils).to receive(:rm).with('parts.b')
+
+ command.call
+ end
+end
diff --git a/spec/thinking_sphinx/commands/clear_sql_spec.rb b/spec/thinking_sphinx/commands/clear_sql_spec.rb
new file mode 100644
index 000000000..a5e0a83d8
--- /dev/null
+++ b/spec/thinking_sphinx/commands/clear_sql_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::ClearSQL do
+ let(:command) { ThinkingSphinx::Commands::ClearSQL.new(
+ configuration, {:indices => [users_index, parts_index]}, stream
+ ) }
+ let(:configuration) { double 'configuration', :preload_indices => true,
+ :render => true, :indices => [users_index, parts_index],
+ :indices_location => '/path/to/indices' }
+ let(:stream) { double :puts => nil }
+
+ let(:users_index) { double(:name => 'users', :type => 'plain',
+ :render => true, :path => '/path/to/my/index/users') }
+ let(:parts_index) { double(:name => 'users', :type => 'plain',
+ :render => true, :path => '/path/to/my/index/parts') }
+
+ before :each do
+ allow(Dir).to receive(:[]).with('/path/to/my/index/users.*').
+ and_return(['users.a', 'users.b'])
+ allow(Dir).to receive(:[]).with('/path/to/my/index/parts.*').
+ and_return(['parts.a', 'parts.b'])
+ allow(Dir).to receive(:[]).with('/path/to/indices/ts-*.tmp').
+ and_return(['/path/to/indices/ts-foo.tmp'])
+
+ allow(FileUtils).to receive_messages :rm_rf => true, :rm => true
+ allow(File).to receive_messages :exist? => true
+ end
+
+ it 'finds each file for sql-backed indices' do
+ expect(Dir).to receive(:[]).with('/path/to/my/index/users.*').
+ and_return([])
+
+ command.call
+ end
+
+ it "removes each file for real-time indices" do
+ expect(FileUtils).to receive(:rm).with('users.a')
+ expect(FileUtils).to receive(:rm).with('users.b')
+ expect(FileUtils).to receive(:rm).with('parts.a')
+ expect(FileUtils).to receive(:rm).with('parts.b')
+
+ command.call
+ end
+
+ it "removes any indexing guard files" do
+ expect(FileUtils).to receive(:rm_rf).with(["/path/to/indices/ts-foo.tmp"])
+
+ command.call
+ end
+end
diff --git a/spec/thinking_sphinx/commands/configure_spec.rb b/spec/thinking_sphinx/commands/configure_spec.rb
new file mode 100644
index 000000000..99f254cfd
--- /dev/null
+++ b/spec/thinking_sphinx/commands/configure_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::Configure do
+ let(:command) { ThinkingSphinx::Commands::Configure.new(
+ configuration, {}, stream
+ ) }
+ let(:configuration) { double 'configuration' }
+ let(:stream) { double :puts => nil }
+
+ before :each do
+ allow(configuration).to receive_messages(
+ :configuration_file => '/path/to/foo.conf',
+ :render_to_file => true
+ )
+ end
+
+ it "renders the configuration to a file" do
+ expect(configuration).to receive(:render_to_file)
+
+ command.call
+ end
+
+ it "prints a message stating the file is being generated" do
+ expect(stream).to receive(:puts).
+ with('Generating configuration to /path/to/foo.conf')
+
+ command.call
+ end
+end
diff --git a/spec/thinking_sphinx/commands/index_real_time_spec.rb b/spec/thinking_sphinx/commands/index_real_time_spec.rb
new file mode 100644
index 000000000..98d171998
--- /dev/null
+++ b/spec/thinking_sphinx/commands/index_real_time_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::IndexRealTime do
+ let(:command) { ThinkingSphinx::Commands::IndexRealTime.new(
+ configuration, {:indices => [users_index, parts_index]}, stream
+ ) }
+ let(:configuration) { double 'configuration', :controller => controller }
+ let(:controller) { double 'controller', :rotate => nil }
+ let(:stream) { double :puts => nil }
+ let(:users_index) { double(name: 'users') }
+ let(:parts_index) { double(name: 'parts') }
+
+ before :each do
+ allow(ThinkingSphinx::RealTime::Populator).to receive(:populate)
+ end
+
+ it 'populates each real-index' do
+ expect(ThinkingSphinx::RealTime::Populator).to receive(:populate).
+ with(users_index)
+ expect(ThinkingSphinx::RealTime::Populator).to receive(:populate).
+ with(parts_index)
+
+ command.call
+ end
+
+ it "rotates the daemon for each index" do
+ expect(controller).to receive(:rotate).twice
+
+ command.call
+ end
+end
diff --git a/spec/thinking_sphinx/commands/index_sql_spec.rb b/spec/thinking_sphinx/commands/index_sql_spec.rb
new file mode 100644
index 000000000..eba36df47
--- /dev/null
+++ b/spec/thinking_sphinx/commands/index_sql_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::IndexSQL do
+ let(:command) { ThinkingSphinx::Commands::IndexSQL.new(
+ configuration, {:verbose => true}, stream
+ ) }
+ let(:configuration) { double 'configuration', :controller => controller,
+ :indexing_strategy => indexing_strategy,
+ :guarding_strategy => guarding_strategy }
+ let(:controller) { double 'controller', :index => true }
+ let(:stream) { double :puts => nil }
+ let(:indexing_strategy) { Proc.new { |names, &block| block.call names } }
+ let(:guarding_strategy) { Proc.new { |names, &block| block.call names } }
+
+ before :each do
+ allow(ThinkingSphinx).to receive_messages :before_index_hooks => []
+ end
+
+ it "calls all registered hooks" do
+ called = false
+ ThinkingSphinx.before_index_hooks << Proc.new { called = true }
+
+ command.call
+
+ expect(called).to eq(true)
+ end
+
+ it "indexes all indices verbosely" do
+ expect(controller).to receive(:index).with(:verbose => true)
+
+ command.call
+ end
+
+ it "does not index verbosely if requested" do
+ command = ThinkingSphinx::Commands::IndexSQL.new(
+ configuration, {:verbose => false}, stream
+ )
+
+ expect(controller).to receive(:index).with(:verbose => false)
+
+ command.call
+ end
+
+ it "ignores a nil indices filter" do
+ command = ThinkingSphinx::Commands::IndexSQL.new(
+ configuration, {:verbose => false, :indices => nil}, stream
+ )
+
+ expect(controller).to receive(:index).with(:verbose => false)
+
+ command.call
+ end
+
+ it "ignores an empty indices filter" do
+ command = ThinkingSphinx::Commands::IndexSQL.new(
+ configuration, {:verbose => false, :indices => []}, stream
+ )
+
+ expect(controller).to receive(:index).with(:verbose => false)
+
+ command.call
+ end
+
+ it "uses filtered index names" do
+ command = ThinkingSphinx::Commands::IndexSQL.new(
+ configuration, {:verbose => false, :indices => ['foo_bar']}, stream
+ )
+
+ expect(controller).to receive(:index).with('foo_bar', :verbose => false)
+
+ command.call
+ end
+
+ it "does not call hooks when filtering by index" do
+ called = false
+ ThinkingSphinx.before_index_hooks << Proc.new { called = true }
+
+ ThinkingSphinx::Commands::IndexSQL.new(
+ configuration, {:verbose => false, :indices => ['foo_bar']}, stream
+ ).call
+
+ expect(called).to eq(false)
+ end
+end
diff --git a/spec/thinking_sphinx/commands/merge_and_update_spec.rb b/spec/thinking_sphinx/commands/merge_and_update_spec.rb
new file mode 100644
index 000000000..7fd7fdbc8
--- /dev/null
+++ b/spec/thinking_sphinx/commands/merge_and_update_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::MergeAndUpdate do
+ let(:command) { ThinkingSphinx::Commands::MergeAndUpdate.new(
+ configuration, {}, stream
+ ) }
+ let(:configuration) { double "configuration", :preload_indices => nil,
+ :render => "", :indices => [core_index_a, delta_index_a, rt_index,
+ plain_index, core_index_b, delta_index_b] }
+ let(:stream) { double :puts => nil }
+ let(:commander) { double :call => true }
+ let(:core_index_a) { double "index", :type => "plain", :options => {:delta_processor => true}, :delta? => false, :name => "index_a_core", :model => model_a, :path => "index_a_core" }
+ let(:delta_index_a) { double "index", :type => "plain", :options => {:delta_processor => true}, :delta? => true, :name => "index_a_delta", :path => "index_a_delta" }
+ let(:core_index_b) { double "index", :type => "plain", :options => {:delta_processor => true}, :delta? => false, :name => "index_b_core", :model => model_b, :path => "index_b_core" }
+ let(:delta_index_b) { double "index", :type => "plain", :options => {:delta_processor => true}, :delta? => true, :name => "index_b_delta", :path => "index_b_delta" }
+ let(:rt_index) { double "index", :type => "rt", :name => "rt_index" }
+ let(:plain_index) { double "index", :type => "plain", :name => "plain_index", :options => {:delta_processor => nil} }
+ let(:model_a) { double "model", :where => where_a }
+ let(:model_b) { double "model", :where => where_b }
+ let(:where_a) { double "where", :update_all => nil }
+ let(:where_b) { double "where", :update_all => nil }
+
+ before :each do
+ stub_const 'ThinkingSphinx::Commander', commander
+ end
+
+ it "merges core/delta pairs" do
+ expect(commander).to receive(:call).with(
+ :merge, configuration, hash_including(
+ :core_index => core_index_a,
+ :delta_index => delta_index_a,
+ :filters => {:sphinx_deleted => 0}
+ ), stream
+ )
+ expect(commander).to receive(:call).with(
+ :merge, configuration, hash_including(
+ :core_index => core_index_b,
+ :delta_index => delta_index_b,
+ :filters => {:sphinx_deleted => 0}
+ ), stream
+ )
+
+ command.call
+ end
+
+ it "unflags delta records" do
+ expect(model_a).to receive(:where).with(:delta => true).and_return(where_a)
+ expect(where_a).to receive(:update_all).with(:delta => false)
+
+ expect(model_b).to receive(:where).with(:delta => true).and_return(where_b)
+ expect(where_b).to receive(:update_all).with(:delta => false)
+
+ command.call
+ end
+
+ it "ignores real-time indices" do
+ expect(commander).to_not receive(:call).with(
+ :merge, configuration, hash_including(:core_index => rt_index), stream
+ )
+ expect(commander).to_not receive(:call).with(
+ :merge, configuration, hash_including(:delta_index => rt_index), stream
+ )
+
+ command.call
+ end
+
+ it "ignores non-delta SQL indices" do
+ expect(commander).to_not receive(:call).with(
+ :merge, configuration, hash_including(:core_index => plain_index),
+ stream
+ )
+ expect(commander).to_not receive(:call).with(
+ :merge, configuration, hash_including(:delta_index => plain_index),
+ stream
+ )
+
+ command.call
+ end
+
+ context "with index name filter" do
+ let(:command) { ThinkingSphinx::Commands::MergeAndUpdate.new(
+ configuration, {:index_names => ["index_a"]}, stream
+ ) }
+
+ it "only processes matching indices" do
+ expect(commander).to receive(:call).with(
+ :merge, configuration, hash_including(
+ :core_index => core_index_a,
+ :delta_index => delta_index_a,
+ :filters => {:sphinx_deleted => 0}
+ ), stream
+ )
+ expect(commander).to_not receive(:call).with(
+ :merge, configuration, hash_including(
+ :core_index => core_index_b,
+ :delta_index => delta_index_b,
+ :filters => {:sphinx_deleted => 0}
+ ), stream
+ )
+
+ command.call
+ end
+ end
+end
diff --git a/spec/thinking_sphinx/commands/merge_spec.rb b/spec/thinking_sphinx/commands/merge_spec.rb
new file mode 100644
index 000000000..159161246
--- /dev/null
+++ b/spec/thinking_sphinx/commands/merge_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::Merge do
+ let(:command) { ThinkingSphinx::Commands::Merge.new(
+ configuration, {:core_index => core_index, :delta_index => delta_index,
+ :filters => {:sphinx_deleted => 0}}, stream
+ ) }
+ let(:configuration) { double "configuration", :controller => controller }
+ let(:stream) { double :puts => nil }
+ let(:controller) { double "controller", :merge => nil }
+ let(:core_index) { double "index", :path => "index_a_core",
+ :name => "index_a_core" }
+ let(:delta_index) { double "index", :path => "index_a_delta",
+ :name => "index_a_delta" }
+
+ before :each do
+ allow(File).to receive(:exist?).and_return(true)
+ end
+
+ it "merges core/delta pairs" do
+ expect(controller).to receive(:merge).with(
+ "index_a_core",
+ "index_a_delta",
+ :filters => {:sphinx_deleted => 0},
+ :verbose => nil
+ )
+
+ command.call
+ end
+
+ it "does not merge if just the core does not exist" do
+ allow(File).to receive(:exist?).with("index_a_core.spi").and_return(false)
+
+ expect(controller).to_not receive(:merge)
+
+ command.call
+ end
+
+ it "does not merge if just the delta does not exist" do
+ allow(File).to receive(:exist?).with("index_a_delta.spi").and_return(false)
+
+ expect(controller).to_not receive(:merge)
+
+ command.call
+ end
+end
diff --git a/spec/thinking_sphinx/commands/prepare_spec.rb b/spec/thinking_sphinx/commands/prepare_spec.rb
new file mode 100644
index 000000000..2a99c337b
--- /dev/null
+++ b/spec/thinking_sphinx/commands/prepare_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::Prepare do
+ let(:command) { ThinkingSphinx::Commands::Prepare.new(
+ configuration, {}, stream
+ ) }
+ let(:configuration) { double 'configuration',
+ :indices_location => '/path/to/indices', :settings => {}
+ }
+ let(:stream) { double :puts => nil }
+
+ before :each do
+ allow(FileUtils).to receive_messages :mkdir_p => true
+ end
+
+ it "creates the directory for the index files" do
+ expect(FileUtils).to receive(:mkdir_p).with('/path/to/indices')
+
+ command.call
+ end
+
+ it "skips directory creation if flag is set" do
+ configuration.settings['skip_directory_creation'] = true
+
+ expect(FileUtils).to_not receive(:mkdir_p)
+
+ command.call
+ end
+end
diff --git a/spec/thinking_sphinx/commands/running_spec.rb b/spec/thinking_sphinx/commands/running_spec.rb
new file mode 100644
index 000000000..a82a99794
--- /dev/null
+++ b/spec/thinking_sphinx/commands/running_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::Running do
+ let(:command) { ThinkingSphinx::Commands::Running.new(
+ configuration, {}, stream
+ ) }
+ let(:configuration) {
+ double 'configuration', :controller => controller, :settings => {}
+ }
+ let(:stream) { double :puts => nil }
+ let(:controller) { double 'controller', :running? => false }
+
+ it 'returns true when Sphinx is running' do
+ allow(controller).to receive(:running?).and_return(true)
+
+ expect(command.call).to eq(true)
+ end
+
+ it 'returns false when Sphinx is not running' do
+ expect(command.call).to eq(false)
+ end
+
+ it 'returns true if the flag is set' do
+ configuration.settings['skip_running_check'] = true
+
+ expect(command.call).to eq(true)
+ end
+end
diff --git a/spec/thinking_sphinx/commands/start_detached_spec.rb b/spec/thinking_sphinx/commands/start_detached_spec.rb
new file mode 100644
index 000000000..29e14d76e
--- /dev/null
+++ b/spec/thinking_sphinx/commands/start_detached_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::StartDetached do
+ let(:command) {
+ ThinkingSphinx::Commands::StartDetached.new(configuration, {}, stream)
+ }
+ let(:configuration) {
+ double 'configuration', :controller => controller, :settings => {}
+ }
+ let(:controller) { double 'controller', :start => result, :pid => 101 }
+ let(:result) { double 'result', :command => 'start', :status => 1,
+ :output => '' }
+ let(:stream) { double :puts => nil }
+
+ before :each do
+ allow(controller).to receive(:running?).and_return(true)
+ allow(configuration).to receive_messages(
+ :indices_location => 'my/index/files',
+ :searchd => double(:log => '/path/to/log')
+ )
+ allow(command).to receive(:exit).and_return(true)
+
+ allow(FileUtils).to receive_messages :mkdir_p => true
+ end
+
+ it "creates the index files directory" do
+ expect(FileUtils).to receive(:mkdir_p).with('my/index/files')
+
+ command.call
+ end
+
+ it "skips directory creation if flag is set" do
+ configuration.settings['skip_directory_creation'] = true
+
+ expect(FileUtils).to_not receive(:mkdir_p)
+
+ command.call
+ end
+
+ it "starts the daemon" do
+ expect(controller).to receive(:start)
+
+ command.call
+ end
+
+ it "prints a success message if the daemon has started" do
+ allow(controller).to receive(:running?).and_return(true)
+
+ expect(stream).to receive(:puts).
+ with('Started searchd successfully (pid: 101).')
+
+ command.call
+ end
+
+ it "prints a failure message if the daemon does not start" do
+ allow(controller).to receive(:running?).and_return(false)
+ allow(command).to receive(:exit)
+
+ expect(stream).to receive(:puts) do |string|
+ expect(string).to match('The Sphinx start command failed')
+ end
+
+ command.call
+ end
+end
diff --git a/spec/thinking_sphinx/commands/stop_spec.rb b/spec/thinking_sphinx/commands/stop_spec.rb
new file mode 100644
index 000000000..d1bd037e9
--- /dev/null
+++ b/spec/thinking_sphinx/commands/stop_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Commands::Stop do
+ let(:command) {
+ ThinkingSphinx::Commands::Stop.new(configuration, {}, stream)
+ }
+ let(:configuration) { double 'configuration', :controller => controller }
+ let(:controller) { double 'controller', :stop => true, :pid => 101 }
+ let(:stream) { double :puts => nil }
+ let(:commander) { double :call => nil }
+
+ before :each do
+ stub_const 'ThinkingSphinx::Commander', commander
+
+ allow(commander).to receive(:call).
+ with(:running, configuration, {}, stream).and_return(true, true, false)
+ end
+
+ it "prints a message if the daemon is not already running" do
+ allow(commander).to receive(:call).
+ with(:running, configuration, {}, stream).and_return(false)
+
+ expect(stream).to receive(:puts).with('searchd is not currently running.').
+ and_return(nil)
+ expect(stream).to_not receive(:puts).
+ with('"Stopped searchd daemon (pid: ).')
+
+ command.call
+ end
+
+ it "does not try to stop the daemon if it's not running" do
+ allow(commander).to receive(:call).
+ with(:running, configuration, {}, stream).and_return(false)
+
+ expect(controller).to_not receive(:stop)
+
+ command.call
+ end
+
+ it "stops the daemon" do
+ expect(controller).to receive(:stop)
+
+ command.call
+ end
+
+ it "prints a message informing the daemon has stopped" do
+ expect(stream).to receive(:puts).with('Stopped searchd daemon (pid: 101).')
+
+ command.call
+ end
+
+ it "should retry stopping the daemon until it stops" do
+ allow(commander).to receive(:call).
+ with(:running, configuration, {}, stream).
+ and_return(true, true, true, false)
+
+ expect(controller).to receive(:stop).twice
+
+ command.call
+ end
+end
diff --git a/spec/thinking_sphinx/configuration/minimum_fields_spec.rb b/spec/thinking_sphinx/configuration/minimum_fields_spec.rb
new file mode 100644
index 000000000..9c85db788
--- /dev/null
+++ b/spec/thinking_sphinx/configuration/minimum_fields_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Configuration::MinimumFields do
+ let(:indices) { [index_a, index_b] }
+ let(:index_a) { double 'Index A', :model => model_a, :type => 'plain',
+ :sources => [double(:fields => [field_a1, field_a2])] }
+ let(:index_b) { double 'Index B', :model => model_b, :type => 'rt',
+ :fields => [field_b1, field_b2] }
+ let(:field_a1) { double :name => 'sphinx_internal_class_name' }
+ let(:field_a2) { double :name => 'name' }
+ let(:field_b1) { double :name => 'sphinx_internal_class_name' }
+ let(:field_b2) { double :name => 'name' }
+ let(:model_a) { double :inheritance_column => 'type',
+ :table_exists? => true }
+ let(:model_b) { double :inheritance_column => 'type',
+ :table_exists? => true }
+ let(:subject) { ThinkingSphinx::Configuration::MinimumFields.new indices }
+
+ it 'removes the class name fields when no index models have type columns' do
+ allow(model_a).to receive(:column_names).and_return(['id', 'name'])
+ allow(model_b).to receive(:column_names).and_return(['id', 'name'])
+
+ subject.reconcile
+
+ expect(index_a.sources.first.fields).to eq([field_a2])
+ expect(index_b.fields).to eq([field_b2])
+ end
+
+ it 'removes the class name fields when models have no tables' do
+ allow(model_a).to receive(:table_exists?).and_return(false)
+ allow(model_b).to receive(:table_exists?).and_return(false)
+
+ subject.reconcile
+
+ expect(index_a.sources.first.fields).to eq([field_a2])
+ expect(index_b.fields).to eq([field_b2])
+ end
+
+ it 'removes the class name fields only for the rt indices without type column' do
+ allow(model_a).to receive(:column_names).and_return(['id', 'name', 'type'])
+ allow(model_b).to receive(:column_names).and_return(['id', 'name'])
+
+ subject.reconcile
+
+ expect(index_a.sources.first.fields).to eq([field_a1, field_a2])
+ expect(index_b.fields).to eq([field_b2])
+ end
+
+ it 'removes the class name fields only for the plain indices without type column' do
+ allow(model_a).to receive(:column_names).and_return(['id', 'name'])
+ allow(model_b).to receive(:column_names).and_return(['id', 'name', 'type'])
+
+ subject.reconcile
+
+ expect(index_a.sources.first.fields).to eq([field_a2])
+ expect(index_b.fields).to eq([field_b1, field_b2])
+ end
+end
diff --git a/spec/thinking_sphinx/configuration_spec.rb b/spec/thinking_sphinx/configuration_spec.rb
index 4a190cab0..b7c9df672 100644
--- a/spec/thinking_sphinx/configuration_spec.rb
+++ b/spec/thinking_sphinx/configuration_spec.rb
@@ -1,22 +1,31 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::Configuration do
let(:config) { ThinkingSphinx::Configuration.instance }
+ let(:use_load?) { ActiveRecord::VERSION::MAJOR > 5 }
+ let(:loading_object) { use_load? ? config : ActiveSupport::Dependencies }
+ let(:loading_method) { use_load? ? :load : :require_or_load }
after :each do
ThinkingSphinx::Configuration.reset
end
+ def expect_loading_of(file)
+ expect(loading_object).to receive(loading_method).with(file).once
+ end
+
describe '.instance' do
it "returns an instance of ThinkingSphinx::Configuration" do
- ThinkingSphinx::Configuration.instance.
- should be_a(ThinkingSphinx::Configuration)
+ expect(ThinkingSphinx::Configuration.instance).
+ to be_a(ThinkingSphinx::Configuration)
end
it "memoizes the instance" do
config = double('configuration')
- ThinkingSphinx::Configuration.should_receive(:new).once.and_return(config)
+ expect(ThinkingSphinx::Configuration).to receive(:new).once.and_return(config)
ThinkingSphinx::Configuration.instance
ThinkingSphinx::Configuration.instance
@@ -29,8 +38,13 @@
end
it 'does not cache settings after reset' do
- File.stub :exists? => true
- File.stub :read => {
+ allow(File).to receive(:exist?).and_wrap_original do |original, path|
+ next true if path.to_s == File.absolute_path("config/thinking_sphinx.yml", Rails.root)
+
+ original.call(path)
+ end
+
+ allow(File).to receive_messages :read => {
'test' => {'foo' => 'bugs'},
'production' => {'foo' => 'bar'}
}.to_yaml
@@ -38,33 +52,33 @@
ThinkingSphinx::Configuration.reset
# Grab a new copy of the instance.
config = ThinkingSphinx::Configuration.instance
- config.settings['foo'].should == 'bugs'
+ expect(config.settings['foo']).to eq('bugs')
config.framework = double :environment => 'production', :root => Pathname.new(__FILE__).join('..', '..', 'internal')
- config.settings['foo'].should == 'bar'
+ expect(config.settings['foo']).to eq('bar')
end
end
describe '#configuration_file' do
it "uses the Rails environment in the configuration file name" do
- config.configuration_file.
- should == File.join(Rails.root, 'config', 'test.sphinx.conf')
+ expect(config.configuration_file).
+ to eq(File.join(Rails.root, 'config', 'test.sphinx.conf'))
end
it "respects provided settings" do
write_configuration 'configuration_file' => '/path/to/foo.conf'
- config.configuration_file.should == '/path/to/foo.conf'
+ expect(config.configuration_file).to eq('/path/to/foo.conf')
end
end
describe '#controller' do
it "returns an instance of Riddle::Controller" do
- config.controller.should be_a(Riddle::Controller)
+ expect(config.controller).to be_a(Riddle::Controller)
end
it "memoizes the instance" do
- Riddle::Controller.should_receive(:new).once.
+ expect(Riddle::Controller).to receive(:new).once.
and_return(double('controller'))
config.controller
@@ -74,19 +88,19 @@
it "sets the bin path from the thinking_sphinx.yml file" do
write_configuration('bin_path' => '/foo/bar/bin/')
- config.controller.bin_path.should == '/foo/bar/bin/'
+ expect(config.controller.bin_path).to eq('/foo/bar/bin/')
end
it "appends a backslash to the bin_path if appropriate" do
write_configuration('bin_path' => '/foo/bar/bin')
- config.controller.bin_path.should == '/foo/bar/bin/'
+ expect(config.controller.bin_path).to eq('/foo/bar/bin/')
end
end
describe '#index_paths' do
it "uses app/indices in the Rails app by default" do
- config.index_paths.should include(File.join(Rails.root, 'app', 'indices'))
+ expect(config.index_paths).to include(File.join(Rails.root, 'app', 'indices'))
end
it "uses app/indices in the Rails engines" do
@@ -95,22 +109,69 @@
} }
engine_class = double :instance => engine
- Rails::Engine.should_receive(:subclasses).and_return([ engine_class ])
+ expect(Rails::Engine).to receive(:subclasses).and_return([ engine_class ])
- config.index_paths.should include('/engine/app/indices')
+ expect(config.index_paths).to include('/engine/app/indices')
end
end
describe '#indices_location' do
it "stores index files in db/sphinx/ENVIRONMENT" do
- config.indices_location.
- should == File.join(Rails.root, 'db', 'sphinx', 'test')
+ expect(config.indices_location).
+ to eq(File.join(Rails.root, 'db', 'sphinx', 'test'))
end
it "respects provided settings" do
write_configuration 'indices_location' => '/my/index/files'
- config.indices_location.should == '/my/index/files'
+ expect(config.indices_location).to eq('/my/index/files')
+ end
+
+ it "respects relative paths" do
+ write_configuration 'indices_location' => 'my/index/files'
+
+ expect(config.indices_location).to eq('my/index/files')
+ end
+
+ it "translates relative paths to absolute if config requests it" do
+ write_configuration(
+ 'indices_location' => 'my/index/files',
+ 'absolute_paths' => true
+ )
+
+ expect(config.indices_location).to eq(
+ File.join(config.framework.root, 'my/index/files')
+ )
+ end
+
+ it "respects paths that are already absolute" do
+ write_configuration(
+ 'indices_location' => '/my/index/files',
+ 'absolute_paths' => true
+ )
+
+ expect(config.indices_location).to eq('/my/index/files')
+ end
+
+ it "translates linked directories" do
+ write_configuration(
+ 'indices_location' => 'mine/index/files',
+ 'absolute_paths' => true
+ )
+
+ framework = ThinkingSphinx::Frameworks.current
+ local_path = File.join framework.root, "mine"
+ linked_path = File.join framework.root, "my"
+
+ FileUtils.mkdir_p linked_path
+ `ln -s #{linked_path} #{local_path}`
+
+ expect(config.indices_location).to eq(
+ File.join(config.framework.root, "my/index/files")
+ )
+
+ FileUtils.rm_rf local_path
+ FileUtils.rm_rf linked_path
end
end
@@ -120,30 +181,30 @@
end
it "sets the daemon pid file within log for the Rails app" do
- config.searchd.pid_file.
- should == File.join(Rails.root, 'log', 'test.sphinx.pid')
+ expect(config.searchd.pid_file).
+ to eq(File.join(Rails.root, 'log', 'test.sphinx.pid'))
end
it "sets the daemon log within log for the Rails app" do
- config.searchd.log.
- should == File.join(Rails.root, 'log', 'test.searchd.log')
+ expect(config.searchd.log).
+ to eq(File.join(Rails.root, 'log', 'test.searchd.log'))
end
it "sets the query log within log for the Rails app" do
- config.searchd.query_log.
- should == File.join(Rails.root, 'log', 'test.searchd.query.log')
+ expect(config.searchd.query_log).
+ to eq(File.join(Rails.root, 'log', 'test.searchd.query.log'))
end
it "sets indexer settings if within thinking_sphinx.yml" do
write_configuration 'mem_limit' => '128M'
- config.indexer.mem_limit.should == '128M'
+ expect(config.indexer.mem_limit).to eq('128M')
end
it "sets searchd settings if within thinking_sphinx.yml" do
write_configuration 'workers' => 'none'
- config.searchd.workers.should == 'none'
+ expect(config.searchd.workers).to eq('none')
end
it 'adds settings to indexer without common section' do
@@ -164,18 +225,18 @@
let(:reference) { double('reference') }
it "starts at 0" do
- config.next_offset(reference).should == 0
+ expect(config.next_offset(reference)).to eq(0)
end
it "increments for each new reference" do
- config.next_offset(double('reference')).should == 0
- config.next_offset(double('reference')).should == 1
- config.next_offset(double('reference')).should == 2
+ expect(config.next_offset(double('reference'))).to eq(0)
+ expect(config.next_offset(double('reference'))).to eq(1)
+ expect(config.next_offset(double('reference'))).to eq(2)
end
it "doesn't increment for recorded references" do
- config.next_offset(reference).should == 0
- config.next_offset(reference).should == 0
+ expect(config.next_offset(reference)).to eq(0)
+ expect(config.next_offset(reference)).to eq(0)
end
end
@@ -190,9 +251,9 @@
it "searches each index path for ruby files" do
config.index_paths.replace ['/path/to/indices', '/path/to/other/indices']
- Dir.should_receive(:[]).with('/path/to/indices/**/*.rb').once.
+ expect(Dir).to receive(:[]).with('/path/to/indices/**/*.rb').once.
and_return([])
- Dir.should_receive(:[]).with('/path/to/other/indices/**/*.rb').once.
+ expect(Dir).to receive(:[]).with('/path/to/other/indices/**/*.rb').once.
and_return([])
config.preload_indices
@@ -200,37 +261,33 @@
it "loads each file returned" do
config.index_paths.replace ['/path/to/indices']
- Dir.stub! :[] => [
+ allow(Dir).to receive_messages :[] => [
'/path/to/indices/foo_index.rb',
'/path/to/indices/bar_index.rb'
]
- ActiveSupport::Dependencies.should_receive(:require_or_load).
- with('/path/to/indices/foo_index.rb').once
- ActiveSupport::Dependencies.should_receive(:require_or_load).
- with('/path/to/indices/bar_index.rb').once
+ expect_loading_of('/path/to/indices/foo_index.rb')
+ expect_loading_of('/path/to/indices/bar_index.rb')
config.preload_indices
end
it "does not double-load indices" do
config.index_paths.replace ['/path/to/indices']
- Dir.stub! :[] => [
+ allow(Dir).to receive_messages :[] => [
'/path/to/indices/foo_index.rb',
'/path/to/indices/bar_index.rb'
]
- ActiveSupport::Dependencies.should_receive(:require_or_load).
- with('/path/to/indices/foo_index.rb').once
- ActiveSupport::Dependencies.should_receive(:require_or_load).
- with('/path/to/indices/bar_index.rb').once
+ expect_loading_of('/path/to/indices/foo_index.rb')
+ expect_loading_of('/path/to/indices/bar_index.rb')
config.preload_indices
config.preload_indices
end
it 'adds distributed indices' do
- distributor.should_receive(:reconcile)
+ expect(distributor).to receive(:reconcile)
config.preload_indices
end
@@ -238,7 +295,7 @@
it 'does not add distributed indices if disabled' do
write_configuration('distributed_indices' => false)
- distributor.should_not_receive(:reconcile)
+ expect(distributor).not_to receive(:reconcile)
config.preload_indices
end
@@ -246,15 +303,15 @@
describe '#render' do
before :each do
- config.searchd.stub! :render => 'searchd { }'
+ allow(config.searchd).to receive_messages :render => 'searchd { }'
end
it "searches each index path for ruby files" do
config.index_paths.replace ['/path/to/indices', '/path/to/other/indices']
- Dir.should_receive(:[]).with('/path/to/indices/**/*.rb').once.
+ expect(Dir).to receive(:[]).with('/path/to/indices/**/*.rb').once.
and_return([])
- Dir.should_receive(:[]).with('/path/to/other/indices/**/*.rb').once.
+ expect(Dir).to receive(:[]).with('/path/to/other/indices/**/*.rb').once.
and_return([])
config.render
@@ -262,30 +319,26 @@
it "loads each file returned" do
config.index_paths.replace ['/path/to/indices']
- Dir.stub! :[] => [
+ allow(Dir).to receive_messages :[] => [
'/path/to/indices/foo_index.rb',
'/path/to/indices/bar_index.rb'
]
- ActiveSupport::Dependencies.should_receive(:require_or_load).
- with('/path/to/indices/foo_index.rb').once
- ActiveSupport::Dependencies.should_receive(:require_or_load).
- with('/path/to/indices/bar_index.rb').once
+ expect_loading_of('/path/to/indices/foo_index.rb')
+ expect_loading_of('/path/to/indices/bar_index.rb')
config.render
end
it "does not double-load indices" do
config.index_paths.replace ['/path/to/indices']
- Dir.stub! :[] => [
+ allow(Dir).to receive_messages :[] => [
'/path/to/indices/foo_index.rb',
'/path/to/indices/bar_index.rb'
]
- ActiveSupport::Dependencies.should_receive(:require_or_load).
- with('/path/to/indices/foo_index.rb').once
- ActiveSupport::Dependencies.should_receive(:require_or_load).
- with('/path/to/indices/bar_index.rb').once
+ expect_loading_of('/path/to/indices/foo_index.rb')
+ expect_loading_of('/path/to/indices/bar_index.rb')
config.preload_indices
config.preload_indices
@@ -293,70 +346,162 @@
end
describe '#render_to_file' do
- let(:file) { double('file') }
- let(:output) { config.render }
+ let(:file) { double('file') }
+ let(:output) { config.render }
+ let(:skip_directories) { false }
before :each do
- config.searchd.stub! :render => 'searchd { }'
+ write_configuration('skip_directory_creation' => skip_directories)
+
+ allow(config.searchd).to receive_messages :render => 'searchd { }'
end
it "writes the rendered configuration to the file" do
config.configuration_file = '/path/to/file.config'
- config.should_receive(:open).with('/path/to/file.config', 'w').
+ expect(config).to receive(:open).with('/path/to/file.config', 'w').
and_yield(file)
- file.should_receive(:write).with(output)
+ expect(file).to receive(:write).with(output)
config.render_to_file
end
it "creates a directory at the binlog_path" do
- FileUtils.stub :mkdir_p => true
- config.stub :searchd => double(:binlog_path => '/path/to/binlog')
+ allow(FileUtils).to receive_messages :mkdir_p => true
+ allow(config).to receive_messages :searchd => double(:binlog_path => '/path/to/binlog')
- FileUtils.should_receive(:mkdir_p).with('/path/to/binlog')
+ expect(FileUtils).to receive(:mkdir_p).with('/path/to/binlog')
config.render_to_file
end
it "skips creating a directory when the binlog_path is blank" do
- FileUtils.stub :mkdir_p => true
- config.stub :searchd => double(:binlog_path => '')
+ allow(loading_object).to receive(loading_method)
+
+ allow(FileUtils).to receive_messages :mkdir_p => true
+ allow(config).to receive_messages :searchd => double(:binlog_path => '')
- FileUtils.should_not_receive(:mkdir_p)
+ expect(FileUtils).not_to receive(:mkdir_p)
config.render_to_file
end
+
+ context 'skipping directory creation' do
+ let(:skip_directories) { true }
+
+ it "skips creating a directory when flag is set" do
+ expect(FileUtils).not_to receive(:mkdir_p)
+
+ config.render_to_file
+ end
+ end
end
describe '#searchd' do
describe '#address' do
it "defaults to 127.0.0.1" do
- config.searchd.address.should == '127.0.0.1'
+ expect(config.searchd.address).to eq('127.0.0.1')
end
it "respects the address setting" do
write_configuration('address' => '10.11.12.13')
- config.searchd.address.should == '10.11.12.13'
+ expect(config.searchd.address).to eq('10.11.12.13')
end
end
+ describe '#log' do
+ it "defaults to an environment-specific file" do
+ expect(config.searchd.log).to eq(
+ File.join(config.framework.root, "log/test.searchd.log")
+ )
+ end
+
+ it "translates linked directories" do
+ framework = ThinkingSphinx::Frameworks.current
+ log_path = File.join framework.root, "log"
+ linked_path = File.join framework.root, "logging"
+ log_exists = File.exist? log_path
+
+ FileUtils.mv log_path, "#{log_path}-tmp" if log_exists
+ FileUtils.mkdir_p linked_path
+ `ln -s #{linked_path} #{log_path}`
+
+ expect(config.searchd.log).to eq(
+ File.join(config.framework.root, "logging/test.searchd.log")
+ )
+
+ FileUtils.rm log_path
+ FileUtils.rmdir linked_path
+ FileUtils.mv "#{log_path}-tmp", log_path if log_exists
+ end unless RUBY_PLATFORM == "java"
+ end
+
describe '#mysql41' do
it "defaults to 9306" do
- config.searchd.mysql41.should == 9306
+ expect(config.searchd.mysql41).to eq(9306)
end
it "respects the port setting" do
write_configuration('port' => 9313)
- config.searchd.mysql41.should == 9313
+ expect(config.searchd.mysql41).to eq(9313)
end
it "respects the mysql41 setting" do
write_configuration('mysql41' => 9307)
- config.searchd.mysql41.should == 9307
+ expect(config.searchd.mysql41).to eq(9307)
+ end
+ end
+
+ describe "#socket" do
+ it "does not set anything by default" do
+ expect(config.searchd.socket).to be_nil
+ end
+
+ it "ignores unspecified address and port when socket is set" do
+ write_configuration("socket" => "/my/socket")
+
+ expect(config.searchd.socket).to eq("/my/socket:mysql41")
+ expect(config.searchd.address).to be_nil
+ expect(config.searchd.mysql41).to be_nil
+ end
+
+ it "allows address and socket settings" do
+ write_configuration("socket" => "/my/socket", "address" => "1.1.1.1")
+
+ expect(config.searchd.socket).to eq("/my/socket:mysql41")
+ expect(config.searchd.address).to eq("1.1.1.1")
+ expect(config.searchd.mysql41).to eq(9306)
+ end
+
+ it "allows mysql41 and socket settings" do
+ write_configuration("socket" => "/my/socket", "mysql41" => 9307)
+
+ expect(config.searchd.socket).to eq("/my/socket:mysql41")
+ expect(config.searchd.address).to eq("127.0.0.1")
+ expect(config.searchd.mysql41).to eq(9307)
+ end
+
+ it "allows port and socket settings" do
+ write_configuration("socket" => "/my/socket", "port" => 9307)
+
+ expect(config.searchd.socket).to eq("/my/socket:mysql41")
+ expect(config.searchd.address).to eq("127.0.0.1")
+ expect(config.searchd.mysql41).to eq(9307)
+ end
+
+ it "allows address, mysql41 and socket settings" do
+ write_configuration(
+ "socket" => "/my/socket",
+ "address" => "1.2.3.4",
+ "mysql41" => 9307
+ )
+
+ expect(config.searchd.socket).to eq("/my/socket:mysql41")
+ expect(config.searchd.address).to eq("1.2.3.4")
+ expect(config.searchd.mysql41).to eq(9307)
end
end
end
@@ -364,66 +509,74 @@
describe '#settings' do
context 'YAML file exists' do
before :each do
- File.stub :exists? => true
+ allow(File).to receive(:exist?).and_wrap_original do |original, path|
+ next true if path.to_s == File.absolute_path("config/thinking_sphinx.yml", Rails.root)
+
+ original.call(path)
+ end
end
it "reads from the YAML file" do
- File.should_receive(:read).and_return('')
+ expect(File).to receive(:read).and_return('')
config.settings
end
it "uses the settings for the given environment" do
- File.stub :read => {
+ allow(File).to receive_messages :read => {
'test' => {'foo' => 'bar'},
'staging' => {'baz' => 'qux'}
}.to_yaml
- Rails.stub :env => 'staging'
+ allow(Rails).to receive_messages :env => 'staging'
- config.settings['baz'].should == 'qux'
+ expect(config.settings['baz']).to eq('qux')
end
it "remembers the file contents" do
- File.should_receive(:read).and_return('')
+ expect(File).to receive(:read).and_return('')
config.settings
config.settings
end
- it "returns an empty hash when no settings for the environment exist" do
- File.stub :read => {'test' => {'foo' => 'bar'}}.to_yaml
- Rails.stub :env => 'staging'
+ it "returns the default hash when no settings for the environment exist" do
+ allow(File).to receive_messages :read => {'test' => {'foo' => 'bar'}}.to_yaml
+ allow(Rails).to receive_messages :env => 'staging'
- config.settings.should == {}
+ expect(config.settings.class).to eq(Hash)
end
end
context 'YAML file does not exist' do
before :each do
- File.stub :exists? => false
+ allow(File).to receive(:exist?).and_wrap_original do |original, path|
+ next false if path.to_s == File.absolute_path("config/thinking_sphinx.yml", Rails.root)
+
+ original.call(path)
+ end
end
it "does not read the file" do
- File.should_not_receive(:read)
+ expect(File).not_to receive(:read)
config.settings
end
- it "returns an empty hash" do
- config.settings.should == {}
+ it "returns a hash" do
+ expect(config.settings.class).to eq(Hash)
end
end
end
describe '#version' do
- it "defaults to 2.1.4" do
- config.version.should == '2.1.4'
+ it "defaults to 2.2.11" do
+ expect(config.version).to eq('2.2.11')
end
it "respects supplied YAML versions" do
write_configuration 'version' => '2.0.4'
- config.version.should == '2.0.4'
+ expect(config.version).to eq('2.0.4')
end
end
end
diff --git a/spec/thinking_sphinx/connection/mri_spec.rb b/spec/thinking_sphinx/connection/mri_spec.rb
new file mode 100644
index 000000000..a54926ea7
--- /dev/null
+++ b/spec/thinking_sphinx/connection/mri_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.describe ThinkingSphinx::Connection::MRI do
+ subject { described_class.new :host => "127.0.0.1", :port => 9306 }
+
+ let(:client) { double :client, :query => "result", :next_result => false }
+
+ before :each do
+ allow(Mysql2::Client).to receive(:new).and_return(client)
+ end
+
+ after :each do
+ ThinkingSphinx::Configuration.reset
+ end
+
+ describe "#execute" do
+ it "sends the query to the client" do
+ subject.execute "SELECT QUERY"
+
+ expect(client).to have_received(:query).with("SELECT QUERY")
+ end
+
+ it "returns a result" do
+ expect(subject.execute("SELECT QUERY")).to eq("result")
+ end
+
+ context "with long queries" do
+ let(:maximum) { (2 ** 23) - 5 }
+ let(:query) { String.new "SELECT * FROM book_core WHERE MATCH('')" }
+ let(:difference) { maximum - query.length }
+
+ it 'does not allow overly long queries' do
+ expect {
+ subject.execute(query.insert(-3, 'a' * (difference + 5)))
+ }.to raise_error(ThinkingSphinx::QueryLengthError)
+ end
+
+ it 'does not allow queries longer than specified in the settings' do
+ ThinkingSphinx::Configuration.reset
+
+ write_configuration('maximum_statement_length' => maximum - 5)
+
+ expect {
+ subject.execute(query.insert(-3, 'a' * (difference)))
+ }.to raise_error(ThinkingSphinx::QueryLengthError)
+ end
+ end
+ end
+end if RUBY_PLATFORM != 'java'
diff --git a/spec/thinking_sphinx/connection_spec.rb b/spec/thinking_sphinx/connection_spec.rb
index 91d4c22ec..fbe14f51f 100644
--- a/spec/thinking_sphinx/connection_spec.rb
+++ b/spec/thinking_sphinx/connection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::Connection do
@@ -8,9 +10,9 @@
let(:translated_error) { ThinkingSphinx::SphinxError.new }
before :each do
- ThinkingSphinx::Connection.stub :pool => pool
- ThinkingSphinx::SphinxError.stub :new_from_mysql => translated_error
- pool.stub(:take).and_yield(connection)
+ allow(ThinkingSphinx::Connection).to receive_messages :pool => pool
+ allow(ThinkingSphinx::SphinxError).to receive_messages :new_from_mysql => translated_error
+ allow(pool).to receive(:take).and_yield(connection)
error.statement = 'SELECT * FROM article_core'
translated_error.statement = 'SELECT * FROM article_core'
@@ -18,41 +20,41 @@
it "yields a connection from the pool" do
ThinkingSphinx::Connection.take do |c|
- c.should == connection
+ expect(c).to eq(connection)
end
end
it "retries errors once" do
tries = 0
- lambda {
+ expect {
ThinkingSphinx::Connection.take do |c|
tries += 1
raise error if tries < 2
end
- }.should_not raise_error
+ }.not_to raise_error
end
it "retries errors twice" do
tries = 0
- lambda {
+ expect {
ThinkingSphinx::Connection.take do |c|
tries += 1
raise error if tries < 3
end
- }.should_not raise_error
+ }.not_to raise_error
end
it "raises a translated error if it fails three times" do
tries = 0
- lambda {
+ expect {
ThinkingSphinx::Connection.take do |c|
tries += 1
raise error if tries < 4
end
- }.should raise_error(ThinkingSphinx::SphinxError)
+ }.to raise_error(ThinkingSphinx::SphinxError)
end
[ThinkingSphinx::SyntaxError, ThinkingSphinx::ParseError].each do |klass|
@@ -60,9 +62,9 @@
let(:translated_error) { klass.new }
it "raises the error" do
- lambda {
+ expect {
ThinkingSphinx::Connection.take { |c| raise error }
- }.should raise_error(klass)
+ }.to raise_error(klass)
end
it "does not yield the connection more than once" do
@@ -77,7 +79,7 @@
#
end
- yields.should == 1
+ expect(yields).to eq(1)
end
end
end
diff --git a/spec/thinking_sphinx/deletion_spec.rb b/spec/thinking_sphinx/deletion_spec.rb
index 1fd54501a..d3efd2bd9 100644
--- a/spec/thinking_sphinx/deletion_spec.rb
+++ b/spec/thinking_sphinx/deletion_spec.rb
@@ -1,56 +1,56 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::Deletion do
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
- ThinkingSphinx::Connection.stub(:take).and_yield(connection)
- Riddle::Query.stub :update => 'UPDATE STATEMENT'
+ allow(ThinkingSphinx::Connection).to receive(:take).and_yield(connection)
+ allow(Riddle::Query).to receive_messages :update => 'UPDATE STATEMENT'
end
context 'index is SQL-backed' do
it "updates the deleted flag to false" do
- connection.should_receive(:execute).with <<-SQL
-UPDATE foo_core
-SET sphinx_deleted = 1
-WHERE id IN (14)
- SQL
+ 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
it "doesn't care about Sphinx errors" do
- connection.stub(:execute).
+ allow(connection).to receive(:execute).
and_raise(ThinkingSphinx::ConnectionError.new(''))
- lambda {
+ expect {
ThinkingSphinx::Deletion.perform index, 7
- }.should_not raise_error
+ }.not_to raise_error
end
end
context "index is real-time" do
before :each do
- index.stub :type => 'rt'
+ allow(index).to receive_messages :type => 'rt'
end
it "deletes the record to false" do
- connection.should_receive(:execute).
- with('DELETE FROM foo_core WHERE id = 14')
+ expect(connection).to receive(:execute).
+ with('DELETE FROM foo_core WHERE sphinx_internal_id IN (7)')
ThinkingSphinx::Deletion.perform index, 7
end
it "doesn't care about Sphinx errors" do
- connection.stub(:execute).
+ allow(connection).to receive(:execute).
and_raise(ThinkingSphinx::ConnectionError.new(''))
- lambda {
+ expect {
ThinkingSphinx::Deletion.perform index, 7
- }.should_not raise_error
+ }.not_to raise_error
end
end
end
diff --git a/spec/thinking_sphinx/deltas/default_delta_spec.rb b/spec/thinking_sphinx/deltas/default_delta_spec.rb
index 5fa341fc0..f5088c3f4 100644
--- a/spec/thinking_sphinx/deltas/default_delta_spec.rb
+++ b/spec/thinking_sphinx/deltas/default_delta_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::Deltas::DefaultDelta do
@@ -9,11 +11,11 @@
describe '#clause' do
context 'for a delta source' do
before :each do
- adapter.stub :boolean_value => 't'
+ allow(adapter).to receive_messages :boolean_value => 't'
end
it "limits results to those flagged as deltas" do
- delta.clause(true).should == "articles.delta = t"
+ expect(delta.clause(true)).to eq("articles.delta = t")
end
end
end
@@ -21,46 +23,46 @@
describe '#delete' do
let(:connection) { double('connection', :execute => nil) }
let(:index) { double('index', :name => 'foo_core',
- :document_id_for_key => 14) }
+ :document_id_for_instance => 14) }
let(:instance) { double('instance', :id => 7) }
before :each do
- ThinkingSphinx::Connection.stub(:take).and_yield(connection)
- Riddle::Query.stub :update => 'UPDATE STATEMENT'
+ allow(ThinkingSphinx::Connection).to receive(:take).and_yield(connection)
+ allow(Riddle::Query).to receive_messages :update => 'UPDATE STATEMENT'
end
it "updates the deleted flag to false" do
- connection.should_receive(:execute).with('UPDATE STATEMENT')
+ expect(connection).to receive(:execute).with('UPDATE STATEMENT')
delta.delete index, instance
end
it "builds the update query for the given index" do
- Riddle::Query.should_receive(:update).
+ expect(Riddle::Query).to receive(:update).
with('foo_core', anything, anything).and_return('')
delta.delete index, instance
end
it "builds the update query for the sphinx document id" do
- Riddle::Query.should_receive(:update).
+ expect(Riddle::Query).to receive(:update).
with(anything, 14, anything).and_return('')
delta.delete index, instance
end
it "builds the update query for setting sphinx_deleted to true" do
- Riddle::Query.should_receive(:update).
+ expect(Riddle::Query).to receive(:update).
with(anything, anything, :sphinx_deleted => true).and_return('')
delta.delete index, instance
end
it "doesn't care about Sphinx errors" do
- connection.stub(:execute).
+ allow(connection).to receive(:execute).
and_raise(ThinkingSphinx::ConnectionError.new(''))
- lambda { delta.delete index, instance }.should_not raise_error
+ expect { delta.delete index, instance }.not_to raise_error
end
end
@@ -68,13 +70,18 @@
let(:config) { double('config', :controller => controller,
:settings => {}) }
let(:controller) { double('controller') }
+ let(:commander) { double('commander', :call => true) }
before :each do
- ThinkingSphinx::Configuration.stub :instance => config
+ stub_const 'ThinkingSphinx::Commander', commander
+
+ allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
it "indexes the given index" do
- controller.should_receive(:index).with('foo_delta', :verbose => true)
+ expect(commander).to receive(:call).with(
+ :index_sql, config, :indices => ['foo_delta'], :verbose => false
+ )
delta.index double('index', :name => 'foo_delta')
end
@@ -82,9 +89,9 @@
describe '#reset_query' do
it "updates the table to set delta flags to false" do
- adapter.stub(:boolean_value) { |value| value ? 't' : 'f' }
- delta.reset_query.
- should == 'UPDATE articles SET delta = f WHERE delta = t'
+ allow(adapter).to receive(:boolean_value) { |value| value ? 't' : 'f' }
+ expect(delta.reset_query).
+ to eq('UPDATE articles SET delta = f WHERE delta = t')
end
end
@@ -92,7 +99,7 @@
let(:instance) { double('instance') }
it "sets instance's delta flag to true" do
- instance.should_receive(:delta=).with(true)
+ expect(instance).to receive(:delta=).with(true)
delta.toggle(instance)
end
@@ -102,15 +109,15 @@
let(:instance) { double('instance') }
it "returns the delta flag value when true" do
- instance.stub! :delta? => true
+ allow(instance).to receive_messages :delta? => true
- delta.toggled?(instance).should be_true
+ expect(delta.toggled?(instance)).to be_truthy
end
it "returns the delta flag value when false" do
- instance.stub! :delta? => false
+ allow(instance).to receive_messages :delta? => false
- delta.toggled?(instance).should be_false
+ expect(delta.toggled?(instance)).to be_falsey
end
end
end
diff --git a/spec/thinking_sphinx/deltas_spec.rb b/spec/thinking_sphinx/deltas_spec.rb
index be55bf7f2..4e62fdcfd 100644
--- a/spec/thinking_sphinx/deltas_spec.rb
+++ b/spec/thinking_sphinx/deltas_spec.rb
@@ -1,21 +1,23 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::Deltas do
describe '.processor_for' do
it "returns the default processor class when given true" do
- ThinkingSphinx::Deltas.processor_for(true).
- should == ThinkingSphinx::Deltas::DefaultDelta
+ expect(ThinkingSphinx::Deltas.processor_for(true)).
+ to eq(ThinkingSphinx::Deltas::DefaultDelta)
end
it "returns the class when given one" do
klass = Class.new
- ThinkingSphinx::Deltas.processor_for(klass).should == klass
+ expect(ThinkingSphinx::Deltas.processor_for(klass)).to eq(klass)
end
it "instantiates a class from the name as a string" do
- ThinkingSphinx::Deltas.
- processor_for('ThinkingSphinx::Deltas::DefaultDelta').
- should == ThinkingSphinx::Deltas::DefaultDelta
+ expect(ThinkingSphinx::Deltas.
+ processor_for('ThinkingSphinx::Deltas::DefaultDelta')).
+ to eq(ThinkingSphinx::Deltas::DefaultDelta)
end
end
@@ -29,7 +31,7 @@
let(:processor) { double('processor', :index => true) }
before :each do
- ThinkingSphinx::Configuration.stub :instance => config
+ allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
it "executes the given block" do
@@ -39,12 +41,12 @@
variable = :bar
end
- variable.should == :bar
+ expect(variable).to eq(:bar)
end
it "suspends deltas within the block" do
ThinkingSphinx::Deltas.suspend :user do
- ThinkingSphinx::Deltas.should be_suspended
+ expect(ThinkingSphinx::Deltas).to be_suspended
end
end
@@ -53,11 +55,11 @@
#
end
- ThinkingSphinx::Deltas.should_not be_suspended
+ expect(ThinkingSphinx::Deltas).not_to be_suspended
end
it "processes the delta indices for the given reference" do
- processor.should_receive(:index).with(delta_index)
+ expect(processor).to receive(:index).with(delta_index)
ThinkingSphinx::Deltas.suspend :user do
#
@@ -65,7 +67,7 @@
end
it "does not process the core indices for the given reference" do
- processor.should_not_receive(:index).with(core_index)
+ expect(processor).not_to receive(:index).with(core_index)
ThinkingSphinx::Deltas.suspend :user do
#
diff --git a/spec/thinking_sphinx/errors_spec.rb b/spec/thinking_sphinx/errors_spec.rb
index c40b01b7b..99ea2a2b0 100644
--- a/spec/thinking_sphinx/errors_spec.rb
+++ b/spec/thinking_sphinx/errors_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::SphinxError do
@@ -6,66 +8,96 @@
:backtrace => ['foo', 'bar'] }
it "translates syntax errors" do
- error.stub :message => 'index foo: syntax error: something is wrong'
+ allow(error).to receive_messages :message => 'index foo: syntax error: something is wrong'
- ThinkingSphinx::SphinxError.new_from_mysql(error).
- should be_a(ThinkingSphinx::SyntaxError)
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::SyntaxError)
end
it "translates parse errors" do
- error.stub :message => 'index foo: parse error: something is wrong'
+ allow(error).to receive_messages :message => 'index foo: parse error: something is wrong'
+
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::ParseError)
+ end
- ThinkingSphinx::SphinxError.new_from_mysql(error).
- should be_a(ThinkingSphinx::ParseError)
+ it "translates 'query is non-computable' errors" do
+ allow(error).to receive_messages :message => 'index model_core: query is non-computable (single NOT operator)'
+
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::ParseError)
end
it "translates query errors" do
- error.stub :message => 'index foo: query error: something is wrong'
+ allow(error).to receive_messages :message => 'index foo: query error: something is wrong'
- ThinkingSphinx::SphinxError.new_from_mysql(error).
- should be_a(ThinkingSphinx::QueryError)
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::QueryError)
end
it "translates connection errors" do
- error.stub :message => "Can't connect to MySQL server on '127.0.0.1' (61)"
+ allow(error).to receive_messages :message => "Can't connect to MySQL server on '127.0.0.1' (61)"
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::ConnectionError)
+
+ allow(error).to receive_messages :message => "Communications link failure"
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::ConnectionError)
+
+ allow(error).to receive_messages :message => "Lost connection to MySQL server"
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::ConnectionError)
+
+ # MariaDB has removed mention of MySQL in error messages:
+ allow(error).to receive_messages :message => "Can't connect to server on '127.0.0.1' (61)"
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::ConnectionError)
+
+ allow(error).to receive_messages :message => "Lost connection to server"
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::ConnectionError)
+ end
+
+ it 'translates out-of-bounds errors' do
+ allow(error).to receive_messages :message => "offset out of bounds (offset=1001, max_matches=1000)"
- ThinkingSphinx::SphinxError.new_from_mysql(error).
- should be_a(ThinkingSphinx::ConnectionError)
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to 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)"
+ allow(error).to receive_messages :message => "Can't connect to MySQL server on '127.0.0.1' (61)"
- ThinkingSphinx::SphinxError.new_from_mysql(error).message.
- should == "Error connecting to Sphinx via the MySQL protocol. Can't connect to MySQL server on '127.0.0.1' (61)"
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error).message).
+ to eq("Error connecting to Sphinx via the MySQL protocol. Can't connect to MySQL server on '127.0.0.1' (61)")
end
it "translates jdbc connection errors" do
- error.stub :message => "Communications link failure"
+ allow(error).to receive_messages :message => "Communications link failure"
- ThinkingSphinx::SphinxError.new_from_mysql(error).
- should be_a(ThinkingSphinx::ConnectionError)
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::ConnectionError)
end
it 'prefixes the jdbc connection error message' do
- error.stub :message => "Communications link failure"
+ allow(error).to receive_messages :message => "Communications link failure"
- ThinkingSphinx::SphinxError.new_from_mysql(error).message.
- should == "Error connecting to Sphinx via the MySQL protocol. Communications link failure"
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error).message).
+ to eq("Error connecting to Sphinx via the MySQL protocol. Communications link failure")
end
it "defaults to sphinx errors" do
- error.stub :message => 'index foo: unknown error: something is wrong'
+ allow(error).to receive_messages :message => 'index foo: unknown error: something is wrong'
- ThinkingSphinx::SphinxError.new_from_mysql(error).
- should be_a(ThinkingSphinx::SphinxError)
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
+ to be_a(ThinkingSphinx::SphinxError)
end
it "keeps the original error's backtrace" do
- error.stub :message => 'index foo: unknown error: something is wrong'
+ allow(error).to receive_messages :message => 'index foo: unknown error: something is wrong'
- ThinkingSphinx::SphinxError.new_from_mysql(error).
- backtrace.should == error.backtrace
+ expect(ThinkingSphinx::SphinxError.new_from_mysql(error).
+ backtrace).to eq(error.backtrace)
end
end
end
diff --git a/spec/thinking_sphinx/excerpter_spec.rb b/spec/thinking_sphinx/excerpter_spec.rb
index 6ad095a30..abb6e0ae7 100644
--- a/spec/thinking_sphinx/excerpter_spec.rb
+++ b/spec/thinking_sphinx/excerpter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::Excerpter do
@@ -7,13 +9,13 @@
}
before :each do
- ThinkingSphinx::Connection.stub(:take).and_yield(connection)
- Riddle::Query.stub :snippets => 'CALL SNIPPETS'
+ allow(ThinkingSphinx::Connection).to receive(:take).and_yield(connection)
+ allow(Riddle::Query).to receive_messages :snippets => 'CALL SNIPPETS'
end
describe '#excerpt!' do
it "generates a snippets call" do
- Riddle::Query.should_receive(:snippets).
+ expect(Riddle::Query).to receive(:snippets).
with('all of the words', 'index', 'all words',
ThinkingSphinx::Excerpter::DefaultOptions).
and_return('CALL SNIPPETS')
@@ -25,26 +27,27 @@
excerpter = ThinkingSphinx::Excerpter.new('index', 'all words',
:before_match => '', :chunk_separator => ' -- ')
- Riddle::Query.should_receive(:snippets).
- with('all of the words', 'index', 'all words',
+ expect(Riddle::Query).to receive(:snippets).
+ with('all of the words', 'index', 'all words', {
:before_match => '', :after_match => '',
- :chunk_separator => ' -- ').
+ :chunk_separator => ' -- '
+ }).
and_return('CALL SNIPPETS')
excerpter.excerpt!('all of the words')
end
it "sends the snippets call to Sphinx" do
- connection.should_receive(:execute).with('CALL SNIPPETS').
+ expect(connection).to receive(:execute).with('CALL SNIPPETS').
and_return([{'snippet' => ''}])
excerpter.excerpt!('all of the words')
end
it "returns the first value returned by Sphinx" do
- connection.stub :execute => [{'snippet' => 'some highlighted words'}]
+ allow(connection).to receive_messages :execute => [{'snippet' => 'some highlighted words'}]
- excerpter.excerpt!('all of the words').should == 'some highlighted words'
+ expect(excerpter.excerpt!('all of the words')).to eq('some highlighted words')
end
end
end
diff --git a/spec/thinking_sphinx/facet_search_spec.rb b/spec/thinking_sphinx/facet_search_spec.rb
index bab2f32c3..e91fa48f3 100644
--- a/spec/thinking_sphinx/facet_search_spec.rb
+++ b/spec/thinking_sphinx/facet_search_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::FacetSearch do
@@ -25,19 +27,19 @@
DumbSearch = ::Struct.new(:query, :options) do
def raw
[{
- 'sphinx_internal_class' => 'Foo',
- 'price_bracket' => 3,
- 'tag_ids' => '1,2',
- 'category_id' => 11,
- ThinkingSphinx::SphinxQL.count[:column] => 5,
- ThinkingSphinx::SphinxQL.group_by[:column] => 2
+ 'sphinx_internal_class' => 'Foo',
+ 'price_bracket' => 3,
+ 'tag_ids' => '1,2',
+ 'category_id' => 11,
+ "sphinx_internal_count" => 5,
+ "sphinx_internal_group" => 2
}]
end
end
describe '#[]' do
it "populates facet results" do
- facet_search[:price_bracket].should == {3 => 5}
+ expect(facet_search[:price_bracket]).to eq({3 => 5})
end
end
@@ -45,17 +47,17 @@ def raw
it "queries on each facet with a grouped search in a batch" do
facet_search.populate
- batch.searches.detect { |search|
+ expect(batch.searches.detect { |search|
search.options[:group_by] == 'price_bracket'
- }.should_not be_nil
+ }).not_to be_nil
end
it "limits query for a facet to just indices that have that facet" do
facet_search.populate
- batch.searches.detect { |search|
+ expect(batch.searches.detect { |search|
search.options[:indices] == ['foo_core']
- }.should_not be_nil
+ }).not_to be_nil
end
it "limits facets to the specified set" do
@@ -63,25 +65,25 @@ def raw
facet_search.populate
- batch.searches.collect { |search|
+ expect(batch.searches.collect { |search|
search.options[:group_by]
- }.should == ['category_id']
+ }).to eq(['category_id'])
end
it "aliases the class facet from sphinx_internal_class" do
- property_a.stub :name => 'sphinx_internal_class'
+ allow(property_a).to receive_messages :name => 'sphinx_internal_class'
facet_search.populate
- facet_search[:class].should == {'Foo' => 5}
+ expect(facet_search[:class]).to eq({'Foo' => 5})
end
it "uses the @groupby value for MVAs" do
- property_a.stub :name => 'tag_ids', :multi? => true
+ allow(property_a).to receive_messages :name => 'tag_ids', :multi? => true
facet_search.populate
- facet_search[:tag_ids].should == {2 => 5}
+ expect(facet_search[:tag_ids]).to eq({2 => 5})
end
[:max_matches, :limit].each do |setting|
@@ -89,7 +91,7 @@ def raw
facet_search.populate
batch.searches.each { |search|
- search.options[setting].should == 1000
+ expect(search.options[setting]).to eq(1000)
}
end
@@ -99,7 +101,7 @@ def raw
facet_search.populate
batch.searches.each { |search|
- search.options[setting].should == 1234
+ expect(search.options[setting]).to eq(1234)
}
end
end
@@ -111,7 +113,7 @@ def raw
facet_search.populate
batch.searches.each { |search|
- search.options[setting].should == 42
+ expect(search.options[setting]).to eq(42)
}
end
@@ -122,8 +124,8 @@ def raw
facet_search.populate
batch.searches.each do |search|
- search.options[setting].should == 10
- search.options[:max_matches].should == 500
+ expect(search.options[setting]).to eq(10)
+ expect(search.options[:max_matches]).to eq(500)
end
end
end
diff --git a/spec/thinking_sphinx/hooks/guard_presence_spec.rb b/spec/thinking_sphinx/hooks/guard_presence_spec.rb
new file mode 100644
index 000000000..c144b6342
--- /dev/null
+++ b/spec/thinking_sphinx/hooks/guard_presence_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe ThinkingSphinx::Hooks::GuardPresence do
+ let(:subject) do
+ ThinkingSphinx::Hooks::GuardPresence.new configuration, stream
+ end
+ let(:configuration) { double "configuration", :indices_location => "/path" }
+ let(:stream) { double "stream", :puts => nil }
+
+ describe "#call" do
+ it "outputs nothing if no guard files exist" do
+ allow(Dir).to receive(:[]).with('/path/ts-*.tmp').and_return([])
+
+ expect(stream).not_to receive(:puts)
+
+ subject.call
+ end
+
+ it "outputs a warning if a guard file exists" do
+ allow(Dir).to receive(:[]).with('/path/ts-*.tmp').
+ and_return(['/path/ts-foo.tmp'])
+
+ expect(stream).to receive(:puts)
+
+ subject.call
+ end
+ end
+end
diff --git a/spec/thinking_sphinx/index_set_spec.rb b/spec/thinking_sphinx/index_set_spec.rb
index 5c58a7cfd..3197009bd 100644
--- a/spec/thinking_sphinx/index_set_spec.rb
+++ b/spec/thinking_sphinx/index_set_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx; end
require 'active_support/core_ext/string/inflections'
@@ -15,15 +17,31 @@ module ThinkingSphinx; end
stub_const 'ActiveRecord::Base', ar_base
end
- def class_double(name, *superclasses)
+ def class_double(name, methods = {}, *superclasses)
klass = double 'class', :name => name, :class => Class
- klass.stub :ancestors => ([klass] + superclasses + [ar_base])
+
+ allow(klass).to receive_messages(
+ :ancestors => ([klass] + superclasses + [ar_base]),
+ :inheritance_column => :type
+ )
+ allow(klass).to receive_messages(methods)
+
klass
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
- configuration.should_receive(:preload_indices)
+ expect(configuration).to receive(:preload_indices)
set.to_a
end
@@ -37,33 +55,56 @@ def class_double(name, *superclasses)
configuration.indices.replace [article_core, user_core, distributed]
- set.to_a.should == [article_core, user_core]
+ expect(set.to_a).to eq([article_core, user_core])
end
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')]
+ options[:classes] = [class_double('Article', :column_names => [])]
- set.to_a.length.should == 1
+ expect(set.to_a).to eq([article_index])
end
- it "requests indices for any superclasses" do
+ it "uses indices for the given instance's class" 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('OpinionArticle', class_double('Article'))
+ 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 [
+ article_index, opinion_article_index, page_index
+ ]
+
+ article = class_double('Article', :column_names => [:type])
+ opinion = class_double('OpinionArticle', {:column_names => [:type]},
+ article)
+
+ options[:classes] = [opinion]
+
+ expect(set.to_a).to eq([article_index, opinion_article_index])
+ end
+
+ it "does not use MTI superclasses" do
+ configuration.indices.replace [
+ article_index, opinion_article_index, page_index
]
- set.to_a.length.should == 2
+ article = class_double('Article', :column_names => [])
+ opinion = class_double('OpinionArticle', {:column_names => []}, article)
+
+ options[:classes] = [opinion]
+
+ expect(set.to_a).to eq([opinion_article_index])
end
it "uses named indices if names are provided" do
@@ -73,7 +114,7 @@ def class_double(name, *superclasses)
options[:indices] = ['article_core']
- set.to_a.should == [article_core]
+ expect(set.to_a).to eq([article_core])
end
it "selects from the full index set those with matching references" do
@@ -85,7 +126,7 @@ def class_double(name, *superclasses)
options[:references] = [:book, :article]
- set.to_a.length.should == 2
+ expect(set.to_a.length).to eq(2)
end
end
end
diff --git a/spec/thinking_sphinx/index_spec.rb b/spec/thinking_sphinx/index_spec.rb
index 129201a95..7d16e2767 100644
--- a/spec/thinking_sphinx/index_spec.rb
+++ b/spec/thinking_sphinx/index_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::Index do
let(:configuration) { Struct.new(:indices, :settings).new([], {}) }
before :each do
- ThinkingSphinx::Configuration.stub :instance => configuration
+ allow(ThinkingSphinx::Configuration).to receive_messages :instance => configuration
end
describe '.define' do
@@ -12,29 +14,29 @@
context 'with ActiveRecord' do
before :each do
- ThinkingSphinx::ActiveRecord::Index.stub :new => index
+ allow(ThinkingSphinx::ActiveRecord::Index).to receive_messages :new => index
end
it "creates an ActiveRecord index" do
- ThinkingSphinx::ActiveRecord::Index.should_receive(:new).
- with(:user, :with => :active_record).and_return index
+ expect(ThinkingSphinx::ActiveRecord::Index).to receive(:new).
+ with(:user, { :with => :active_record }).and_return index
ThinkingSphinx::Index.define(:user, :with => :active_record)
end
it "returns the ActiveRecord index" do
- ThinkingSphinx::Index.define(:user, :with => :active_record).
- should == [index]
+ expect(ThinkingSphinx::Index.define(:user, :with => :active_record)).
+ to eq([index])
end
it "adds the index to the collection of indices" do
ThinkingSphinx::Index.define(:user, :with => :active_record)
- configuration.indices.should include(index)
+ expect(configuration.indices).to include(index)
end
it "sets the block in the index" do
- index.should_receive(:definition_block=).with instance_of(Proc)
+ expect(index).to receive(:definition_block=).with instance_of(Proc)
ThinkingSphinx::Index.define(:user, :with => :active_record) do
indexes name
@@ -46,19 +48,19 @@
let(:processor) { double('delta processor') }
before :each do
- ThinkingSphinx::Deltas.stub :processor_for => processor
- ThinkingSphinx::ActiveRecord::Index.stub(:new).
+ allow(ThinkingSphinx::Deltas).to receive_messages :processor_for => processor
+ allow(ThinkingSphinx::ActiveRecord::Index).to receive(:new).
and_return(index, delta_index)
end
it "creates two indices with delta settings" do
- ThinkingSphinx::ActiveRecord::Index.unstub :new
- ThinkingSphinx::ActiveRecord::Index.should_receive(:new).
+ allow(ThinkingSphinx::ActiveRecord::Index).to receive(:new).and_call_original
+ expect(ThinkingSphinx::ActiveRecord::Index).to receive(:new).
with(:user,
hash_including(:delta? => false, :delta_processor => processor)
).once.
and_return index
- ThinkingSphinx::ActiveRecord::Index.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::Index).to receive(:new).
with(:user,
hash_including(:delta? => true, :delta_processor => processor)
).once.
@@ -74,13 +76,13 @@
:with => :active_record,
:delta => true
- configuration.indices.should include(index)
- configuration.indices.should include(delta_index)
+ expect(configuration.indices).to include(index)
+ expect(configuration.indices).to include(delta_index)
end
it "sets the block in the index" do
- index.should_receive(:definition_block=).with instance_of(Proc)
- delta_index.should_receive(:definition_block=).with instance_of(Proc)
+ expect(index).to receive(:definition_block=).with instance_of(Proc)
+ expect(delta_index).to receive(:definition_block=).with instance_of(Proc)
ThinkingSphinx::Index.define(:user,
:with => :active_record,
@@ -93,29 +95,29 @@
context 'with Real-Time' do
before :each do
- ThinkingSphinx::RealTime::Index.stub :new => index
+ allow(ThinkingSphinx::RealTime::Index).to receive_messages :new => index
end
it "creates a real-time index" do
- ThinkingSphinx::RealTime::Index.should_receive(:new).
- with(:user, :with => :real_time).and_return index
+ expect(ThinkingSphinx::RealTime::Index).to receive(:new).
+ with(:user, { :with => :real_time }).and_return index
ThinkingSphinx::Index.define(:user, :with => :real_time)
end
it "returns the ActiveRecord index" do
- ThinkingSphinx::Index.define(:user, :with => :real_time).
- should == [index]
+ expect(ThinkingSphinx::Index.define(:user, :with => :real_time)).
+ to eq([index])
end
it "adds the index to the collection of indices" do
ThinkingSphinx::Index.define(:user, :with => :real_time)
- configuration.indices.should include(index)
+ expect(configuration.indices).to include(index)
end
it "sets the block in the index" do
- index.should_receive(:definition_block=).with instance_of(Proc)
+ expect(index).to receive(:definition_block=).with instance_of(Proc)
ThinkingSphinx::Index.define(:user, :with => :real_time) do
indexes name
@@ -126,13 +128,13 @@
describe '#initialize' do
it "is fine with no defaults from settings" do
- ThinkingSphinx::Index.new(:user, {}).options.should == {}
+ expect(ThinkingSphinx::Index.new(:user, {}).options).to eq({})
end
it "respects defaults from settings" do
configuration.settings['index_options'] = {'delta' => true}
- ThinkingSphinx::Index.new(:user, {}).options.should == {:delta => true}
+ expect(ThinkingSphinx::Index.new(:user, {}).options).to eq({:delta => true})
end
end
end
diff --git a/spec/thinking_sphinx/interfaces/daemon_spec.rb b/spec/thinking_sphinx/interfaces/daemon_spec.rb
new file mode 100644
index 000000000..d2f5e8fc0
--- /dev/null
+++ b/spec/thinking_sphinx/interfaces/daemon_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Interfaces::Daemon do
+ let(:configuration) { double 'configuration' }
+ let(:stream) { double 'stream', :puts => true }
+ let(:commander) { double :call => nil }
+ let(:interface) {
+ ThinkingSphinx::Interfaces::Daemon.new(configuration, {}, stream)
+ }
+
+ before :each do
+ stub_const 'ThinkingSphinx::Commander', commander
+
+ allow(commander).to receive(:call).
+ with(:running, configuration, {}, stream).and_return(false)
+ end
+
+ describe '#start' do
+ it "starts the daemon" do
+ expect(commander).to receive(:call).with(
+ :start_detached, configuration, {}, stream
+ )
+
+ interface.start
+ end
+
+ it "raises an error if the daemon is already running" do
+ allow(commander).to receive(:call).
+ with(:running, configuration, {}, stream).and_return(true)
+
+ expect {
+ interface.start
+ }.to raise_error(ThinkingSphinx::SphinxAlreadyRunning)
+ end
+ end
+
+ describe '#status' do
+ it "reports when the daemon is running" do
+ allow(commander).to receive(:call).
+ with(:running, configuration, {}, stream).and_return(true)
+
+ expect(stream).to receive(:puts).
+ with('The Sphinx daemon searchd is currently running.')
+
+ interface.status
+ end
+
+ it "reports when the daemon is not running" do
+ allow(commander).to receive(:call).
+ with(:running, configuration, {}, stream).and_return(false)
+
+ expect(stream).to receive(:puts).
+ with('The Sphinx daemon searchd is not currently running.')
+
+ interface.status
+ end
+ end
+end
diff --git a/spec/thinking_sphinx/interfaces/real_time_spec.rb b/spec/thinking_sphinx/interfaces/real_time_spec.rb
new file mode 100644
index 000000000..09a1a39ea
--- /dev/null
+++ b/spec/thinking_sphinx/interfaces/real_time_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Interfaces::SQL do
+ let(:interface) { ThinkingSphinx::Interfaces::RealTime.new(
+ configuration, {}, stream
+ ) }
+ let(:configuration) { double 'configuration', :controller => controller,
+ :render => true, :indices_location => '/path/to/indices',
+ :preload_indices => true }
+ let(:controller) { double 'controller', :running? => true }
+ let(:commander) { double :call => true }
+ let(:stream) { double :puts => nil }
+
+ before :each do
+ stub_const "ThinkingSphinx::Commander", commander
+ end
+
+ describe '#clear' do
+ let(:plain_index) { double(:type => 'plain') }
+ let(:users_index) { double(:name => 'users', :type => 'rt', :render => true,
+ :path => '/path/to/my/index/users') }
+ let(:parts_index) { double(:name => 'parts', :type => 'rt', :render => true,
+ :path => '/path/to/my/index/parts') }
+
+ before :each do
+ allow(configuration).to receive_messages(
+ :indices => [plain_index, users_index, parts_index]
+ )
+ end
+
+ it 'prepares the indices' do
+ expect(commander).to receive(:call).with(
+ :prepare, configuration, {}, stream
+ )
+
+ interface.clear
+ end
+
+ it 'invokes the clear command' do
+ expect(commander).to receive(:call).with(
+ :clear_real_time,
+ configuration,
+ {:indices => [users_index, parts_index]},
+ stream
+ )
+
+ interface.clear
+ end
+
+ context "with options[:index_names]" do
+ let(:interface) { ThinkingSphinx::Interfaces::RealTime.new(
+ configuration, {:index_names => ['users']}, stream
+ ) }
+
+ it "removes each file for real-time indices that match :index_filter" do
+ expect(commander).to receive(:call).with(
+ :clear_real_time,
+ configuration,
+ {:index_names => ['users'], :indices => [users_index]},
+ stream
+ )
+
+ interface.clear
+ end
+ end
+ end
+
+ describe '#index' do
+ let(:plain_index) { double(:type => 'plain') }
+ let(:users_index) { double(name: 'users', :type => 'rt') }
+ let(:parts_index) { double(name: 'parts', :type => 'rt') }
+
+ before :each do
+ allow(configuration).to receive_messages(
+ :indices => [plain_index, users_index, parts_index]
+ )
+ end
+
+ it 'invokes the index command with real-time indices' do
+ expect(commander).to receive(:call).with(
+ :index_real_time,
+ configuration,
+ {:indices => [users_index, parts_index]},
+ stream
+ )
+
+ interface.index
+ end
+
+ context "with options[:index_names]" do
+ let(:interface) { ThinkingSphinx::Interfaces::RealTime.new(
+ configuration, {:index_names => ['users']}, stream
+ ) }
+
+ it 'invokes the index command for matching indices' do
+ expect(commander).to receive(:call).with(
+ :index_real_time,
+ configuration,
+ {:index_names => ['users'], :indices => [users_index]},
+ stream
+ )
+
+ interface.index
+ end
+ end
+ end
+end
diff --git a/spec/thinking_sphinx/interfaces/sql_spec.rb b/spec/thinking_sphinx/interfaces/sql_spec.rb
new file mode 100644
index 000000000..9a6293c07
--- /dev/null
+++ b/spec/thinking_sphinx/interfaces/sql_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Interfaces::SQL do
+ let(:interface) { ThinkingSphinx::Interfaces::SQL.new(
+ configuration, {:verbose => true}, stream
+ ) }
+ let(:commander) { double :call => true }
+ let(:configuration) { double 'configuration', :preload_indices => true,
+ :render => true, :indices => [double(:index, :type => 'plain')] }
+ let(:stream) { double :puts => nil }
+
+ before :each do
+ stub_const 'ThinkingSphinx::Commander', commander
+ end
+
+ describe '#clear' do
+ let(:users_index) { double(:type => 'plain') }
+ let(:parts_index) { double(:type => 'plain') }
+ let(:rt_index) { double(:type => 'rt') }
+
+ before :each do
+ allow(configuration).to receive(:indices).
+ and_return([users_index, parts_index, rt_index])
+ end
+
+ it "invokes the clear_sql command" do
+ expect(commander).to receive(:call).with(
+ :clear_sql,
+ configuration,
+ {:verbose => true, :indices => [users_index, parts_index]},
+ stream
+ )
+
+ interface.clear
+ end
+ end
+
+ describe '#index' do
+ it "invokes the prepare command" do
+ expect(commander).to receive(:call).with(
+ :prepare, configuration, {:verbose => true}, stream
+ )
+
+ interface.index
+ end
+
+ it "renders the configuration to a file by default" do
+ expect(commander).to receive(:call).with(
+ :configure, configuration, {:verbose => true}, stream
+ )
+
+ interface.index
+ end
+
+ it "does not render the configuration if requested" do
+ expect(commander).not_to receive(:call).with(
+ :configure, configuration, {:verbose => true}, stream
+ )
+
+ interface.index false
+ end
+
+ it "executes the index command" do
+ expect(commander).to receive(:call).with(
+ :index_sql, configuration, {:verbose => true, :indices => nil}, stream
+ )
+
+ interface.index
+ end
+
+ context "with options[:index_names]" do
+ let(:users_index) { double(:name => 'users', :type => 'plain') }
+ let(:parts_index) { double(:name => 'parts', :type => 'plain') }
+ let(:rt_index) { double(:type => 'rt') }
+ let(:interface) { ThinkingSphinx::Interfaces::SQL.new(
+ configuration, {:index_names => ['users']}, stream
+ ) }
+
+ before :each do
+ allow(configuration).to receive(:indices).
+ and_return([users_index, parts_index, rt_index])
+ end
+
+ it 'invokes the index command for matching indices' do
+ expect(commander).to receive(:call).with(
+ :index_sql,
+ configuration,
+ {:index_names => ['users'], :indices => ['users']},
+ stream
+ )
+
+ interface.index
+ end
+ end
+ end
+
+ describe '#merge' do
+ it "invokes the merge command" do
+ expect(commander).to receive(:call).with(
+ :merge_and_update, configuration, {:verbose => true}, stream
+ )
+
+ interface.merge
+ end
+
+ context "with options[:index_names]" do
+ let(:interface) { ThinkingSphinx::Interfaces::SQL.new(
+ configuration, {:index_names => ['users']}, stream
+ ) }
+
+ it 'invokes the merge command with the index_names option' do
+ expect(commander).to receive(:call).with(
+ :merge_and_update, configuration, {:index_names => ['users']}, stream
+ )
+
+ interface.merge
+ end
+ end
+ end
+end
diff --git a/spec/thinking_sphinx/masks/pagination_mask_spec.rb b/spec/thinking_sphinx/masks/pagination_mask_spec.rb
index 433cbe7f4..1c8ed3afc 100644
--- a/spec/thinking_sphinx/masks/pagination_mask_spec.rb
+++ b/spec/thinking_sphinx/masks/pagination_mask_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Masks; end
end
@@ -12,13 +14,13 @@ module Masks; end
describe '#first_page?' do
it "returns true when on the first page" do
- mask.should be_first_page
+ expect(mask).to be_first_page
end
it "returns false on other pages" do
- search.stub :current_page => 2
+ allow(search).to receive_messages :current_page => 2
- mask.should_not be_first_page
+ expect(mask).not_to be_first_page
end
end
@@ -28,13 +30,13 @@ module Masks; end
end
it "is true when there's no more pages" do
- search.stub :current_page => 3
+ allow(search).to receive_messages :current_page => 3
- mask.should be_last_page
+ expect(mask).to be_last_page
end
it "is false when there's still more pages" do
- mask.should_not be_last_page
+ expect(mask).not_to be_last_page
end
end
@@ -44,13 +46,13 @@ module Masks; end
end
it "should return one more than the current page" do
- mask.next_page.should == 2
+ expect(mask.next_page).to eq(2)
end
it "should return nil if on the last page" do
- search.stub :current_page => 3
+ allow(search).to receive_messages :current_page => 3
- mask.next_page.should be_nil
+ expect(mask.next_page).to be_nil
end
end
@@ -60,13 +62,13 @@ module Masks; end
end
it "is true when there is a second page" do
- mask.next_page?.should be_true
+ expect(mask.next_page?).to be_truthy
end
it "is false when there's no more pages" do
- search.stub :current_page => 3
+ allow(search).to receive_messages :current_page => 3
- mask.next_page?.should be_false
+ expect(mask.next_page?).to be_falsey
end
end
@@ -76,13 +78,13 @@ module Masks; end
end
it "should return one less than the current page" do
- search.stub :current_page => 2
+ allow(search).to receive_messages :current_page => 2
- mask.previous_page.should == 1
+ expect(mask.previous_page).to eq(1)
end
it "should return nil if on the first page" do
- mask.previous_page.should be_nil
+ expect(mask.previous_page).to be_nil
end
end
@@ -92,7 +94,7 @@ module Masks; end
end
it "returns the total found from the search request metadata" do
- mask.total_entries.should == 12
+ expect(mask.total_entries).to eq(12)
end
end
@@ -103,19 +105,19 @@ module Masks; end
end
it "uses the total available from the search request metadata" do
- mask.total_pages.should == 2
+ expect(mask.total_pages).to eq(2)
end
it "should allow for custom per_page values" do
- search.stub :per_page => 40
+ allow(search).to receive_messages :per_page => 40
- mask.total_pages.should == 1
+ expect(mask.total_pages).to eq(1)
end
it "should return 0 if there is no index and therefore no results" do
search.meta.clear
- mask.total_pages.should == 0
+ expect(mask.total_pages).to eq(0)
end
end
end
diff --git a/spec/thinking_sphinx/masks/scopes_mask_spec.rb b/spec/thinking_sphinx/masks/scopes_mask_spec.rb
index 334b1d2be..7f4db502e 100644
--- a/spec/thinking_sphinx/masks/scopes_mask_spec.rb
+++ b/spec/thinking_sphinx/masks/scopes_mask_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Masks; end
end
@@ -10,18 +12,18 @@ module Masks; end
let(:mask) { ThinkingSphinx::Masks::ScopesMask.new search }
before :each do
- FileUtils.stub :mkdir_p => true
+ allow(FileUtils).to receive_messages :mkdir_p => true
end
describe '#search' do
it "replaces the query if one is supplied" do
- search.should_receive(:query=).with('bar')
+ expect(search).to receive(:query=).with('bar')
mask.search('bar')
end
it "keeps the existing query when only options are offered" do
- search.should_not_receive(:query=)
+ expect(search).not_to receive(:query=)
mask.search :with => {:foo => :bar}
end
@@ -31,7 +33,7 @@ module Masks; end
mask.search :conditions => {:baz => 'qux'}
- search.options[:conditions].should == {:foo => 'bar', :baz => 'qux'}
+ expect(search.options[:conditions]).to eq({:foo => 'bar', :baz => 'qux'})
end
it "merges filters" do
@@ -39,7 +41,7 @@ module Masks; end
mask.search :with => {:baz => :qux}
- search.options[:with].should == {:foo => :bar, :baz => :qux}
+ expect(search.options[:with]).to eq({:foo => :bar, :baz => :qux})
end
it "merges exclusive filters" do
@@ -47,7 +49,7 @@ module Masks; end
mask.search :without => {:baz => :qux}
- search.options[:without].should == {:foo => :bar, :baz => :qux}
+ expect(search.options[:without]).to eq({:foo => :bar, :baz => :qux})
end
it "appends excluded ids" do
@@ -55,7 +57,7 @@ module Masks; end
mask.search :without_ids => [5, 7]
- search.options[:without_ids].should == [1, 3, 5, 7]
+ expect(search.options[:without_ids]).to eq([1, 3, 5, 7])
end
it "replaces the retry_stale option" do
@@ -63,23 +65,23 @@ module Masks; end
mask.search :retry_stale => 6
- search.options[:retry_stale].should == 6
+ expect(search.options[:retry_stale]).to eq(6)
end
it "returns the original search object" do
- mask.search.object_id.should == search.object_id
+ expect(mask.search.object_id).to eq(search.object_id)
end
end
describe '#search_for_ids' do
it "replaces the query if one is supplied" do
- search.should_receive(:query=).with('bar')
+ expect(search).to receive(:query=).with('bar')
mask.search_for_ids('bar')
end
it "keeps the existing query when only options are offered" do
- search.should_not_receive(:query=)
+ expect(search).not_to receive(:query=)
mask.search_for_ids :with => {:foo => :bar}
end
@@ -89,7 +91,7 @@ module Masks; end
mask.search_for_ids :conditions => {:baz => 'qux'}
- search.options[:conditions].should == {:foo => 'bar', :baz => 'qux'}
+ expect(search.options[:conditions]).to eq({:foo => 'bar', :baz => 'qux'})
end
it "merges filters" do
@@ -97,7 +99,7 @@ module Masks; end
mask.search_for_ids :with => {:baz => :qux}
- search.options[:with].should == {:foo => :bar, :baz => :qux}
+ expect(search.options[:with]).to eq({:foo => :bar, :baz => :qux})
end
it "merges exclusive filters" do
@@ -105,7 +107,7 @@ module Masks; end
mask.search_for_ids :without => {:baz => :qux}
- search.options[:without].should == {:foo => :bar, :baz => :qux}
+ expect(search.options[:without]).to eq({:foo => :bar, :baz => :qux})
end
it "appends excluded ids" do
@@ -113,7 +115,7 @@ module Masks; end
mask.search_for_ids :without_ids => [5, 7]
- search.options[:without_ids].should == [1, 3, 5, 7]
+ expect(search.options[:without_ids]).to eq([1, 3, 5, 7])
end
it "replaces the retry_stale option" do
@@ -121,17 +123,17 @@ module Masks; end
mask.search_for_ids :retry_stale => 6
- search.options[:retry_stale].should == 6
+ expect(search.options[:retry_stale]).to eq(6)
end
it "adds the ids_only option" do
mask.search_for_ids
- search.options[:ids_only].should be_true
+ expect(search.options[:ids_only]).to be_truthy
end
it "returns the original search object" do
- mask.search_for_ids.object_id.should == search.object_id
+ expect(mask.search_for_ids.object_id).to eq(search.object_id)
end
end
end
diff --git a/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb b/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb
index 366a77de0..127a8cbb5 100644
--- a/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb
+++ b/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Middlewares; end
class Search; end
@@ -11,9 +13,10 @@ class Search; end
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}
@@ -21,20 +24,21 @@ def raw_result(id, model_name)
describe '#call' do
before :each do
- context.stub :search => search
- model.stub :unscoped => model
+ allow(context).to receive_messages :search => search
+ allow(context).to receive_messages :configuration => configuration
+ allow(model).to receive_messages :unscoped => model
end
it "translates records to ActiveRecord objects" do
model_name = double('article', :constantize => model)
instance = double('instance', :id => 24)
- model.stub :where => [instance]
+ allow(model).to receive_messages :where => [instance]
context[:results] << raw_result(24, model_name)
middleware.call [context]
- context[:results].should == [instance]
+ expect(context[:results]).to eq([instance])
end
it "only queries the model once for the given search results" do
@@ -44,7 +48,7 @@ def raw_result(id, model_name)
context[:results] << raw_result(24, model_name)
context[:results] << raw_result(42, model_name)
- model.should_receive(:where).once.and_return([instance_a, instance_b])
+ expect(model).to receive(:where).once.and_return([instance_a, instance_b])
middleware.call [context]
end
@@ -58,14 +62,14 @@ def raw_result(id, model_name)
user_name = double('user name', :constantize => user_model)
user = double('user instance', :id => 12)
- article_model.stub :unscoped => article_model
- user_model.stub :unscoped => user_model
+ allow(article_model).to receive_messages :unscoped => article_model
+ allow(user_model).to receive_messages :unscoped => user_model
context[:results] << raw_result(24, article_name)
context[:results] << raw_result(12, user_name)
- article_model.should_receive(:where).once.and_return([article])
- user_model.should_receive(:where).once.and_return([user])
+ expect(article_model).to receive(:where).once.and_return([article])
+ expect(user_model).to receive(:where).once.and_return([user])
middleware.call [context]
end
@@ -78,11 +82,11 @@ def raw_result(id, model_name)
context[:results] << raw_result(2, model_name)
context[:results] << raw_result(1, model_name)
- model.stub(:where => [instance_1, instance_2])
+ allow(model).to receive_messages(:where => [instance_1, instance_2])
middleware.call [context]
- context[:results].should == [instance_2, instance_1]
+ expect(context[:results]).to eq([instance_2, instance_1])
end
it "returns objects in database order if a SQL order clause is supplied" do
@@ -93,28 +97,40 @@ def raw_result(id, model_name)
context[:results] << raw_result(2, model_name)
context[:results] << raw_result(1, model_name)
- model.stub(:order => model, :where => [instance_1, instance_2])
+ allow(model).to receive_messages(:order => model, :where => [instance_1, instance_2])
search.options[:sql] = {:order => 'name DESC'}
middleware.call [context]
- context[:results].should == [instance_1, instance_2]
+ expect(context[:results]).to eq([instance_1, instance_2])
+ end
+
+ it "handles model without primary key" do
+ no_primary_key_model = double('no primary key model')
+ allow(no_primary_key_model).to receive_messages :unscoped => no_primary_key_model
+ model_name = double('article', :constantize => no_primary_key_model)
+ instance = double('instance', :id => 1)
+ allow(no_primary_key_model).to receive_messages :where => [instance]
+
+ context[:results] << raw_result(1, model_name)
+
+ middleware.call [context]
end
context 'SQL options' do
let(:relation) { double('relation', :where => []) }
+ let(:model_name) { double('article', :constantize => model) }
before :each do
- model.stub :unscoped => relation
+ allow(model).to receive_messages :unscoped => relation
- model_name = double('article', :constantize => model)
context[:results] << raw_result(1, model_name)
end
it "passes through SQL include options to the relation" do
search.options[:sql] = {:include => :association}
- relation.should_receive(:includes).with(:association).
+ expect(relation).to receive(:includes).with(:association).
and_return(relation)
middleware.call [context]
@@ -123,7 +139,7 @@ def raw_result(id, model_name)
it "passes through SQL join options to the relation" do
search.options[:sql] = {:joins => :association}
- relation.should_receive(:joins).with(:association).and_return(relation)
+ expect(relation).to receive(:joins).with(:association).and_return(relation)
middleware.call [context]
end
@@ -131,7 +147,7 @@ def raw_result(id, model_name)
it "passes through SQL order options to the relation" do
search.options[:sql] = {:order => 'name DESC'}
- relation.should_receive(:order).with('name DESC').and_return(relation)
+ expect(relation).to receive(:order).with('name DESC').and_return(relation)
middleware.call [context]
end
@@ -139,7 +155,7 @@ def raw_result(id, model_name)
it "passes through SQL select options to the relation" do
search.options[:sql] = {:select => :column}
- relation.should_receive(:select).with(:column).and_return(relation)
+ expect(relation).to receive(:select).with(:column).and_return(relation)
middleware.call [context]
end
@@ -147,7 +163,15 @@ def raw_result(id, model_name)
it "passes through SQL group options to the relation" do
search.options[:sql] = {:group => :column}
- relation.should_receive(:group).with(:column).and_return(relation)
+ expect(relation).to receive(:group).with(:column).and_return(relation)
+
+ middleware.call [context]
+ end
+
+ it "passes through SQL options defined by model to the relation" do
+ search.options[:sql] = {model_name => {:joins => :association}}
+
+ expect(relation).to receive(:joins).with(:association).and_return(relation)
middleware.call [context]
end
diff --git a/spec/thinking_sphinx/middlewares/geographer_spec.rb b/spec/thinking_sphinx/middlewares/geographer_spec.rb
index 78c5fc20a..d4bdd6b5e 100644
--- a/spec/thinking_sphinx/middlewares/geographer_spec.rb
+++ b/spec/thinking_sphinx/middlewares/geographer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Middlewares; end
end
@@ -16,7 +18,7 @@ module Middlewares; end
before :each do
stub_const 'ThinkingSphinx::Panes::DistancePane', double
- context.stub :search => search
+ allow(context).to receive_messages :search => search
end
describe '#call' do
@@ -26,7 +28,7 @@ module Middlewares; end
end
it "doesn't add anything if :geo is nil" do
- sphinx_sql.should_not_receive(:prepend_values)
+ expect(sphinx_sql).not_to receive(:prepend_values)
middleware.call [context]
end
@@ -38,7 +40,7 @@ module Middlewares; end
end
it "adds the geodist function when given a :geo option" do
- sphinx_sql.should_receive(:prepend_values).
+ expect(sphinx_sql).to receive(:prepend_values).
with('GEODIST(0.1, 0.2, lat, lng) AS geodist').
and_return(sphinx_sql)
@@ -46,18 +48,18 @@ module Middlewares; end
end
it "adds the distance pane" do
- sphinx_sql.stub :prepend_values => sphinx_sql
+ allow(sphinx_sql).to receive_messages :prepend_values => sphinx_sql
middleware.call [context]
- context[:panes].should include(ThinkingSphinx::Panes::DistancePane)
+ expect(context[:panes]).to include(ThinkingSphinx::Panes::DistancePane)
end
it "respects :latitude_attr and :longitude_attr options" do
search.options[:latitude_attr] = 'side_to_side'
search.options[:longitude_attr] = 'up_or_down'
- sphinx_sql.should_receive(:prepend_values).
+ expect(sphinx_sql).to receive(:prepend_values).
with('GEODIST(0.1, 0.2, side_to_side, up_or_down) AS geodist').
and_return(sphinx_sql)
@@ -68,7 +70,7 @@ module Middlewares; end
context[:indices] << double('index',
:unique_attribute_names => ['latitude'], :name => 'an_index')
- sphinx_sql.should_receive(:prepend_values).
+ expect(sphinx_sql).to receive(:prepend_values).
with('GEODIST(0.1, 0.2, latitude, lng) AS geodist').
and_return(sphinx_sql)
@@ -79,7 +81,7 @@ module Middlewares; end
context[:indices] << double('index',
:unique_attribute_names => ['longitude'], :name => 'an_index')
- sphinx_sql.should_receive(:prepend_values).
+ expect(sphinx_sql).to receive(:prepend_values).
with('GEODIST(0.1, 0.2, lat, longitude) AS geodist').
and_return(sphinx_sql)
@@ -89,7 +91,7 @@ module Middlewares; end
it "handles very small values" do
search.options[:geo] = [0.0000001, 0.00000000002]
- sphinx_sql.should_receive(:prepend_values).
+ expect(sphinx_sql).to receive(:prepend_values).
with('GEODIST(0.0000001, 0.00000000002, lat, lng) AS geodist').
and_return(sphinx_sql)
diff --git a/spec/thinking_sphinx/middlewares/glazier_spec.rb b/spec/thinking_sphinx/middlewares/glazier_spec.rb
index 9d7a1804d..f7c31a551 100644
--- a/spec/thinking_sphinx/middlewares/glazier_spec.rb
+++ b/spec/thinking_sphinx/middlewares/glazier_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Middlewares; end
end
@@ -10,11 +12,12 @@ module Middlewares; end
let(:middleware) { ThinkingSphinx::Middlewares::Glazier.new app }
let(:context) { {:results => [result], :indices => [index],
:meta => {}, :raw => [raw_result], :panes => []} }
- let(:result) { double('result', :id => 10,
- :class => double(:name => 'Article')) }
- let(:index) { double('index', :name => 'foo_core') }
- let(:search) { double('search', :options => {}) }
- let(:glazed_result) { double('glazed result') }
+ let(:result) { double 'result', :id => 10, :class => model }
+ let(:model) { double 'model', :name => 'Article' }
+ let(:index) { double 'index', :name => 'foo_core', :model => model,
+ :primary_key => :id }
+ let(:search) { double 'search', :options => {} }
+ let(:glazed_result) { double 'glazed result' }
let(:raw_result) {
{'sphinx_internal_class' => 'Article', 'sphinx_internal_id' => 10} }
@@ -22,7 +25,7 @@ module Middlewares; end
before :each do
stub_const 'ThinkingSphinx::Search::Glaze', double(:new => glazed_result)
- context.stub :search => search
+ allow(context).to receive_messages :search => search
end
context 'No panes provided' do
@@ -33,7 +36,7 @@ module Middlewares; end
it "leaves the results as they are" do
middleware.call [context]
- context[:results].should == [result]
+ expect(context[:results]).to eq([result])
end
end
@@ -47,11 +50,11 @@ module Middlewares; end
it "replaces each result with a glazed version" do
middleware.call [context]
- context[:results].should == [glazed_result]
+ expect(context[:results]).to eq([glazed_result])
end
it "creates a glazed result for each result" do
- ThinkingSphinx::Search::Glaze.should_receive(:new).
+ expect(ThinkingSphinx::Search::Glaze).to receive(:new).
with(context, result, raw_result, [pane_class]).
and_return(glazed_result)
diff --git a/spec/thinking_sphinx/middlewares/inquirer_spec.rb b/spec/thinking_sphinx/middlewares/inquirer_spec.rb
index 4867f0f33..5f54c040c 100644
--- a/spec/thinking_sphinx/middlewares/inquirer_spec.rb
+++ b/spec/thinking_sphinx/middlewares/inquirer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Middlewares; end
end
@@ -16,7 +18,7 @@ module Middlewares; end
before :each do
batch_class = double
- batch_class.stub(:new).and_return(batch_inquirer)
+ allow(batch_class).to receive(:new).and_return(batch_inquirer)
stub_const 'Riddle::Query', double(:meta => 'SHOW META')
stub_const 'ThinkingSphinx::Search::BatchInquirer', batch_class
@@ -24,8 +26,8 @@ module Middlewares; end
describe '#call' do
it "passes through the SphinxQL from a Riddle::Query::Select object" do
- batch_inquirer.should_receive(:append_query).with('SELECT * FROM index')
- batch_inquirer.should_receive(:append_query).with('SHOW META')
+ expect(batch_inquirer).to receive(:append_query).with('SELECT * FROM index')
+ expect(batch_inquirer).to receive(:append_query).with('SHOW META')
middleware.call [context]
end
@@ -33,19 +35,19 @@ module Middlewares; end
it "sets up the raw results" do
middleware.call [context]
- context[:raw].should == [:raw]
+ expect(context[:raw]).to eq([:raw])
end
it "sets up the meta results as a hash" do
middleware.call [context]
- context[:meta].should == {'meta' => 'value'}
+ expect(context[:meta]).to eq({'meta' => 'value'})
end
it "uses the raw values as the initial results" do
middleware.call [context]
- context[:results].should == [:raw]
+ expect(context[:results]).to eq([:raw])
end
context "with mysql2 result" do
@@ -63,7 +65,7 @@ def each; [{"fake" => "value"}].each { |m| yield m }; end
it "converts the results into an array" do
middleware.call [context]
- context[:results].should be_a Array
+ expect(context[:results]).to be_a Array
end
end
end
diff --git a/spec/thinking_sphinx/middlewares/sphinxql_spec.rb b/spec/thinking_sphinx/middlewares/sphinxql_spec.rb
index 1c448c426..96f1d7605 100644
--- a/spec/thinking_sphinx/middlewares/sphinxql_spec.rb
+++ b/spec/thinking_sphinx/middlewares/sphinxql_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Middlewares; end
end
@@ -16,7 +18,6 @@ class SphinxQLSubclass
require 'thinking_sphinx/middlewares/middleware'
require 'thinking_sphinx/middlewares/sphinxql'
require 'thinking_sphinx/errors'
-require 'thinking_sphinx/sphinxql'
describe ThinkingSphinx::Middlewares::SphinxQL do
let(:app) { double('app', :call => true) }
@@ -30,13 +31,13 @@ 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)
stub_const 'ThinkingSphinx::Search::Query', double(:new => query)
- context.stub :search => search, :configuration => configuration
+ allow(context).to receive_messages :search => search, :configuration => configuration
end
describe '#call' do
@@ -46,7 +47,7 @@ class SphinxQLSubclass
double('index', :name => 'user_core', :options => {})
]
- sphinx_sql.should_receive(:from).with('`article_core`', '`user_core`').
+ expect(sphinx_sql).to receive(:from).with('`article_core`', '`user_core`').
and_return(sphinx_sql)
middleware.call [context]
@@ -57,10 +58,10 @@ class SphinxQLSubclass
:name => 'User')
search.options[:classes] = [klass]
search.options[:indices] = ['user_core']
- index_set.first.stub :reference => :user
+ allow(index_set.first).to receive_messages :reference => :user
- set_class.should_receive(:new).
- with(:classes => [klass], :indices => ['user_core']).
+ expect(set_class).to receive(:new).
+ with({ :classes => [klass], :indices => ['user_core'] }).
and_return(index_set)
middleware.call [context]
@@ -75,19 +76,19 @@ class SphinxQLSubclass
end
it "generates a Sphinx query from the provided keyword and conditions" do
- search.stub :query => 'tasty'
+ allow(search).to receive_messages :query => 'tasty'
search.options[:conditions] = {:title => 'pancakes'}
- ThinkingSphinx::Search::Query.should_receive(:new).
+ expect(ThinkingSphinx::Search::Query).to receive(:new).
with('tasty', {:title => 'pancakes'}, anything).and_return(query)
middleware.call [context]
end
it "matches on the generated query" do
- query.stub :to_s => 'waffles'
+ allow(query).to receive_messages :to_s => 'waffles'
- sphinx_sql.should_receive(:matching).with('waffles')
+ expect(sphinx_sql).to receive(:matching).with('waffles')
middleware.call [context]
end
@@ -95,15 +96,15 @@ class SphinxQLSubclass
it "requests a starred query if the :star option is set to true" do
search.options[:star] = true
- ThinkingSphinx::Search::Query.should_receive(:new).
+ expect(ThinkingSphinx::Search::Query).to receive(:new).
with(anything, anything, true).and_return(query)
middleware.call [context]
end
it "doesn't append a field condition by default" do
- ThinkingSphinx::Search::Query.should_receive(:new) do |query, conditions, star|
- conditions[:sphinx_internal_class_name].should be_nil
+ expect(ThinkingSphinx::Search::Query).to receive(:new) do |query, conditions, star|
+ expect(conditions[:sphinx_internal_class_name]).to be_nil
query
end
@@ -113,12 +114,13 @@ class SphinxQLSubclass
it "doesn't append a field condition if all classes match index references" do
model = double('model', :connection => double,
:ancestors => [ActiveRecord::Base], :name => 'Animal')
- index_set.first.stub :reference => :animal
+ allow(index_set.first).to receive_messages :reference => :animal
+ allow(set_class).to receive_messages(:reference_name => :animal)
search.options[:classes] = [model]
- ThinkingSphinx::Search::Query.should_receive(:new) do |query, conditions, star|
- conditions[:sphinx_internal_class_name].should be_nil
+ expect(ThinkingSphinx::Search::Query).to receive(:new) do |query, conditions, star|
+ expect(conditions[:sphinx_internal_class_name]).to be_nil
query
end
@@ -132,19 +134,19 @@ class SphinxQLSubclass
def self.name; 'Cat'; end
def self.inheritance_column; 'type'; end
end
- supermodel.stub :connection => db_connection, :column_names => ['type']
+ allow(supermodel).to receive_messages :connection => db_connection, :column_names => ['type']
submodel = Class.new(supermodel) do
def self.name; 'Lion'; end
def self.inheritance_column; 'type'; end
def self.table_name; 'cats'; end
end
- submodel.stub :connection => db_connection, :column_names => ['type'],
+ allow(submodel).to receive_messages :connection => db_connection, :column_names => ['type'],
:descendants => []
- index_set.first.stub :reference => :cat
+ allow(index_set.first).to receive_messages :reference => :cat
search.options[:classes] = [submodel]
- ThinkingSphinx::Search::Query.should_receive(:new).with(anything,
+ expect(ThinkingSphinx::Search::Query).to receive(:new).with(anything,
hash_including(:sphinx_internal_class_name => '(Lion)'), anything).
and_return(query)
@@ -158,19 +160,19 @@ def self.table_name; 'cats'; end
def self.name; 'Animals::Cat'; end
def self.inheritance_column; 'type'; end
end
- supermodel.stub :connection => db_connection, :column_names => ['type']
+ allow(supermodel).to receive_messages :connection => db_connection, :column_names => ['type']
submodel = Class.new(supermodel) do
def self.name; 'Animals::Lion'; end
def self.inheritance_column; 'type'; end
def self.table_name; 'cats'; end
end
- submodel.stub :connection => db_connection, :column_names => ['type'],
+ allow(submodel).to receive_messages :connection => db_connection, :column_names => ['type'],
:descendants => []
- index_set.first.stub :reference => :"animals/cat"
+ allow(index_set.first).to receive_messages :reference => :"animals/cat"
search.options[:classes] = [submodel]
- ThinkingSphinx::Search::Query.should_receive(:new).with(anything,
+ expect(ThinkingSphinx::Search::Query).to receive(:new).with(anything,
hash_including(:sphinx_internal_class_name => '("Animals::Lion")'), anything).
and_return(query)
@@ -180,12 +182,12 @@ def self.table_name; 'cats'; end
it "does not query the database for subclasses if :skip_sti is set to true" do
model = double('model', :connection => double,
:ancestors => [ActiveRecord::Base], :name => 'Animal')
- index_set.first.stub :reference => :animal
+ allow(index_set.first).to receive_messages :reference => :animal
search.options[:classes] = [model]
search.options[:skip_sti] = true
- model.connection.should_not_receive(:select_values)
+ expect(model.connection).not_to receive(:select_values)
middleware.call [context]
end
@@ -197,15 +199,15 @@ def self.table_name; 'cats'; end
def self.name; 'Cat'; end
def self.inheritance_column; 'type'; end
end
- supermodel.stub :connection => db_connection, :column_names => ['type']
+ allow(supermodel).to receive_messages :connection => db_connection, :column_names => ['type']
submodel = Class.new(supermodel) do
def self.name; 'Lion'; end
def self.inheritance_column; 'type'; end
def self.table_name; 'cats'; end
end
- submodel.stub :connection => db_connection, :column_names => ['type'],
+ allow(submodel).to receive_messages :connection => db_connection, :column_names => ['type'],
:descendants => []
- index_set.first.stub :reference => :cat
+ allow(index_set.first).to receive_messages :reference => :cat
search.options[:classes] = [submodel]
@@ -213,7 +215,7 @@ def self.table_name; 'cats'; end
end
it "filters out deleted values by default" do
- sphinx_sql.should_receive(:where).with(:sphinx_deleted => false).
+ expect(sphinx_sql).to receive(:where).with({ :sphinx_deleted => false }).
and_return(sphinx_sql)
middleware.call [context]
@@ -222,7 +224,7 @@ def self.table_name; 'cats'; end
it "appends boolean attribute filters to the query" do
search.options[:with] = {:visible => true}
- sphinx_sql.should_receive(:where).with(hash_including(:visible => true)).
+ expect(sphinx_sql).to receive(:where).with(hash_including(:visible => true)).
and_return(sphinx_sql)
middleware.call [context]
@@ -231,7 +233,7 @@ def self.table_name; 'cats'; end
it "appends exclusive filters to the query" do
search.options[:without] = {:tag_ids => [2, 4, 8]}
- sphinx_sql.should_receive(:where_not).
+ expect(sphinx_sql).to receive(:where_not).
with(hash_including(:tag_ids => [2, 4, 8])).and_return(sphinx_sql)
middleware.call [context]
@@ -240,7 +242,7 @@ def self.table_name; 'cats'; end
it "appends the without_ids option as an exclusive filter" do
search.options[:without_ids] = [1, 4, 9]
- sphinx_sql.should_receive(:where_not).
+ expect(sphinx_sql).to receive(:where_not).
with(hash_including(:sphinx_internal_id => [1, 4, 9])).
and_return(sphinx_sql)
@@ -250,8 +252,8 @@ def self.table_name; 'cats'; end
it "appends MVA matches with all values" do
search.options[:with_all] = {:tag_ids => [1, 7]}
- sphinx_sql.should_receive(:where_all).
- with(:tag_ids => [1, 7]).and_return(sphinx_sql)
+ expect(sphinx_sql).to receive(:where_all).
+ with({ :tag_ids => [1, 7] }).and_return(sphinx_sql)
middleware.call [context]
end
@@ -259,8 +261,8 @@ def self.table_name; 'cats'; end
it "appends MVA matches without all of the given values" do
search.options[:without_all] = {:tag_ids => [1, 7]}
- sphinx_sql.should_receive(:where_not_all).
- with(:tag_ids => [1, 7]).and_return(sphinx_sql)
+ expect(sphinx_sql).to receive(:where_not_all).
+ with({ :tag_ids => [1, 7] }).and_return(sphinx_sql)
middleware.call [context]
end
@@ -268,7 +270,7 @@ def self.table_name; 'cats'; end
it "appends order clauses to the query" do
search.options[:order] = 'created_at ASC'
- sphinx_sql.should_receive(:order_by).with('created_at ASC').
+ expect(sphinx_sql).to receive(:order_by).with('created_at ASC').
and_return(sphinx_sql)
middleware.call [context]
@@ -277,7 +279,7 @@ def self.table_name; 'cats'; end
it "presumes attributes given as symbols should be sorted ascendingly" do
search.options[:order] = :updated_at
- sphinx_sql.should_receive(:order_by).with('updated_at ASC').
+ expect(sphinx_sql).to receive(:order_by).with('updated_at ASC').
and_return(sphinx_sql)
middleware.call [context]
@@ -285,10 +287,10 @@ def self.table_name; 'cats'; end
it "appends a group by clause to the query" do
search.options[:group_by] = :foreign_id
- search.stub :masks => []
- sphinx_sql.stub :values => sphinx_sql
+ allow(search).to receive_messages :masks => []
+ allow(sphinx_sql).to receive_messages :values => sphinx_sql
- sphinx_sql.should_receive(:group_by).with('foreign_id').
+ expect(sphinx_sql).to receive(:group_by).with('foreign_id').
and_return(sphinx_sql)
middleware.call [context]
@@ -297,24 +299,24 @@ def self.table_name; 'cats'; end
it "appends a sort within group clause to the query" do
search.options[:order_group_by] = :title
- sphinx_sql.should_receive(:order_within_group_by).with('title ASC').
+ expect(sphinx_sql).to receive(:order_within_group_by).with('title ASC').
and_return(sphinx_sql)
middleware.call [context]
end
it "uses the provided offset" do
- search.stub :offset => 50
+ allow(search).to receive_messages :offset => 50
- sphinx_sql.should_receive(:offset).with(50).and_return(sphinx_sql)
+ expect(sphinx_sql).to receive(:offset).with(50).and_return(sphinx_sql)
middleware.call [context]
end
it "uses the provided limit" do
- search.stub :per_page => 24
+ allow(search).to receive_messages :per_page => 24
- sphinx_sql.should_receive(:limit).with(24).and_return(sphinx_sql)
+ expect(sphinx_sql).to receive(:limit).with(24).and_return(sphinx_sql)
middleware.call [context]
end
@@ -322,7 +324,7 @@ def self.table_name; 'cats'; end
it "adds the provided select statement" do
search.options[:select] = 'foo as bar'
- sphinx_sql.should_receive(:values).with('foo as bar').
+ expect(sphinx_sql).to receive(:values).with('foo as bar').
and_return(sphinx_sql)
middleware.call [context]
@@ -331,7 +333,7 @@ def self.table_name; 'cats'; end
it "adds the provided group-best count" do
search.options[:group_best] = 5
- sphinx_sql.should_receive(:group_best).with(5).and_return(sphinx_sql)
+ expect(sphinx_sql).to receive(:group_best).with(5).and_return(sphinx_sql)
middleware.call [context]
end
@@ -339,7 +341,7 @@ def self.table_name; 'cats'; end
it "adds the provided having clause" do
search.options[:having] = 'foo > 1'
- sphinx_sql.should_receive(:having).with('foo > 1').and_return(sphinx_sql)
+ expect(sphinx_sql).to receive(:having).with('foo > 1').and_return(sphinx_sql)
middleware.call [context]
end
@@ -347,8 +349,8 @@ def self.table_name; 'cats'; end
it "uses any provided field weights" do
search.options[:field_weights] = {:title => 3}
- sphinx_sql.should_receive(:with_options) do |options|
- options[:field_weights].should == {:title => 3}
+ expect(sphinx_sql).to receive(:with_options) do |options|
+ expect(options[:field_weights]).to eq({:title => 3})
sphinx_sql
end
@@ -358,7 +360,7 @@ def self.table_name; 'cats'; end
it "uses index-defined field weights if they're available" do
index_set.first.options[:field_weights] = {:title => 3}
- sphinx_sql.should_receive(:with_options).with(
+ expect(sphinx_sql).to receive(:with_options).with(
hash_including(:field_weights => {:title => 3})
).and_return(sphinx_sql)
@@ -368,7 +370,7 @@ def self.table_name; 'cats'; end
it "uses index-defined max matches if it's available" do
index_set.first.options[:max_matches] = 100
- sphinx_sql.should_receive(:with_options).with(
+ expect(sphinx_sql).to receive(:with_options).with(
hash_including(:max_matches => 100)
).and_return(sphinx_sql)
@@ -378,7 +380,7 @@ def self.table_name; 'cats'; end
it "uses configuration-level max matches if set" do
configuration.settings['max_matches'] = 120
- sphinx_sql.should_receive(:with_options).with(
+ expect(sphinx_sql).to receive(:with_options).with(
hash_including(:max_matches => 120)
).and_return(sphinx_sql)
@@ -388,8 +390,8 @@ def self.table_name; 'cats'; end
it "uses any given ranker option" do
search.options[:ranker] = 'proximity'
- sphinx_sql.should_receive(:with_options) do |options|
- options[:ranker].should == 'proximity'
+ expect(sphinx_sql).to receive(:with_options) do |options|
+ expect(options[:ranker]).to eq('proximity')
sphinx_sql
end
diff --git a/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb b/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb
index 8a9768afa..726cefbad 100644
--- a/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb
+++ b/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Middlewares; end
class Search; end
@@ -25,7 +27,7 @@ def raw_result(id, model_name)
context[:results] << double('instance', :id => 24)
context[:results] << double('instance', :id => 42)
- app.should_receive(:call)
+ expect(app).to receive(:call)
middleware.call [context]
end
@@ -37,10 +39,11 @@ def raw_result(id, model_name)
context[:results] << double('instance', :id => 24)
context[:results] << nil
- lambda {
+ expect {
middleware.call [context]
- }.should raise_error(ThinkingSphinx::Search::StaleIdsException) { |err|
- err.ids.should == [42]
+ }.to raise_error(ThinkingSphinx::Search::StaleIdsException) { |err|
+ expect(err.ids).to eq([42])
+ expect(err.context).to eq(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 c272ba015..8ac6db2af 100644
--- a/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb
+++ b/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Middlewares; end
class Search; end
@@ -15,22 +17,22 @@ class Search; end
describe '#call' do
before :each do
- context.stub :search => search
+ allow(context).to receive_messages :search => search
end
context 'one stale ids exception' do
before :each do
- app.stub(:call) do
+ allow(app).to receive(: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
it "appends the ids to the without_ids filter" do
middleware.call [context]
- search.options[:without_ids].should == [12]
+ expect(search.options[:without_ids]).to eq([12])
end
it "respects existing without_ids filters" do
@@ -38,24 +40,24 @@ class Search; end
middleware.call [context]
- search.options[:without_ids].should == [11, 12]
+ expect(search.options[:without_ids]).to eq([11, 12])
end
end
context 'two stale ids exceptions' do
before :each do
- app.stub(:call) do
+ allow(app).to receive(: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
it "appends the ids to the without_ids filter" do
middleware.call [context]
- search.options[:without_ids].should == [12, 13]
+ expect(search.options[:without_ids]).to eq([12, 13])
end
it "respects existing without_ids filters" do
@@ -63,29 +65,49 @@ class Search; end
middleware.call [context]
- search.options[:without_ids].should == [11, 12, 13]
+ expect(search.options[:without_ids]).to eq([11, 12, 13])
end
end
context 'three stale ids exceptions' do
before :each do
- app.stub(:call) do
+ allow(app).to receive(: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, [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
it "raises the final stale ids exceptions" do
- lambda {
+ expect {
middleware.call [context]
- }.should raise_error(ThinkingSphinx::Search::StaleIdsException) { |err|
- err.ids.should == [14]
+ }.to raise_error(ThinkingSphinx::Search::StaleIdsException) { |err|
+ expect(err.ids).to eq([14])
}
end
end
+
+ context 'stale ids exceptions with multiple contexts' do
+ let(:context2) { {:raw => [], :results => []} }
+ let(:search2) { double('search2', :options => {}) }
+ before :each do
+ allow(context2).to receive_messages :search => search2
+ allow(app).to receive(: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]
+ expect(search.options[:without_ids]).to eq(nil)
+ expect(search2.options[:without_ids]).to eq([12])
+ end
+ end
+
end
end
diff --git a/spec/thinking_sphinx/middlewares/valid_options_spec.rb b/spec/thinking_sphinx/middlewares/valid_options_spec.rb
new file mode 100644
index 000000000..52930405b
--- /dev/null
+++ b/spec/thinking_sphinx/middlewares/valid_options_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::Middlewares::ValidOptions do
+ let(:app) { double 'app', :call => true }
+ let(:middleware) { ThinkingSphinx::Middlewares::ValidOptions.new app }
+ let(:context) { double 'context', :search => search }
+ let(:search) { double 'search', :options => {} }
+
+ before :each do
+ allow(ThinkingSphinx::Logger).to receive(:log)
+ end
+
+ context 'with unknown options' do
+ before :each do
+ search.options[:foo] = :bar
+ end
+
+ it "adds a warning" do
+ expect(ThinkingSphinx::Logger).to receive(:log).
+ with(:caution, "Unexpected search options: [:foo]")
+
+ middleware.call [context]
+ end
+
+ it 'continues on' do
+ expect(app).to receive(:call).with([context])
+
+ middleware.call [context]
+ end
+ end
+
+ context "with known options" do
+ before :each do
+ search.options[:ids_only] = true
+ end
+
+ it "is silent" do
+ expect(ThinkingSphinx::Logger).to_not receive(:log)
+
+ middleware.call [context]
+ end
+
+ it 'continues on' do
+ expect(app).to receive(:call).with([context])
+
+ middleware.call [context]
+ end
+ end
+end
diff --git a/spec/thinking_sphinx/panes/attributes_pane_spec.rb b/spec/thinking_sphinx/panes/attributes_pane_spec.rb
index 9a7c00cd6..a52273d79 100644
--- a/spec/thinking_sphinx/panes/attributes_pane_spec.rb
+++ b/spec/thinking_sphinx/panes/attributes_pane_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Panes; end
end
@@ -15,7 +17,7 @@ module Panes; end
it "returns the object's sphinx attributes by default" do
raw['foo'] = 24
- pane.sphinx_attributes.should == {'foo' => 24}
+ expect(pane.sphinx_attributes).to eq({'foo' => 24})
end
end
end
diff --git a/spec/thinking_sphinx/panes/distance_pane_spec.rb b/spec/thinking_sphinx/panes/distance_pane_spec.rb
index 0f35e791b..6ea31caf7 100644
--- a/spec/thinking_sphinx/panes/distance_pane_spec.rb
+++ b/spec/thinking_sphinx/panes/distance_pane_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Panes; end
end
@@ -15,13 +17,13 @@ module Panes; end
it "returns the object's geodistance attribute by default" do
raw['geodist'] = 123.45
- pane.distance.should == 123.45
+ expect(pane.distance).to eq(123.45)
end
it "converts string geodistances to floats" do
raw['geodist'] = '123.450'
- pane.distance.should == 123.45
+ expect(pane.distance).to eq(123.45)
end
end
@@ -29,13 +31,13 @@ module Panes; end
it "returns the object's geodistance attribute by default" do
raw['geodist'] = 123.45
- pane.geodist.should == 123.45
+ expect(pane.geodist).to eq(123.45)
end
it "converts string geodistances to floats" do
raw['geodist'] = '123.450'
- pane.geodist.should == 123.45
+ expect(pane.geodist).to eq(123.45)
end
end
end
diff --git a/spec/thinking_sphinx/panes/excerpts_pane_spec.rb b/spec/thinking_sphinx/panes/excerpts_pane_spec.rb
index 9425bde8e..3adf50b89 100644
--- a/spec/thinking_sphinx/panes/excerpts_pane_spec.rb
+++ b/spec/thinking_sphinx/panes/excerpts_pane_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Panes; end
end
@@ -13,7 +15,7 @@ module Panes; end
let(:search) { double('search', :query => 'foo', :options => {}) }
before :each do
- context.stub :search => search
+ allow(context).to receive_messages :search => search
end
describe '#excerpts' do
@@ -22,18 +24,18 @@ module Panes; end
before :each do
stub_const 'ThinkingSphinx::Excerpter', double(:new => excerpter)
- ThinkingSphinx::Panes::ExcerptsPane::Excerpts.stub :new => excerpts
+ allow(ThinkingSphinx::Panes::ExcerptsPane::Excerpts).to receive_messages :new => excerpts
end
it "returns an excerpt glazing" do
- pane.excerpts.should == excerpts
+ expect(pane.excerpts).to eq(excerpts)
end
it "creates an excerpter with the first index and the query and conditions values" do
context[:indices] = [double(:name => 'alpha'), double(:name => 'beta')]
context.search.options[:conditions] = {:baz => 'bar'}
- ThinkingSphinx::Excerpter.should_receive(:new).
+ expect(ThinkingSphinx::Excerpter).to receive(:new).
with('alpha', 'foo bar', anything).and_return(excerpter)
pane.excerpts
@@ -42,8 +44,8 @@ module Panes; end
it "passes through excerpts options" do
search.options[:excerpts] = {:before_match => 'foo'}
- ThinkingSphinx::Excerpter.should_receive(:new).
- with(anything, anything, :before_match => 'foo').and_return(excerpter)
+ expect(ThinkingSphinx::Excerpter).to receive(:new).
+ with(anything, anything, { :before_match => 'foo' }).and_return(excerpter)
pane.excerpts
end
diff --git a/spec/thinking_sphinx/panes/weight_pane_spec.rb b/spec/thinking_sphinx/panes/weight_pane_spec.rb
index ca1f19ebb..42bb682d5 100644
--- a/spec/thinking_sphinx/panes/weight_pane_spec.rb
+++ b/spec/thinking_sphinx/panes/weight_pane_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
module Panes; end
end
@@ -12,9 +14,9 @@ module Panes; end
describe '#weight' do
it "returns the object's weight by default" do
- raw[ThinkingSphinx::SphinxQL.weight[:column]] = 101
+ raw["weight()"] = 101
- pane.weight.should == 101
+ expect(pane.weight).to eq(101)
end
end
end
diff --git a/spec/thinking_sphinx/rake_interface_spec.rb b/spec/thinking_sphinx/rake_interface_spec.rb
index cde504840..0e04a01f6 100644
--- a/spec/thinking_sphinx/rake_interface_spec.rb
+++ b/spec/thinking_sphinx/rake_interface_spec.rb
@@ -1,257 +1,39 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::RakeInterface do
- let(:configuration) { double('configuration', :controller => controller) }
- let(:interface) { ThinkingSphinx::RakeInterface.new }
+ let(:interface) { ThinkingSphinx::RakeInterface.new }
+ let(:commander) { double :call => nil }
before :each do
- ThinkingSphinx::Configuration.stub :instance => configuration
- interface.stub(:puts => nil)
- end
-
- describe '#clear_all' do
- let(:controller) { double 'controller' }
-
- before :each do
- configuration.stub(
- :indices_location => '/path/to/indices',
- :searchd => double(:binlog_path => '/path/to/binlog')
- )
-
- FileUtils.stub :rm_r => true
- File.stub :exists? => true
- end
-
- it "removes the directory for the index files" do
- FileUtils.should_receive(:rm_r).with('/path/to/indices')
-
- interface.clear_all
- end
-
- it "removes the directory for the binlog files" do
- FileUtils.should_receive(:rm_r).with('/path/to/binlog')
-
- interface.clear_all
- end
- end
-
- describe '#clear_real_time' do
- let(:controller) { double 'controller' }
- let(:index) {
- double(:type => 'rt', :render => true, :path => '/path/to/my/index')
- }
-
- before :each do
- configuration.stub(
- :indices => [double(:type => 'plain'), index],
- :searchd => double(:binlog_path => '/path/to/binlog')
- )
-
- Dir.stub :[] => ['foo.a', 'foo.b']
- FileUtils.stub :rm_r => true, :rm => true
- File.stub :exists? => true
- end
-
- it 'finds each file for real-time indices' do
- Dir.should_receive(:[]).with('/path/to/my/index.*').and_return([])
-
- interface.clear_real_time
- end
-
- it "removes each file for real-time indices" do
- FileUtils.should_receive(:rm).with('foo.a')
- FileUtils.should_receive(:rm).with('foo.b')
-
- interface.clear_real_time
- end
-
- it "removes the directory for the binlog files" do
- FileUtils.should_receive(:rm_r).with('/path/to/binlog')
-
- interface.clear_real_time
- end
+ stub_const 'ThinkingSphinx::Commander', commander
end
describe '#configure' do
- let(:controller) { double('controller') }
-
- before :each do
- configuration.stub(
- :configuration_file => '/path/to/foo.conf',
- :render_to_file => true
- )
- end
-
- it "renders the configuration to a file" do
- configuration.should_receive(:render_to_file)
-
- interface.configure
- end
-
- it "prints a message stating the file is being generated" do
- interface.should_receive(:puts).
- with('Generating configuration to /path/to/foo.conf')
+ it 'sends the configure command' do
+ expect(commander).to receive(:call).
+ with(:configure, anything, {:verbose => true})
interface.configure
end
end
- describe '#index' do
- let(:controller) { double('controller', :index => true) }
-
- before :each do
- ThinkingSphinx.stub :before_index_hooks => []
- configuration.stub(
- :configuration_file => '/path/to/foo.conf',
- :render_to_file => true,
- :indices_location => '/path/to/indices'
- )
-
- FileUtils.stub :mkdir_p => true
- end
-
- it "renders the configuration to a file by default" do
- configuration.should_receive(:render_to_file)
-
- interface.index
- end
-
- it "does not render the configuration if requested" do
- configuration.should_not_receive(:render_to_file)
-
- interface.index false
- end
-
- it "creates the directory for the index files" do
- FileUtils.should_receive(:mkdir_p).with('/path/to/indices')
-
- interface.index
- end
-
- it "calls all registered hooks" do
- called = false
- ThinkingSphinx.before_index_hooks << Proc.new { called = true }
-
- interface.index
-
- called.should be_true
- end
-
- it "indexes all indices verbosely" do
- controller.should_receive(:index).with(:verbose => true)
-
- interface.index
- end
-
- it "does not index verbosely if requested" do
- controller.should_receive(:index).with(:verbose => false)
-
- interface.index true, false
- end
- end
-
- describe '#start' do
- let(:controller) { double('controller', :start => true, :pid => 101) }
-
- before :each do
- controller.stub(:running?).and_return(false, true)
- configuration.stub :indices_location => 'my/index/files'
-
- FileUtils.stub :mkdir_p => true
- end
-
- it "creates the index files directory" do
- FileUtils.should_receive(:mkdir_p).with('my/index/files')
-
- interface.start
- end
-
- it "starts the daemon" do
- controller.should_receive(:start)
-
- interface.start
- end
-
- it "raises an error if the daemon is already running" do
- controller.stub :running? => true
-
- lambda {
- interface.start
- }.should raise_error(RuntimeError)
- end
-
- it "prints a success message if the daemon has started" do
- controller.stub(:running?).and_return(false, true)
-
- interface.should_receive(:puts).
- with('Started searchd successfully (pid: 101).')
-
- interface.start
- end
-
- it "prints a failure message if the daemon does not start" do
- controller.stub(:running?).and_return(false, false)
-
- interface.should_receive(:puts).
- with('Failed to start searchd. Check the log files for more information.')
-
- interface.start
+ describe '#daemon' do
+ it 'returns a daemon interface' do
+ expect(interface.daemon.class).to eq(ThinkingSphinx::Interfaces::Daemon)
end
end
- describe '#stop' do
- let(:controller) { double('controller', :stop => true, :pid => 101) }
-
- before :each do
- controller.stub :running? => true
- end
-
- it "prints a message if the daemon is not already running" do
- controller.stub :running? => false
-
- interface.should_receive(:puts).with('searchd is not currently running.')
-
- interface.stop
- end
-
- it "stops the daemon" do
- controller.should_receive(:stop)
-
- interface.stop
- end
-
- it "prints a message informing the daemon has stopped" do
- interface.should_receive(:puts).with('Stopped searchd daemon (pid: 101).')
-
- interface.stop
- end
-
- it "should retry stopping the daemon until it stops" do
- controller.should_receive(:stop).twice.and_return(false, true)
-
- interface.stop
+ describe '#rt' do
+ it 'returns a real-time interface' do
+ expect(interface.rt.class).to eq(ThinkingSphinx::Interfaces::RealTime)
end
end
- describe '#status' do
- let(:controller) { double('controller') }
-
- it "reports when the daemon is running" do
- controller.stub :running? => true
-
- interface.should_receive(:puts).
- with('The Sphinx daemon searchd is currently running.')
-
- interface.status
- end
-
- it "reports when the daemon is not running" do
- controller.stub :running? => false
-
- interface.should_receive(:puts).
- with('The Sphinx daemon searchd is not currently running.')
-
- interface.status
+ describe '#sql' do
+ it 'returns an SQL interface' do
+ expect(interface.sql.class).to eq(ThinkingSphinx::Interfaces::SQL)
end
end
end
diff --git a/spec/thinking_sphinx/real_time/attribute_spec.rb b/spec/thinking_sphinx/real_time/attribute_spec.rb
index b8dd057a5..43cef1857 100644
--- a/spec/thinking_sphinx/real_time/attribute_spec.rb
+++ b/spec/thinking_sphinx/real_time/attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::RealTime::Attribute do
@@ -7,11 +9,11 @@
describe '#name' do
it "uses the provided option by default" do
attribute = ThinkingSphinx::RealTime::Attribute.new column, :as => :foo
- attribute.name.should == 'foo'
+ expect(attribute.name).to eq('foo')
end
it "falls back to the column's name" do
- attribute.name.should == 'created_at'
+ expect(attribute.name).to eq('created_at')
end
end
@@ -21,34 +23,34 @@
let(:parent) { klass.new 'the parent name', nil }
it "returns the column's name if it's a string" do
- column.stub :__name => 'value'
+ allow(column).to receive_messages :__name => 'value'
- attribute.translate(object).should == 'value'
+ expect(attribute.translate(object)).to eq('value')
end
it "returns the column's name if it's an integer" do
- column.stub :__name => 404
+ allow(column).to receive_messages :__name => 404
- attribute.translate(object).should == 404
+ expect(attribute.translate(object)).to eq(404)
end
it "returns the object's method matching the column's name" do
- object.stub :created_at => 'a time'
+ allow(object).to receive_messages :created_at => 'a time'
- attribute.translate(object).should == 'a time'
+ expect(attribute.translate(object)).to eq('a time')
end
it "uses the column's stack to navigate through the object tree" do
- column.stub :__name => :name, :__stack => [:parent]
+ allow(column).to receive_messages :__name => :name, :__stack => [:parent]
- attribute.translate(object).should == 'the parent name'
+ expect(attribute.translate(object)).to eq('the parent name')
end
it "returns zero if any element in the object tree is nil" do
- column.stub :__name => :name, :__stack => [:parent]
+ allow(column).to receive_messages :__name => :name, :__stack => [:parent]
object.parent = nil
- attribute.translate(object).should be_zero
+ expect(attribute.translate(object)).to be_zero
end
end
@@ -56,7 +58,7 @@
it "returns the given type option" do
attribute = ThinkingSphinx::RealTime::Attribute.new column,
:type => :string
- attribute.type.should == :string
+ expect(attribute.type).to eq(:string)
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 71d35f5e7..c0be290e3 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks do
@@ -9,15 +11,15 @@
:settings => {}) }
let(:index) { double('index', :name => 'my_index', :is_a? => true,
:document_id_for_key => 123, :fields => [], :attributes => [],
- :conditions => []) }
+ :conditions => [], :primary_key => :id) }
let(:connection) { double('connection', :execute => true) }
before :each do
- ThinkingSphinx::Configuration.stub :instance => config
- ThinkingSphinx::Connection.stub_chain(:pool, :take).and_yield connection
+ allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
+ allow(ThinkingSphinx::Connection).to receive_message_chain(:pool, :take).and_yield connection
end
- describe '#after_save' do
+ describe '#after_save, #after_commit' do
let(:insert) { double('insert', :to_sql => 'REPLACE INTO my_index') }
let(:time) { 1.day.ago }
let(:field) { double('field', :name => 'name', :translate => 'Foo') }
@@ -25,32 +27,52 @@
:translate => time) }
before :each do
- ThinkingSphinx::Configuration.stub :instance => config
- Riddle::Query::Insert.stub :new => insert
- insert.stub :replace! => insert
- index.stub :fields => [field], :attributes => [attribute]
+ allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
+ allow(Riddle::Query::Insert).to receive_messages :new => insert
+ allow(insert).to receive_messages :replace! => insert
+ allow(index).to receive_messages :fields => [field], :attributes => [attribute]
end
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]).
+ expect(Riddle::Query::Insert).to receive(:new).
+ with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
and_return(insert)
callbacks.after_save instance
end
it "switches the insert to a replace statement" do
- insert.should_receive(:replace!).and_return(insert)
+ expect(insert).to receive(:replace!).and_return(insert)
callbacks.after_save instance
end
it "sends the insert through to the server" do
- connection.should_receive(:execute).with('REPLACE INTO my_index')
+ expect(connection).to receive(:execute).with('REPLACE INTO my_index')
callbacks.after_save instance
end
+ it "creates an insert statement with all fields and attributes" do
+ expect(Riddle::Query::Insert).to receive(:new).
+ with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
+ and_return(insert)
+
+ callbacks.after_commit instance
+ end
+
+ it "switches the insert to a replace statement" do
+ expect(insert).to receive(:replace!).and_return(insert)
+
+ callbacks.after_commit instance
+ end
+
+ it "sends the insert through to the server" do
+ expect(connection).to receive(:execute).with('REPLACE INTO my_index')
+
+ callbacks.after_commit instance
+ end
+
context 'with a given path' do
let(:callbacks) {
ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks.new(
@@ -61,24 +83,44 @@
let(:user) { double('user', :id => 13, :persisted? => true) }
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]).
+ expect(Riddle::Query::Insert).to receive(:new).
+ with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
and_return(insert)
callbacks.after_save instance
end
it "gets the document id for the user object" do
- index.should_receive(:document_id_for_key).with(13).and_return(123)
+ expect(index).to receive(:document_id_for_key).with(13).and_return(123)
callbacks.after_save instance
end
it "translates values for the user object" do
- field.should_receive(:translate).with(user).and_return('Foo')
+ expect(field).to receive(:translate).with(user).and_return('Foo')
callbacks.after_save instance
end
+
+ it "creates an insert statement with all fields and attributes" do
+ expect(Riddle::Query::Insert).to receive(:new).
+ with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
+ and_return(insert)
+
+ callbacks.after_commit instance
+ end
+
+ it "gets the document id for the user object" do
+ expect(index).to receive(:document_id_for_key).with(13).and_return(123)
+
+ callbacks.after_commit instance
+ end
+
+ it "translates values for the user object" do
+ expect(field).to receive(:translate).with(user).and_return('Foo')
+
+ callbacks.after_commit instance
+ end
end
context 'with a path returning multiple objects' do
@@ -93,26 +135,48 @@
let(:user_b) { double('user', :id => 14, :persisted? => true) }
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]).
+ expect(Riddle::Query::Insert).to receive(:new).twice.
+ with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
and_return(insert)
callbacks.after_save instance
end
it "gets the document id for each reader" do
- index.should_receive(:document_id_for_key).with(13).and_return(123)
- index.should_receive(:document_id_for_key).with(14).and_return(123)
+ expect(index).to receive(:document_id_for_key).with(13).and_return(123)
+ expect(index).to receive(:document_id_for_key).with(14).and_return(123)
callbacks.after_save instance
end
it "translates values for each reader" do
- field.should_receive(:translate).with(user_a).and_return('Foo')
- field.should_receive(:translate).with(user_b).and_return('Foo')
+ expect(field).to receive(:translate).with(user_a).and_return('Foo')
+ expect(field).to receive(:translate).with(user_b).and_return('Foo')
callbacks.after_save instance
end
+
+ it "creates insert statements with all fields and attributes" do
+ expect(Riddle::Query::Insert).to receive(:new).twice.
+ with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
+ and_return(insert)
+
+ callbacks.after_commit instance
+ end
+
+ it "gets the document id for each reader" do
+ expect(index).to receive(:document_id_for_key).with(13).and_return(123)
+ expect(index).to receive(:document_id_for_key).with(14).and_return(123)
+
+ callbacks.after_commit instance
+ end
+
+ it "translates values for each reader" do
+ expect(field).to receive(:translate).with(user_a).and_return('Foo')
+ expect(field).to receive(:translate).with(user_b).and_return('Foo')
+
+ callbacks.after_commit instance
+ end
end
context 'with a block instead of a path' do
@@ -127,26 +191,48 @@
let(:user_b) { double('user', :id => 14, :persisted? => true) }
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]).
+ expect(Riddle::Query::Insert).to receive(:new).twice.
+ with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
and_return(insert)
callbacks.after_save instance
end
it "gets the document id for each reader" do
- index.should_receive(:document_id_for_key).with(13).and_return(123)
- index.should_receive(:document_id_for_key).with(14).and_return(123)
+ expect(index).to receive(:document_id_for_key).with(13).and_return(123)
+ expect(index).to receive(:document_id_for_key).with(14).and_return(123)
callbacks.after_save instance
end
it "translates values for each reader" do
- field.should_receive(:translate).with(user_a).and_return('Foo')
- field.should_receive(:translate).with(user_b).and_return('Foo')
+ expect(field).to receive(:translate).with(user_a).and_return('Foo')
+ expect(field).to receive(:translate).with(user_b).and_return('Foo')
callbacks.after_save instance
end
+
+ it "creates insert statements with all fields and attributes" do
+ expect(Riddle::Query::Insert).to receive(:new).twice.
+ with('my_index', ['id', 'name', 'created_at'], [[123, 'Foo', time]]).
+ and_return(insert)
+
+ callbacks.after_commit instance
+ end
+
+ it "gets the document id for each reader" do
+ expect(index).to receive(:document_id_for_key).with(13).and_return(123)
+ expect(index).to receive(:document_id_for_key).with(14).and_return(123)
+
+ callbacks.after_commit instance
+ end
+
+ it "translates values for each reader" do
+ expect(field).to receive(:translate).with(user_a).and_return('Foo')
+ expect(field).to receive(:translate).with(user_b).and_return('Foo')
+
+ callbacks.after_commit instance
+ end
end
end
end
diff --git a/spec/thinking_sphinx/real_time/field_spec.rb b/spec/thinking_sphinx/real_time/field_spec.rb
index 6989dd2a1..4adbe0b0b 100644
--- a/spec/thinking_sphinx/real_time/field_spec.rb
+++ b/spec/thinking_sphinx/real_time/field_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::RealTime::Field do
@@ -6,11 +8,11 @@
describe '#column' do
it 'returns the provided Column object' do
- field.column.should == column
+ expect(field.column).to eq(column)
end
it 'translates symbols to Column objects' do
- ThinkingSphinx::ActiveRecord::Column.should_receive(:new).with(:title).
+ expect(ThinkingSphinx::ActiveRecord::Column).to receive(:new).with(:title).
and_return(column)
ThinkingSphinx::RealTime::Field.new :title
@@ -20,11 +22,11 @@
describe '#name' do
it "uses the provided option by default" do
field = ThinkingSphinx::RealTime::Field.new column, :as => :foo
- field.name.should == 'foo'
+ expect(field.name).to eq('foo')
end
it "falls back to the column's name" do
- field.name.should == 'created_at'
+ expect(field.name).to eq('created_at')
end
end
@@ -34,34 +36,34 @@
let(:parent) { klass.new 'the parent name', nil }
it "returns the column's name if it's a string" do
- column.stub :__name => 'value'
+ allow(column).to receive_messages :__name => 'value'
- field.translate(object).should == 'value'
+ expect(field.translate(object)).to eq('value')
end
it "returns the column's name as a string if it's an integer" do
- column.stub :__name => 404
+ allow(column).to receive_messages :__name => 404
- field.translate(object).should == '404'
+ expect(field.translate(object)).to eq('404')
end
it "returns the object's method matching the column's name" do
- object.stub :created_at => 'a time'
+ allow(object).to receive_messages :created_at => 'a time'
- field.translate(object).should == 'a time'
+ expect(field.translate(object)).to eq('a time')
end
it "uses the column's stack to navigate through the object tree" do
- column.stub :__name => :name, :__stack => [:parent]
+ allow(column).to receive_messages :__name => :name, :__stack => [:parent]
- field.translate(object).should == 'the parent name'
+ expect(field.translate(object)).to eq('the parent name')
end
it "returns a blank string if any element in the object tree is nil" do
- column.stub :__name => :name, :__stack => [:parent]
+ allow(column).to receive_messages :__name => :name, :__stack => [:parent]
object.parent = nil
- field.translate(object).should == ''
+ expect(field.translate(object)).to eq('')
end
end
end
diff --git a/spec/thinking_sphinx/real_time/index_spec.rb b/spec/thinking_sphinx/real_time/index_spec.rb
index 54eb2c573..06e1e152d 100644
--- a/spec/thinking_sphinx/real_time/index_spec.rb
+++ b/spec/thinking_sphinx/real_time/index_spec.rb
@@ -1,47 +1,98 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::RealTime::Index do
let(:index) { ThinkingSphinx::RealTime::Index.new :user }
- let(:indices_path) { double('indices path', :join => '') }
let(:config) { double('config', :settings => {},
- :indices_location => indices_path, :next_offset => 8) }
+ :indices_location => 'location', :next_offset => 8,
+ :index_set_class => index_set_class) }
+ let(:index_set_class) { double(:index_set_class, :reference_name => :user) }
before :each do
- ThinkingSphinx::Configuration.stub :instance => config
+ 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
- index.attributes.collect(&:name).should include('sphinx_internal_id')
+ expect(index.attributes.collect(&:name)).to include('sphinx_internal_id')
end
it "has the class name attribute by default" do
- index.attributes.collect(&:name).should include('sphinx_internal_class')
+ expect(index.attributes.collect(&:name)).to include('sphinx_internal_class')
end
it "has the internal deleted attribute by default" do
- index.attributes.collect(&:name).should include('sphinx_deleted')
+ expect(index.attributes.collect(&:name)).to include('sphinx_deleted')
+ end
+
+ it "does not have an internal updated_at attribute by default" do
+ expect(index.attributes.collect(&:name)).to_not include('sphinx_updated_at')
+ end
+
+ it "has an internal updated_at attribute if real_time_tidy is true" do
+ config.settings["real_time_tidy"] = true
+
+ expect(index.attributes.collect(&:name)).to include('sphinx_updated_at')
end
end
describe '#delta?' do
it "always returns false" do
- index.should_not be_delta
+ expect(index).not_to be_delta
end
end
describe '#document_id_for_key' do
it "calculates the document id based on offset and number of indices" do
- config.stub_chain(:indices, :count).and_return(5)
- config.stub :next_offset => 7
+ allow(config).to receive_message_chain(:indices, :count).and_return(5)
+ allow(config).to receive_messages :next_offset => 7
- index.document_id_for_key(123).should == 622
+ expect(index.document_id_for_key(123)).to eq(622)
end
end
describe '#fields' do
it "has the internal class field by default" do
- index.fields.collect(&:name).should include('sphinx_internal_class_name')
+ expect(index.fields.collect(&:name)).to include('sphinx_internal_class_name')
end
end
@@ -53,14 +104,14 @@
end
it "interprets the definition block" do
- ThinkingSphinx::RealTime::Interpreter.should_receive(:translate!).
+ expect(ThinkingSphinx::RealTime::Interpreter).to receive(:translate!).
with(index, block)
index.interpret_definition!
end
it "only interprets the definition block once" do
- ThinkingSphinx::RealTime::Interpreter.should_receive(:translate!).
+ expect(ThinkingSphinx::RealTime::Interpreter).to receive(:translate!).
once
index.interpret_definition!
@@ -69,16 +120,16 @@
end
describe '#model' do
- let(:model) { double('model') }
+ let(:model) { double('model', :primary_key => :id) }
it "translates symbol references to model class" do
- ActiveSupport::Inflector.stub(:constantize => model)
+ allow(ActiveSupport::Inflector).to receive_messages(:constantize => model)
- index.model.should == model
+ expect(index.model).to eq(model)
end
it "memoizes the result" do
- ActiveSupport::Inflector.should_receive(:constantize).with('User').once.
+ expect(ActiveSupport::Inflector).to receive(:constantize).with('User').once.
and_return(model)
index.model
@@ -88,7 +139,7 @@
describe '#morphology' do
before :each do
- pending
+ skip
end
context 'with a render' do
@@ -98,7 +149,7 @@
rescue Riddle::Configuration::ConfigurationError
end
- index.morphology.should be_nil
+ expect(index.morphology).to be_nil
end
it "reads from the settings file if provided" do
@@ -109,7 +160,7 @@
rescue Riddle::Configuration::ConfigurationError
end
- index.morphology.should == 'stem_en'
+ expect(index.morphology).to eq('stem_en')
end
end
end
@@ -117,21 +168,21 @@
describe '#name' do
it "always uses the core suffix" do
index = ThinkingSphinx::RealTime::Index.new :user
- index.name.should == 'user_core'
+ expect(index.name).to eq('user_core')
end
end
describe '#offset' do
before :each do
- config.stub :next_offset => 4
+ allow(config).to receive_messages :next_offset => 4
end
it "uses the next offset value from the configuration" do
- index.offset.should == 4
+ expect(index.offset).to eq(4)
end
it "uses the reference to get a unique offset" do
- config.should_receive(:next_offset).with(:user).and_return(2)
+ expect(config).to receive(:next_offset).with(:user).and_return(2)
index.offset
end
@@ -139,11 +190,11 @@
describe '#render' do
before :each do
- FileUtils.stub :mkdir_p => true
+ allow(FileUtils).to receive_messages :mkdir_p => true
end
it "interprets the provided definition" do
- index.should_receive(:interpret_definition!).at_least(:once)
+ expect(index).to receive(:interpret_definition!).at_least(:once)
begin
index.render
@@ -154,26 +205,26 @@
end
describe '#scope' do
- let(:model) { double('model') }
+ let(:model) { double('model', :primary_key => :id) }
it "returns the model by default" do
- ActiveSupport::Inflector.stub(:constantize => model)
+ allow(ActiveSupport::Inflector).to receive_messages(:constantize => model)
- index.scope.should == model
+ expect(index.scope).to eq(model)
end
it "returns the evaluated scope if provided" do
index.scope = lambda { :foo }
- index.scope.should == :foo
+ expect(index.scope).to eq(:foo)
end
end
describe '#unique_attribute_names' do
it "returns all attribute names" do
- index.unique_attribute_names.should == [
+ expect(index.unique_attribute_names).to eq([
'sphinx_internal_id', 'sphinx_internal_class', 'sphinx_deleted'
- ]
+ ])
end
end
end
diff --git a/spec/thinking_sphinx/real_time/interpreter_spec.rb b/spec/thinking_sphinx/real_time/interpreter_spec.rb
index 1655e05e2..eb6f789dd 100644
--- a/spec/thinking_sphinx/real_time/interpreter_spec.rb
+++ b/spec/thinking_sphinx/real_time/interpreter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::RealTime::Interpreter do
@@ -8,19 +10,23 @@
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) }
it "creates a new interpreter instance with the given block and index" do
- ThinkingSphinx::RealTime::Interpreter.should_receive(:new).
+ expect(ThinkingSphinx::RealTime::Interpreter).to receive(:new).
with(index, block).and_return(instance)
ThinkingSphinx::RealTime::Interpreter.translate! index, block
end
it "calls translate! on the instance" do
- ThinkingSphinx::RealTime::Interpreter.stub!(:new => instance)
- instance.should_receive(:translate!)
+ allow(ThinkingSphinx::RealTime::Interpreter).to receive_messages(:new => instance)
+ expect(instance).to receive(:translate!)
ThinkingSphinx::RealTime::Interpreter.translate! index, block
end
@@ -31,35 +37,33 @@
let(:attribute) { double('attribute') }
before :each do
- ThinkingSphinx::RealTime::Attribute.stub! :new => attribute
+ allow(ThinkingSphinx::RealTime::Attribute).to receive_messages :new => attribute
end
it "creates a new attribute with the provided column" do
- ThinkingSphinx::RealTime::Attribute.should_receive(:new).
+ expect(ThinkingSphinx::RealTime::Attribute).to receive(:new).
with(column, {}).and_return(attribute)
instance.has column
end
it "passes through options to the attribute" do
- ThinkingSphinx::RealTime::Attribute.should_receive(:new).
- with(column, :as => :other_name).and_return(attribute)
+ expect(ThinkingSphinx::RealTime::Attribute).to receive(:new).
+ with(column, { :as => :other_name }).and_return(attribute)
instance.has column, :as => :other_name
end
it "adds an attribute to the index" do
- instance.has column
+ expect(index).to receive(:add_attribute).with(attribute)
- index.attributes.should 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
- index.attributes.select { |saved_attribute|
- saved_attribute == attribute
- }.length.should == 2
+ instance.has column, column
end
end
@@ -68,74 +72,72 @@
let(:field) { double('field') }
before :each do
- ThinkingSphinx::RealTime::Field.stub! :new => field
+ allow(ThinkingSphinx::RealTime::Field).to receive_messages :new => field
end
it "creates a new field with the provided column" do
- ThinkingSphinx::RealTime::Field.should_receive(:new).
+ expect(ThinkingSphinx::RealTime::Field).to receive(:new).
with(column, {}).and_return(field)
instance.indexes column
end
it "passes through options to the field" do
- ThinkingSphinx::RealTime::Field.should_receive(:new).
- with(column, :as => :other_name).and_return(field)
+ expect(ThinkingSphinx::RealTime::Field).to receive(:new).
+ with(column, { :as => :other_name }).and_return(field)
instance.indexes column, :as => :other_name
end
it "adds a field to the index" do
- instance.indexes column
+ expect(index).to receive(:add_field).with(field)
- index.fields.should 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
- index.fields.select { |saved_field|
- saved_field == field
- }.length.should == 2
+ instance.indexes column, column
end
context 'sortable' do
let(:attribute) { double('attribute') }
before :each do
- ThinkingSphinx::RealTime::Attribute.stub! :new => attribute
+ allow(ThinkingSphinx::RealTime::Attribute).to receive_messages :new => attribute
- column.stub :__name => :col
+ allow(column).to receive_messages :__name => :col
end
it "adds the _sort suffix to the field's name" do
- ThinkingSphinx::RealTime::Attribute.should_receive(:new).
- with(column, :as => :col_sort, :type => :string).
+ expect(ThinkingSphinx::RealTime::Attribute).to receive(:new).
+ with(column, { :as => :col_sort, :type => :string }).
and_return(attribute)
instance.indexes column, :sortable => true
end
it "respects given aliases" do
- ThinkingSphinx::RealTime::Attribute.should_receive(:new).
- with(column, :as => :other_sort, :type => :string).
+ expect(ThinkingSphinx::RealTime::Attribute).to receive(:new).
+ with(column, { :as => :other_sort, :type => :string }).
and_return(attribute)
instance.indexes column, :sortable => true, :as => :other
end
it "respects symbols instead of columns" do
- ThinkingSphinx::RealTime::Attribute.should_receive(:new).
- with(:title, :as => :title_sort, :type => :string).
+ expect(ThinkingSphinx::RealTime::Attribute).to receive(:new).
+ with(:title, { :as => :title_sort, :type => :string }).
and_return(attribute)
instance.indexes :title, :sortable => true
end
it "adds an attribute to the index" do
- instance.indexes column, :sortable => true
+ expect(index).to receive(:add_attribute).with(attribute)
- index.attributes.should include(attribute)
+ instance.indexes column, :sortable => true
end
end
end
@@ -144,15 +146,15 @@
let(:column) { double('column') }
before :each do
- ThinkingSphinx::ActiveRecord::Column.stub!(:new => column)
+ allow(ThinkingSphinx::ActiveRecord::Column).to receive_messages(:new => column)
end
it "returns a new column for the given method" do
- instance.id.should == column
+ expect(instance.id).to eq(column)
end
it "should initialise the column with the method name and arguments" do
- ThinkingSphinx::ActiveRecord::Column.should_receive(:new).
+ expect(ThinkingSphinx::ActiveRecord::Column).to receive(:new).
with(:users, :posts, :subject).and_return(column)
instance.users(:posts, :subject)
@@ -161,7 +163,7 @@
describe '#scope' do
it "passes the scope block through to the index" do
- index.should_receive(:scope=).with(instance_of(Proc))
+ expect(index).to receive(:scope=).with(instance_of(Proc))
instance.scope { :foo }
end
@@ -169,18 +171,18 @@
describe '#set_property' do
before :each do
- index.class.stub :settings => [:morphology]
+ allow(index.class).to receive_messages :settings => [:morphology]
end
it 'saves other settings as index options' do
instance.set_property :field_weights => {:name => 10}
- index.options[:field_weights].should == {:name => 10}
+ expect(index.options[:field_weights]).to eq({:name => 10})
end
context 'index settings' do
it "sets the provided setting" do
- index.should_receive(:morphology=).with('stem_en')
+ expect(index).to receive(:morphology=).with('stem_en')
instance.set_property :morphology => 'stem_en'
end
@@ -194,8 +196,8 @@
}
interpreter = ThinkingSphinx::RealTime::Interpreter.new index, block
- interpreter.translate!.
- should == interpreter.__id__
+ expect(interpreter.translate!).
+ to eq(interpreter.__id__)
end
end
end
diff --git a/spec/thinking_sphinx/real_time/transcribe_instance_spec.rb b/spec/thinking_sphinx/real_time/transcribe_instance_spec.rb
new file mode 100644
index 000000000..4351f45fd
--- /dev/null
+++ b/spec/thinking_sphinx/real_time/transcribe_instance_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::RealTime::TranscribeInstance do
+ let(:subject) do
+ ThinkingSphinx::RealTime::TranscribeInstance.call(
+ instance, index, [property_a, property_b, property_c]
+ )
+ end
+ let(:instance) { double :id => 43 }
+ let(:index) { double :document_id_for_key => 46, :primary_key => :id }
+ let(:property_a) { double :translate => 'A' }
+ let(:property_b) { double :translate => 'B' }
+ let(:property_c) { double :translate => 'C' }
+
+ it 'returns an array of each translated property, and the document id' do
+ expect(subject).to eq([46, 'A', 'B', 'C'])
+ end
+
+ it 'raises an error if something goes wrong' do
+ allow(property_b).to receive(:translate).and_raise(StandardError)
+
+ expect { subject }.to raise_error(ThinkingSphinx::TranscriptionError)
+ end
+
+ it 'notes the instance and property in the wrapper error' do
+ allow(property_b).to receive(:translate).and_raise(StandardError)
+
+ expect { subject }.to raise_error do |wrapper|
+ expect(wrapper.instance).to eq(instance)
+ expect(wrapper.property).to eq(property_b)
+ end
+ end
+end
diff --git a/spec/thinking_sphinx/real_time/transcriber_spec.rb b/spec/thinking_sphinx/real_time/transcriber_spec.rb
new file mode 100644
index 000000000..e18a08114
--- /dev/null
+++ b/spec/thinking_sphinx/real_time/transcriber_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ThinkingSphinx::RealTime::Transcriber do
+ 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')],
+ :primary_key => :id }
+ let(:insert) { double :replace! => replace }
+ let(:replace) { double :to_sql => 'REPLACE QUERY' }
+ let(:connection) { double :execute => true }
+ let(:instance_a) { double :id => 48, :persisted? => true }
+ let(:instance_b) { double :id => 49, :persisted? => true }
+ let(:properties_a) { double }
+ let(:properties_b) { double }
+
+ before :each do
+ allow(Riddle::Query::Insert).to receive(:new).and_return(insert)
+ allow(ThinkingSphinx::Connection).to receive(:take).and_yield(connection)
+ allow(ThinkingSphinx::RealTime::TranscribeInstance).to receive(:call).
+ with(instance_a, index, anything).and_return(properties_a)
+ allow(ThinkingSphinx::RealTime::TranscribeInstance).to receive(:call).
+ with(instance_b, index, anything).and_return(properties_b)
+ end
+
+ it "generates a SphinxQL command" do
+ expect(Riddle::Query::Insert).to receive(:new).with(
+ 'foo_core',
+ ['id', 'field_a', 'field_b', 'attr_a', 'attr_b'],
+ [properties_a, properties_b]
+ )
+
+ subject.copy instance_a, instance_b
+ end
+
+ it "executes the SphinxQL command" do
+ expect(connection).to receive(:execute).with('REPLACE QUERY')
+
+ 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)
+
+ expect(Riddle::Query::Insert).to receive(:new).with(
+ 'foo_core',
+ ['id', 'field_a', 'field_b', 'attr_a', 'attr_b'],
+ [properties_b]
+ )
+
+ subject.copy instance_a, instance_b
+ end
+
+ it "skips instances that fail a symbol condition" do
+ index.conditions << :ok?
+ allow(instance_a).to receive(:ok?).and_return(true)
+ allow(instance_b).to receive(:ok?).and_return(false)
+
+ expect(Riddle::Query::Insert).to receive(:new).with(
+ 'foo_core',
+ ['id', 'field_a', 'field_b', 'attr_a', 'attr_b'],
+ [properties_a]
+ )
+
+ subject.copy instance_a, instance_b
+ end
+
+ it "skips instances that fail a Proc condition" do
+ index.conditions << Proc.new { |instance| instance.ok? }
+ allow(instance_a).to receive(:ok?).and_return(true)
+ allow(instance_b).to receive(:ok?).and_return(false)
+
+ expect(Riddle::Query::Insert).to receive(:new).with(
+ 'foo_core',
+ ['id', 'field_a', 'field_b', 'attr_a', 'attr_b'],
+ [properties_a]
+ )
+
+ subject.copy instance_a, instance_b
+ end
+
+ it "skips instances that throw an error while transcribing values" do
+ error = ThinkingSphinx::TranscriptionError.new
+ error.instance = instance_a
+ error.inner_exception = StandardError.new
+
+ allow(ThinkingSphinx::RealTime::TranscribeInstance).to receive(:call).
+ with(instance_a, index, anything).
+ and_raise(error)
+ allow(ThinkingSphinx.output).to receive(:puts).and_return(nil)
+
+ expect(Riddle::Query::Insert).to receive(:new).with(
+ 'foo_core',
+ ['id', 'field_a', 'field_b', 'attr_a', 'attr_b'],
+ [properties_b]
+ )
+
+ subject.copy instance_a, instance_b
+ end
+end
diff --git a/spec/thinking_sphinx/real_time/translator_spec.rb b/spec/thinking_sphinx/real_time/translator_spec.rb
new file mode 100644
index 000000000..4ab7710df
--- /dev/null
+++ b/spec/thinking_sphinx/real_time/translator_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ThinkingSphinx::RealTime::Translator do
+ let(:subject) { ThinkingSphinx::RealTime::Translator.call object, column }
+ let(:object) { double }
+ let(:column) { double :__stack => [], :__name => :title }
+
+ it "converts non-UTF-8 strings to UTF-8" do
+ allow(object).to receive(:title).
+ and_return "hello".dup.force_encoding("ASCII-8BIT")
+
+ expect(subject).to eq("hello")
+ expect(subject.encoding.name).to eq("UTF-8")
+ end
+end
diff --git a/spec/thinking_sphinx/scopes_spec.rb b/spec/thinking_sphinx/scopes_spec.rb
index 301f66bab..a2bd8ec62 100644
--- a/spec/thinking_sphinx/scopes_spec.rb
+++ b/spec/thinking_sphinx/scopes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::Scopes do
@@ -16,30 +18,34 @@ def self.search(query = nil, options = {})
model.sphinx_scopes[:foo] = Proc.new { {:with => {:foo => :bar}} }
end
+ it "implements respond_to" do
+ expect(model).to respond_to(:foo)
+ end
+
it "creates new search" do
- model.foo.class.should == ThinkingSphinx::Search
+ expect(model.foo.class).to eq(ThinkingSphinx::Search)
end
it "passes block result to constructor" do
- model.foo.options[:with].should == {:foo => :bar}
+ expect(model.foo.options[:with]).to eq({:foo => :bar})
end
it "passes non-scopes through to the standard method error call" do
- lambda { model.bar }.should raise_error(NoMethodError)
+ expect { model.bar }.to raise_error(NoMethodError)
end
end
describe '#sphinx_scope' do
it "saves the given block with a name" do
model.sphinx_scope(:foo) { 27 }
- model.sphinx_scopes[:foo].call.should == 27
+ expect(model.sphinx_scopes[:foo].call).to eq(27)
end
end
describe '#default_sphinx_scope' do
it "gets and sets the default scope depending on the argument" do
model.default_sphinx_scope :foo
- model.default_sphinx_scope.should == :foo
+ expect(model.default_sphinx_scope).to eq(:foo)
end
end
end
diff --git a/spec/thinking_sphinx/search/glaze_spec.rb b/spec/thinking_sphinx/search/glaze_spec.rb
index 43a8844f6..06734195e 100644
--- a/spec/thinking_sphinx/search/glaze_spec.rb
+++ b/spec/thinking_sphinx/search/glaze_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
class Search; end
end
@@ -12,11 +14,11 @@ class Search; end
describe '#!=' do
it "is true for objects that don't match" do
- (glaze != double('foo')).should be_true
+ expect(glaze != double('foo')).to be_truthy
end
it "is false when the underlying object is a match" do
- (glaze != object).should be_false
+ expect(glaze != object).to be_falsey
end
end
@@ -28,50 +30,50 @@ class Search; end
let(:pane_two) { double('pane two', :foo => 'two', :bar => 'two') }
before :each do
- klass.stub(:new).and_return(pane_one, pane_two)
+ allow(klass).to receive(:new).and_return(pane_one, pane_two)
end
it "respects objects existing methods" do
- object.stub :foo => 'original'
+ allow(object).to receive_messages :foo => 'original'
- glaze.foo.should == 'original'
+ expect(glaze.foo).to eq('original')
end
it "uses the first pane that responds to the method" do
- glaze.foo.should == 'one'
- glaze.bar.should == 'two'
+ expect(glaze.foo).to eq('one')
+ expect(glaze.bar).to eq('two')
end
it "raises the method missing error otherwise" do
- object.stub :respond_to? => false
- object.stub(:baz).and_raise(NoMethodError)
+ allow(object).to receive_messages :respond_to? => false
+ allow(object).to receive(:baz).and_raise(NoMethodError)
- lambda { glaze.baz }.should raise_error(NoMethodError)
+ expect { glaze.baz }.to raise_error(NoMethodError)
end
end
describe '#respond_to?' do
it "responds to underlying object methods" do
- object.stub :foo => true
+ allow(object).to receive_messages :foo => true
- glaze.respond_to?(:foo).should be_true
+ expect(glaze.respond_to?(:foo)).to be_truthy
end
it "responds to underlying pane methods" do
pane = double('Pane Class', :new => double('pane', :bar => true))
glaze = ThinkingSphinx::Search::Glaze.new context, object, raw, [pane]
- glaze.respond_to?(:bar).should be_true
+ expect(glaze.respond_to?(:bar)).to be_truthy
end
it "does not to respond to methods that don't exist" do
- glaze.respond_to?(:something).should be_false
+ expect(glaze.respond_to?(:something)).to be_falsey
end
end
describe '#unglazed' do
it "returns the original object" do
- glaze.unglazed.should == object
+ expect(glaze.unglazed).to eq(object)
end
end
end
diff --git a/spec/thinking_sphinx/search/query_spec.rb b/spec/thinking_sphinx/search/query_spec.rb
index 28b8cc58e..55514f37a 100644
--- a/spec/thinking_sphinx/search/query_spec.rb
+++ b/spec/thinking_sphinx/search/query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ThinkingSphinx
class Search; end
end
@@ -14,30 +16,30 @@ class Search; end
it "passes through the keyword as provided" do
query = ThinkingSphinx::Search::Query.new 'pancakes'
- query.to_s.should == 'pancakes'
+ expect(query.to_s).to eq('pancakes')
end
it "pairs fields and keywords for given conditions" do
query = ThinkingSphinx::Search::Query.new '', :title => 'pancakes'
- query.to_s.should == '@title pancakes'
+ expect(query.to_s).to eq('@title pancakes')
end
it "combines both keywords and conditions" do
query = ThinkingSphinx::Search::Query.new 'tasty', :title => 'pancakes'
- query.to_s.should == 'tasty @title pancakes'
+ expect(query.to_s).to eq('tasty @title pancakes')
end
it "automatically stars keywords if requested" do
- ThinkingSphinx::Query.should_receive(:wildcard).with('cake', true).
+ expect(ThinkingSphinx::Query).to receive(:wildcard).with('cake', true).
and_return('*cake*')
ThinkingSphinx::Search::Query.new('cake', {}, true).to_s
end
it "automatically stars condition keywords if requested" do
- ThinkingSphinx::Query.should_receive(:wildcard).with('pan', true).
+ expect(ThinkingSphinx::Query).to receive(:wildcard).with('pan', true).
and_return('*pan*')
ThinkingSphinx::Search::Query.new('', {:title => 'pan'}, true).to_s
@@ -47,32 +49,39 @@ class Search; end
query = ThinkingSphinx::Search::Query.new '',
{:sphinx_internal_class_name => 'article'}, true
- query.to_s.should == '@sphinx_internal_class_name article'
+ expect(query.to_s).to eq('@sphinx_internal_class_name article')
end
it "handles null values by removing them from the conditions hash" do
query = ThinkingSphinx::Search::Query.new '', :title => nil
- query.to_s.should == ''
+ expect(query.to_s).to eq('')
end
it "handles empty string values by removing them from the conditions hash" do
query = ThinkingSphinx::Search::Query.new '', :title => ''
- query.to_s.should == ''
+ expect(query.to_s).to eq('')
end
it "handles nil queries" do
query = ThinkingSphinx::Search::Query.new nil, {}
- query.to_s.should == ''
+ expect(query.to_s).to eq('')
end
it "allows mixing of blank and non-blank conditions" do
query = ThinkingSphinx::Search::Query.new 'tasty', :title => 'pancakes',
:ingredients => nil
- query.to_s.should == 'tasty @title pancakes'
+ expect(query.to_s).to eq('tasty @title pancakes')
+ end
+
+ it "handles multiple fields for a single condition" do
+ query = ThinkingSphinx::Search::Query.new '',
+ [:title, :content] => 'pancakes'
+
+ expect(query.to_s).to eq('@(title,content) pancakes')
end
end
end
diff --git a/spec/thinking_sphinx/search_spec.rb b/spec/thinking_sphinx/search_spec.rb
index c1721e53f..a5224e35c 100644
--- a/spec/thinking_sphinx/search_spec.rb
+++ b/spec/thinking_sphinx/search_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ThinkingSphinx::Search do
@@ -34,26 +36,26 @@
end
before :each do
- ThinkingSphinx::Search::Context.stub :new => context
+ allow(ThinkingSphinx::Search::Context).to receive_messages :new => context
stub_const 'ThinkingSphinx::Middlewares::DEFAULT', stack
end
describe '#current_page' do
it "should return 1 by default" do
- search.current_page.should == 1
+ expect(search.current_page).to eq(1)
end
it "should handle string page values" do
- ThinkingSphinx::Search.new(:page => '2').current_page.should == 2
+ expect(ThinkingSphinx::Search.new(:page => '2').current_page).to eq(2)
end
it "should handle empty string page values" do
- ThinkingSphinx::Search.new(:page => '').current_page.should == 1
+ expect(ThinkingSphinx::Search.new(:page => '').current_page).to eq(1)
end
it "should return the requested page" do
- ThinkingSphinx::Search.new(:page => 10).current_page.should == 10
+ expect(ThinkingSphinx::Search.new(:page => 10).current_page).to eq(10)
end
end
@@ -61,25 +63,25 @@
it "returns false if there is anything in the data set" do
context[:results] << double
- search.should_not be_empty
+ expect(search).not_to be_empty
end
it "returns true if the data set is empty" do
context[:results].clear
- search.should be_empty
+ expect(search).to be_empty
end
end
describe '#initialize' do
it "lazily loads by default" do
- stack.should_not_receive(:call)
+ expect(stack).not_to receive(:call)
ThinkingSphinx::Search.new
end
it "should automatically populate when :populate is set to true" do
- stack.should_receive(:call).and_return(true)
+ expect(stack).to receive(:call).and_return(true)
ThinkingSphinx::Search.new(:populate => true)
end
@@ -87,74 +89,74 @@
describe '#offset' do
it "should default to 0" do
- search.offset.should == 0
+ expect(search.offset).to eq(0)
end
it "should increase by the per_page value for each page in" do
- ThinkingSphinx::Search.new(:per_page => 25, :page => 2).offset.
- should == 25
+ expect(ThinkingSphinx::Search.new(:per_page => 25, :page => 2).offset).
+ to eq(25)
end
it "should prioritise explicit :offset over calculated if given" do
- ThinkingSphinx::Search.new(:offset => 5).offset.should == 5
+ expect(ThinkingSphinx::Search.new(:offset => 5).offset).to eq(5)
end
end
describe '#page' do
it "sets the current page" do
search.page(3)
- search.current_page.should == 3
+ expect(search.current_page).to eq(3)
end
it "returns the search object" do
- search.page(2).should == search
+ expect(search.page(2)).to eq(search)
end
end
describe '#per' do
it "sets the current per_page value" do
search.per(29)
- search.per_page.should == 29
+ expect(search.per_page).to eq(29)
end
it "returns the search object" do
- search.per(29).should == search
+ expect(search.per(29)).to eq(search)
end
end
describe '#per_page' do
it "defaults to 20" do
- search.per_page.should == 20
+ expect(search.per_page).to eq(20)
end
it "is set as part of the search options" do
- ThinkingSphinx::Search.new(:per_page => 10).per_page.should == 10
+ expect(ThinkingSphinx::Search.new(:per_page => 10).per_page).to eq(10)
end
it "should prioritise :limit over :per_page if given" do
- ThinkingSphinx::Search.new(:per_page => 30, :limit => 40).per_page.
- should == 40
+ expect(ThinkingSphinx::Search.new(:per_page => 30, :limit => 40).per_page).
+ to eq(40)
end
it "should allow for string arguments" do
- ThinkingSphinx::Search.new(:per_page => '10').per_page.should == 10
+ expect(ThinkingSphinx::Search.new(:per_page => '10').per_page).to eq(10)
end
it "allows setting of the per_page value" do
search.per_page(24)
- search.per_page.should == 24
+ expect(search.per_page).to eq(24)
end
end
describe '#populate' do
it "runs the middleware" do
- stack.should_receive(:call).with([context]).and_return(true)
+ expect(stack).to receive(:call).with([context]).and_return(true)
search.populate
end
it "does not retrieve results twice" do
- stack.should_receive(:call).with([context]).once.and_return(true)
+ expect(stack).to receive(:call).with([context]).once.and_return(true)
search.populate
search.populate
@@ -163,11 +165,11 @@
describe '#respond_to?' do
it "should respond to Array methods" do
- search.respond_to?(:each).should be_true
+ expect(search.respond_to?(:each)).to be_truthy
end
it "should respond to Search methods" do
- search.respond_to?(:per_page).should be_true
+ expect(search.respond_to?(:per_page)).to be_truthy
end
it "should return true for methods delegated to pagination mask by method_missing" do
@@ -196,7 +198,7 @@
context[:results] << glazed
- search.to_a.first.__id__.should == unglazed.__id__
+ expect(search.to_a.first.__id__).to eq(unglazed.__id__)
end
end
diff --git a/spec/thinking_sphinx/wildcard_spec.rb b/spec/thinking_sphinx/wildcard_spec.rb
index c9b72f1ef..238d68d7d 100644
--- a/spec/thinking_sphinx/wildcard_spec.rb
+++ b/spec/thinking_sphinx/wildcard_spec.rb
@@ -1,4 +1,6 @@
# encoding: utf-8
+# frozen_string_literal: true
+
module ThinkingSphinx; end
require './lib/thinking_sphinx/wildcard'
@@ -6,41 +8,46 @@ module ThinkingSphinx; end
describe ThinkingSphinx::Wildcard do
describe '.call' do
it "does not star quorum operators" do
- ThinkingSphinx::Wildcard.call("foo/3").should == "*foo*/3"
+ expect(ThinkingSphinx::Wildcard.call("foo/3")).to eq("*foo*/3")
end
it "does not star proximity operators or quoted strings" do
- ThinkingSphinx::Wildcard.call(%q{"hello world"~3}).
- should == %q{"hello world"~3}
+ expect(ThinkingSphinx::Wildcard.call(%q{"hello world"~3})).
+ to eq(%q{"hello world"~3})
end
it "treats slashes as a separator when starring" do
- ThinkingSphinx::Wildcard.call("a\\/c").should == "*a*\\/*c*"
+ expect(ThinkingSphinx::Wildcard.call("a\\/c")).to eq("*a*\\/*c*")
end
it "separates escaping from the end of words" do
- ThinkingSphinx::Wildcard.call("\\(913\\)").should == "\\(*913*\\)"
+ expect(ThinkingSphinx::Wildcard.call("\\(913\\)")).to eq("\\(*913*\\)")
end
it "ignores escaped slashes" do
- ThinkingSphinx::Wildcard.call("\\/\\/pan").should == "\\/\\/*pan*"
+ expect(ThinkingSphinx::Wildcard.call("\\/\\/pan")).to eq("\\/\\/*pan*")
end
it "does not star manually provided field tags" do
- ThinkingSphinx::Wildcard.call("@title pan").should == "@title *pan*"
+ expect(ThinkingSphinx::Wildcard.call("@title pan")).to eq("@title *pan*")
+ end
+
+ it 'does not star multiple field tags' do
+ expect(ThinkingSphinx::Wildcard.call("@title pan @tags food")).
+ to eq("@title *pan* @tags *food*")
end
it "does not star manually provided arrays of field tags" do
- ThinkingSphinx::Wildcard.call("@(title, body) pan").
- should == "@(title, body) *pan*"
+ expect(ThinkingSphinx::Wildcard.call("@(title, body) pan")).
+ to eq("@(title, body) *pan*")
end
it "handles nil queries" do
- ThinkingSphinx::Wildcard.call(nil).should == ''
+ expect(ThinkingSphinx::Wildcard.call(nil)).to eq('')
end
it "handles unicode values" do
- ThinkingSphinx::Wildcard.call('älytön').should == '*älytön*'
+ expect(ThinkingSphinx::Wildcard.call('älytön')).to eq('*älytön*')
end
end
end
diff --git a/spec/thinking_sphinx_spec.rb b/spec/thinking_sphinx_spec.rb
index 5b0e4b3f5..775966e77 100644
--- a/spec/thinking_sphinx_spec.rb
+++ b/spec/thinking_sphinx_spec.rb
@@ -1,19 +1,22 @@
+# frozen_string_literal: true
+
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
+ allow(ThinkingSphinx::Search).to receive_messages :new => search
end
it "returns the total entries of the search object" do
- ThinkingSphinx.count.should == search.total_entries
+ expect(ThinkingSphinx.count).to eq(search.total_entries)
end
it "passes through the given query and options" do
- ThinkingSphinx::Search.should_receive(:new).with('foo', :bar => :baz).
+ expect(ThinkingSphinx::Search).to receive(:new).with('foo', { :bar => :baz }).
and_return(search)
ThinkingSphinx.count('foo', :bar => :baz)
@@ -24,15 +27,15 @@
let(:search) { double('search') }
before :each do
- ThinkingSphinx::Search.stub :new => search
+ allow(ThinkingSphinx::Search).to receive_messages :new => search
end
it "returns a new search object" do
- ThinkingSphinx.search.should == search
+ expect(ThinkingSphinx.search).to eq(search)
end
it "passes through the given query and options" do
- ThinkingSphinx::Search.should_receive(:new).with('foo', :bar => :baz).
+ expect(ThinkingSphinx::Search).to receive(:new).with('foo', { :bar => :baz }).
and_return(search)
ThinkingSphinx.search('foo', :bar => :baz)
diff --git a/thinking-sphinx.gemspec b/thinking-sphinx.gemspec
index 06a43ec62..a4e0587bb 100644
--- a/thinking-sphinx.gemspec
+++ b/thinking-sphinx.gemspec
@@ -1,19 +1,19 @@
+# frozen_string_literal: true
+
# -*- encoding: utf-8 -*-
$:.push File.expand_path('../lib', __FILE__)
Gem::Specification.new do |s|
s.name = 'thinking-sphinx'
- s.version = '3.1.3'
+ s.version = '5.6.0'
s.platform = Gem::Platform::RUBY
s.authors = ["Pat Allan"]
s.email = ["pat@freelancing-gods.com"]
- s.homepage = 'http://pat.github.io/thinking-sphinx/'
+ s.homepage = 'https://pat.github.io/thinking-sphinx/'
s.summary = 'A smart wrapper over Sphinx for ActiveRecord'
s.description = %Q{An intelligent layer for ActiveRecord (via Rails and Sinatra) for the Sphinx full-text search tool.}
s.license = 'MIT'
- s.rubyforge_project = 'thinking-sphinx'
-
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f|
@@ -21,15 +21,16 @@ 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', '>= 1.5.11'
+ s.add_runtime_dependency 'riddle', '~> 2.3'
- s.add_development_dependency 'appraisal', '~> 0.5.2'
- s.add_development_dependency 'combustion', '~> 0.4.0'
- s.add_development_dependency 'database_cleaner', '~> 1.2.0'
- s.add_development_dependency 'rspec', '~> 2.13.0'
+ s.add_development_dependency 'appraisal', '~> 1.0.2'
+ s.add_development_dependency 'combustion', '~> 1.1'
+ s.add_development_dependency 'database_cleaner', '~> 2.0.2'
+ s.add_development_dependency 'rspec', '~> 3.12.0'
+ s.add_development_dependency 'rspec-retry', '~> 0.5.6'
end