pax_global_header 0000666 0000000 0000000 00000000064 13411321301 0014477 g ustar 00root root 0000000 0000000 52 comment=edc0050394deebb1dc55c617c0b62e70f9618394
thinking-sphinx-4.1.0/ 0000775 0000000 0000000 00000000000 13411321301 0014623 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/.gitignore 0000664 0000000 0000000 00000000333 13411321301 0016612 0 ustar 00root root 0000000 0000000 *.gem
.bundle
.rbx
.rspec
.tool-versions
gemfiles
Gemfile.lock
*.sublime-*
pkg/*
spec/internal/config/test.sphinx.conf
spec/internal/db/sphinx
spec/internal/log
!spec/internal/tmp/.gitkeep
spec/internal/tmp/*
tmp
_site
thinking-sphinx-4.1.0/.travis.yml 0000664 0000000 0000000 00000003033 13411321301 0016733 0 ustar 00root root 0000000 0000000 language: ruby
rvm:
- 2.3.8
- 2.4.5
- 2.5.3
- 2.6.0
addons:
apt:
packages:
- cmake
- bison
- flex
before_install:
- pip install --upgrade --user awscli
- gem update --system
- gem install bundler
before_script:
- mysql -e 'create database thinking_sphinx;' > /dev/null
- psql -c 'create database thinking_sphinx;' -U postgres >/dev/null
- "./bin/loadsphinx $SPHINX_VERSION $SPHINX_ENGINE"
- bundle exec appraisal install
script: bundle exec appraisal rspec
env:
global:
- SPHINX_BIN=ext/sphinx/bin/
- secure: cUPinkilBafqDSPsTkl/PXYc2aXNKUQKXGK8poBBMqKN9/wjfJx1DWgtowDKalekdZELxDhc85Ye3bL1xlW4nLjOu+U6Tkt8eNw2Nhs1flodHzA/RyENdBLr/tBHt43EjkrDehZx5sBHmWQY4miHs8AJz0oKO9Ae2inTOHx9Iuc=
matrix:
- DATABASE=mysql2 SPHINX_VERSION=2.1.9 SPHINX_ENGINE=sphinx
- DATABASE=postgresql SPHINX_VERSION=2.1.9 SPHINX_ENGINE=sphinx
- DATABASE=mysql2 SPHINX_VERSION=2.2.11 SPHINX_ENGINE=sphinx
- DATABASE=postgresql SPHINX_VERSION=2.2.11 SPHINX_ENGINE=sphinx
- DATABASE=mysql2 SPHINX_VERSION=3.0.3 SPHINX_ENGINE=sphinx
- DATABASE=postgresql SPHINX_VERSION=3.0.3 SPHINX_ENGINE=sphinx
- DATABASE=mysql2 SPHINX_VERSION=3.1.1 SPHINX_ENGINE=sphinx
- DATABASE=mysql2 SPHINX_VERSION=2.6.3 SPHINX_ENGINE=manticore
- DATABASE=postgresql SPHINX_VERSION=2.6.3 SPHINX_ENGINE=manticore
- DATABASE=mysql2 SPHINX_VERSION=2.7.4 SPHINX_ENGINE=manticore
- DATABASE=postgresql SPHINX_VERSION=2.7.4 SPHINX_ENGINE=manticore
# - DATABASE=postgresql SPHINX_VERSION=3.1.1 SPHINX_ENGINE=sphinx
sudo: false
addons:
postgresql: '9.4'
services:
- postgresql
thinking-sphinx-4.1.0/Appraisals 0000664 0000000 0000000 00000002470 13411321301 0016650 0 ustar 00root root 0000000 0000000 appraise 'rails_3_2' do
gem 'rails', '~> 3.2.22.2'
gem 'mysql2', '~> 0.3.10', :platform => :ruby
end if RUBY_VERSION.to_f <= 2.3
appraise 'rails_4_0' do
gem 'rails', '~> 4.0.13'
gem 'mysql2', '~> 0.3.10', :platform => :ruby
end if RUBY_VERSION.to_f <= 2.3
appraise 'rails_4_1' do
gem 'rails', '~> 4.1.15'
gem 'mysql2', '~> 0.3.13', :platform => :ruby
end if RUBY_VERSION.to_f <= 2.3
appraise 'rails_4_2' do
gem 'rails', '~> 4.2.6'
gem 'mysql2', '~> 0.4.0', :platform => :ruby
end if RUBY_VERSION.to_f <= 2.3
appraise 'rails_5_0' do
if RUBY_PLATFORM == "java"
gem 'rails', '5.0.6'
else
gem 'rails', '~> 5.0.7'
end
gem 'mysql2', '~> 0.4.0', :platform => :ruby
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
appraise 'rails_5_1' do
gem 'rails', '~> 5.1.0'
gem 'mysql2', '~> 0.4.0', :platform => :ruby
end if RUBY_PLATFORM != 'java'
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 >= 2.3
thinking-sphinx-4.1.0/CHANGELOG.markdown 0000664 0000000 0000000 00000065660 13411321301 0017673 0 ustar 00root root 0000000 0000000 # Changelog
All notable changes to this project (at least, from v3.0.0 onwards) are documented in this file.
## 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.
thinking-sphinx-4.1.0/Gemfile 0000664 0000000 0000000 00000000717 13411321301 0016123 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
source 'https://rubygems.org'
gemspec
gem 'mysql2', '~> 0.5.0', :platform => :ruby
gem 'pg', '~> 0.18.4', :platform => :ruby
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
thinking-sphinx-4.1.0/LICENCE 0000664 0000000 0000000 00000002035 13411321301 0015610 0 ustar 00root root 0000000 0000000 Copyright (c) 2011 Pat Allan
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
thinking-sphinx-4.1.0/README.textile 0000664 0000000 0000000 00000012241 13411321301 0017160 0 ustar 00root root 0000000 0000000 h1. Thinking Sphinx
Thinking Sphinx is a library for connecting ActiveRecord to the Sphinx full-text search tool, and integrates closely with Rails (but also works with other Ruby web frameworks). The current release is v4.1.0.
h2. Upgrading
Please refer to "the changelog":https://github.com/pat/thinking-sphinx/blob/develop/CHANGELOG.markdown and "release notes":https://github.com/pat/thinking-sphinx/releases for any changes you need to make when upgrading. The release notes in particular are quite good at covering breaking changes and more details for new features.
The documentation also has more details on what's involved for upgrading from "v3 to v4":https://freelancing-gods.com/thinking-sphinx/v4/upgrading.html, and "v1/v2 to v3":https://freelancing-gods.com/thinking-sphinx/v3/upgrading.html.
h2. Installation
It's a gem, so install it like you would any other gem. You will also need to specify the mysql2 gem if you're using MRI, or jdbc-mysql if you're using JRuby:
gem 'mysql2', '~> 0.3', :platform => :ruby
gem 'jdbc-mysql', '~> 5.1.35', :platform => :jruby
gem 'thinking-sphinx', '~> 4.1'
The MySQL gems mentioned are required for connecting to Sphinx, so please include it even when you're using PostgreSQL for your database. If you're using JRuby with a version of Sphinx prior to 2.2.11, there is "currently an issue with Sphinx and jdbc-mysql 5.1.36 or newer":http://sphinxsearch.com/forum/view.html?id=13939, so you'll need to stick to nothing more recent than 5.1.35, or upgrade Sphinx.
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":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
The current release of Thinking Sphinx works with the following versions of its dependencies:
|_. Library |_. Minimum |_. Tested Against |
| Ruby | v2.3 | v2.3.8, v2.4.5, v2.5.3, v2.6.0 |
| Sphinx | v2.1.2 | v2.1.9, v2.2.11, v3.0.3, v3.1.1 |
| Manticore | v2.6.3 | v2.6.3, v2.7.4 |
| ActiveRecord | v3.2 | v3.2, v4.0, v4.1, v4.2, v5.0, v5.1, v5.2 |
It _might_ work with older versions of Ruby, but it's highly recommended to update to a supported release.
It should also work with JRuby, but the test environment on Travis CI has been timing out, hence that's not actively tested against at the moment.
h3. Sphinx or Manticore
Thinking Sphinx v3 is currently built for Sphinx 2.1.2 or newer, or Manticore v2.6+.
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.
Please note that if you're referring to polymorphic associations in your index definitions, you'll want to be using Rails/ActiveRecord 4.0 or newer. Supporting polymorphic associations and Rails/ActiveRecord 3.2 is problematic, and likely will not be addressed in the future.
If you want ActiveRecord 3.1 support, then refer to the 3.0.x releases of Thinking Sphinx. Anything older than that, then you're stuck with Thinking Sphinx v2.x (for Rails/ActiveRecord 3.0) or v1.x (Rails 2.3). Please note that these older versions are no longer actively supported.
h3. Ruby
You'll need either the standard Ruby (v2.2 or newer) or JRuby (9.1 or newer).
h3. Database Versions
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.
In order to run the tests, you'll need to create a database named @thinking_sphinx@:
# Either fire up a MySQL console:
mysql -u root
# OR a PostgreSQL console:
psql
# In that console, create the database:
CREATE DATABASE thinking_sphinx;
You can then run the unit tests with @rake spec:unit@, the acceptance tests with @rake spec:acceptance@, or all of the tests with just @rake@. To run these with PostgreSQL, you'll need to set the @DATABASE@ environment variable accordingly:
DATABASE=postgresql rake
h2. Licence
Copyright (c) 2007-2018, 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.
thinking-sphinx-4.1.0/Rakefile 0000664 0000000 0000000 00000001004 13411321301 0016263 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'bundler'
require 'appraisal'
Bundler::GemHelper.install_tasks
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new
namespace :spec do
desc 'Run unit specs only'
RSpec::Core::RakeTask.new(:unit) do |task|
task.pattern = 'spec'
task.rspec_opts = '--tag "~live"'
end
desc 'Run acceptance specs only'
RSpec::Core::RakeTask.new(:acceptance) do |task|
task.pattern = 'spec'
task.rspec_opts = '--tag "live"'
end
end
task :default => :spec
thinking-sphinx-4.1.0/bin/ 0000775 0000000 0000000 00000000000 13411321301 0015373 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/bin/console 0000775 0000000 0000000 00000000575 13411321301 0016772 0 ustar 00root root 0000000 0000000 #! /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__)
thinking-sphinx-4.1.0/bin/loadsphinx 0000775 0000000 0000000 00000002566 13411321301 0017503 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
version=$1
engine=$2
name="$engine-$version"
bucket="thinking-sphinx"
directory="ext/sphinx"
prefix="`pwd`/$directory"
file="ext/$name.tar.gz"
if [ "$engine" == "sphinx" ]; then
url="http://sphinxsearch.com/files/$name-release.tar.gz"
else
url="https://github.com/manticoresoftware/manticore.git"
fi
download_and_compile_source () {
if [ "$engine" == "sphinx" ]; then
download_and_compile_sphinx
else
download_and_compile_manticore
fi
}
download_and_compile_sphinx () {
curl -O $url
tar -zxf $name-release.tar.gz
cd $name-release
./configure --with-mysql --with-pgsql --enable-id64 --prefix=$prefix
make
make install
cd ..
rm -rf $name-release.tar.gz $name-release
}
download_and_compile_manticore () {
git clone $url $engine
cd $engine
git checkout $version
mkdir build
cd build
cmake -D WITH_MYSQL=TRUE -D WITH_PGSQL=TRUE -D DISABLE_TESTING=TRUE -D CMAKE_INSTALL_PREFIX=$prefix ..
make -j4
make install
cd ../..
rm -rf $engine
}
load_cache () {
mkdir ext
curl -o $file http://$bucket.s3.amazonaws.com/bincaches/$name.tar.gz
tar -zxf $file
}
push_cache () {
tar -czf $file $directory
aws s3 cp $file s3://$bucket/bincaches/$name.tar.gz --acl public-read
}
if curl -i --head --fail http://$bucket.s3.amazonaws.com/bincaches/$name.tar.gz
then
load_cache
else
download_and_compile_source
push_cache
fi
thinking-sphinx-4.1.0/lib/ 0000775 0000000 0000000 00000000000 13411321301 0015371 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking-sphinx.rb 0000664 0000000 0000000 00000000071 13411321301 0021036 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'thinking_sphinx'
thinking-sphinx-4.1.0/lib/thinking/ 0000775 0000000 0000000 00000000000 13411321301 0017204 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking/sphinx.rb 0000664 0000000 0000000 00000000071 13411321301 0021040 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'thinking_sphinx'
thinking-sphinx-4.1.0/lib/thinking_sphinx.rb 0000664 0000000 0000000 00000005645 13411321301 0021134 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
if RUBY_PLATFORM == 'java'
require 'java'
require 'jdbc/mysql'
Jdbc::MySQL.load_driver
else
require 'mysql2'
end
require 'riddle'
require 'riddle/2.1.0'
require 'middleware'
require 'active_record'
require 'innertube'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/module/attribute_accessors'
module ThinkingSphinx
MAXIMUM_STATEMENT_LENGTH = (2 ** 23) - 5
def self.count(query = '', options = {})
search_for_ids(query, options).total_entries
end
def self.facets(query = '', options = {})
ThinkingSphinx::FacetSearch.new query, options
end
def self.search(query = '', options = {})
ThinkingSphinx::Search.new query, options
end
def self.search_for_ids(query = '', options = {})
search = ThinkingSphinx::Search.new query, options
ThinkingSphinx::Search::Merger.new(search).merge! nil, :ids_only => 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/deletion'
require 'thinking_sphinx/errors'
require 'thinking_sphinx/excerpter'
require 'thinking_sphinx/facet'
require 'thinking_sphinx/facet_search'
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/query'
require 'thinking_sphinx/rake_interface'
require 'thinking_sphinx/scopes'
require 'thinking_sphinx/search'
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
thinking-sphinx-4.1.0/lib/thinking_sphinx/ 0000775 0000000 0000000 00000000000 13411321301 0020575 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record.rb 0000664 0000000 0000000 00000003465 13411321301 0023743 0 ustar 00root root 0000000 0000000 # 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'
require 'thinking_sphinx/active_record/association'
require 'thinking_sphinx/active_record/association_proxy'
require 'thinking_sphinx/active_record/attribute'
require 'thinking_sphinx/active_record/base'
require 'thinking_sphinx/active_record/column'
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/index'
require 'thinking_sphinx/active_record/interpreter'
require 'thinking_sphinx/active_record/join_association'
require 'thinking_sphinx/active_record/log_subscriber'
require 'thinking_sphinx/active_record/polymorpher'
require 'thinking_sphinx/active_record/property_query'
require 'thinking_sphinx/active_record/property_sql_presenter'
require 'thinking_sphinx/active_record/simple_many_query'
require 'thinking_sphinx/active_record/source_joins'
require 'thinking_sphinx/active_record/sql_builder'
require 'thinking_sphinx/active_record/sql_source'
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'
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/ 0000775 0000000 0000000 00000000000 13411321301 0023406 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/association.rb 0000664 0000000 0000000 00000000423 13411321301 0026246 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Association
def initialize(column)
@column = column
end
def stack
@column.__stack + [@column.__name]
end
def string?
@column.is_a?(String)
end
def to_s
@column.to_s
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/association_proxy.rb 0000664 0000000 0000000 00000001724 13411321301 0027514 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::ActiveRecord::AssociationProxy
extend ActiveSupport::Concern
def search(query = nil, options = {})
perform_search super(*normalise_search_arguments(query, options))
end
def search_for_ids(query = nil, options = {})
perform_search super(*normalise_search_arguments(query, options))
end
private
def normalise_search_arguments(query, options)
query, options = nil, query if query.is_a?(Hash)
options[:ignore_scopes] = true
[query, options]
end
def perform_search(searcher)
ThinkingSphinx::Search::Merger.new(searcher).merge! nil,
:with => association_filter
end
def association_filter
attribute = AttributeFinder.new(proxy_association).attribute
{attribute.name.to_sym => proxy_association.owner.id}
end
end
require 'thinking_sphinx/active_record/association_proxy/attribute_finder'
require 'thinking_sphinx/active_record/association_proxy/attribute_matcher'
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/association_proxy/ 0000775 0000000 0000000 00000000000 13411321301 0027163 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb 0000664 0000000 0000000 00000001776 13411321301 0033055 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::AssociationProxy::AttributeFinder
def initialize(association)
@association = association
end
def attribute
attributes.detect { |attribute|
ThinkingSphinx::ActiveRecord::AssociationProxy::AttributeMatcher.new(
attribute, foreign_key
).matches?
} or raise "Missing Attribute for Foreign Key #{foreign_key}"
end
private
def attributes
indices.collect(&:attributes).flatten
end
def configuration
ThinkingSphinx::Configuration.instance
end
def foreign_key
@foreign_key ||= reflection_target.foreign_key
end
def indices
@indices ||= begin
configuration.preload_indices
configuration.indices_for_references(
*ThinkingSphinx::IndexSet.reference_name(@association.klass)
).reject &:distributed?
end
end
def reflection_target
target = @association.reflection
target = target.through_reflection if target.through_reflection
target
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb 0000664 0000000 0000000 00000001574 13411321301 0033225 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::AssociationProxy::AttributeMatcher
def initialize(attribute, foreign_key)
@attribute, @foreign_key = attribute, foreign_key.to_s
end
def matches?
return false if many?
column_name_matches? || attribute_name_matches? || multi_singular_match?
end
private
attr_reader :attribute, :foreign_key
delegate :name, :multi?, :to => :attribute
def attribute_name_matches?
name == foreign_key
end
def column_name_matches?
column.__name.to_s == foreign_key
end
def column
attribute.respond_to?(:columns) ? attribute.columns.first :
attribute.column
end
def many?
attribute.respond_to?(:columns) && attribute.columns.many?
end
def multi_singular_match?
multi? && name.singularize == foreign_key
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/attribute.rb 0000664 0000000 0000000 00000001174 13411321301 0025741 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Attribute <
ThinkingSphinx::ActiveRecord::Property
delegate :type, :type=, :multi?, :updateable?, :to => :typist
delegate :value_for, :to => :values
private
def typist
@typist ||= ThinkingSphinx::ActiveRecord::Attribute::Type.new self, @model
end
def values
@values ||= ThinkingSphinx::ActiveRecord::Attribute::Values.new self
end
end
require 'thinking_sphinx/active_record/attribute/sphinx_presenter'
require 'thinking_sphinx/active_record/attribute/type'
require 'thinking_sphinx/active_record/attribute/values'
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/attribute/ 0000775 0000000 0000000 00000000000 13411321301 0025411 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb 0000664 0000000 0000000 00000001765 13411321301 0031347 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Attribute::SphinxPresenter
SPHINX_TYPES = {
:integer => :uint,
:boolean => :bool,
:timestamp => :timestamp,
:float => :float,
:string => :string,
:bigint => :bigint,
:ordinal => :str2ordinal,
:wordcount => :str2wordcount,
:json => :json
}
def initialize(attribute, source)
@attribute, @source = attribute, source
end
def collection_type
@attribute.multi? ? :multi : sphinx_type
end
def declaration
if @attribute.multi?
multi_declaration
else
@attribute.name
end
end
def sphinx_type
SPHINX_TYPES[@attribute.type]
end
private
def multi_declaration
case @attribute.source_type
when :query, :ranged_query
query
else
"#{sphinx_type} #{@attribute.name} from field"
end
end
def query
ThinkingSphinx::ActiveRecord::PropertyQuery.new(
@attribute, @source, sphinx_type
).to_s
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/attribute/type.rb 0000664 0000000 0000000 00000004741 13411321301 0026725 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Attribute::Type
UPDATEABLE_TYPES = [:integer, :timestamp, :boolean, :float]
def initialize(attribute, model)
@attribute, @model = attribute, model
end
def multi?
@multi ||= attribute.options[:multi] || multi_from_associations
end
def timestamp?
type == :timestamp
end
def type
@type ||= attribute.options[:type] || type_from_database
end
def type=(value)
@type = attribute.options[:type] = value
end
def updateable?
UPDATEABLE_TYPES.include?(type) && single_column_reference?
end
private
attr_reader :attribute, :model
def associations
@associations ||= begin
klass = model
attribute.columns.first.__stack.collect { |name|
association = klass.reflect_on_association(name)
klass = association.klass
association
}
end
end
def big_integer?
type_symbol == :integer && database_column.sql_type[/bigint/i]
end
def column_name
attribute.columns.first.__name.to_s
end
def database_column
@database_column ||= klass.columns.detect { |db_column|
db_column.name == column_name
}
end
def klass
@klass ||= associations.any? ? associations.last.klass : model
end
def multi_from_associations
associations.any? { |association|
[:has_many, :has_and_belongs_to_many].include?(association.macro)
}
end
def single_column_reference?
attribute.columns.length == 1 &&
attribute.columns.first.__stack.length == 0 &&
!attribute.columns.first.string?
end
def type_from_database
raise ThinkingSphinx::MissingColumnError,
"Cannot determine the database type of column #{column_name}, as it does not exist" if database_column.nil?
return :bigint if big_integer?
case type_symbol
when :datetime, :date
:timestamp
when :text
:string
when :decimal
:float
when :integer, :boolean, :timestamp, :float, :string, :bigint, :json
type_symbol
else
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
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/attribute/values.rb 0000664 0000000 0000000 00000000626 13411321301 0027241 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Attribute::Values
def initialize(attribute)
@attribute = attribute
end
def value_for(instance)
object = column.__stack.inject(instance) { |object, name|
object.nil? ? nil : object.send(name)
}
object.nil? ? nil : object.send(column.__name)
end
private
def column
@attribute.columns.first
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/base.rb 0000664 0000000 0000000 00000003441 13411321301 0024647 0 ustar 00root root 0000000 0000000 # 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
::ActiveRecord::Associations::CollectionProxy.send :include,
ThinkingSphinx::ActiveRecord::AssociationProxy
end
module ClassMethods
def facets(query = nil, options = {})
merge_search ThinkingSphinx.facets, query, options
end
def search(query = nil, options = {})
merge_search ThinkingSphinx.search, query, options
end
def search_count(query = nil, options = {})
search_for_ids(query, options).total_entries
end
def search_for_ids(query = nil, options = {})
ThinkingSphinx::Search::Merger.new(
search(query, options)
).merge! nil, :ids_only => true
end
private
def default_sphinx_scope?
respond_to?(:default_sphinx_scope) && default_sphinx_scope
end
def default_sphinx_scope_response
[sphinx_scopes[default_sphinx_scope].call].flatten
end
def merge_search(search, query, options)
merger = ThinkingSphinx::Search::Merger.new search
merger.merge! *default_sphinx_scope_response if default_sphinx_scope?
merger.merge! query, options
if current_scope && !merger.search.options[:ignore_scopes]
raise ThinkingSphinx::MixedScopesError,
'You cannot search with Sphinx through ActiveRecord scopes'
end
result = merger.merge! nil, :classes => [self]
result.populate if result.options[:populate]
result
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/callbacks/ 0000775 0000000 0000000 00000000000 13411321301 0025325 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb 0000664 0000000 0000000 00000001147 13411321301 0031116 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
ThinkingSphinx::Callbacks
callbacks :after_destroy, :after_rollback
def after_destroy
delete_from_sphinx
end
def after_rollback
delete_from_sphinx
end
private
def delete_from_sphinx
return if ThinkingSphinx::Callbacks.suspended? || instance.new_record?
indices.each { |index|
ThinkingSphinx::Deletion.perform index, instance.id
}
end
def indices
ThinkingSphinx::Configuration.instance.index_set_class.new(
:classes => [instance.class]
).to_a
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb 0000664 0000000 0000000 00000002556 13411321301 0030752 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks <
ThinkingSphinx::Callbacks
callbacks :after_commit, :before_save
def after_commit
return unless !suspended? && delta_indices? && toggled?
delta_indices.each do |index|
index.delta_processor.index index
end
core_indices.each do |index|
index.delta_processor.delete index, instance
end
end
def before_save
return unless !ThinkingSphinx::Callbacks.suspended? && delta_indices? &&
new_or_changed?
processors.each { |processor| processor.toggle instance }
end
private
def config
ThinkingSphinx::Configuration.instance
end
def core_indices
@core_indices ||= indices.select(&:delta_processor).reject(&:delta?)
end
def delta_indices
@delta_indices ||= indices.select &:delta?
end
def delta_indices?
delta_indices.any?
end
def indices
@indices ||= config.index_set_class.new(: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
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb 0000664 0000000 0000000 00000003541 13411321301 0031136 0 ustar 00root root 0000000 0000000 # 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 !ThinkingSphinx::Callbacks.suspended? && updates_enabled?
indices.each do |index|
update index unless index.distributed?
end
end
private
def attributes_hash_for(index)
updateable_attributes_for(index).inject({}) do |hash, attribute|
if changed_attributes.include?(attribute.columns.first.__name.to_s)
hash[attribute.name] = attribute.value_for(instance)
end
hash
end
end
def changed_attributes
@changed_attributes ||= CHANGED_ATTRIBUTES.call instance
end
def configuration
ThinkingSphinx::Configuration.instance
end
def indices
@indices ||= begin
all = configuration.indices_for_references(reference)
all.reject { |index| index.type == 'rt' }
end
end
def reference
ThinkingSphinx::IndexSet.reference_name(instance.class)
end
def update(index)
attributes = attributes_hash_for(index)
return if attributes.empty?
sphinxql = Riddle::Query.update(
index.name, index.document_id_for_key(instance.id), attributes
)
ThinkingSphinx::Connection.take do |connection|
connection.execute(sphinxql)
end
rescue ThinkingSphinx::ConnectionError => error
# This isn't vital, so don't raise the error.
end
def updateable_attributes_for(index)
index.sources.collect(&:attributes).flatten.select { |attribute|
attribute.updateable?
}
end
def updates_enabled?
configuration.settings['attribute_updates']
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/column.rb 0000664 0000000 0000000 00000001241 13411321301 0025226 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Column
def initialize(*stack)
@stack = stack
@name = stack.pop
end
def __name
@name
end
def __path
@stack + [@name]
end
def __replace(stack, replacements)
return [self] if string? || __stack[0..stack.length-1] != stack
replacements.collect { |replacement|
self.class.new *(replacement + __stack[stack.length..-1]), __name
}
end
def __stack
@stack
end
def string?
__name.is_a?(String)
end
def to_ary
[self]
end
private
def method_missing(method, *args, &block)
@stack << @name
@name = method
self
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/column_sql_presenter.rb 0000664 0000000 0000000 00000002073 13411321301 0030200 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::ColumnSQLPresenter
def initialize(model, column, adapter, associations)
@model, @column, @adapter, @associations = model, column, adapter, associations
end
def aggregate?
path.aggregate?
rescue Joiner::AssociationNotFound
false
end
def with_table
return __name if string?
return nil unless exists?
quoted_table = escape_table? ? escape_table(table) : table
"#{quoted_table}.#{adapter.quote __name}"
end
private
attr_reader :model, :column, :adapter, :associations
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
false
end
def path
Joiner::Path.new model, column.__stack
end
def table
associations.alias_for __stack
end
def version
ActiveRecord::VERSION
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/database_adapters.rb 0000664 0000000 0000000 00000003110 13411321301 0027355 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::ActiveRecord::DatabaseAdapters
class << self
attr_accessor :default
def adapter_for(model)
return default.new(model) if default
adapter = adapter_type_for(model)
klass = case adapter
when :mysql
MySQLAdapter
when :postgresql
PostgreSQLAdapter
else
raise ThinkingSphinx::InvalidDatabaseAdapter, "Invalid adapter '#{adapter}': Thinking Sphinx only supports MySQL and PostgreSQL."
end
klass.new model
end
def adapter_type_for(model)
class_name = model.connection.class.name
case class_name.split('::').last
when 'MysqlAdapter', 'Mysql2Adapter'
:mysql
when 'PostgreSQLAdapter'
:postgresql
when 'JdbcAdapter'
adapter_type_for_jdbc(model)
else
class_name
end
end
def adapter_type_for_jdbc(model)
case adapter = model.connection.config[:adapter]
when 'jdbcmysql'
:mysql
when 'jdbcpostgresql'
:postgresql
when 'jdbc'
adapter_type_for_jdbc_plain(adapter, model.connection.config[:url])
else adapter
end
end
def adapter_type_for_jdbc_plain(adapter, url)
return adapter unless match = /^jdbc:(?mysql|postgresql):\/\//.match(url)
match[:adapter].to_sym
end
end
end
require 'thinking_sphinx/active_record/database_adapters/abstract_adapter'
require 'thinking_sphinx/active_record/database_adapters/mysql_adapter'
require 'thinking_sphinx/active_record/database_adapters/postgresql_adapter'
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/database_adapters/ 0000775 0000000 0000000 00000000000 13411321301 0027035 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb 0000664 0000000 0000000 00000000504 13411321301 0032664 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::DatabaseAdapters::AbstractAdapter
def initialize(model)
@model = model
end
def quote(column)
@model.connection.quote_column_name(column)
end
def quoted_table_name
@model.quoted_table_name
end
def utf8_query_pre
[]
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb 0000664 0000000 0000000 00000001666 13411321301 0032240 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter <
ThinkingSphinx::ActiveRecord::DatabaseAdapters::AbstractAdapter
def boolean_value(value)
value ? 1 : 0
end
def cast_to_bigint(clause)
"CAST(#{clause} AS UNSIGNED INTEGER)"
end
def cast_to_string(clause)
"CAST(#{clause} AS char)"
end
def cast_to_timestamp(clause)
"UNIX_TIMESTAMP(#{clause})"
end
def concatenate(clause, separator = ' ')
"CONCAT_WS('#{separator}', #{clause})"
end
def convert_nulls(clause, default = '')
"IFNULL(#{clause}, #{default})"
end
def convert_blank(clause, default = '')
"COALESCE(NULLIF(#{clause}, ''), #{default})"
end
def group_concatenate(clause, separator = ' ')
"GROUP_CONCAT(DISTINCT #{clause} SEPARATOR '#{separator}')"
end
def time_zone_query_pre
["SET TIME_ZONE = '+0:00'"]
end
def utf8_query_pre
['SET NAMES utf8']
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb 0000664 0000000 0000000 00000002124 13411321301 0033264 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter <
ThinkingSphinx::ActiveRecord::DatabaseAdapters::AbstractAdapter
def boolean_value(value)
value ? 'TRUE' : 'FALSE'
end
def cast_to_bigint(clause)
"#{clause}::bigint"
end
def cast_to_string(clause)
"#{clause}::varchar"
end
def cast_to_timestamp(clause)
if ThinkingSphinx::Configuration.instance.settings['64bit_timestamps']
"extract(epoch from #{clause})::bigint"
else
"extract(epoch from #{clause})::int"
end
end
def concatenate(clause, separator = ' ')
clause.split(', ').collect { |part|
convert_nulls(part, "''")
}.join(" || '#{separator}' || ")
end
def convert_nulls(clause, default = '')
"COALESCE(#{clause}, #{default})"
end
def convert_blank(clause, default = '')
"COALESCE(NULLIF(#{clause}, ''), #{default})"
end
def group_concatenate(clause, separator = ' ')
"array_to_string(array_agg(DISTINCT #{clause}), '#{separator}')"
end
def time_zone_query_pre
['SET TIME ZONE UTC']
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/depolymorph/ 0000775 0000000 0000000 00000000000 13411321301 0025750 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/depolymorph/association_reflection.rb 0000664 0000000 0000000 00000001603 13411321301 0033023 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/depolymorph/base_reflection.rb 0000664 0000000 0000000 00000001215 13411321301 0031420 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/depolymorph/conditions_reflection.rb 0000664 0000000 0000000 00000001732 13411321301 0032663 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/depolymorph/overridden_reflection.rb 0000664 0000000 0000000 00000001556 13411321301 0032657 0 ustar 00root root 0000000 0000000 # 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 JoinConstraint
def build_join_constraint(table, foreign_table)
super.and(
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 JoinConstraint
subclass
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/depolymorph/scoped_reflection.rb 0000664 0000000 0000000 00000001350 13411321301 0031763 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/field.rb 0000664 0000000 0000000 00000000473 13411321301 0025022 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Field <
ThinkingSphinx::ActiveRecord::Property
include ThinkingSphinx::Core::Field
def file?
options[:file]
end
def with_attribute?
options[:sortable] || options[:facet]
end
def wordcount?
options[:wordcount]
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/filter_reflection.rb 0000664 0000000 0000000 00000001125 13411321301 0027431 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::FilterReflection
ReflectionGenerator = case ActiveRecord::VERSION::STRING.to_f
when 5.2..7.0
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 self.call(reflection, name, class_name)
ReflectionGenerator.new(reflection, name, class_name).call
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/index.rb 0000664 0000000 0000000 00000002616 13411321301 0025047 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Index < Riddle::Configuration::Index
include ThinkingSphinx::Core::Index
attr_reader :reference
attr_writer :definition_block
def append_source
ThinkingSphinx::ActiveRecord::SQLSource.new(
model, source_options.merge(:position => sources.length)
).tap do |source|
sources << source
end
end
def attributes
sources.collect(&:attributes).flatten
end
def delta?
@options[:delta?]
end
def delta_processor
@options[:delta_processor].try(:new, adapter, @options[:delta_options] || {})
end
def facets
@facets ||= sources.collect(&:facets).flatten
end
def fields
sources.collect(&:fields).flatten
end
def sources
interpret_definition!
super
end
def unique_attribute_names
attributes.collect(&:name)
end
private
def adapter
@adapter ||= ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_for(model)
end
def interpreter
ThinkingSphinx::ActiveRecord::Interpreter
end
def name_suffix
@options[:delta?] ? 'delta' : 'core'
end
def source_options
{
:name => name,
:offset => offset,
:delta? => @options[:delta?],
:delta_processor => @options[:delta_processor],
:delta_options => @options[:delta_options],
:primary_key => primary_key
}
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/interpreter.rb 0000664 0000000 0000000 00000003622 13411321301 0026301 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Interpreter <
::ThinkingSphinx::Core::Interpreter
def define_source(&block)
@source = @index.append_source
instance_eval &block
end
def group_by(*columns)
__source.groupings += columns
end
def has(*columns)
__source.attributes += build_properties(
::ThinkingSphinx::ActiveRecord::Attribute, columns
)
end
def indexes(*columns)
__source.fields += build_properties(
::ThinkingSphinx::ActiveRecord::Field, columns
)
end
def join(*columns)
__source.associations += columns.collect { |column|
::ThinkingSphinx::ActiveRecord::Association.new column
}
end
def polymorphs(column, options)
__source.polymorphs << ::ThinkingSphinx::ActiveRecord::Polymorpher.new(
__source, column, options[:to]
)
end
def sanitize_sql(*arguments)
__source.model.send :sanitize_sql, *arguments
end
def set_database(hash_or_key)
configuration = hash_or_key.is_a?(::Hash) ? hash_or_key :
::ActiveRecord::Base.configurations[hash_or_key.to_s]
__source.set_database_settings configuration.symbolize_keys
end
def set_property(properties)
properties.each do |key, value|
@index.send("#{key}=", value) if @index.class.settings.include?(key)
__source.send("#{key}=", value) if __source.class.settings.include?(key)
__source.options[key] = value if source_option?(key)
@index.options[key] = value if search_option?(key)
end
end
def where(*conditions)
__source.conditions += conditions
end
private
def __source
@source ||= @index.append_source
end
def build_properties(klass, columns)
options = columns.extract_options!
columns.collect { |column| klass.new(__source.model, column, options) }
end
def source_option?(key)
::ThinkingSphinx::ActiveRecord::SQLSource::OPTIONS.include?(key)
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/join_association.rb 0000664 0000000 0000000 00000000714 13411321301 0027270 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::JoinAssociation <
::ActiveRecord::Associations::JoinDependency::JoinAssociation
def build_constraint(klass, table, key, foreign_table, foreign_key)
constraint = super
constraint = constraint.and(
foreign_table[reflection.options[:foreign_type]].eq(
base_klass.base_class.name
)
) if reflection.options[:sphinx_internal_filtered]
constraint
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/log_subscriber.rb 0000664 0000000 0000000 00000001326 13411321301 0026741 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::LogSubscriber < ActiveSupport::LogSubscriber
def guard(event)
identifier = color 'Sphinx', GREEN, true
warn " #{identifier} #{event.payload[:guard]}"
end
def message(event)
identifier = color 'Sphinx', GREEN, true
debug " #{identifier} #{event.payload[:message]}"
end
def query(event)
identifier = color('Sphinx Query (%.1fms)' % event.duration, GREEN, true)
debug " #{identifier} #{event.payload[:query]}"
end
def caution(event)
identifier = color 'Sphinx', GREEN, true
warn " #{identifier} #{event.payload[:caution]}"
end
end
ThinkingSphinx::ActiveRecord::LogSubscriber.attach_to :thinking_sphinx
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/polymorpher.rb 0000664 0000000 0000000 00000002756 13411321301 0026325 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Polymorpher
def initialize(source, column, class_names)
@source, @column, @class_names = source, column, class_names
end
def morph!
append_reflections
morph_properties
end
private
attr_reader :source, :column, :class_names
def append_reflections
mappings.each do |class_name, name|
next if klass.reflect_on_association(name)
reflection = clone_with name, class_name
if ActiveRecord::Reflection.respond_to?(:add_reflection)
ActiveRecord::Reflection.add_reflection klass, name, reflection
else
klass.reflections[name] = reflection
end
end
end
def clone_with(name, class_name)
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, name, class_name
)
end
def mappings
@mappings ||= class_names.inject({}) do |hash, class_name|
hash[class_name] = "#{column.__name}_#{class_name.downcase}".to_sym
hash
end
end
def morphed_stacks
@morphed_stacks ||= mappings.values.collect { |key|
column.__stack + [key]
}
end
def morph_properties
(source.fields + source.attributes).each do |property|
property.rebase column.__path, :to => morphed_stacks
end
end
def reflection
@reflection ||= klass.reflect_on_association column.__name
end
def klass
@klass ||= column.__stack.inject(source.model) { |parent, key|
parent.reflect_on_association(key).klass
}
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/property.rb 0000664 0000000 0000000 00000001260 13411321301 0025616 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::Property
include ThinkingSphinx::Core::Property
attr_reader :model, :columns, :options
def initialize(model, columns, options = {})
@model, @options = model, options
@columns = Array(columns).collect { |column|
column.respond_to?(:__name) ? column :
ThinkingSphinx::ActiveRecord::Column.new(column)
}
end
def rebase(associations, options)
@columns = columns.inject([]) do |array, column|
array + column.__replace(associations, options[:to])
end
end
def name
(options[:as] || columns.first.__name).to_s
end
def source_type
options[:source]
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/property_query.rb 0000664 0000000 0000000 00000006722 13411321301 0027053 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::PropertyQuery
def initialize(property, source, type = nil)
@property, @source, @type = property, source, type
end
def to_s
if unsafe_habtm_column?
raise <<-MESSAGE
Source queries cannot be used with HABTM joins if they use anything beyond the
primary key.
MESSAGE
end
if safe_habtm_column?
ThinkingSphinx::ActiveRecord::SimpleManyQuery.new(
property, source, type
).to_s
else
"#{identifier} from #{source_type}; #{queries.join('; ')}"
end
end
private
attr_reader :property, :source, :type
delegate :unscoped, :to => :base_association_class, :prefix => true
def base_association
reflections.first
end
def base_association_class
base_association.klass
end
def column
@column ||= property.columns.first
end
def extend_reflection(reflection)
return [reflection] unless reflection.through_reflection
[reflection.through_reflection, reflection.source_reflection]
end
def identifier
[type, property.name].compact.join(' ')
end
def joins
@joins ||= begin
remainder = reflections.collect(&:name)[1..-1]
return nil if remainder.empty?
return remainder.first if remainder.length == 1
remainder[0..-2].reverse.inject(remainder.last) { |value, key|
{key => value}
}
end
end
def macros
reflections.collect &:macro
end
def offset
"* #{ThinkingSphinx::Configuration.instance.indices.count} + #{source.offset}"
end
def queries
queries = []
if column.string?
queries << column.__name.strip.gsub(/\n/, "\\\n")
else
queries << to_sql
queries << range_sql if ranged?
end
queries
end
def quoted_foreign_key
quote_with_table(base_association_class.table_name, base_association.foreign_key)
end
def quoted_primary_key
quote_with_table(reflections.last.klass.table_name, column.__name)
end
def quote_with_table(table, column)
"#{quote_column(table)}.#{quote_column(column)}"
end
def quote_column(column)
ActiveRecord::Base.connection.quote_column_name(column)
end
def ranged?
property.source_type == :ranged_query
end
def range_sql
base_association_class_unscoped.select(
"MIN(#{quoted_foreign_key}), MAX(#{quoted_foreign_key})"
).to_sql
end
def reflections
@reflections ||= begin
base = source.model
column.__stack.collect { |key|
reflection = base.reflect_on_association key
base = reflection.klass
extend_reflection reflection
}.flatten
end
end
def safe_habtm_column?
macros == [:has_and_belongs_to_many] && column.__name == :id
end
def source_type
property.source_type.to_s.dasherize
end
def to_sql
raise "Could not determine SQL for MVA" if reflections.empty?
relation = base_association_class_unscoped.select("#{quoted_foreign_key} #{offset} AS #{quote_column('id')}, #{quoted_primary_key} AS #{quote_column(property.name)}")
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.to_sql
end
def unsafe_habtm_column?
macros.include?(:has_and_belongs_to_many) && (
macros.length > 1 || column.__name != :id
)
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/property_sql_presenter.rb 0000664 0000000 0000000 00000003666 13411321301 0030600 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::PropertySQLPresenter
attr_reader :property, :adapter, :associations
def initialize(property, adapter, associations)
@property, @adapter, @associations = property, adapter, associations
end
def to_group
return nil if sourced_by_query? || !group?
columns_with_table
end
def to_select
return nil if sourced_by_query?
"#{casted_column_with_table} AS #{adapter.quote property.name}"
end
private
delegate :multi?, :to => :property
def aggregate?
column_presenters.any? &:aggregate?
end
def aggregate_separator
multi? ? ',' : ' '
end
def cast_to_timestamp(clause)
return adapter.cast_to_timestamp clause if property.columns.any?(&:string?)
clause.split(', ').collect { |part|
adapter.cast_to_timestamp part
}.join(', ')
end
def casted_column_with_table
clause = columns_with_table
clause = cast_to_timestamp clause if property.type == :timestamp
clause = concatenate clause
if aggregate?
clause = adapter.group_concatenate(clause, aggregate_separator)
end
clause
end
def column_presenters
@column_presenters ||= property.columns.collect { |column|
ThinkingSphinx::ActiveRecord::ColumnSQLPresenter.new(
property.model, column, adapter, associations
)
}
end
def columns_with_table
column_presenters.collect(&:with_table).compact.join(', ')
end
def concatenating?
property.columns.length > 1
end
def concatenate(clause)
return clause unless concatenating?
if property.type.nil?
adapter.concatenate clause, ' '
else
clause = clause.split(', ').collect { |part|
adapter.cast_to_string part
}.join(', ')
adapter.concatenate clause, ','
end
end
def group?
!(aggregate? || property.columns.any?(&:string?))
end
def sourced_by_query?
property.source_type.to_s[/query/]
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/simple_many_query.rb 0000664 0000000 0000000 00000001770 13411321301 0027502 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::SimpleManyQuery <
ThinkingSphinx::ActiveRecord::PropertyQuery
def to_s
"#{identifier} from #{source_type}; #{queries.join('; ')}"
end
private
def reflection
@reflection ||= source.model.reflect_on_association column.__stack.first
end
def quoted_foreign_key
quote_with_table reflection.join_table, reflection.foreign_key
end
def quoted_primary_key
quote_with_table reflection.join_table, reflection.association_foreign_key
end
def range_sql
"SELECT MIN(#{quoted_foreign_key}), MAX(#{quoted_foreign_key}) FROM #{quote_column reflection.join_table}"
end
def to_sql
selects = [
"#{quoted_foreign_key} #{offset} AS #{quote_column('id')}",
"#{quoted_primary_key} AS #{quote_column(property.name)}"
]
sql = "SELECT #{selects.join(', ')} FROM #{quote_column reflection.join_table}"
sql += " WHERE (#{quoted_foreign_key} BETWEEN $start AND $end)" if ranged?
sql
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/source_joins.rb 0000664 0000000 0000000 00000002314 13411321301 0026435 0 ustar 00root root 0000000 0000000 # 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?
joins.add_join_to column.__stack if column_exists?(column)
end
def append_specified_associations
source.associations.reject(&:string?).each do |association|
joins.add_join_to association.stack
end
end
def column_exists?(column)
Joiner::Path.new(model, column.__stack).model
true
rescue Joiner::AssociationNotFound
false
end
def joins
@joins ||= begin
joins = Joiner::Joins.new model
if joins.respond_to?(:join_association_class)
joins.join_association_class = ThinkingSphinx::ActiveRecord::JoinAssociation
end
joins
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/sql_builder.rb 0000664 0000000 0000000 00000005024 13411321301 0026241 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module ActiveRecord
class SQLBuilder
attr_reader :source
def initialize(source)
@source = source
end
def sql_query
statement.to_relation.to_sql.gsub(/\n/, "\\\n")
end
def sql_query_range
return nil if source.disable_range?
statement.to_query_range_relation.to_sql
end
def sql_query_pre
query.to_query
end
private
delegate :adapter, :model, :delta_processor, :to => :source
delegate :convert_nulls, :time_zone_query_pre, :utf8_query_pre,
:cast_to_bigint, :to => :adapter
def query
Query.new(self)
end
def statement
Statement.new(self)
end
def config
ThinkingSphinx::Configuration.instance
end
def relation
model.unscoped
end
def associations
@associations ||= ThinkingSphinx::ActiveRecord::SourceJoins.call(
model, source
)
end
def quote_column(column)
model.connection.quote_column_name(column)
end
def quoted_primary_key
"#{model.quoted_table_name}.#{quote_column(source.primary_key)}"
end
def quoted_inheritance_column
"#{model.quoted_table_name}.#{quote_column(model.inheritance_column)}"
end
def pre_select
('SQL_NO_CACHE ' if source.type == 'mysql').to_s
end
def big_document_ids?
source.options[:big_document_ids] || config.settings['big_document_ids']
end
def document_id
quoted_alias = quote_column source.primary_key
column = quoted_primary_key
column = cast_to_bigint column if big_document_ids?
column = "#{column} * #{config.indices.count} + #{source.offset}"
"#{column} AS #{quoted_alias}"
end
def range_condition
condition = []
condition << "#{quoted_primary_key} BETWEEN $start AND $end" unless source.disable_range?
condition += source.conditions
condition
end
def groupings
groupings = source.groupings
if model.column_names.include?(model.inheritance_column)
groupings << quoted_inheritance_column
end
groupings
end
def model_name
klass = model.name
klass = klass.demodulize unless model.store_full_sti_class
klass
end
end
end
end
require 'thinking_sphinx/active_record/sql_builder/statement'
require 'thinking_sphinx/active_record/sql_builder/query'
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/sql_builder/ 0000775 0000000 0000000 00000000000 13411321301 0025713 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb 0000664 0000000 0000000 00000000775 13411321301 0031233 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module ActiveRecord
class SQLBuilder::ClauseBuilder
def initialize(first_element)
@clauses = [first_element]
end
def compose(*additions)
additions.each &method(:add_clause)
self
end
def add_clause(clause)
@clauses += Array(clause)
end
def separated(by = ', ')
clauses.flatten.compact.join(by)
end
private
attr_reader :clauses
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/sql_builder/query.rb 0000664 0000000 0000000 00000002262 13411321301 0027407 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module ActiveRecord
class SQLBuilder::Query
def initialize(report)
self.report = report
self.scope = []
end
def to_query
filter_by_query_pre
scope.compact
end
protected
attr_accessor :report, :scope
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]
self.scope << "SET SESSION group_concat_max_len = #{max_len}"
end
def scope_by_time_zone
return if config.settings['skip_time_zone']
self.scope += time_zone_query_pre
end
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
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/sql_builder/statement.rb 0000664 0000000 0000000 00000007003 13411321301 0030244 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'thinking_sphinx/active_record/sql_builder/clause_builder'
module ThinkingSphinx
module ActiveRecord
class SQLBuilder::Statement
def initialize(report)
@report = report
@scope = relation
end
def to_relation
filter_by_scopes
scope
end
def to_query_range_relation
filter_by_query_range
scope
end
def to_query_pre
filter_by_query_pre
scope
end
private
attr_reader :report, :scope
def custom_joins
@custom_joins ||= source.associations.select(&:string?).collect(&:to_s)
end
def filter_by_query_range
minimum = convert_nulls "MIN(#{quoted_primary_key})", 1
maximum = convert_nulls "MAX(#{quoted_primary_key})", 1
@scope = scope.select("#{minimum}, #{maximum}").where(
where_clause(true)
)
end
def filter_by_scopes
scope_by_select
scope_by_where_clause
scope_by_group_clause
scope_by_joins
scope_by_custom_joins
scope_by_order
end
def attribute_presenters
@attribute_presenters ||= property_sql_presenters_for source.attributes
end
def field_presenters
@field_presenters ||= property_sql_presenters_for source.fields
end
def presenters_to_group(presenters)
presenters.collect(&:to_group)
end
def presenters_to_select(presenters)
presenters.collect(&:to_select)
end
def property_sql_presenters_for(properties)
properties.collect { |property| property_sql_presenter_for(property) }
end
def property_sql_presenter_for(property)
ThinkingSphinx::ActiveRecord::PropertySQLPresenter.new(
property, source.adapter, associations
)
end
def scope_by_select
@scope = scope.select(pre_select + select_clause)
end
def scope_by_where_clause
@scope = scope.where where_clause
end
def scope_by_group_clause
@scope = scope.group(group_clause)
end
def scope_by_joins
@scope = scope.joins(associations.join_values)
end
def scope_by_custom_joins
@scope = scope.joins(custom_joins) if custom_joins.any?
end
def scope_by_order
@scope = scope.order('NULL') if source.type == 'mysql'
end
def source
report.source
end
def method_missing(*args, &block)
report.send *args, &block
end
def select_clause
SQLBuilder::ClauseBuilder.new(document_id).compose(
presenters_to_select(field_presenters),
presenters_to_select(attribute_presenters)
).separated
end
def where_clause(for_range = false)
builder = SQLBuilder::ClauseBuilder.new(nil)
builder.add_clause delta_processor.clause(source.delta?) if delta_processor
builder.add_clause range_condition unless for_range
builder.separated(' AND ')
end
def group_clause
builder = SQLBuilder::ClauseBuilder.new(quoted_primary_key)
builder.compose(
presenters_to_group(field_presenters),
presenters_to_group(attribute_presenters)
) 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/sql_source.rb 0000664 0000000 0000000 00000010201 13411321301 0026104 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module ActiveRecord
class SQLSource < Riddle::Configuration::SQLSource
include ThinkingSphinx::Core::Settings
attr_reader :model, :options
attr_accessor :fields, :attributes, :associations, :conditions,
:groupings, :polymorphs
OPTIONS = [:name, :offset, :delta_processor, :delta?, :delta_options,
:disable_range?, :group_concat_max_len, :utf8?, :position,
:minimal_group_by?, :big_document_ids]
def initialize(model, options = {})
@model = model
@options = {
:utf8? => (database_settings[:encoding].to_s[/^utf8/])
}.merge options
@fields = []
@attributes = []
@associations = []
@conditions = []
@groupings = []
@polymorphs = []
Template.new(self).apply
name = "#{options[:name] || model.name.downcase}_#{options[:position]}"
super name, type
apply_defaults!
end
def adapter
@adapter ||= DatabaseAdapters.adapter_for(@model)
end
def delta_processor
options[:delta_processor].try(:new, adapter, @options[:delta_options] || {})
end
def delta?
options[:delta?]
end
def disable_range?
options[:disable_range?]
end
def facets
properties.select(&:facet?)
end
def offset
options[:offset]
end
def primary_key
options[:primary_key]
end
def properties
fields + attributes
end
def render
prepare_for_render unless @prepared
super
end
def set_database_settings(settings)
@sql_host ||= settings[:host] || 'localhost'
@sql_user ||= settings[:username] || settings[:user] || ENV['USER']
@sql_pass ||= settings[:password].to_s.gsub('#', '\#')
@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
@type ||= case adapter
when DatabaseAdapters::MySQLAdapter
'mysql'
when DatabaseAdapters::PostgreSQLAdapter
'pgsql'
else
raise UnknownDatabaseAdapter, "Provided type: #{adapter.class.name}"
end
end
private
def append_presenter_to_attribute_array
attributes.each do |attribute|
presenter = Attribute::SphinxPresenter.new(attribute, self)
attribute_array_for(presenter.collection_type) << presenter.declaration
end
end
def attribute_array_for(type)
instance_variable_get "@sql_attr_#{type}".to_sym
end
def builder
@builder ||= SQLBuilder.new self
end
def build_sql_fields
fields.each do |field|
@sql_field_string << field.name if field.with_attribute?
@sql_field_str2wordcount << field.name if field.wordcount?
@sql_file_field << field.name if field.file?
@sql_joined_field << PropertyQuery.new(field, self).to_s if field.source_type
end
end
def build_sql_query
@sql_query = builder.sql_query
@sql_query_range ||= builder.sql_query_range
@sql_query_pre += builder.sql_query_pre
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
set_database_settings database_settings
build_sql_fields
build_sql_query
@prepared = true
end
end
end
end
require 'thinking_sphinx/active_record/sql_source/template'
thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/sql_source/ 0000775 0000000 0000000 00000000000 13411321301 0025565 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/active_record/sql_source/template.rb 0000664 0000000 0000000 00000002574 13411321301 0027735 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::ActiveRecord::SQLSource::Template
attr_reader :source
def initialize(source)
@source = source
end
def apply
add_field class_column, :sphinx_internal_class_name
add_attribute primary_key, :sphinx_internal_id, nil
add_attribute class_column, :sphinx_internal_class, :string, :facet => true
add_attribute '0', :sphinx_deleted, :integer
end
private
def add_attribute(column, name, type, options = {})
source.attributes << 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.model, ThinkingSphinx::ActiveRecord::Column.new(column),
options.merge(:as => name)
)
end
def class_column
if inheriting?
adapter = source.adapter
quoted_column = "#{adapter.quoted_table_name}.#{adapter.quote(model.inheritance_column)}"
source.adapter.convert_blank quoted_column, "'#{model.sti_name}'"
else
"'#{model.name}'"
end
end
def inheriting?
model.column_names.include?(model.inheritance_column)
end
def model
source.model
end
def primary_key
source.options[:primary_key].to_sym
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/attribute_types.rb 0000664 0000000 0000000 00000003361 13411321301 0024354 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/batched_search.rb 0000664 0000000 0000000 00000000674 13411321301 0024050 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::BatchedSearch
attr_accessor :searches
def initialize
@searches = []
end
def populate(middleware = ThinkingSphinx::Middlewares::DEFAULT)
return if populated? || searches.empty?
middleware.call contexts
searches.each &:populated!
@populated = true
end
private
def contexts
searches.collect &:context
end
def populated?
@populated
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/callbacks.rb 0000664 0000000 0000000 00000001051 13411321301 0023036 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Callbacks
attr_reader :instance
def self.callbacks(*methods)
mod = Module.new
methods.each do |method|
mod.send(:define_method, method) { |instance| new(instance).send(method) }
end
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
thinking-sphinx-4.1.0/lib/thinking_sphinx/capistrano.rb 0000664 0000000 0000000 00000000370 13411321301 0023265 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
if defined?(Capistrano::VERSION)
if Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
recipe_version = 3
end
end
recipe_version ||= 2
require_relative "capistrano/v#{recipe_version}"
thinking-sphinx-4.1.0/lib/thinking_sphinx/capistrano/ 0000775 0000000 0000000 00000000000 13411321301 0022740 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/capistrano/v2.rb 0000664 0000000 0000000 00000003613 13411321301 0023617 0 ustar 00root root 0000000 0000000 # 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)} }
namespace :thinking_sphinx do
desc 'Generate the Sphinx configuration file.'
task :configure, fetch(:thinking_sphinx_options) do
rake 'ts:configure'
end
desc 'Build Sphinx indexes into the shared path.'
task :index, fetch(:thinking_sphinx_options) do
rake 'ts:index'
end
desc 'Generate Sphinx indexes into the shared path.'
task :generate, fetch(:thinking_sphinx_options) do
rake 'ts:generate'
end
desc 'Start the Sphinx search daemon.'
task :start, fetch(:thinking_sphinx_options) do
rake 'ts:start'
end
before 'thinking_sphinx:start', 'thinking_sphinx:configure'
desc 'Stop the Sphinx search daemon.'
task :stop, fetch(:thinking_sphinx_options) do
rake 'ts:stop'
end
desc 'Restart the Sphinx search daemon.'
task :restart, fetch(:thinking_sphinx_options) do
rake 'ts:stop ts:configure ts:start'
end
desc <<-DESC
Stop, reindex, and then start the Sphinx search daemon. This task must be executed \
if you alter the structure of your indexes.
DESC
task :rebuild, fetch(:thinking_sphinx_options) do
rake 'ts:rebuild'
end
desc 'Stop Sphinx, clear Sphinx index files, generate configuration file, start Sphinx, repopulate all data.'
task :regenerate, fetch(:thinking_sphinx_options) do
rake 'ts:regenerate'
end
def rake(tasks)
rails_env = fetch(:rails_env, 'production')
rake = fetch(:rake, 'rake')
tasks += ' INDEX_ONLY=true' if ENV['INDEX_ONLY'] == 'true'
run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; if [ -f Rakefile ]; then #{rake} RAILS_ENV=#{rails_env} #{tasks}; fi;"
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/capistrano/v3.rb 0000664 0000000 0000000 00000005240 13411321301 0023616 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
namespace :load do
task :defaults do
set :thinking_sphinx_roles, :db
set :thinking_sphinx_rails_env, -> { fetch(:rails_env) || fetch(:stage) }
end
end
namespace :thinking_sphinx do
desc <<-DESC
Stop, reindex, and then start the Sphinx search daemon. This task must be executed \
if you alter the structure of your indexes.
DESC
task :rebuild do
on roles fetch(:thinking_sphinx_roles) do
within current_path do
with rails_env: fetch(:thinking_sphinx_rails_env) do
execute :rake, "ts:rebuild"
end
end
end
end
desc 'Stop Sphinx, clear Sphinx index files, generate configuration file, start Sphinx, repopulate all data.'
task :regenerate do
on roles fetch(:thinking_sphinx_roles) do
within current_path do
with rails_env: fetch(:thinking_sphinx_rails_env) do
execute :rake, 'ts:regenerate'
end
end
end
end
desc 'Build Sphinx indexes into the shared path.'
task :index do
on roles fetch(:thinking_sphinx_roles) do
within current_path do
with rails_env: fetch(:thinking_sphinx_rails_env) do
execute :rake, 'ts:index'
end
end
end
end
desc 'Generate Sphinx indexes into the shared path.'
task :generate do
on roles fetch(:thinking_sphinx_roles) do
within current_path do
with rails_env: fetch(:thinking_sphinx_rails_env) do
execute :rake, 'ts:generate'
end
end
end
end
desc 'Restart the Sphinx search daemon.'
task :restart do
on roles fetch(:thinking_sphinx_roles) do
within current_path do
with rails_env: fetch(:thinking_sphinx_rails_env) do
%w(stop configure start).each do |task|
execute :rake, "ts:#{task}"
end
end
end
end
end
desc 'Start the Sphinx search daemon.'
task :start do
on roles fetch(:thinking_sphinx_roles) do
within current_path do
with rails_env: fetch(:thinking_sphinx_rails_env) do
execute :rake, 'ts:start'
end
end
end
end
before :start, 'thinking_sphinx:configure'
desc 'Generate the Sphinx configuration file.'
task :configure do
on roles fetch(:thinking_sphinx_roles) do
within current_path do
with rails_env: fetch(:thinking_sphinx_rails_env) do
execute :rake, 'ts:configure'
end
end
end
end
desc 'Stop the Sphinx search daemon.'
task :stop do
on roles fetch(:thinking_sphinx_roles) do
within current_path do
with rails_env: fetch(:thinking_sphinx_rails_env) do
execute :rake, 'ts:stop'
end
end
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/commander.rb 0000664 0000000 0000000 00000002215 13411321301 0023067 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands.rb 0000664 0000000 0000000 00000001306 13411321301 0022723 0 ustar 00root root 0000000 0000000 # 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'
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/ 0000775 0000000 0000000 00000000000 13411321301 0022376 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/base.rb 0000664 0000000 0000000 00000002076 13411321301 0023642 0 ustar 00root root 0000000 0000000 # 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
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/clear_real_time.rb 0000664 0000000 0000000 00000000662 13411321301 0026036 0 ustar 00root root 0000000 0000000 # 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_r(binlog_path) if File.exists?(binlog_path)
end
private
def binlog_path
configuration.searchd.binlog_path
end
def type
'clear_realtime'
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/clear_sql.rb 0000664 0000000 0000000 00000000561 13411321301 0024672 0 ustar 00root root 0000000 0000000 # 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_r Dir["#{configuration.indices_location}/ts-*.tmp"]
end
private
def type
'clear_sql'
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/configure.rb 0000664 0000000 0000000 00000000430 13411321301 0024701 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/index_real_time.rb 0000664 0000000 0000000 00000000455 13411321301 0026057 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Commands::IndexRealTime < ThinkingSphinx::Commands::Base
def call
options[:indices].each do |index|
ThinkingSphinx::RealTime::Populator.populate index
command :rotate
end
end
private
def type
'indexing'
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/index_sql.rb 0000664 0000000 0000000 00000001023 13411321301 0024705 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/merge.rb 0000664 0000000 0000000 00000001047 13411321301 0024024 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/merge_and_update.rb 0000664 0000000 0000000 00000002460 13411321301 0026210 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/prepare.rb 0000664 0000000 0000000 00000000335 13411321301 0024362 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Commands::Prepare < ThinkingSphinx::Commands::Base
def call
FileUtils.mkdir_p configuration.indices_location
end
private
def type
'prepare'
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/rotate.rb 0000664 0000000 0000000 00000000274 13411321301 0024224 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Commands::Rotate < ThinkingSphinx::Commands::Base
def call
controller.rotate
end
private
def type
'rotate'
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/running.rb 0000664 0000000 0000000 00000000300 13411321301 0024374 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Commands::Running < ThinkingSphinx::Commands::Base
def call
controller.running?
end
private
def type
'running'
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/start_attached.rb 0000664 0000000 0000000 00000000712 13411321301 0025715 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Commands::StartAttached < ThinkingSphinx::Commands::Base
def call
FileUtils.mkdir_p configuration.indices_location
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
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/start_detached.rb 0000664 0000000 0000000 00000000647 13411321301 0025710 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Commands::StartDetached < ThinkingSphinx::Commands::Base
def call
FileUtils.mkdir_p configuration.indices_location
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
thinking-sphinx-4.1.0/lib/thinking_sphinx/commands/stop.rb 0000664 0000000 0000000 00000000642 13411321301 0023712 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/configuration.rb 0000664 0000000 0000000 00000010756 13411321301 0024002 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'pathname'
class ThinkingSphinx::Configuration < Riddle::Configuration
attr_accessor :configuration_file, :indices_location, :version, :batch_size
attr_reader :index_paths
attr_writer :controller, :index_set_class, :indexing_strategy,
:guarding_strategy
delegate :environment, :to => :framework
@@mutex = Mutex.new
def initialize
super
reset
end
def self.instance
@instance ||= new
end
def self.reset
@instance = nil
end
def bin_path
settings['bin_path']
end
def controller
@controller ||= begin
rc = Riddle::Controller.new self, configuration_file
rc.bin_path = bin_path.gsub(/([^\/])$/, '\1/') if bin_path.present?
rc
end
end
def framework
@framework ||= ThinkingSphinx::Frameworks.current
end
def framework=(framework)
@framework = framework
reset
framework
end
def engine_index_paths
return [] unless defined?(Rails)
engine_indice_paths.flatten.compact.sort
end
def engine_indice_paths
Rails::Engine.subclasses.collect(&:instance).collect do |engine|
engine.paths.add 'app/indices' unless engine.paths['app/indices']
engine.paths['app/indices'].existent
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
def next_offset(reference)
@offsets[reference] ||= @offsets.keys.count
end
def preload_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
end
end
normalise
verify
@preloaded_indices = true
end
end
def render
preload_indices
super
end
def render_to_file
FileUtils.mkdir_p searchd.binlog_path unless searchd.binlog_path.blank?
open(configuration_file, 'w') { |file| file.write render }
end
def settings
@settings ||= ThinkingSphinx::Settings.call self
end
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
if settings['common_sphinx_configuration']
common.common_sphinx_configuration = true
indexer.common_sphinx_configuration = true
end
configure_searchd
apply_sphinx_settings!
@offsets = {}
end
private
def apply_sphinx_settings!
sphinx_sections.each do |object|
settings.each do |key, value|
next unless object.class.settings.include?(key.to_sym)
object.send("#{key}=", value)
end
end
end
def configure_searchd
searchd.socket = "#{settings["socket"]}:mysql41" if socket?
if tcp?
searchd.address = settings['address'].presence || Defaults::ADDRESS
searchd.mysql41 = settings['mysql41'] || settings['port'] || Defaults::PORT
end
searchd.mysql_version_string = '5.5.21' if RUBY_PLATFORM == 'java'
end
def normalise
if settings['distributed_indices'].nil? || settings['distributed_indices']
ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
end
ThinkingSphinx::Configuration::ConsistentIds.new(indices).reconcile
ThinkingSphinx::Configuration::MinimumFields.new(indices).reconcile
end
def reset
@settings = nil
setup
end
def socket?
settings["socket"].present?
end
def sphinx_sections
sections = [indexer, searchd]
sections.unshift common if settings['common_sphinx_configuration']
sections
end
def tcp?
settings["socket"].nil? ||
settings["address"].present? ||
settings["mysql41"].present? ||
settings["port"].present?
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'
thinking-sphinx-4.1.0/lib/thinking_sphinx/configuration/ 0000775 0000000 0000000 00000000000 13411321301 0023444 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/configuration/consistent_ids.rb 0000664 0000000 0000000 00000001306 13411321301 0027021 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Configuration::ConsistentIds
def initialize(indices)
@indices = indices
end
def reconcile
return unless sphinx_internal_ids.any? { |attribute|
attribute.type == :bigint
}
sphinx_internal_ids.each do |attribute|
attribute.type = :bigint
end
end
private
def attributes
@attributes = sources.collect(&:attributes).flatten
end
def sphinx_internal_ids
@sphinx_internal_ids ||= attributes.select { |attribute|
attribute.name == 'sphinx_internal_id'
}
end
def sources
@sources ||= @indices.select { |index|
index.respond_to?(:sources)
}.collect(&:sources).flatten
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/configuration/defaults.rb 0000664 0000000 0000000 00000000212 13411321301 0025573 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Configuration::Defaults
ADDRESS = '127.0.0.1'
PORT = 9306
PANES = []
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/configuration/distributed_indices.rb 0000664 0000000 0000000 00000001171 13411321301 0030011 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Configuration::DistributedIndices
def initialize(indices)
@indices = indices
end
def reconcile
grouped_indices.each do |reference, indices|
append distributed_index(reference, indices)
end
end
private
attr_reader :indices
def append(index)
ThinkingSphinx::Configuration.instance.indices << index
end
def distributed_index(reference, indices)
index = ThinkingSphinx::Distributed::Index.new reference
index.local_indices += indices.collect &:name
index
end
def grouped_indices
indices.group_by &:reference
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/configuration/duplicate_names.rb 0000664 0000000 0000000 00000001325 13411321301 0027127 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/configuration/minimum_fields.rb 0000664 0000000 0000000 00000001421 13411321301 0026770 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Configuration::MinimumFields
def initialize(indices)
@indices = indices
end
def reconcile
return unless no_inheritance_columns?
field_collections.each do |collection|
collection.fields.delete_if do |field|
field.name == 'sphinx_internal_class_name'
end
end
end
private
attr_reader :indices
def field_collections
indices_of_type('plain').collect(&:sources).flatten +
indices_of_type('rt')
end
def indices_of_type(type)
indices.select { |index| index.type == type }
end
def no_inheritance_columns?
indices.select { |index|
index.model.table_exists? &&
index.model.column_names.include?(index.model.inheritance_column)
}.empty?
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/connection.rb 0000664 0000000 0000000 00000003400 13411321301 0023256 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Connection
MAXIMUM_RETRIES = 3
def self.new
configuration = ThinkingSphinx::Configuration.instance
options = {
: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.connection_class
return ThinkingSphinx::Connection::JRuby if RUBY_PLATFORM == 'java'
ThinkingSphinx::Connection::MRI
end
def self.pool
@pool ||= Innertube::Pool.new(
Proc.new { ThinkingSphinx::Connection.new },
Proc.new { |connection| connection.close! }
)
end
def self.take
retries = 0
original = nil
begin
pool.take do |connection|
begin
yield connection
rescue ThinkingSphinx::QueryExecutionError, connection.base_error => error
original = ThinkingSphinx::SphinxError.new_from_mysql error
retries += MAXIMUM_RETRIES if original.is_a?(ThinkingSphinx::QueryError)
raise Innertube::Pool::BadResource
end
end
rescue Innertube::Pool::BadResource
retries += 1
raise original unless retries < MAXIMUM_RETRIES
ActiveSupport::Notifications.instrument(
"message.thinking_sphinx", :message => "Retrying query \"#{original.statement}\" after error: #{original.message}"
)
retry
end
end
def self.persistent?
@persistent
end
def self.persistent=(persist)
@persistent = persist
end
@persistent = true
end
require 'thinking_sphinx/connection/client'
require 'thinking_sphinx/connection/jruby'
require 'thinking_sphinx/connection/mri'
thinking-sphinx-4.1.0/lib/thinking_sphinx/connection/ 0000775 0000000 0000000 00000000000 13411321301 0022734 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/connection/client.rb 0000664 0000000 0000000 00000003061 13411321301 0024537 0 ustar 00root root 0000000 0000000 # 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 > ThinkingSphinx::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 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/connection/jruby.rb 0000664 0000000 0000000 00000002533 13411321301 0024417 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/connection/mri.rb 0000664 0000000 0000000 00000001120 13411321301 0024042 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/core.rb 0000664 0000000 0000000 00000000411 13411321301 0022046 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Core
#
end
require 'thinking_sphinx/core/settings'
require 'thinking_sphinx/core/field'
require 'thinking_sphinx/core/index'
require 'thinking_sphinx/core/interpreter'
require 'thinking_sphinx/core/property'
thinking-sphinx-4.1.0/lib/thinking_sphinx/core/ 0000775 0000000 0000000 00000000000 13411321301 0021525 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/core/field.rb 0000664 0000000 0000000 00000000241 13411321301 0023132 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Core::Field
def infixing?
options[:infixes]
end
def prefixing?
options[:prefixes]
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/core/index.rb 0000664 0000000 0000000 00000003744 13411321301 0023171 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Core::Index
extend ActiveSupport::Concern
include ThinkingSphinx::Core::Settings
included do
attr_reader :reference, :offset
attr_writer :definition_block
end
def initialize(reference, options = {})
@reference = reference.to_sym
@docinfo = :extern unless config.settings["skip_docinfo"]
@options = options
@offset = config.next_offset(options[:offset_as] || reference)
@type = 'plain'
super "#{options[:name] || reference.to_s.gsub('/', '_')}_#{name_suffix}"
end
def delta?
false
end
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!
return unless model.table_exists?
return if @interpreted_definition
apply_defaults!
@interpreted_definition = true
interpreter.translate! self, @definition_block if @definition_block
end
def model
@model ||= reference.to_s.camelize.constantize
end
def options
interpret_definition!
@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
assign_infix_fields
assign_prefix_fields
super
end
private
def assign_infix_fields
self.infix_fields = fields.select(&:infixing?).collect(&:name)
end
def assign_prefix_fields
self.prefix_fields = fields.select(&:prefixing?).collect(&:name)
end
def config
ThinkingSphinx::Configuration.instance
end
def name_suffix
'core'
end
def path_prefix
options[:path] || config.indices_location
end
def pre_render
interpret_definition!
end
def set_path
FileUtils.mkdir_p path_prefix
@path = File.join path_prefix, name
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/core/interpreter.rb 0000664 0000000 0000000 00000001044 13411321301 0024414 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Core::Interpreter < BasicObject
def self.translate!(index, block)
new(index, block).translate!
end
def initialize(index, block)
@index = index
mod = ::Module.new
mod.send :define_method, :translate!, block
mod.send :extend_object, self
end
private
def search_option?(key)
::ThinkingSphinx::Middlewares::SphinxQL::SELECT_OPTIONS.include? key
end
def method_missing(method, *args)
::ThinkingSphinx::ActiveRecord::Column.new method, *args
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/core/property.rb 0000664 0000000 0000000 00000000250 13411321301 0023733 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Core::Property
def facet?
options[:facet]
end
def multi?
false
end
def type
nil
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/core/settings.rb 0000664 0000000 0000000 00000000425 13411321301 0023713 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Core::Settings
private
def apply_defaults!(defaults = self.class.settings)
defaults.each do |setting|
value = config.settings[setting.to_s]
send("#{setting}=", value) unless value.nil?
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/deletion.rb 0000664 0000000 0000000 00000002754 13411321301 0022735 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Deletion
delegate :name, :to => :index
def self.perform(index, ids)
return if index.distributed?
{
'plain' => PlainDeletion,
'rt' => RealtimeDeletion
}[index.type].new(index, ids).perform
rescue ThinkingSphinx::ConnectionError => error
# This isn't vital, so don't raise the error.
end
def initialize(index, ids)
@index, @ids = index, Array(ids)
end
private
attr_reader :index, :ids
def document_ids_for_keys
ids.collect { |id| index.document_id_for_key id }
end
def execute(statement)
statement = statement.gsub(/\s*\n\s*/, ' ').strip
ThinkingSphinx::Logger.log :query, statement do
ThinkingSphinx::Connection.take do |connection|
connection.execute statement
end
end
end
class RealtimeDeletion < ThinkingSphinx::Deletion
def perform
return unless callbacks_enabled?
execute Riddle::Query::Delete.new(name, document_ids_for_keys).to_sql
end
private
def callbacks_enabled?
setting = configuration.settings['real_time_callbacks']
setting.nil? || setting
end
def configuration
ThinkingSphinx::Configuration.instance
end
end
class PlainDeletion < ThinkingSphinx::Deletion
def perform
document_ids_for_keys.each_slice(1000) do |document_ids|
execute <<-SQL
UPDATE #{name}
SET sphinx_deleted = 1
WHERE id IN (#{document_ids.join(', ')})
SQL
end
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/deltas.rb 0000664 0000000 0000000 00000002147 13411321301 0022402 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Deltas
def self.config
ThinkingSphinx::Configuration.instance
end
def self.processor_for(delta)
case delta
when TrueClass
ThinkingSphinx::Deltas::DefaultDelta
when Class
delta
when String
delta.constantize
else
nil
end
end
def self.resume!
@suspended = false
end
def self.suspend(reference, &block)
suspend!
yield
resume!
config.indices_for_references(reference).each do |index|
index.delta_processor.index index if index.delta?
end
end
def self.suspend_and_update(reference, &block)
suspend reference, &block
ids = reference.to_s.camelize.constantize.where(delta: true).pluck(:id)
config.indices_for_references(reference).each do |index|
ThinkingSphinx::Deletion.perform index, ids unless index.delta?
end
end
def self.suspend!
@suspended = true
end
def self.suspended?
@suspended
end
end
require 'thinking_sphinx/deltas/default_delta'
require 'thinking_sphinx/deltas/delete_job'
require 'thinking_sphinx/deltas/index_job'
thinking-sphinx-4.1.0/lib/thinking_sphinx/deltas/ 0000775 0000000 0000000 00000000000 13411321301 0022051 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/deltas/default_delta.rb 0000664 0000000 0000000 00000002233 13411321301 0025173 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Deltas::DefaultDelta
attr_reader :adapter, :options
def initialize(adapter, options = {})
@adapter, @options = adapter, options
end
def clause(delta_source = false)
return nil unless delta_source
"#{adapter.quoted_table_name}.#{quoted_column} = #{adapter.boolean_value delta_source}"
end
def delete(index, instance)
ThinkingSphinx::Deltas::DeleteJob.new(
index.name, index.document_id_for_instance(instance)
).perform
end
def index(index)
ThinkingSphinx::Deltas::IndexJob.new(index.name).perform
end
def reset_query
(<<-SQL).strip.gsub(/\n\s*/, ' ')
UPDATE #{adapter.quoted_table_name}
SET #{quoted_column} = #{adapter.boolean_value false}
WHERE #{quoted_column} = #{adapter.boolean_value true}
SQL
end
def toggle(instance)
instance.send "#{column}=", true
end
def toggled?(instance)
instance.send "#{column}?"
end
private
def column
options[:column] || :delta
end
def config
ThinkingSphinx::Configuration.instance
end
def controller
config.controller
end
def quoted_column
adapter.quote column
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/deltas/delete_job.rb 0000664 0000000 0000000 00000001170 13411321301 0024471 0 ustar 00root root 0000000 0000000 # 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
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
thinking-sphinx-4.1.0/lib/thinking_sphinx/deltas/index_job.rb 0000664 0000000 0000000 00000001043 13411321301 0024335 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Deltas::IndexJob
def initialize(index_name)
@index_name = index_name
end
def perform
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
thinking-sphinx-4.1.0/lib/thinking_sphinx/distributed.rb 0000664 0000000 0000000 00000000167 13411321301 0023450 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Distributed
#
end
require 'thinking_sphinx/distributed/index'
thinking-sphinx-4.1.0/lib/thinking_sphinx/distributed/ 0000775 0000000 0000000 00000000000 13411321301 0023117 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/distributed/index.rb 0000664 0000000 0000000 00000001112 13411321301 0024546 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Distributed::Index <
Riddle::Configuration::DistributedIndex
attr_reader :reference, :options
def initialize(reference)
@reference = reference
@options = {}
super reference.to_s.gsub('/', '_')
end
def delta?
false
end
def distributed?
true
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
thinking-sphinx-4.1.0/lib/thinking_sphinx/errors.rb 0000664 0000000 0000000 00000005312 13411321301 0022437 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::SphinxError < StandardError
attr_accessor :statement
def self.new_from_mysql(error)
case error.message
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/
replacement = ThinkingSphinx::QueryError.new(error.message)
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
replacement.set_backtrace error.backtrace
replacement.statement = error.statement if error.respond_to?(:statement)
replacement
end
end
class ThinkingSphinx::ConnectionError < ThinkingSphinx::SphinxError
end
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::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
class ThinkingSphinx::MixedScopesError < StandardError
end
class ThinkingSphinx::NoIndicesError < StandardError
end
class ThinkingSphinx::MissingColumnError < StandardError
end
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
thinking-sphinx-4.1.0/lib/thinking_sphinx/excerpter.rb 0000664 0000000 0000000 00000001754 13411321301 0023132 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Excerpter
DefaultOptions = {
:before_match => '',
:after_match => '',
:chunk_separator => ' … ' # ellipsis
}
attr_accessor :index, :words, :options
def initialize(index, words, options = {})
@index, @words = index, words
@options = DefaultOptions.merge(options)
@words = @options.delete(:words) if @options[:words]
end
def excerpt!(text)
result = ThinkingSphinx::Connection.take do |connection|
query = statement_for text
ThinkingSphinx::Logger.log :query, query do
connection.execute(query).first['snippet']
end
end
encoded? ? result : ThinkingSphinx::UTF8.encode(result)
end
private
def statement_for(text)
Riddle::Query.snippets(text, index, words, options)
end
def encoded?
ThinkingSphinx::Configuration.instance.settings['utf8'].nil? ||
ThinkingSphinx::Configuration.instance.settings['utf8']
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/facet.rb 0000664 0000000 0000000 00000001115 13411321301 0022202 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Facet
attr_reader :name
def initialize(name, properties)
@name, @properties = name, properties
end
def filter_type
use_field? ? :conditions : :with
end
def results_from(raw)
raw.inject({}) { |hash, row|
hash[row[group_column]] = row["sphinx_internal_count"]
hash
}
end
private
def group_column
@properties.any?(&:multi?) ? "sphinx_internal_group" : name
end
def use_field?
@properties.any? { |property|
property.type.nil? || property.type == :string
}
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/facet_search.rb 0000664 0000000 0000000 00000005714 13411321301 0023540 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::FacetSearch
include Enumerable
attr_reader :options
attr_accessor :query
def initialize(query = nil, options = {})
query, options = nil, query if query.is_a?(Hash)
@query, @options = query, options
@hash = {}
end
def [](key)
populate
@hash[key]
end
def each(&block)
populate
@hash.each(&block)
end
def for(facet_values)
filter_facets = facet_values.keys.collect { |key|
facets.detect { |facet| facet.name == key.to_s }
}
ThinkingSphinx::Search.new query, options.merge(
:indices => index_names_for(*filter_facets)
).merge(Filter.new(facets, facet_values).to_hash)
end
def populate
return if @populated
batch = ThinkingSphinx::BatchedSearch.new
facets.each do |facet|
batch.searches << ThinkingSphinx::Search.new(query, options_for(facet))
end
batch.populate ThinkingSphinx::Middlewares::RAW_ONLY
facets.each_with_index do |facet, index|
@hash[facet.name.to_sym] = facet.results_from batch.searches[index].raw
end
@hash[:class] = @hash[:sphinx_internal_class]
@populated = true
end
def populated?
@populated
end
def to_hash
populate
@hash
end
private
def configuration
ThinkingSphinx::Configuration.instance
end
def facets
@facets ||= properties.group_by(&:name).collect { |name, matches|
ThinkingSphinx::Facet.new name, matches
}
end
def properties
properties = indices.collect(&:facets).flatten
if options[:facets].present?
properties = properties.select { |property|
options[:facets].include? property.name.to_sym
}
end
properties
end
def index_names_for(*facets)
facet_names(
indices.select do |index|
facets.all? { |facet| facet_names(index.facets).include?(facet.name) }
end
)
end
def facet_names(facets)
facets.collect(&:name)
end
def indices
@indices ||= configuration.index_set_class.new(
options.slice(:classes, :indices)
)
end
def max_matches
configuration.settings['max_matches'] || 1000
end
def limit
limit = options[:limit] || options[:per_page] || max_matches
end
def options_for(facet)
options.merge(
:select => [(options[: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),
:max_matches => max_matches,
:limit => limit
)
end
class Filter
def initialize(facets, hash)
@facets, @hash = facets, hash
end
def to_hash
@hash.keys.inject({}) { |options, key|
type = @facets.detect { |facet| facet.name == key.to_s }.filter_type
options[type] ||= {}
options[type][key] = @hash[key]
options
}
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/float_formatter.rb 0000664 0000000 0000000 00000001062 13411321301 0024311 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::FloatFormatter
PATTERN = /(\d+)e\-(\d+)$/
def initialize(float)
@float = float
end
def fixed
return float.to_s unless exponent_present?
("%0.#{decimal_places}f" % float).gsub(/0+$/, '')
end
private
attr_reader :float
def exponent_decimal_places
float.to_s[PATTERN, 1].length
end
def exponent_factor
float.to_s[PATTERN, 2].to_i
end
def exponent_present?
float.to_s['e']
end
def decimal_places
exponent_factor + exponent_decimal_places
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/frameworks.rb 0000664 0000000 0000000 00000000441 13411321301 0023301 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Frameworks
def self.current
defined?(::Rails) ? ThinkingSphinx::Frameworks::Rails.new :
ThinkingSphinx::Frameworks::Plain.new
end
end
require 'thinking_sphinx/frameworks/plain'
require 'thinking_sphinx/frameworks/rails'
thinking-sphinx-4.1.0/lib/thinking_sphinx/frameworks/ 0000775 0000000 0000000 00000000000 13411321301 0022755 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/frameworks/plain.rb 0000664 0000000 0000000 00000000302 13411321301 0024400 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Frameworks::Plain
attr_accessor :environment, :root
def initialize
@environment = 'production'
@root = Dir.pwd
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/frameworks/rails.rb 0000664 0000000 0000000 00000000222 13411321301 0024410 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Frameworks::Rails
def environment
Rails.env
end
def root
Rails.root
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/guard.rb 0000664 0000000 0000000 00000000265 13411321301 0022227 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Guard
#
end
require 'thinking_sphinx/guard/file'
require 'thinking_sphinx/guard/files'
require 'thinking_sphinx/guard/none'
thinking-sphinx-4.1.0/lib/thinking_sphinx/guard/ 0000775 0000000 0000000 00000000000 13411321301 0021677 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/guard/file.rb 0000664 0000000 0000000 00000000633 13411321301 0023145 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Guard::File
attr_reader :name
def initialize(name)
@name = name
end
def lock
FileUtils.touch path
end
def locked?
File.exists? path
end
def path
@path ||= File.join(
ThinkingSphinx::Configuration.instance.indices_location,
"ts-#{name}.tmp"
)
end
def unlock
FileUtils.rm(path) if locked?
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/guard/files.rb 0000664 0000000 0000000 00000001350 13411321301 0023325 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Guard::Files
def self.call(names, &block)
new(names).call(&block)
end
def initialize(names)
@names = names
end
def call(&block)
return if unlocked.empty?
unlocked.each &:lock
block.call unlocked.collect(&:name)
rescue => error
raise error
ensure
unlocked.each &:unlock
end
private
attr_reader :names
def log_lock(file)
ThinkingSphinx::Logger.log :guard,
"Guard file for index #{file.name} exists, not indexing: #{file.path}."
end
def unlocked
@unlocked ||= names.collect { |name|
ThinkingSphinx::Guard::File.new name
}.reject { |file|
log_lock file if file.locked?
file.locked?
}
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/guard/none.rb 0000664 0000000 0000000 00000000177 13411321301 0023170 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Guard::None
def self.call(names, &block)
block.call names
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/hooks/ 0000775 0000000 0000000 00000000000 13411321301 0021720 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/hooks/guard_presence.rb 0000664 0000000 0000000 00000001501 13411321301 0025230 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/index.rb 0000664 0000000 0000000 00000002516 13411321301 0022235 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Index
attr_reader :reference, :options, :block
def self.define(reference, options = {}, &block)
new(reference, options, &block).indices.each do |index|
ThinkingSphinx::Configuration.instance.indices << index
end
end
def initialize(reference, options, &block)
defaults = ThinkingSphinx::Configuration.instance.
settings['index_options'] || {}
defaults.symbolize_keys!
@reference, @options, @block = reference, defaults.merge(options), block
end
def indices
options[:delta] ? delta_indices : [single_index]
end
private
def index_class
case options[:with]
when :active_record
ThinkingSphinx::ActiveRecord::Index
when :real_time
ThinkingSphinx::RealTime::Index
else
raise "Unknown index type: #{options[:with]}"
end
end
def single_index
index_class.new(reference, options).tap do |index|
index.definition_block = block
end
end
def delta_indices
[false, true].collect do |delta|
index_class.new(
reference,
options.merge(:delta? => delta, :delta_processor => processor)
).tap do |index|
index.definition_block = block
end
end
end
def processor
@processor ||= ThinkingSphinx::Deltas.processor_for options.delete(:delta)
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/index_set.rb 0000664 0000000 0000000 00000003655 13411321301 0023115 0 ustar 00root root 0000000 0000000 # 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)
@options = options
@index_names = options[:indices] || []
@configuration = configuration || ThinkingSphinx::Configuration.instance
end
def ancestors
classes_and_ancestors - classes
end
def to_a
indices
end
private
attr_reader :configuration, :options
def all_indices
configuration.preload_indices
configuration.indices
end
def classes
options[:classes] || []
end
def classes_specified?
classes.any? || references_specified?
end
def classes_and_ancestors
@classes_and_ancestors ||= mti_classes + sti_classes.collect { |model|
model.ancestors.take_while { |klass|
klass != ActiveRecord::Base
}.select { |klass|
klass.class == Class
}
}.flatten
end
def index_names
options[:indices] || []
end
def indices
return all_indices.select { |index|
index_names.include?(index.name)
} if index_names.any?
everything = classes_specified? ? indices_for_references : all_indices
everything.reject &:distributed?
end
def indices_for_references
all_indices.select { |index| references.include? index.reference }
end
def mti_classes
classes.reject { |klass|
klass.column_names.include?(klass.inheritance_column)
}
end
def references
options[:references] || classes_and_ancestors.collect { |klass|
ThinkingSphinx::IndexSet.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
thinking-sphinx-4.1.0/lib/thinking_sphinx/indexing_strategies/ 0000775 0000000 0000000 00000000000 13411321301 0024634 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/indexing_strategies/all_at_once.rb 0000664 0000000 0000000 00000000304 13411321301 0027416 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::IndexingStrategies::AllAtOnce
def self.call(indices = [], &block)
indices << '--all' if indices.empty?
block.call indices
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/indexing_strategies/one_at_a_time.rb 0000664 0000000 0000000 00000000665 13411321301 0027753 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/interfaces.rb 0000664 0000000 0000000 00000000370 13411321301 0023245 0 ustar 00root root 0000000 0000000 # 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'
thinking-sphinx-4.1.0/lib/thinking_sphinx/interfaces/ 0000775 0000000 0000000 00000000000 13411321301 0022720 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/interfaces/base.rb 0000664 0000000 0000000 00000000431 13411321301 0024155 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/interfaces/daemon.rb 0000664 0000000 0000000 00000001134 13411321301 0024507 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/interfaces/real_time.rb 0000664 0000000 0000000 00000001667 13411321301 0025220 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/interfaces/sql.rb 0000664 0000000 0000000 00000002357 13411321301 0024053 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/logger.rb 0000664 0000000 0000000 00000000360 13411321301 0022400 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Logger
def self.log(notification, message, &block)
ActiveSupport::Notifications.instrument(
"#{notification}.thinking_sphinx", notification => message, &block
)
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/masks.rb 0000664 0000000 0000000 00000000417 13411321301 0022242 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Masks
#
end
require 'thinking_sphinx/masks/group_enumerators_mask'
require 'thinking_sphinx/masks/pagination_mask'
require 'thinking_sphinx/masks/scopes_mask'
require 'thinking_sphinx/masks/weight_enumerator_mask'
thinking-sphinx-4.1.0/lib/thinking_sphinx/masks/ 0000775 0000000 0000000 00000000000 13411321301 0021713 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/masks/group_enumerators_mask.rb 0000664 0000000 0000000 00000001307 13411321301 0027034 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Masks::GroupEnumeratorsMask
def initialize(search)
@search = search
end
def can_handle?(method)
public_methods(false).include?(method)
end
def each_with_count(&block)
@search.raw.each_with_index do |row, index|
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["sphinx_internal_group"]
end
end
def each_with_group_and_count(&block)
@search.raw.each_with_index do |row, index|
yield @search[index], row["sphinx_internal_group"],
row["sphinx_internal_count"]
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/masks/pagination_mask.rb 0000664 0000000 0000000 00000002151 13411321301 0025403 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Masks::PaginationMask
def initialize(search)
@search = search
end
def can_handle?(method)
public_methods(false).include?(method)
end
def first_page?
search.current_page == 1
end
def last_page?
next_page.nil?
end
def next_page
search.current_page >= total_pages ? nil : search.current_page + 1
end
def next_page?
!next_page.nil?
end
def page(number)
search.options[:page] = number
search
end
def per(limit)
search.options[:limit] = limit
search
end
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
alias_method :total_count, :total_entries
alias_method :count, :total_entries
def total_pages
return 0 if search.meta['total'].nil?
@total_pages ||= (search.meta['total'].to_i / search.per_page.to_f).ceil
end
alias_method :page_count, :total_pages
alias_method :num_pages, :total_pages
private
attr_reader :search
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/masks/scopes_mask.rb 0000664 0000000 0000000 00000002367 13411321301 0024557 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Masks::ScopesMask
def initialize(search)
@search = search
end
def can_handle?(method)
public_methods(false).include?(method) || can_apply_scope?(method)
end
def facets(query = nil, options = {})
search = ThinkingSphinx.facets query, options
ThinkingSphinx::Search::Merger.new(search).merge!(
@search.query, @search.options
)
end
def search(query = nil, options = {})
query, options = nil, query if query.is_a?(Hash)
ThinkingSphinx::Search::Merger.new(@search).merge! query, options
end
def search_for_ids(query = nil, options = {})
query, options = nil, query if query.is_a?(Hash)
search query, options.merge(:ids_only => true)
end
private
def apply_scope(scope, *args)
query, options = sphinx_scopes[scope].call(*args)
search query, options
end
def can_apply_scope?(scope)
@search.options[:classes].present? &&
@search.options[:classes].length == 1 &&
@search.options[:classes].first.respond_to?(:sphinx_scopes) &&
sphinx_scopes[scope].present?
end
def method_missing(method, *args, &block)
apply_scope method, *args
end
def sphinx_scopes
@search.options[:classes].first.sphinx_scopes
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/masks/weight_enumerator_mask.rb 0000664 0000000 0000000 00000000537 13411321301 0027010 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Masks::WeightEnumeratorMask
def initialize(search)
@search = search
end
def can_handle?(method)
public_methods(false).include?(method)
end
def each_with_weight(&block)
@search.raw.each_with_index do |row, index|
yield @search[index], row["weight()"]
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares.rb 0000664 0000000 0000000 00000001624 13411321301 0023425 0 ustar 00root root 0000000 0000000 # 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 valid_options
].each do |middleware|
require "thinking_sphinx/middlewares/#{middleware}"
end
module ThinkingSphinx::Middlewares
def self.use(builder, middlewares)
middlewares.each { |m| builder.use m }
end
BASE_MIDDLEWARES = [ValidOptions, SphinxQL, Geographer, Inquirer]
DEFAULT = ::Middleware::Builder.new do
use StaleIdFilter
ThinkingSphinx::Middlewares.use self, BASE_MIDDLEWARES
use ActiveRecordTranslator
use StaleIdChecker
use Glazier
end
RAW_ONLY = ::Middleware::Builder.new do
ThinkingSphinx::Middlewares.use self, BASE_MIDDLEWARES
end
IDS_ONLY = ::Middleware::Builder.new do
ThinkingSphinx::Middlewares.use self, BASE_MIDDLEWARES
use IdsOnly
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/ 0000775 0000000 0000000 00000000000 13411321301 0023075 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/active_record_translator.rb 0000664 0000000 0000000 00000005252 13411321301 0030510 0 ustar 00root root 0000000 0000000 # 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
end
app.call contexts
end
private
class Inner
def initialize(context)
@context = context
end
def call
results_for_models # load now to avoid segfaults
context[:results] = if sql_options[:order]
results_for_models.values.first
else
context[:results].collect { |row| result_for(row) }
end
end
private
attr_reader :context
def ids_for_model(model_name)
context[:results].collect { |row|
row['sphinx_internal_id'] if row['sphinx_internal_class'] == 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
end
def result_for(row)
results_for_models[row['sphinx_internal_class']].detect { |record|
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
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, 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
def sql_options
context.search.options[:sql] || {}
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/geographer.rb 0000664 0000000 0000000 00000004045 13411321301 0025550 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'active_support/core_ext/module/delegation'
class ThinkingSphinx::Middlewares::Geographer <
ThinkingSphinx::Middlewares::Middleware
def call(contexts)
contexts.each do |context|
Inner.new(context).call
end
app.call contexts
end
private
class Inner
def initialize(context)
@context = context
end
def call
return unless geo
context[:sphinxql].prepend_values geodist_clause
context[:panes] << ThinkingSphinx::Panes::DistancePane
end
private
attr_reader :context
delegate :geo, :latitude, :longitude, :to => :geolocation_attributes
def fixed_format(float)
ThinkingSphinx::FloatFormatter.new(float).fixed
end
def geolocation_attributes
@geolocation_attributes ||= GeolocationAttributes.new(context)
end
def geodist_clause
"GEODIST(#{fixed_format geo.first}, #{fixed_format geo.last}, #{latitude}, #{longitude}) AS geodist"
end
class GeolocationAttributes
attr_accessor :latitude, :longitude
def initialize(context)
self.context = context
self.latitude = latitude_attr if latitude_attr
self.longitude = longitude_attr if longitude_attr
end
def geo
search_context_options[:geo]
end
def latitude
@latitude ||= names.detect { |name| %w[lat latitude].include?(name) } || 'lat'
end
def longitude
@longitude ||= names.detect { |name| %w[lng longitude].include?(name) } || 'lng'
end
private
attr_accessor :context
def latitude_attr
@latitude_attr ||= search_context_options[:latitude_attr]
end
def longitude_attr
@longitude_attr ||= search_context_options[:longitude_attr]
end
def indices
context[:indices]
end
def names
@names ||= indices.collect(&:unique_attribute_names).flatten.uniq
end
def search_context_options
@search_context_options ||= context.search.options
end
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/glazier.rb 0000664 0000000 0000000 00000001434 13411321301 0025061 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Middlewares::Glazier <
ThinkingSphinx::Middlewares::Middleware
def call(contexts)
contexts.each do |context|
Inner.new(context).call
end
app.call contexts
end
private
class Inner
def initialize(context)
@context = context
end
def call
return if context[:panes].empty?
context[:results] = context[:results].collect { |result|
ThinkingSphinx::Search::Glaze.new context, result, row_for(result),
context[:panes]
}
end
private
attr_reader :context
def row_for(result)
context[:raw].detect { |row|
row['sphinx_internal_class'] == result.class.name &&
row['sphinx_internal_id'] == result.id
}
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/ids_only.rb 0000664 0000000 0000000 00000000472 13411321301 0025245 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Middlewares::IdsOnly <
ThinkingSphinx::Middlewares::Middleware
def call(contexts)
contexts.each do |context|
context[:results] = context[:results].collect { |row|
row['sphinx_internal_id']
}
end
app.call contexts
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/inquirer.rb 0000664 0000000 0000000 00000002523 13411321301 0025262 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Middlewares::Inquirer <
ThinkingSphinx::Middlewares::Middleware
def call(contexts)
@contexts = contexts
@batch = nil
ThinkingSphinx::Logger.log :query, combined_queries do
batch.results
end
index = 0
contexts.each do |context|
Inner.new(context).call batch.results[index], batch.results[index + 1]
index += 2
end
app.call contexts
end
private
def batch
@batch ||= begin
batch = ThinkingSphinx::Search::BatchInquirer.new
@contexts.each do |context|
batch.append_query context[:sphinxql].to_sql
batch.append_query Riddle::Query.meta
end
batch
end
end
def combined_queries
@contexts.collect { |context| context[:sphinxql].to_sql }.join('; ')
end
class Inner
def initialize(context)
@context = context
end
def call(raw_results, meta_results)
context[:results] = raw_results.to_a
context[:raw] = context[:results].dup
context[:meta] = meta_results.inject({}) { |hash, row|
hash[row['Variable_name']] = row['Value']
hash
}
total = context[:meta]['total_found']
ThinkingSphinx::Logger.log :message, "Found #{total} result#{'s' unless total == 1}"
end
private
attr_reader :context
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/middleware.rb 0000664 0000000 0000000 00000000245 13411321301 0025540 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Middlewares::Middleware
def initialize(app)
@app = app
end
private
attr_reader :app, :context
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/sphinxql.rb 0000664 0000000 0000000 00000014516 13411321301 0025277 0 ustar 00root root 0000000 0000000 # 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, :rand_seed]
def call(contexts)
contexts.each do |context|
Inner.new(context).call
end
app.call contexts
end
private
class Inner
def initialize(context)
@context = context
end
def call
context[:indices] = indices
context[:sphinxql] = statement
end
private
attr_reader :context
delegate :search, :configuration, :to => :context
delegate :options, :to => :search
delegate :settings, :to => :configuration
def classes
options[:classes] || []
end
def classes_and_descendants
classes + descendants
end
def classes_and_descendants_names
classes_and_descendants.collect do |klass|
name = klass.name
name = %Q{"#{name}"} if name[/:/]
name
end
end
def classes_with_inheritance_column
classes.select { |klass|
klass.column_names.include?(klass.inheritance_column)
}
end
def class_condition
"(#{classes_and_descendants_names.join('|')})"
end
def class_condition_required?
classes.any? && !indices_match_classes?
end
def constantize_inheritance_column(klass)
values = klass.connection.select_values inheritance_column_select(klass)
values.reject(&:blank?).each(&:constantize)
end
def descendants
@descendants ||= options[:skip_sti] ? [] : descendants_from_tables
end
def descendants_from_tables
classes_with_inheritance_column.collect do |klass|
constantize_inheritance_column(klass)
klass.descendants
end.flatten
end
def indices_match_classes?
indices.collect(&:reference).uniq.sort == classes.collect { |klass|
ThinkingSphinx::IndexSet.reference_name(klass)
}.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?
end
end
def extended_query
conditions = options[:conditions] || {}
if class_condition_required?
conditions[:sphinx_internal_class_name] = class_condition
end
@extended_query ||= ThinkingSphinx::Search::Query.new(
context.search.query, conditions, options[:star]
).to_s
end
def group_attribute
options[:group_by].to_s if options[:group_by]
end
def group_order_clause
group_by = options[:order_group_by]
group_by = "#{group_by} ASC" if group_by.is_a? Symbol
group_by
end
def inclusive_filters
(options[:with] || {}).merge({:sphinx_deleted => false})
end
def index_names
indices.collect(&:name)
end
def index_options
indices.first.options
end
def indices
@indices ||= begin
set = configuration.index_set_class.new(
options.slice(:classes, :indices)
)
raise ThinkingSphinx::NoIndicesError if set.empty?
set
end
end
def order_clause
order_by = options[:order]
order_by = "#{order_by} ASC" if order_by.is_a? Symbol
order_by
end
def select_options
@select_options ||= SELECT_OPTIONS.inject({}) do |hash, key|
hash[key] = settings[key.to_s] if settings.key? key.to_s
hash[key] = index_options[key] if index_options.key? key
hash[key] = options[key] if options.key? key
hash
end
end
def values
options[: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
def statement
Statement.new(self).to_riddle_query_select
end
class Statement
def initialize(report)
self.report = report
self.query = Riddle::Query::Select.new
end
def to_riddle_query_select
filter_by_scopes
query
end
protected
attr_accessor :report, :query
def filter_by_scopes
scope_by_from
scope_by_values
scope_by_extended_query
scope_by_inclusive_filters
scope_by_with_all
scope_by_exclusive_filters
scope_by_without_all
scope_by_order
scope_by_group
scope_by_pagination
scope_by_options
end
def scope_by_from
query.from *(index_names.collect { |index| "`#{index}`" })
end
def scope_by_values
query.values(values.present? ? values : '*')
end
def scope_by_extended_query
query.matching extended_query if extended_query.present?
end
def scope_by_inclusive_filters
query.where inclusive_filters if inclusive_filters.any?
end
def scope_by_with_all
query.where_all options[:with_all] if options[:with_all]
end
def scope_by_exclusive_filters
query.where_not exclusive_filters if exclusive_filters.any?
end
def scope_by_without_all
query.where_not_all options[:without_all] if options[:without_all]
end
def scope_by_order
query.order_by order_clause if order_clause.present?
end
def scope_by_group
query.group_by group_attribute if group_attribute.present?
query.group_best options[:group_best] if options[:group_best]
query.order_within_group_by group_order_clause if group_order_clause.present?
query.having options[:having] if options[:having]
end
def scope_by_pagination
query.offset context.search.offset
query.limit context.search.per_page
end
def scope_by_options
query.with_options select_options if select_options.keys.any?
end
def method_missing(*args, &block)
report.send *args, &block
end
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/stale_id_checker.rb 0000664 0000000 0000000 00000001651 13411321301 0026675 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Middlewares::StaleIdChecker <
ThinkingSphinx::Middlewares::Middleware
def call(contexts)
contexts.each do |context|
Inner.new(context).call
end
app.call contexts
end
private
class Inner
def initialize(context)
@context = context
end
def call
raise_exception if context[:results].any?(&:nil?)
end
private
attr_reader :context
def actual_ids
context[:results].compact.collect(&:id)
end
def expected_ids
context[:raw].collect { |row| row['sphinx_internal_id'].to_i }
end
def raise_exception
raise ThinkingSphinx::Search::StaleIdsException.new(stale_ids, context)
end
def stale_ids
# Currently only works with single-model queries. Has at no point done
# otherwise, but such an improvement would be nice.
expected_ids - actual_ids
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/stale_id_filter.rb 0000664 0000000 0000000 00000002003 13411321301 0026546 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Middlewares::StaleIdFilter <
ThinkingSphinx::Middlewares::Middleware
def call(contexts)
@context = contexts.first
@stale_ids = []
@retries = stale_retries
begin
app.call contexts
rescue ThinkingSphinx::Search::StaleIdsException => error
raise error if @retries <= 0
append_stale_ids error.ids, error.context
ThinkingSphinx::Logger.log :message, log_message
@retries -= 1 and retry
end
end
private
def append_stale_ids(ids, context)
@stale_ids |= ids
context.search.options[:without_ids] ||= []
context.search.options[:without_ids] |= ids
end
def log_message
'Stale Ids (%s %s left): %s' % [
@retries, (@retries == 1 ? 'try' : 'tries'), @stale_ids.join(', ')
]
end
def stale_retries
case context.search.options[:retry_stale]
when nil, TrueClass
2
when FalseClass
0
else
context.search.options[:retry_stale].to_i
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/middlewares/valid_options.rb 0000664 0000000 0000000 00000001033 13411321301 0026271 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/panes.rb 0000664 0000000 0000000 00000000375 13411321301 0022235 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Panes
#
end
require 'thinking_sphinx/panes/attributes_pane'
require 'thinking_sphinx/panes/distance_pane'
require 'thinking_sphinx/panes/excerpts_pane'
require 'thinking_sphinx/panes/weight_pane'
thinking-sphinx-4.1.0/lib/thinking_sphinx/panes/ 0000775 0000000 0000000 00000000000 13411321301 0021703 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/panes/attributes_pane.rb 0000664 0000000 0000000 00000000263 13411321301 0025422 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Panes::AttributesPane
def initialize(context, object, raw)
@raw = raw
end
def sphinx_attributes
@raw
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/panes/distance_pane.rb 0000664 0000000 0000000 00000000346 13411321301 0025030 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Panes::DistancePane
def initialize(context, object, raw)
@raw = raw
end
def distance
@raw['geodist'].to_f
end
def geodist
@raw['geodist'].to_f
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/panes/excerpts_pane.rb 0000664 0000000 0000000 00000001727 13411321301 0025077 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Panes::ExcerptsPane
def initialize(context, object, raw)
@context, @object = context, object
end
def excerpts
@excerpt_glazing ||= Excerpts.new @object, excerpter
end
private
def excerpter
@excerpter ||= ThinkingSphinx::Excerpter.new(
@context[:indices].first.name,
excerpt_words,
@context.search.options[:excerpts] || {}
)
end
def excerpt_words
@excerpt_words ||= begin
conditions = @context.search.options[:conditions] || {}
ThinkingSphinx::Search::Query.new(
([@context.search.query] + conditions.values).compact.join(' '),
{}, @context.search.options[:star]
).to_s
end
end
class Excerpts
def initialize(object, excerpter)
@object, @excerpter = object, excerpter
end
private
def method_missing(method, *args, &block)
@excerpter.excerpt! @object.send(method, *args, &block).to_s
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/panes/weight_pane.rb 0000664 0000000 0000000 00000000260 13411321301 0024520 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Panes::WeightPane
def initialize(context, object, raw)
@raw = raw
end
def weight
@raw["weight()"]
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/query.rb 0000664 0000000 0000000 00000000341 13411321301 0022265 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Query
def self.escape(query)
Riddle::Query.escape query
end
def self.wildcard(query, pattern = true)
ThinkingSphinx::Wildcard.call query, pattern
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/railtie.rb 0000664 0000000 0000000 00000000524 13411321301 0022554 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Railtie < Rails::Railtie
initializer 'thinking_sphinx.initialisation' do
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.send :include, ThinkingSphinx::ActiveRecord::Base
end
end
rake_tasks do
load File.expand_path('../tasks.rb', __FILE__)
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/rake_interface.rb 0000664 0000000 0000000 00000001334 13411321301 0024065 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RakeInterface
DEFAULT_OPTIONS = {:verbose => true}
def initialize(options = {})
@options = DEFAULT_OPTIONS.merge options
@options[:verbose] = false if @options[:silent]
end
def configure
ThinkingSphinx::Commander.call :configure, configuration, options
end
def daemon
@daemon ||= ThinkingSphinx::Interfaces::Daemon.new configuration, options
end
def rt
@rt ||= ThinkingSphinx::Interfaces::RealTime.new configuration, options
end
def sql
@sql ||= ThinkingSphinx::Interfaces::SQL.new configuration, options
end
private
attr_reader :options
def configuration
ThinkingSphinx::Configuration.instance
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time.rb 0000664 0000000 0000000 00000001313 13411321301 0023061 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::RealTime
module Callbacks
#
end
def self.callback_for(reference, path = [], &block)
Callbacks::RealTimeCallbacks.new reference.to_sym, path, &block
end
end
require 'thinking_sphinx/real_time/property'
require 'thinking_sphinx/real_time/attribute'
require 'thinking_sphinx/real_time/field'
require 'thinking_sphinx/real_time/index'
require 'thinking_sphinx/real_time/interpreter'
require 'thinking_sphinx/real_time/populator'
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'
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/ 0000775 0000000 0000000 00000000000 13411321301 0022536 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/attribute.rb 0000664 0000000 0000000 00000000620 13411321301 0025064 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property
def multi?
@options[:multi]
end
def type
@options[:type]
end
def translate(object)
output = super || default_value
json? ? output.to_json : output
end
private
def default_value
type == :string ? '' : 0
end
def json?
type == :json
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/callbacks/ 0000775 0000000 0000000 00000000000 13411321301 0024455 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb 0000664 0000000 0000000 00000002376 13411321301 0030752 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks
def initialize(reference, path = [], &block)
@reference, @path, @block = reference, path, block
end
def after_commit(instance)
persist_changes instance
end
def after_save(instance)
persist_changes instance
end
private
attr_reader :reference, :path, :block
def callbacks_enabled?
setting = configuration.settings['real_time_callbacks']
setting.nil? || setting
end
def configuration
ThinkingSphinx::Configuration.instance
end
def indices
configuration.indices_for_references reference
end
def objects_for(instance)
if block
results = block.call instance
else
results = path.inject(instance) { |object, method| object.send method }
end
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
def real_time_indices
indices.select { |index| index.is_a? ThinkingSphinx::RealTime::Index }
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/field.rb 0000664 0000000 0000000 00000000324 13411321301 0024145 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RealTime::Field < ThinkingSphinx::RealTime::Property
include ThinkingSphinx::Core::Field
def translate(object)
Array(super || '').join(' ')
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/index.rb 0000664 0000000 0000000 00000003433 13411321301 0024175 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
include ThinkingSphinx::Core::Index
attr_writer :fields, :attributes, :conditions, :scope
def initialize(reference, options = {})
@fields = []
@attributes = []
@conditions = []
super reference, options
Template.new(self).apply
end
def add_attribute(attribute)
@attributes << attribute
end
def add_field(field)
@fields << field
end
def attributes
interpret_definition!
@attributes
end
def conditions
interpret_definition!
@conditions
end
def facets
properties.select(&:facet?)
end
def fields
interpret_definition!
@fields
end
def scope
@scope.nil? ? model : @scope.call
end
def unique_attribute_names
attributes.collect(&:name)
end
private
def append_unique_attribute(collection, attribute)
collection << attribute.name unless collection.include?(attribute.name)
end
def collection_for(attribute)
case attribute.type
when :integer, :boolean
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
end
def interpreter
ThinkingSphinx::RealTime::Interpreter
end
def pre_render
super
@rt_field = fields.collect &:name
attributes.each do |attribute|
append_unique_attribute collection_for(attribute), attribute
end
end
def properties
fields + attributes
end
end
require 'thinking_sphinx/real_time/index/template'
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/index/ 0000775 0000000 0000000 00000000000 13411321301 0023645 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/index/template.rb 0000664 0000000 0000000 00000001674 13411321301 0026015 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RealTime::Index::Template
attr_reader :index
def initialize(index)
@index = index
end
def apply
add_field class_column, :sphinx_internal_class_name
add_attribute primary_key, :sphinx_internal_id, :bigint
add_attribute class_column, :sphinx_internal_class, :string, :facet => true
add_attribute 0, :sphinx_deleted, :integer
end
private
def add_attribute(column, name, type, options = {})
index.add_attribute ThinkingSphinx::RealTime::Attribute.new(
ThinkingSphinx::ActiveRecord::Column.new(*column),
options.merge(:as => name, :type => type)
)
end
def add_field(column, name)
index.add_field ThinkingSphinx::RealTime::Field.new(
ThinkingSphinx::ActiveRecord::Column.new(*column), :as => name
)
end
def class_column
[:class, :name]
end
def primary_key
index.primary_key.to_sym
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/interpreter.rb 0000664 0000000 0000000 00000002501 13411321301 0025424 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RealTime::Interpreter <
::ThinkingSphinx::Core::Interpreter
def has(*columns)
options = columns.extract_options!
@index.attributes += columns.collect { |column|
::ThinkingSphinx::RealTime::Attribute.new column, options
}
end
def indexes(*columns)
options = columns.extract_options!
@index.fields += columns.collect { |column|
::ThinkingSphinx::RealTime::Field.new column, options
}
append_sortable_attributes columns, options if options[:sortable]
end
def scope(&block)
@index.scope = block
end
def set_property(properties)
properties.each do |key, value|
@index.send("#{key}=", value) if @index.class.settings.include?(key)
@index.options[key] = value if search_option?(key)
end
end
def where(condition)
@index.conditions << condition
end
private
def append_sortable_attributes(columns, options)
options = options.except(:sortable).merge(:type => :string)
@index.attributes += columns.collect { |column|
aliased_name = options[:as]
aliased_name ||= column.__name.to_sym if column.respond_to?(:__name)
aliased_name ||= column
options[:as] = "#{aliased_name}_sort".to_sym
::ThinkingSphinx::RealTime::Attribute.new column, options
}
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/populator.rb 0000664 0000000 0000000 00000001644 13411321301 0025115 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RealTime::Populator
def self.populate(index)
new(index).populate
end
def initialize(index)
@index = index
end
def populate(&block)
instrument 'start_populating'
scope.find_in_batches(:batch_size => batch_size) do |instances|
transcriber.copy *instances
instrument 'populated', :instances => instances
end
instrument 'finish_populating'
end
private
attr_reader :index
delegate :controller, :batch_size, :to => :configuration
delegate :scope, :to => :index
def configuration
ThinkingSphinx::Configuration.instance
end
def instrument(message, options = {})
ActiveSupport::Notifications.instrument(
"#{message}.thinking_sphinx.real_time", options.merge(:index => index)
)
end
def transcriber
@transcriber ||= ThinkingSphinx::RealTime::Transcriber.new index
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/property.rb 0000664 0000000 0000000 00000000743 13411321301 0024753 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RealTime::Property
include ThinkingSphinx::Core::Property
attr_reader :column, :options
def initialize(column, options = {})
@options = options
@column = column.respond_to?(:__name) ? column :
ThinkingSphinx::ActiveRecord::Column.new(column)
end
def name
(@options[:as] || @column.__name).to_s
end
def translate(object)
ThinkingSphinx::RealTime::Translator.call(object, @column)
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/transcribe_instance.rb 0000664 0000000 0000000 00000001647 13411321301 0027113 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/transcriber.rb 0000664 0000000 0000000 00000003053 13411321301 0025402 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::RealTime::Transcriber
def initialize(index)
@index = index
end
def copy(*instances)
items = instances.select { |instance|
instance.persisted? && copy?(instance)
}
return unless items.present?
values = []
items.each do |instance|
begin
values << ThinkingSphinx::RealTime::TranscribeInstance.call(
instance, index, properties
)
rescue ThinkingSphinx::TranscriptionError => error
instrument 'error', :error => error
end
end
insert = Riddle::Query::Insert.new index.name, columns, values
sphinxql = insert.replace!.to_sql
ThinkingSphinx::Logger.log :query, sphinxql do
ThinkingSphinx::Connection.take do |connection|
connection.execute sphinxql
end
end
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
when Symbol
instance.send(condition)
when Proc
condition.call instance
else
"Unexpected condition: #{condition}. Expecting Symbol or Proc."
end
}
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
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/real_time/translator.rb 0000664 0000000 0000000 00000001176 13411321301 0025261 0 ustar 00root root 0000000 0000000 # 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 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
thinking-sphinx-4.1.0/lib/thinking_sphinx/scopes.rb 0000664 0000000 0000000 00000001175 13411321301 0022422 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx::Scopes
extend ActiveSupport::Concern
module ClassMethods
def default_sphinx_scope(scope_name = nil)
return @default_sphinx_scope unless scope_name
@default_sphinx_scope = scope_name
end
def sphinx_scope(name, &block)
sphinx_scopes[name] = block
end
def sphinx_scopes
@sphinx_scopes ||= {}
end
private
def method_missing(method, *args, &block)
return super unless sphinx_scopes.keys.include?(method)
query, options = sphinx_scopes[method].call(*args)
search query, (options || {})
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/search.rb 0000664 0000000 0000000 00000007640 13411321301 0022376 0 ustar 00root root 0000000 0000000 # 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_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, :geo, :group_by, :ids_only, :ignore_scopes,
:indices, :limit, :masks, :max_matches, :middleware, :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,
ThinkingSphinx::Masks::GroupEnumeratorsMask
]
instance_methods.select { |method|
method.to_s[/^__/].nil? && !CORE_METHODS.include?(method.to_s)
}.each { |method|
undef_method method
}
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
populate if options[:populate]
end
def context
@context ||= ThinkingSphinx::Search::Context.new self,
ThinkingSphinx::Configuration.instance
end
def current_page
options[:page] = 1 if options[:page].blank?
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
def meta
populate
context[:meta]
end
def offset
@options[:offset] || ((current_page - 1) * per_page)
end
alias_method :offset_value, :offset
def per_page(value = nil)
@options[:limit] = value unless value.nil?
@options[:limit] ||= (@options[:per_page] || 20)
@options[:limit].to_i
end
alias_method :limit_value, :per_page
def populate
return self if @populated
middleware.call [context]
@populated = true
self
end
def populated!
@populated = true
end
def populated?
@populated
end
def query_time
meta['time'].to_f
end
def raw
populate
context[:raw]
end
def to_a
populate
context[:results].collect { |result|
result.respond_to?(:unglazed) ? result.unglazed : result
}
end
private
def default_middleware
options[:ids_only] ? ThinkingSphinx::Middlewares::IDS_ONLY :
ThinkingSphinx::Middlewares::DEFAULT
end
def mask_stack
@mask_stack ||= masks.collect { |klass| klass.new self }
end
def masks_respond_to?(method)
mask_stack.any? { |mask| mask.can_handle? method }
end
def method_missing(method, *args, &block)
mask_stack.each do |mask|
return mask.send(method, *args, &block) if mask.can_handle?(method)
end
populate if !SAFE_METHODS.include?(method.to_s)
context[:results].send(method, *args, &block)
end
def respond_to_missing?(method, include_private = false)
super ||
masks_respond_to?(method) ||
results_respond_to?(method, include_private)
end
def middleware
@options[:middleware] || default_middleware
end
def results_respond_to?(method, include_private = true)
context[:results].respond_to?(method, include_private)
end
end
require 'thinking_sphinx/search/batch_inquirer'
require 'thinking_sphinx/search/context'
require 'thinking_sphinx/search/glaze'
require 'thinking_sphinx/search/merger'
require 'thinking_sphinx/search/query'
require 'thinking_sphinx/search/stale_ids_exception'
thinking-sphinx-4.1.0/lib/thinking_sphinx/search/ 0000775 0000000 0000000 00000000000 13411321301 0022042 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/search/batch_inquirer.rb 0000664 0000000 0000000 00000000611 13411321301 0025364 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Search::BatchInquirer
def initialize(&block)
@queries = []
yield self if block_given?
end
def append_query(query)
@queries << query
end
def results
@results ||= begin
@queries.freeze
ThinkingSphinx::Connection.take do |connection|
connection.query_all *@queries
end
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/search/context.rb 0000664 0000000 0000000 00000001135 13411321301 0024053 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Search::Context
attr_reader :search, :configuration
def initialize(search, configuration = nil)
@search = search
@configuration = configuration || ThinkingSphinx::Configuration.instance
@memory = {
:results => [],
:panes => ThinkingSphinx::Configuration::Defaults::PANES.clone
}
end
def [](key)
@memory[key]
end
def []=(key, value)
@memory[key] = value
end
def marshal_dump
[@memory.except(:raw, :indices)]
end
def marshal_load(array)
@memory = array.first
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/search/glaze.rb 0000664 0000000 0000000 00000001544 13411321301 0023475 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Search::Glaze < BasicObject
def initialize(context, object, raw = {}, pane_classes = [])
@object, @raw = object, raw
@panes = pane_classes.collect { |klass|
klass.new context, object, @raw
}
end
def ==(object)
(@object == object) || super
end
def equal?(object)
@object.equal? object
end
def respond_to?(method, include_private = false)
@object.respond_to?(method, include_private) ||
@panes.any? { |pane| pane.respond_to?(method, include_private) }
end
def unglazed
@object
end
private
def method_missing(method, *args, &block)
pane = @panes.detect { |pane| pane.respond_to?(method) }
if @object.respond_to?(method) || pane.nil?
@object.send(method, *args, &block)
else
pane.send(method, *args, &block)
end
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/search/merger.rb 0000664 0000000 0000000 00000001542 13411321301 0023652 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Search::Merger
attr_reader :search
def initialize(search)
@search = search
end
def merge!(query = nil, options = {})
if search.populated?
raise ThinkingSphinx::PopulatedResultsError, 'This search request has already been made - you can no longer modify it.'
end
query, options = nil, query if query.is_a?(Hash)
@search.query = query unless query.nil?
options.each do |key, value|
case key
when :conditions, :with, :without, :with_all, :without_all
@search.options[key] ||= {}
@search.options[key].merge! value
when :without_ids, :classes
@search.options[key] ||= []
@search.options[key] += value
@search.options[key].uniq!
else
@search.options[key] = value
end
end
@search
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/search/query.rb 0000664 0000000 0000000 00000001433 13411321301 0023535 0 ustar 00root root 0000000 0000000 # encoding: utf-8
# frozen_string_literal: true
class ThinkingSphinx::Search::Query
attr_reader :keywords, :conditions, :star
def initialize(keywords = '', conditions = {}, star = false)
@keywords, @conditions, @star = keywords, conditions, star
end
def to_s
(star_keyword(keywords || '') + ' ' + conditions.keys.collect { |key|
next if conditions[key].blank?
"#{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'
ThinkingSphinx::Query.wildcard keyword, star
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/search/stale_ids_exception.rb 0000664 0000000 0000000 00000000456 13411321301 0026421 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Search::StaleIdsException < StandardError
attr_reader :ids, :context
def initialize(ids, context)
@ids = ids
@context = context
end
def message
"Record IDs found by Sphinx but not by ActiveRecord : #{ids.join(', ')}"
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/settings.rb 0000664 0000000 0000000 00000005054 13411321301 0022766 0 ustar 00root root 0000000 0000000 # 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"
}.freeze
def self.call(configuration)
new(configuration).call
end
def initialize(configuration)
@configuration = configuration
end
def call
return defaults unless File.exists? 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)|
value = value.gsub("ENVIRONMENT", framework.environment)
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
input = File.read file
input = ERB.new(input).result if defined?(ERB)
contents = YAML.load input
contents && 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
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/sinatra.rb 0000664 0000000 0000000 00000000223 13411321301 0022560 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'thinking_sphinx'
ActiveSupport.on_load :active_record do
include ThinkingSphinx::ActiveRecord::Base
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/subscribers/ 0000775 0000000 0000000 00000000000 13411321301 0023123 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/lib/thinking_sphinx/subscribers/populator_subscriber.rb 0000664 0000000 0000000 00000002144 13411321301 0027721 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Subscribers::PopulatorSubscriber
def self.attach_to(namespace)
subscriber = new
subscriber.public_methods(false).each do |event|
next if event == :call
ActiveSupport::Notifications.subscribe(
"#{event}.#{namespace}", subscriber
)
end
end
def call(message, *args)
send message.split('.').first,
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 '.' * event.payload[:instances].length
end
def finish_populating(event)
print "\n"
end
private
delegate :output, :to => ThinkingSphinx
delegate :puts, :print, :to => :output
end
ThinkingSphinx::Subscribers::PopulatorSubscriber.attach_to(
'thinking_sphinx.real_time'
)
thinking-sphinx-4.1.0/lib/thinking_sphinx/tasks.rb 0000664 0000000 0000000 00000004361 13411321301 0022253 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
namespace :ts do
desc 'Generate the Sphinx configuration file'
task :configure => :environment do
interface.configure
end
desc 'Generate the Sphinx configuration file and process all indices'
task :index => ['ts:sql:index', 'ts:rt:index']
desc 'Clear out Sphinx files'
task :clear => ['ts:sql:clear', 'ts:rt:clear']
desc "Merge all delta indices into their respective core indices"
task :merge => ["ts:sql:merge"]
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.daemon.start
end
desc 'Stop the Sphinx daemon'
task :stop => :environment do
interface.daemon.stop
end
desc 'Determine whether Sphinx is running'
task :status => :environment do
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.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
thinking-sphinx-4.1.0/lib/thinking_sphinx/test.rb 0000664 0000000 0000000 00000002042 13411321301 0022077 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Test
def self.init(suppress_delta_output = true)
FileUtils.mkdir_p config.indices_location
config.settings['quiet_deltas'] = suppress_delta_output
end
def self.start(options = {})
config.render_to_file
config.controller.index if options[:index].nil? || options[:index]
config.controller.start
end
def self.start_with_autostop
autostop
start
end
def self.stop
config.controller.stop
sleep(0.5) # Ensure Sphinx has shut down completely
end
def self.autostop
Kernel.at_exit do
ThinkingSphinx::Test.stop
end
end
def self.run(&block)
begin
start
yield
ensure
stop
end
end
def self.clear
[
config.indices_location,
config.searchd.binlog_path
].each do |path|
FileUtils.rm_r(path) if File.exists?(path)
end
end
def self.config
@config ||= ::ThinkingSphinx::Configuration.instance
end
def self.index(*indexes)
config.controller.index *indexes
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/utf8.rb 0000664 0000000 0000000 00000000431 13411321301 0022006 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::UTF8
attr_reader :string
def self.encode(string)
new(string).encode
end
def initialize(string)
@string = string
end
def encode
string.encode!('ISO-8859-1')
string.force_encoding('UTF-8')
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/wildcard.rb 0000664 0000000 0000000 00000002013 13411321301 0022707 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class ThinkingSphinx::Wildcard
DEFAULT_TOKEN = /\p{Word}+/
def self.call(query, pattern = DEFAULT_TOKEN)
new(query, pattern).call
end
def initialize(query, pattern = DEFAULT_TOKEN)
@query = query || ''
@pattern = pattern.is_a?(Regexp) ? pattern : DEFAULT_TOKEN
end
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.match(%r{@$}) ||
pre.match(%r{([^\\]+|\A)[~/]\Z}) ||
pre.match(%r{(\W|^)@\([^\)]*$})
# E.g. "foo bar", with quotes
is_quote = proper[/^".*"$/]
has_star = post[/\*$/] || pre[/^\*/]
if is_operator || is_quote || has_star
proper
else
"*#{proper}*"
end
end
end
private
attr_reader :query, :pattern
def extended_pattern
Regexp.new(
"(\"#{pattern}(.*?#{pattern})?\"|(?![!-])#{pattern})".encode('UTF-8')
)
end
end
thinking-sphinx-4.1.0/lib/thinking_sphinx/with_output.rb 0000664 0000000 0000000 00000000442 13411321301 0023515 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/ 0000775 0000000 0000000 00000000000 13411321301 0015555 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/acceptance/ 0000775 0000000 0000000 00000000000 13411321301 0017643 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/acceptance/association_scoping_spec.rb 0000664 0000000 0000000 00000004456 13411321301 0025251 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Scoping association search calls by foreign keys', :live => true do
describe 'for ActiveRecord indices' do
it "limits results to those matching the foreign key" do
pat = User.create :name => 'Pat'
melbourne = Article.create :title => 'Guide to Melbourne', :user => pat
paul = User.create :name => 'Paul'
dublin = Article.create :title => 'Guide to Dublin', :user => paul
index
expect(pat.articles.search('Guide').to_a).to eq([melbourne])
end
it "limits id-only results to those matching the foreign key" do
pat = User.create :name => 'Pat'
melbourne = Article.create :title => 'Guide to Melbourne', :user => pat
paul = User.create :name => 'Paul'
dublin = Article.create :title => 'Guide to Dublin', :user => paul
index
expect(pat.articles.search_for_ids('Guide').to_a).to eq([melbourne.id])
end
end
describe 'for real-time indices' do
it "limits results to those matching the foreign key" do
porsche = Manufacturer.create :name => 'Porsche'
spyder = Car.create :name => '918 Spyder', :manufacturer => porsche
audi = Manufacturer.create :name => 'Audi'
r_eight = Car.create :name => 'R8 Spyder', :manufacturer => audi
expect(porsche.cars.search('Spyder').to_a).to eq([spyder])
end
it "limits id-only results to those matching the foreign key" do
porsche = Manufacturer.create :name => 'Porsche'
spyder = Car.create :name => '918 Spyder', :manufacturer => porsche
audi = Manufacturer.create :name => 'Audi'
r_eight = Car.create :name => 'R8 Spyder', :manufacturer => audi
expect(porsche.cars.search_for_ids('Spyder').to_a).to eq([spyder.id])
end
end
describe 'with has_many :through associations' do
it 'limits results to those matching the foreign key' do
pancakes = Product.create :name => 'Low fat Pancakes'
waffles = Product.create :name => 'Low fat Waffles'
food = Category.create :name => 'food'
flat = Category.create :name => 'flat'
pancakes.categories << food
pancakes.categories << flat
waffles.categories << food
expect(flat.products.search('Low').to_a).to eq([pancakes])
end
end
end
thinking-sphinx-4.1.0/spec/acceptance/attribute_access_spec.rb 0000664 0000000 0000000 00000002274 13411321301 0024533 0 ustar 00root root 0000000 0000000 # 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
index
search = Book.search('gods')
search.context[:panes] << ThinkingSphinx::Panes::AttributesPane
expect(search.first.sphinx_attributes['year']).to eq(2001)
end
it "provides direct access to the search weight/relevance scores" do
Book.create! :title => 'American Gods', :year => 2001
index
search = Book.search 'gods', :select => "*, weight()"
search.context[:panes] << ThinkingSphinx::Panes::WeightPane
expect(search.first.weight).to eq(2500)
end
it "can enumerate with the weight" do
gods = Book.create! :title => 'American Gods', :year => 2001
index
search = Book.search 'gods', :select => "*, weight()"
search.masks << ThinkingSphinx::Masks::WeightEnumeratorMask
expectations = [[gods, 2500]]
search.each_with_weight do |result, weight|
expectation = expectations.shift
expect(result).to eq(expectation.first)
expect(weight).to eq(expectation.last)
end
end
end
thinking-sphinx-4.1.0/spec/acceptance/attribute_updates_spec.rb 0000664 0000000 0000000 00000000757 13411321301 0024743 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Update attributes automatically where possible', :live => true do
it "updates boolean values" do
article = Article.create :title => 'Pancakes', :published => false
index
expect(Article.search('pancakes', :with => {:published => true})).to be_empty
article.published = true
article.save
expect(Article.search('pancakes', :with => {:published => true}).to_a)
.to eq([article])
end
end
thinking-sphinx-4.1.0/spec/acceptance/batch_searching_spec.rb 0000664 0000000 0000000 00000001277 13411321301 0024315 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Executing multiple searches in one Sphinx call', :live => true do
it "returns results matching the given queries" do
pancakes = Article.create! :title => 'Pancakes'
waffles = Article.create! :title => 'Waffles'
index
batch = ThinkingSphinx::BatchedSearch.new
batch.searches << Article.search('pancakes')
batch.searches << Article.search('waffles')
batch.populate
expect(batch.searches.first).to include(pancakes)
expect(batch.searches.first).not_to include(waffles)
expect(batch.searches.last).to include(waffles)
expect(batch.searches.last).not_to include(pancakes)
end
end
thinking-sphinx-4.1.0/spec/acceptance/big_integers_spec.rb 0000664 0000000 0000000 00000003356 13411321301 0023652 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe '64 bit integer support' do
it "ensures all internal id attributes are big ints if one is" do
large_index = ThinkingSphinx::ActiveRecord::Index.new(:tweet)
large_index.definition_block = Proc.new {
indexes text
}
small_index = ThinkingSphinx::ActiveRecord::Index.new(:article)
small_index.definition_block = Proc.new {
indexes title
}
real_time_index = ThinkingSphinx::RealTime::Index.new(:product)
real_time_index.definition_block = Proc.new {
indexes name
}
ThinkingSphinx::Configuration::ConsistentIds.new(
[small_index, large_index, real_time_index]
).reconcile
expect(large_index.sources.first.attributes.detect { |attribute|
attribute.name == 'sphinx_internal_id'
}.type).to eq(:bigint)
expect(small_index.sources.first.attributes.detect { |attribute|
attribute.name == 'sphinx_internal_id'
}.type).to eq(:bigint)
expect(real_time_index.attributes.detect { |attribute|
attribute.name == 'sphinx_internal_id'
}.type).to eq(:bigint)
end
end
describe '64 bit document ids', :live => true do
context 'with ActiveRecord' do
it 'handles large 32 bit integers with an offset multiplier' do
user = User.create! :name => 'Pat'
user.update_column :id, 980190962
index
expect(User.search('pat').to_a).to eq([user])
end
end
context 'with Real-Time' do
it 'handles large 32 bit integers with an offset multiplier' do
product = Product.create! :name => "Widget"
product.update_attributes :id => 980190962
expect(
Product.search('widget', :indices => ['product_core']).to_a
).to eq([product])
end
end
end
thinking-sphinx-4.1.0/spec/acceptance/connection_spec.rb 0000664 0000000 0000000 00000001351 13411321301 0023341 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
RSpec.describe 'Connections', :live => true do
let(:maximum) { (2 ** 23) - 5 }
let(:query) { String.new "SELECT * FROM book_core WHERE MATCH('')" }
let(:difference) { maximum - query.length }
it 'allows normal length queries through' do
expect {
ThinkingSphinx::Connection.take do |connection|
connection.execute query.insert(-3, 'a' * difference)
end
}.to_not raise_error
end
it 'does not allow overly long queries' do
expect {
ThinkingSphinx::Connection.take do |connection|
connection.execute query.insert(-3, 'a' * (difference + 5))
end
}.to raise_error(ThinkingSphinx::QueryLengthError)
end
end
thinking-sphinx-4.1.0/spec/acceptance/excerpts_spec.rb 0000664 0000000 0000000 00000002742 13411321301 0023044 0 ustar 00root root 0000000 0000000 # 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
index
search = Book.search('gods')
search.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
expect(search.first.excerpts.title).
to eq('American Gods')
end
it "handles UTF-8 text for excerpts" do
Book.create! :title => 'Война и миръ', :year => 1869
index
search = Book.search 'миръ'
search.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
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'
index
search = Book.search('graveyard')
search.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
expect(search.first.excerpts.title).
to eq('The Graveyard Book')
end
it "respects the star option with queries" do
Article.create! :title => 'Something'
index
search = Article.search('thin', :star => true)
search.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
expect(search.first.excerpts.title).
to eq('Something')
end
end
thinking-sphinx-4.1.0/spec/acceptance/facets_spec.rb 0000664 0000000 0000000 00000007140 13411321301 0022451 0 ustar 00root root 0000000 0000000 # encoding: utf-8
# frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Faceted searching', :live => true do
it "provides facet breakdowns across marked integer attributes" do
blue = Colour.create! :name => 'blue'
red = Colour.create! :name => 'red'
green = Colour.create! :name => 'green'
Tee.create! :colour => blue
Tee.create! :colour => blue
Tee.create! :colour => red
Tee.create! :colour => green
Tee.create! :colour => green
Tee.create! :colour => green
index
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
Tee.create!
Tee.create!
City.create!
Article.create!
index
expect(ThinkingSphinx.facets.to_hash[:class]).to eq({
'Tee' => 2, 'City' => 1, 'Article' => 1
})
end
it "handles field facets" do
Book.create! :title => 'American Gods', :author => 'Neil Gaiman'
Book.create! :title => 'Anansi Boys', :author => 'Neil Gaiman'
Book.create! :title => 'Snuff', :author => 'Terry Pratchett'
Book.create! :title => '1Q84', :author => '村上 春樹'
index
expect(Book.facets.to_hash[:author]).to eq({
'Neil Gaiman' => 2, 'Terry Pratchett' => 1, '村上 春樹' => 1
})
end
it "handles MVA facets" do
pancakes = Tag.create! :name => 'pancakes'
waffles = Tag.create! :name => 'waffles'
user = User.create!
Tagging.create! :article => Article.create!(:user => user),
:tag => pancakes
Tagging.create! :article => Article.create!(:user => user),
:tag => waffles
user = User.create!
Tagging.create! :article => Article.create!(:user => user),
:tag => pancakes
index
expect(User.facets.to_hash[:tag_ids]).to eq({
pancakes.id => 2, waffles.id => 1
})
end
it "can filter on integer facet results" do
blue = Colour.create! :name => 'blue'
red = Colour.create! :name => 'red'
b1 = Tee.create! :colour => blue
b2 = Tee.create! :colour => blue
r1 = Tee.create! :colour => red
index
expect(Tee.facets.for(:colour_id => blue.id).to_a).to eq([b1, b2])
end
it "can filter on MVA facet results" do
pancakes = Tag.create! :name => 'pancakes'
waffles = Tag.create! :name => 'waffles'
u1 = User.create!
Tagging.create! :article => Article.create!(:user => u1), :tag => pancakes
Tagging.create! :article => Article.create!(:user => u1), :tag => waffles
u2 = User.create!
Tagging.create! :article => Article.create!(:user => u2), :tag => pancakes
index
expect(User.facets.for(:tag_ids => waffles.id).to_a).to eq([u1])
end
it "can filter on string facet results" do
gods = Book.create! :title => 'American Gods', :author => 'Neil Gaiman'
boys = Book.create! :title => 'Anansi Boys', :author => 'Neil Gaiman'
snuff = Book.create! :title => 'Snuff', :author => 'Terry Pratchett'
index
expect(Book.facets.for(:author => 'Neil Gaiman').to_a).to eq([gods, boys])
end
it "allows enumeration" do
blue = Colour.create! :name => 'blue'
red = Colour.create! :name => 'red'
b1 = Tee.create! :colour => blue
b2 = Tee.create! :colour => blue
r1 = Tee.create! :colour => red
index
calls = 0
expectations = [
[:sphinx_internal_class, {'Tee' => 3}],
[:colour_id, {blue.id => 2, red.id => 1}],
[:class, {'Tee' => 3}]
]
Tee.facets.each do |facet, hash|
expect(facet).to eq(expectations[calls].first)
expect(hash).to eq(expectations[calls].last)
calls += 1
end
end
end
thinking-sphinx-4.1.0/spec/acceptance/geosearching_spec.rb 0000664 0000000 0000000 00000004410 13411321301 0023637 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Searching by latitude and longitude', :live => true do
it "orders by distance" do
mel = City.create :name => 'Melbourne', :lat => -0.6599720, :lng => 2.530082
syd = City.create :name => 'Sydney', :lat => -0.5909679, :lng => 2.639131
bri = City.create :name => 'Brisbane', :lat => -0.4794031, :lng => 2.670838
index
expect(City.search(:geo => [-0.616241, 2.602712], :order => 'geodist ASC').
to_a).to eq([syd, mel, bri])
end
it "filters by distance" do
mel = City.create :name => 'Melbourne', :lat => -0.6599720, :lng => 2.530082
syd = City.create :name => 'Sydney', :lat => -0.5909679, :lng => 2.639131
bri = City.create :name => 'Brisbane', :lat => -0.4794031, :lng => 2.670838
index
expect(City.search(
:geo => [-0.616241, 2.602712],
:with => {:geodist => 0.0..470_000.0}
).to_a).to eq([mel, syd])
end
it "provides the distance for each search result" do
mel = City.create :name => 'Melbourne', :lat => -0.6599720, :lng => 2.530082
syd = City.create :name => 'Sydney', :lat => -0.5909679, :lng => 2.639131
bri = City.create :name => 'Brisbane', :lat => -0.4794031, :lng => 2.670838
index
cities = City.search(:geo => [-0.616241, 2.602712], :order => 'geodist ASC')
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/]
expect(cities.first.geodist).to eq(expected[:postgresql])
else # mysql
expect(cities.first.geodist).to eq(expected[:mysql])
end
end
it "handles custom select clauses that refer to the distance" do
mel = City.create :name => 'Melbourne', :lat => -0.6599720, :lng => 2.530082
syd = City.create :name => 'Sydney', :lat => -0.5909679, :lng => 2.639131
bri = City.create :name => 'Brisbane', :lat => -0.4794031, :lng => 2.670838
index
expect(City.search(
:geo => [-0.616241, 2.602712],
:with => {:geodist => 0.0..470_000.0},
:select => "*, geodist as custom_weight"
).to_a).to eq([mel, syd])
end
end
thinking-sphinx-4.1.0/spec/acceptance/grouping_by_attributes_spec.rb 0000664 0000000 0000000 00000004674 13411321301 0026007 0 ustar 00root root 0000000 0000000 # 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
index
expect(Book.search(:group_by => :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
index
expect(Book.search(:group_by => :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
index
expectations = [[snuff, 1], [earth, 2]]
Book.search(:group_by => :year).each_with_count do |book, count|
expectation = expectations.shift
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
index
expectations = [[snuff, 2011], [earth, 2012]]
Book.search(:group_by => :year).each_with_group do |book, group|
expectation = expectations.shift
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
index
expectations = [[snuff, 2011, 1], [earth, 2012, 2]]
search = Book.search(:group_by => :year)
search.each_with_group_and_count do |book, group, count|
expectation = expectations.shift
expect(book).to eq(expectation[0])
expect(group).to eq(expectation[1])
expect(count).to eq(expectation[2])
end
end
end
thinking-sphinx-4.1.0/spec/acceptance/index_options_spec.rb 0000664 0000000 0000000 00000007616 13411321301 0024076 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Index options' do
let(:index) { ThinkingSphinx::ActiveRecord::Index.new(:article) }
%w( infix prefix ).each do |type|
context "all fields are #{type}ed" do
before :each do
index.definition_block = Proc.new {
indexes title
set_property "min_#{type}_len".to_sym => 3
}
index.render
end
it "keeps #{type}_fields blank" do
expect(index.send("#{type}_fields")).to be_nil
end
it "sets min_#{type}_len" do
expect(index.send("min_#{type}_len")).to eq(3)
end
end
context "some fields are #{type}ed" do
before :each do
index.definition_block = Proc.new {
indexes title, "#{type}es".to_sym => true
indexes content
set_property "min_#{type}_len".to_sym => 3
}
index.render
end
it "#{type}_fields should contain the field" do
expect(index.send("#{type}_fields")).to eq('title')
end
it "sets min_#{type}_len" do
expect(index.send("min_#{type}_len")).to eq(3)
end
end
end
context "multiple source definitions" do
before :each do
index.definition_block = Proc.new {
define_source do
indexes title
end
define_source do
indexes title, content
end
}
index.render
end
it "stores each source definition" do
expect(index.sources.length).to eq(2)
end
it "treats each source as separate" do
expect(index.sources.first.fields.length).to eq(2)
expect(index.sources.last.fields.length).to eq(3)
end
end
context 'wordcount fields and attributes' do
before :each do
index.definition_block = Proc.new {
indexes title, :wordcount => true
has content, :type => :wordcount
}
index.render
end
it "declares wordcount fields" do
expect(index.sources.first.sql_field_str2wordcount).to eq(['title'])
end
it "declares wordcount attributes" do
expect(index.sources.first.sql_attr_str2wordcount).to eq(['content'])
end
end
context 'respecting source options' do
before :each do
index.definition_block = Proc.new {
indexes title
set_property :sql_range_step => 5
set_property :disable_range? => true
set_property :sql_query_pre => ["DO STUFF"]
}
index.render
end
it "allows for core source settings" do
expect(index.sources.first.sql_range_step).to eq(5)
end
it "allows for source options" do
expect(index.sources.first.disable_range?).to be_truthy
end
it "respects sql_query_pre values" do
expect(index.sources.first.sql_query_pre).to include("DO STUFF")
end
end
context 'respecting index options over core configuration' do
before :each do
ThinkingSphinx::Configuration.instance.settings['min_infix_len'] = 2
ThinkingSphinx::Configuration.instance.settings['sql_range_step'] = 2
index.definition_block = Proc.new {
indexes title
set_property :min_infix_len => 1
set_property :sql_range_step => 20
}
index.render
end
after :each do
ThinkingSphinx::Configuration.instance.settings.delete 'min_infix_len'
ThinkingSphinx::Configuration.instance.settings.delete 'sql_range_step'
end
it "prioritises index-level options over YAML options" do
expect(index.min_infix_len).to eq(1)
end
it "prioritises index-level source options" do
expect(index.sources.first.sql_range_step).to eq(20)
end
it "keeps index-level options prioritised when rendered again" do
index.render
expect(index.min_infix_len).to eq(1)
end
it "keeps index-level options prioritised when rendered again" do
index.render
expect(index.sources.first.sql_range_step).to eq(20)
end
end
end
thinking-sphinx-4.1.0/spec/acceptance/indexing_spec.rb 0000664 0000000 0000000 00000002072 13411321301 0023010 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Indexing', :live => true do
it "does not index files where the temp file exists" do
path = Rails.root.join('db/sphinx/test/ts-article_core.tmp')
FileUtils.mkdir_p Rails.root.join('db/sphinx/test')
FileUtils.touch path
article = Article.create! :title => 'Pancakes'
index 'article_core'
expect(Article.search).to be_empty
FileUtils.rm path
end
it "indexes files when other indices are already being processed" do
path = Rails.root.join('db/sphinx/test/ts-book_core.tmp')
FileUtils.mkdir_p Rails.root.join('db/sphinx/test')
FileUtils.touch path
article = Article.create! :title => 'Pancakes'
index 'article_core'
expect(Article.search).not_to be_empty
FileUtils.rm path
end
it "cleans up temp files even when an exception is raised" do
FileUtils.mkdir_p Rails.root.join('db/sphinx/test')
index 'article_core'
file = Rails.root.join('db/sphinx/test/ts-article_core.tmp')
expect(File.exist?(file)).to be_falsey
end
end
thinking-sphinx-4.1.0/spec/acceptance/merging_spec.rb 0000664 0000000 0000000 00000004220 13411321301 0022630 0 ustar 00root root 0000000 0000000 # 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_attributes :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
thinking-sphinx-4.1.0/spec/acceptance/paginating_search_results_spec.rb 0000664 0000000 0000000 00000001235 13411321301 0026432 0 ustar 00root root 0000000 0000000 # 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
21.times { |number| Article.create :title => "Article #{number}" }
index
expect(Article.search.total_entries).to eq(21)
end
it "paginates the result set by default" do
21.times { |number| Article.create :title => "Article #{number}" }
index
expect(Article.search.length).to eq(20)
end
it "tracks the number of pages" do
21.times { |number| Article.create :title => "Article #{number}" }
index
expect(Article.search.total_pages).to eq(2)
end
end
thinking-sphinx-4.1.0/spec/acceptance/real_time_updates_spec.rb 0000664 0000000 0000000 00000001666 13411321301 0024701 0 ustar 00root root 0000000 0000000 # 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"
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'
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_attributes :name => 'Mort'
expect(Admin::Person.search('Death').to_a).to be_empty
expect(Admin::Person.search('Mort').to_a).to eq([person])
end
end
thinking-sphinx-4.1.0/spec/acceptance/remove_deleted_records_spec.rb 0000664 0000000 0000000 00000003445 13411321301 0025714 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Hiding deleted records from search results', :live => true do
it "does not return deleted records" do
pancakes = Article.create! :title => 'Pancakes'
index
expect(Article.search('pancakes')).not_to be_empty
pancakes.destroy
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
expect(Article.search('pancakes')).not_to be_empty
Article.connection.execute "DELETE FROM articles WHERE id = #{pancakes.id}"
expect(Article.search('pancakes')).to be_empty
end
it "removes records from real-time index results" do
product = Product.create! :name => 'Shiny'
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 "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
ThinkingSphinx::Configuration.instance.
settings['real_time_callbacks'] = original
end
it "deletes STI child classes from parent indices" do
duck = Bird.create :name => 'Duck'
index
duck.destroy
expect(Bird.search_for_ids('duck')).to be_empty
end
end
thinking-sphinx-4.1.0/spec/acceptance/search_counts_spec.rb 0000664 0000000 0000000 00000000767 13411321301 0024054 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Get search result counts', :live => true do
it "returns counts for a single model" do
4.times { |i| Article.create :title => "Article #{i}" }
index
expect(Article.search_count).to eq(4)
end
it "returns counts across all models" do
3.times { |i| Article.create :title => "Article #{i}" }
2.times { |i| Book.create :title => "Book #{i}" }
index
expect(ThinkingSphinx.count).to eq(5)
end
end
thinking-sphinx-4.1.0/spec/acceptance/search_for_just_ids_spec.rb 0000664 0000000 0000000 00000001144 13411321301 0025221 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Searching for just instance Ids', :live => true do
it "returns just the instance ids" do
pancakes = Article.create! :title => 'Pancakes'
waffles = Article.create! :title => 'Waffles'
index
expect(Article.search_for_ids('pancakes').to_a).to eq([pancakes.id])
end
it "works across the global context" do
article = Article.create! :title => 'Pancakes'
book = Book.create! :title => 'American Gods'
index
expect(ThinkingSphinx.search_for_ids.to_a).to match_array([article.id, book.id])
end
end
thinking-sphinx-4.1.0/spec/acceptance/searching_across_models_spec.rb 0000664 0000000 0000000 00000002150 13411321301 0026060 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Searching across models', :live => true do
it "returns results" do
article = Article.create! :title => 'Pancakes'
index
expect(ThinkingSphinx.search.first).to eq(article)
end
it "returns results matching the given query" do
pancakes = Article.create! :title => 'Pancakes'
waffles = Article.create! :title => 'Waffles'
index
articles = ThinkingSphinx.search 'pancakes'
expect(articles).to include(pancakes)
expect(articles).not_to include(waffles)
end
it "handles results from different models" do
article = Article.create! :title => 'Pancakes'
book = Book.create! :title => 'American Gods'
index
expect(ThinkingSphinx.search.to_a).to match_array([article, book])
end
it "filters by multiple classes" do
article = Article.create! :title => 'Pancakes'
book = Book.create! :title => 'American Gods'
user = User.create! :name => 'Pat'
index
expect(ThinkingSphinx.search(:classes => [User, Article]).to_a).
to match_array([article, user])
end
end
thinking-sphinx-4.1.0/spec/acceptance/searching_across_schemas_spec.rb 0000664 0000000 0000000 00000002702 13411321301 0026223 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
multi_schema = MultiSchema.new
describe 'Searching across PostgreSQL schemas', :live => true do
before :each do
ThinkingSphinx::Configuration.instance.index_set_class =
MultiSchema::IndexSet
end
after :each do
ThinkingSphinx::Configuration.instance.index_set_class = nil
multi_schema.switch :public
end
it 'can distinguish between objects with the same primary key' do
multi_schema.switch :public
jekyll = Product.create name: 'Doctor 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'
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
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([])
expect(Product.search(
:middleware => ThinkingSphinx::Middlewares::RAW_ONLY,
:indices => ['product_core', 'product_two_core']
).to_a.length).to eq(2)
end
end if multi_schema.active?
thinking-sphinx-4.1.0/spec/acceptance/searching_on_fields_spec.rb 0000664 0000000 0000000 00000003610 13411321301 0025167 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Searching on fields', :live => true do
it "limits results by field" do
pancakes = Article.create! :title => 'Pancakes'
waffles = Article.create! :title => 'Waffles',
:content => 'Different to pancakes - and not quite as tasty.'
index
articles = Article.search :conditions => {:title => 'pancakes'}
expect(articles).to include(pancakes)
expect(articles).not_to include(waffles)
end
it "limits results for a field from an association" do
user = User.create! :name => 'Pat'
pancakes = Article.create! :title => 'Pancakes', :user => user
index
expect(Article.search(:conditions => {:user => 'pat'}).first).to eq(pancakes)
end
it "returns results with matches from grouped fields" do
user = User.create! :name => 'Pat'
pancakes = Article.create! :title => 'Pancakes', :user => user
waffles = Article.create! :title => 'Waffles', :user => user
index
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
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
expect(Book.search(:conditions => {:info => 'Night Watch'}).to_a).to eq([book])
end
it "returns results with matches from file fields" do
file_path = Rails.root.join('tmp', 'caption.txt')
File.open(file_path, 'w') { |file| file.print 'Cyberpunk at its best' }
book = Book.create! :title => 'Accelerando', :blurb_file => file_path.to_s
index
expect(Book.search('cyberpunk').to_a).to eq([book])
end
end
thinking-sphinx-4.1.0/spec/acceptance/searching_with_filters_spec.rb 0000664 0000000 0000000 00000012566 13411321301 0025742 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Searching with filters', :live => true do
it "limits results by single value boolean filters" do
pancakes = Article.create! :title => 'Pancakes', :published => true
waffles = Article.create! :title => 'Waffles', :published => false
index
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
index
expect(Book.search(:with => {:year => [2001, 2005]}).to_a).to eq([gods, boys])
end
it "limits results by a ranged filter" do
gods = Book.create! :title => 'American Gods'
boys = Book.create! :title => 'Anansi Boys'
grave = Book.create! :title => 'The Graveyard Book'
gods.update_column :created_at, 5.days.ago
boys.update_column :created_at, 3.days.ago
grave.update_column :created_at, 1.day.ago
index
expect(Book.search(:with => {:created_at => 6.days.ago..2.days.ago}).to_a).
to eq([gods, boys])
end
it "limits results by exclusive filters on single values" do
pancakes = Article.create! :title => 'Pancakes', :published => true
waffles = Article.create! :title => 'Waffles', :published => false
index
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
index
expect(Book.search(:without => {:year => [2001, 2005]}).to_a).to eq([grave])
end
it "limits results by ranged filters on timestamp MVAs" do
pancakes = Article.create :title => 'Pancakes'
waffles = Article.create :title => 'Waffles'
food = Tag.create :name => 'food'
flat = Tag.create :name => 'flat'
Tagging.create(:tag => food, :article => pancakes).
update_column :created_at, 5.days.ago
Tagging.create :tag => flat, :article => pancakes
Tagging.create(:tag => food, :article => waffles).
update_column :created_at, 3.days.ago
index
expect(Article.search(
:with => {:taggings_at => 1.days.ago..1.day.from_now}
).to_a).to eq([pancakes])
end
it "takes into account local timezones for timestamps" do
pancakes = Article.create :title => 'Pancakes'
waffles = Article.create :title => 'Waffles'
food = Tag.create :name => 'food'
flat = Tag.create :name => 'flat'
Tagging.create(:tag => food, :article => pancakes).
update_column :created_at, 5.minutes.ago
Tagging.create :tag => flat, :article => pancakes
Tagging.create(:tag => food, :article => waffles).
update_column :created_at, 3.minute.ago
index
expect(Article.search(
:with => {:taggings_at => 2.minutes.ago..Time.zone.now}
).to_a).to eq([pancakes])
end
it "limits results with MVAs having all of the given values" do
pancakes = Article.create :title => 'Pancakes'
waffles = Article.create :title => 'Waffles'
food = Tag.create :name => 'food'
flat = Tag.create :name => 'flat'
Tagging.create :tag => food, :article => pancakes
Tagging.create :tag => flat, :article => pancakes
Tagging.create :tag => food, :article => waffles
index
articles = Article.search :with_all => {:tag_ids => [food.id, flat.id]}
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.
skip "SphinxQL doesn't yet support OR in its WHERE clause"
pancakes = Article.create :title => 'Pancakes'
waffles = Article.create :title => 'Waffles'
food = Tag.create :name => 'food'
flat = Tag.create :name => 'flat'
Tagging.create :tag => food, :article => pancakes
Tagging.create :tag => flat, :article => pancakes
Tagging.create :tag => food, :article => waffles
index
articles = Article.search :without_all => {:tag_ids => [food.id, flat.id]}
expect(articles.to_a).to eq([waffles])
end
it "limits results on real-time indices with multi-value integer attributes" do
pancakes = Product.create :name => 'Pancakes'
waffles = Product.create :name => 'Waffles'
food = Category.create :name => 'food'
flat = Category.create :name => 'flat'
pancakes.categories << food
pancakes.categories << flat
waffles.categories << food
products = Product.search :with => {:category_ids => [flat.id]}
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 eq([pancakes, waffles])
end if JSONColumn.call
end
thinking-sphinx-4.1.0/spec/acceptance/searching_with_sti_spec.rb 0000664 0000000 0000000 00000004341 13411321301 0025061 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Searching across STI models', :live => true do
it "returns super- and sub-class results" do
platypus = Animal.create :name => 'Platypus'
duck = Bird.create :name => 'Duck'
index
expect(Animal.search(:indices => ['animal_core']).to_a).to eq([platypus, duck])
end
it "limits results based on subclasses" do
platypus = Animal.create :name => 'Platypus'
duck = Bird.create :name => 'Duck'
index
expect(Bird.search(:indices => ['animal_core']).to_a).to eq([duck])
end
it "returns results for deeper subclasses when searching on their parents" do
platypus = Animal.create :name => 'Platypus'
duck = Bird.create :name => 'Duck'
emu = FlightlessBird.create :name => 'Emu'
index
expect(Bird.search(:indices => ['animal_core']).to_a).to eq([duck, emu])
end
it "returns results for deeper subclasses" do
platypus = Animal.create :name => 'Platypus'
duck = Bird.create :name => 'Duck'
emu = FlightlessBird.create :name => 'Emu'
index
expect(FlightlessBird.search(:indices => ['animal_core']).to_a).to eq([emu])
end
it "filters out sibling subclasses" do
platypus = Animal.create :name => 'Platypus'
duck = Bird.create :name => 'Duck'
otter = Mammal.create :name => 'Otter'
index
expect(Bird.search(:indices => ['animal_core']).to_a).to eq([duck])
end
it "obeys :classes if supplied" do
platypus = Animal.create :name => 'Platypus'
duck = Bird.create :name => 'Duck'
emu = FlightlessBird.create :name => 'Emu'
index
expect(Bird.search(
:indices => ['animal_core'],
:skip_sti => true,
:classes => [Bird, FlightlessBird]
).to_a).to eq([duck, emu])
end
it 'finds root objects when type is blank' do
animal = Animal.create :name => 'Animal', type: ''
index
expect(Animal.search(:indices => ['animal_core']).to_a).to eq([animal])
end
it 'allows for indices on mid-hierarchy classes' do
duck = Bird.create :name => 'Duck'
emu = FlightlessBird.create :name => 'Emu'
index
expect(Bird.search(:indices => ['bird_core']).to_a).to eq([duck, emu])
end
end
thinking-sphinx-4.1.0/spec/acceptance/searching_within_a_model_spec.rb 0000664 0000000 0000000 00000005330 13411321301 0026210 0 ustar 00root root 0000000 0000000 # encoding: UTF-8
# frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Searching within a model', :live => true do
it "returns results" do
article = Article.create! :title => 'Pancakes'
index
expect(Article.search.first).to eq(article)
end
it "returns results matching the given query" do
pancakes = Article.create! :title => 'Pancakes'
waffles = Article.create! :title => 'Waffles'
index
articles = Article.search 'pancakes'
expect(articles).to include(pancakes)
expect(articles).not_to include(waffles)
end
it "handles unicode characters" do
istanbul = City.create! :name => 'İstanbul'
index
expect(City.search('İstanbul').to_a).to eq([istanbul])
end
it "will star provided queries on request" do
article = Article.create! :title => 'Pancakes'
index
expect(Article.search('cake', :star => true).first).to eq(article)
end
it "allows for searching on specific indices" do
article = Article.create :title => 'Pancakes'
index
articles = Article.search('pancake', :indices => ['stemmed_article_core'])
expect(articles.to_a).to eq([article])
end
it "allows for searching on distributed indices" do
article = Article.create :title => 'Pancakes'
index
articles = Article.search('pancake', :indices => ['article'])
expect(articles.to_a).to eq([article])
end
it "can search on namespaced models" do
person = Admin::Person.create :name => 'James Bond'
index
expect(Admin::Person.search('Bond').to_a).to eq([person])
end
it "raises an error if searching through an ActiveRecord scope" do
expect {
City.ordered.search
}.to raise_error(ThinkingSphinx::MixedScopesError)
end
it "does not raise an error when searching with a default ActiveRecord scope" do
expect {
User.search
}.not_to raise_error
end
it "raises an error when searching with default and applied AR scopes" do
expect {
User.recent.search
}.to raise_error(ThinkingSphinx::MixedScopesError)
end
it "raises an error if the model has no indices defined" do
expect {
Category.search.to_a
}.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
end
describe 'Searching within a model with a realtime index', :live => true do
it "returns results" do
product = Product.create! :name => 'Widget'
expect(Product.search.first).to eq(product)
end
end
thinking-sphinx-4.1.0/spec/acceptance/sorting_search_results_spec.rb 0000664 0000000 0000000 00000003460 13411321301 0026000 0 ustar 00root root 0000000 0000000 # 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
index
expect(Book.search(:order => '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
index
expect(Book.search(:order => :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
index
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'
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
index
expect(Book.search(
:select => '*, year MOD 2004 as mod_year', :order => 'mod_year ASC'
).to_a).to eq([boys, grave, gods])
end
end
thinking-sphinx-4.1.0/spec/acceptance/spec_helper.rb 0000664 0000000 0000000 00000000241 13411321301 0022456 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
root = File.expand_path File.dirname(__FILE__)
Dir["#{root}/support/**/*.rb"].each { |file| require file }
thinking-sphinx-4.1.0/spec/acceptance/specifying_sql_spec.rb 0000664 0000000 0000000 00000044162 13411321301 0024230 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'specifying SQL for index definitions' do
it "renders the SQL with the join" do
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
index.definition_block = Proc.new {
indexes title
join user
}
index.render
expect(index.sources.first.sql_query).to match(/LEFT OUTER JOIN .users./)
end
it "handles deep joins" do
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
index.definition_block = Proc.new {
indexes title
join user.articles
}
index.render
query = index.sources.first.sql_query
expect(query).to match(/LEFT OUTER JOIN .users./)
expect(query).to match(/LEFT OUTER JOIN .articles./)
end
it "handles has-many :through joins" do
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
index.definition_block = Proc.new {
indexes tags.name
}
index.render
query = index.sources.first.sql_query
expect(query).to match(/LEFT OUTER JOIN .taggings./)
expect(query).to match(/LEFT OUTER JOIN .tags./)
end
it "handles custom join SQL statements" do
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
index.definition_block = Proc.new {
indexes title
join "INNER JOIN foo ON foo.x = bar.y"
}
index.render
query = index.sources.first.sql_query
expect(query).to match(/INNER JOIN foo ON foo.x = bar.y/)
end
it "handles GROUP BY clauses" do
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
index.definition_block = Proc.new {
indexes title
group_by 'lat'
}
index.render
query = index.sources.first.sql_query
expect(query).to match(/GROUP BY .articles.\..id., .?articles.?\..title., .?articles.?\..id., lat/)
end
it "handles WHERE clauses" do
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
index.definition_block = Proc.new {
indexes title
where "title != 'secret'"
}
index.render
query = index.sources.first.sql_query
expect(query).to match(/WHERE .+title != 'secret'.+ GROUP BY/)
end
it "handles manual MVA declarations" do
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
index.definition_block = Proc.new {
indexes title
has "taggings.tag_ids", :as => :tag_ids, :type => :integer,
:multi => true
}
index.render
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
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
index.definition_block = Proc.new {
indexes title
where sanitize_sql(["title != ?", 'secret'])
}
index.render
query = index.sources.first.sql_query
expect(query).to match(/WHERE .+title != 'secret'.+ GROUP BY/)
end
it "escapes new lines in SQL snippets" do
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
index.definition_block = Proc.new {
indexes title
has <<-SQL, as: :custom_attribute, type: :integer
ARRAY_AGG(
CONCAT(
something
)
)
SQL
}
index.render
query = index.sources.first.sql_query
expect(query).to match(/\\\n/)
end
it "joins each polymorphic relation" do
index = ThinkingSphinx::ActiveRecord::Index.new(:event)
index.definition_block = Proc.new {
indexes eventable.title, :as => :title
polymorphs eventable, :to => %w(Article Book)
}
index.render
query = index.sources.first.sql_query
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)
index.definition_block = Proc.new {
indexes eventable.title, :as => :title
polymorphs eventable, :to => %w(Article User)
}
index.render
query = index.sources.first.sql_query
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)
index.definition_block = Proc.new {
indexes eventable.user.name, :as => :user_name
polymorphs eventable, :to => %w(Article Book)
}
index.render
query = index.sources.first.sql_query
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
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 }
it "generates an appropriate SQL query for an MVA" do
index.definition_block = Proc.new {
indexes title
has taggings.tag_id, :as => :tag_ids, :source => :query
}
index.render
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/tag_ids/]
}
declaration, query = attribute.split(/;\s+/)
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 "generates a SQL query with joins when appropriate for MVAs" do
index.definition_block = Proc.new {
indexes title
has taggings.tag.id, :as => :tag_ids, :source => :query
}
index.render
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/tag_ids/]
}
declaration, query = attribute.split(/;\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
index.definition_block = Proc.new {
indexes title
has tags.id, :as => :tag_ids, :source => :query
}
index.render
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/tag_ids/]
}
declaration, query = attribute.split(/;\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
index = ThinkingSphinx::ActiveRecord::Index.new(:user)
index.definition_block = Proc.new {
indexes name
has articles.tags.id, :as => :tag_ids, :source => :query
}
index.render
source = index.sources.first
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/tag_ids/]
}
declaration, query = attribute.split(/;\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
index = ThinkingSphinx::ActiveRecord::Index.new(:book)
index.definition_block = Proc.new {
indexes title
has genres.id, :as => :genre_ids, :source => :query
}
index.render
source = index.sources.first
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/genre_ids/]
}
declaration, query = attribute.split(/;\s+/)
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 {
indexes title
has taggings.tag_id, :as => :tag_ids, :source => :ranged_query
}
index.render
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/tag_ids/]
}
declaration, query, range = attribute.split(/;\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
index.definition_block = Proc.new {
indexes title
has taggings.tag.id, :as => :tag_ids, :source => :ranged_query
}
index.render
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/tag_ids/]
}
declaration, query, range = attribute.split(/;\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
index = ThinkingSphinx::ActiveRecord::Index.new(:book)
index.definition_block = Proc.new {
indexes title
has genres.id, :as => :genre_ids, :source => :ranged_query
}
index.render
source = index.sources.first
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/genre_ids/]
}
declaration, query, range = attribute.split(/;\s+/)
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 {
indexes title
has 'My Custom SQL Query', :as => :tag_ids, :source => :query,
:type => :integer, :multi => true
}
index.render
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/tag_ids/]
}
declaration, query = attribute.split(/;\s+/)
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
index.definition_block = Proc.new {
indexes title
has 'My Custom SQL Query; And a Range', :as => :tag_ids,
:source => :ranged_query, :type => :integer, :multi => true
}
index.render
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/tag_ids/]
}
declaration, query, range = attribute.split(/;\s+/)
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
index.definition_block = Proc.new {
indexes title
has <<-SQL, :as => :tag_ids, :source => :query, :type => :integer, :multi => true
My Custom
SQL Query
SQL
}
index.render
attribute = source.sql_attr_multi.detect { |attribute|
attribute[/tag_ids/]
}
declaration, query = attribute.split(/;\s+/)
expect(declaration).to eq('uint tag_ids from query')
expect(query).to eq("My Custom\\\nSQL Query")
end
end
describe 'separate queries for field' do
let(:index) { ThinkingSphinx::ActiveRecord::Index.new(:article) }
let(:count) { ThinkingSphinx::Configuration.instance.indices.count }
let(:source) { index.sources.first }
it "generates a SQL query with joins when appropriate for MVF" do
index.definition_block = Proc.new {
indexes taggings.tag.name, :as => :tags, :source => :query
}
index.render
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\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
index.definition_block = Proc.new {
indexes tags.name, :as => :tags, :source => :query
}
index.render
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\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
index = ThinkingSphinx::ActiveRecord::Index.new(:user)
index.definition_block = Proc.new {
indexes articles.tags.name, :as => :tags, :source => :query
}
index.render
source = index.sources.first
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\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
index.definition_block = Proc.new {
indexes taggings.tag.name, :as => :tags, :source => :ranged_query
}
index.render
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query, range = field.split(/;\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 "respects custom SQL snippets as the query value" do
index.definition_block = Proc.new {
indexes 'My Custom SQL Query', :as => :tags, :source => :query
}
index.render
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\s+/)
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
index.definition_block = Proc.new {
indexes 'My Custom SQL Query; And a Range', :as => :tags,
:source => :ranged_query
}
index.render
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query, range = field.split(/;\s+/)
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
index.definition_block = Proc.new {
indexes <<-SQL, :as => :tags, :source => :query
My Custom
SQL Query
SQL
}
index.render
field = source.sql_joined_field.detect { |field| field[/tags/] }
declaration, query = field.split(/;\s+/)
expect(declaration).to eq('tags from query')
expect(query).to eq("My Custom\\\nSQL Query")
end
end
thinking-sphinx-4.1.0/spec/acceptance/sphinx_scopes_spec.rb 0000664 0000000 0000000 00000005164 13411321301 0024075 0 ustar 00root root 0000000 0000000 # 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
index
expect(Book.by_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
index
expect(Book.by_query_and_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
index
expect(Book.by_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
index
expect(Book.by_year(2001).by_query_and_year('Graveyard', 2009).to_a).
to eq([grave])
end
it "allows further search calls on scopes" do
gaiman = Book.create! :title => 'American Gods'
pratchett = Book.create! :title => 'Small Gods'
index
expect(Book.by_query('Gods').search('Small').to_a).to eq([pratchett])
end
it "allows facet calls on scopes" do
Book.create! :title => 'American Gods', :author => 'Neil Gaiman'
Book.create! :title => 'Anansi Boys', :author => 'Neil Gaiman'
Book.create! :title => 'Small Gods', :author => 'Terry Pratchett'
index
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
Book.create! :title => 'American Gods'
Book.create! :title => 'Anansi Boys'
Book.create! :title => 'Small Gods'
Book.create! :title => 'Night Watch'
index
expect(Book.by_query('gods').count).to eq(2)
end
it 'raises an exception when trying to modify a populated request' do
request = Book.by_query('gods')
request.count
expect { request.search('foo') }.to raise_error(
ThinkingSphinx::PopulatedResultsError
)
end
end
thinking-sphinx-4.1.0/spec/acceptance/sql_deltas_spec.rb 0000664 0000000 0000000 00000003272 13411321301 0023341 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'SQL delta indexing', :live => true do
it "automatically indexes new records" do
guards = Book.create(
:title => 'Guards! Guards!', :author => 'Terry Pratchett'
)
index
expect(Book.search('Terry Pratchett').to_a).to eq([guards])
men = Book.create(
:title => 'Men At Arms', :author => 'Terry Pratchett'
)
sleep 0.25
expect(Book.search('Terry Pratchett').to_a).to eq([guards, men])
end
it "automatically indexes updated records" do
book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett'
index
expect(Book.search('Harry').to_a).to eq([book])
book.reload.update_attributes(:author => 'Terry Pratchett')
sleep 0.25
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
expect(Book.search('Harry').to_a).to eq([book])
book.reload.update_attributes(: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])
album.reload.update_attributes(:artist => 'The Whitlams')
sleep 0.25
expect(Book.search('Whitloms')).to be_empty
end
it "automatically indexes new records of subclasses" do
book = Hardcover.create(
:title => 'American Gods', :author => 'Neil Gaiman'
)
sleep 0.25
expect(Book.search('Gaiman').to_a).to eq([book])
end
end
thinking-sphinx-4.1.0/spec/acceptance/support/ 0000775 0000000 0000000 00000000000 13411321301 0021357 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/acceptance/support/database_cleaner.rb 0000664 0000000 0000000 00000000425 13411321301 0025142 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.after(:each) do |example|
if example.example_group_instance.class.metadata[:live]
DatabaseCleaner.clean
end
end
end
thinking-sphinx-4.1.0/spec/acceptance/support/sphinx_controller.rb 0000664 0000000 0000000 00000002454 13411321301 0025465 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class SphinxController
def initialize
config.searchd.mysql41 = 9307
end
def setup
FileUtils.mkdir_p config.indices_location
config.controller.bin_path = ENV['SPHINX_BIN'] || ''
config.render_to_file && index
ThinkingSphinx::Configuration.reset
ActiveSupport::Dependencies.loaded.each do |path|
$LOADED_FEATURES.delete "#{path}.rb"
end
ActiveSupport::Dependencies.clear
config.searchd.mysql41 = 9307
config.settings['quiet_deltas'] = true
config.settings['attribute_updates'] = true
config.controller.bin_path = ENV['SPHINX_BIN'] || ''
end
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
while config.controller.running? do
config.controller.stop
sleep(0.1)
end
end
def index(*indices)
ThinkingSphinx::Commander.call :index_sql, config, :indices => indices
end
def merge
ThinkingSphinx::Commander.call(:merge_and_update, config, {})
end
private
def config
ThinkingSphinx::Configuration.instance
end
end
thinking-sphinx-4.1.0/spec/acceptance/support/sphinx_helpers.rb 0000664 0000000 0000000 00000001545 13411321301 0024744 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module SphinxHelpers
def sphinx
@sphinx ||= SphinxController.new
end
def index(*indices)
sleep 0.5 if ENV['TRAVIS']
yield if block_given?
sphinx.index *indices
sleep 0.25
sleep 0.5 if ENV['TRAVIS']
end
def merge
sleep 0.5 if ENV['TRAVIS']
sleep 0.5
sphinx.merge
sleep 1.5
sleep 0.5 if ENV['TRAVIS']
end
end
RSpec.configure do |config|
config.include SphinxHelpers
config.before :all do |group|
FileUtils.rm_rf ThinkingSphinx::Configuration.instance.indices_location
FileUtils.rm_rf ThinkingSphinx::Configuration.instance.searchd.binlog_path
sphinx.setup && sphinx.start if group.class.metadata[:live]
end
config.after :all do |group|
sphinx.stop if group.class.metadata[:live]
end
config.after :suite do
SphinxController.new.stop
end
end
thinking-sphinx-4.1.0/spec/acceptance/suspended_deltas_spec.rb 0000664 0000000 0000000 00000002774 13411321301 0024542 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'acceptance/spec_helper'
describe 'Suspend deltas for a given action', :live => true do
it "does not update the delta indices until after the block is finished" do
book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett'
index
expect(Book.search('Harry').to_a).to eq([book])
ThinkingSphinx::Deltas.suspend :book do
book.reload.update_attributes(:author => 'Terry Pratchett')
sleep 0.25
expect(Book.search('Terry').to_a).to eq([])
end
sleep 0.25
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
expect(Book.search('Harry').to_a).to eq([book])
ThinkingSphinx::Deltas.suspend :book do
book.reload.update_attributes(:author => 'Terry Pratchett')
sleep 0.25
expect(Book.search('Terry').to_a).to eq([])
end
sleep 0.25
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
expect(Book.search('Harry').to_a).to eq([book])
ThinkingSphinx::Deltas.suspend_and_update :book do
book.reload.update_attributes(:author => 'Terry Pratchett')
sleep 0.25
expect(Book.search('Terry').to_a).to eq([])
end
sleep 0.25
expect(Book.search('Harry').to_a).to be_empty
end
end
thinking-sphinx-4.1.0/spec/fixtures/ 0000775 0000000 0000000 00000000000 13411321301 0017426 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/fixtures/database.yml 0000664 0000000 0000000 00000000103 13411321301 0021707 0 ustar 00root root 0000000 0000000 username: root
password:
host: localhost
database: thinking_sphinx
thinking-sphinx-4.1.0/spec/internal/ 0000775 0000000 0000000 00000000000 13411321301 0017371 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/internal/app/ 0000775 0000000 0000000 00000000000 13411321301 0020151 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/internal/app/indices/ 0000775 0000000 0000000 00000000000 13411321301 0021567 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/internal/app/indices/admin_person_index.rb 0000664 0000000 0000000 00000000356 13411321301 0025765 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/internal/app/indices/album_index.rb 0000664 0000000 0000000 00000000462 13411321301 0024405 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/internal/app/indices/animal_index.rb 0000664 0000000 0000000 00000000163 13411321301 0024544 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
ThinkingSphinx::Index.define :animal, :with => :active_record do
indexes name
end
thinking-sphinx-4.1.0/spec/internal/app/indices/article_index.rb 0000664 0000000 0000000 00000001334 13411321301 0024727 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
ThinkingSphinx::Index.define :article, :with => :active_record do
indexes title, content
indexes user.name, :as => :user
indexes user.articles.title, :as => :related_titles
has published, user_id
has taggings.tag_id, :as => :tag_ids, :source => :query
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,
:name => 'stemmed_article' do
indexes title
has published, user_id
has taggings.tag_id, :as => :tag_ids
has taggings.created_at, :as => :taggings_at, :type => :timestamp
set_property :morphology => 'stem_en'
end
thinking-sphinx-4.1.0/spec/internal/app/indices/bird_index.rb 0000664 0000000 0000000 00000000200 13411321301 0024213 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
FlightlessBird
ThinkingSphinx::Index.define :bird, :with => :active_record do
indexes name
end
thinking-sphinx-4.1.0/spec/internal/app/indices/book_index.rb 0000664 0000000 0000000 00000000464 13411321301 0024241 0 ustar 00root root 0000000 0000000 # 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
has created_at, :type => :timestamp
end
thinking-sphinx-4.1.0/spec/internal/app/indices/car_index.rb 0000664 0000000 0000000 00000000251 13411321301 0024046 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
ThinkingSphinx::Index.define :car, :with => :real_time do
indexes name, :sortable => true
has manufacturer_id, :type => :integer
end
thinking-sphinx-4.1.0/spec/internal/app/indices/city_index.rb 0000664 0000000 0000000 00000000416 13411321301 0024254 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
ThinkingSphinx::Index.define :city, :with => :active_record do
indexes name
has lat, lng
set_property :charset_table => '0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F, U+0130'
set_property :utf8? => true
end
thinking-sphinx-4.1.0/spec/internal/app/indices/product_index.rb 0000664 0000000 0000000 00000001152 13411321301 0024762 0 ustar 00root root 0000000 0000000 # 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?
ThinkingSphinx::Index.define(:product,
:name => :product_two, :offset_as => :product_two, :with => :real_time
) do
indexes name, prefixes: true
set_property min_prefix_len: 1, dict: :keywords
scope do
multi_schema.switch :thinking_sphinx
User
end
end
multi_schema.switch :public
end
thinking-sphinx-4.1.0/spec/internal/app/indices/tee_index.rb 0000664 0000000 0000000 00000000225 13411321301 0024057 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
ThinkingSphinx::Index.define :tee, :with => :active_record do
index colour.name
has colour_id, :facet => true
end
thinking-sphinx-4.1.0/spec/internal/app/indices/user_index.rb 0000664 0000000 0000000 00000000334 13411321301 0024261 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
ThinkingSphinx::Index.define :user, :with => :active_record do
indexes name
has articles.taggings.tag_id, :as => :tag_ids, :facet => true
set_property :big_document_ids => true
end
thinking-sphinx-4.1.0/spec/internal/app/models/ 0000775 0000000 0000000 00000000000 13411321301 0021434 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/internal/app/models/admin/ 0000775 0000000 0000000 00000000000 13411321301 0022524 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/internal/app/models/admin/person.rb 0000664 0000000 0000000 00000000263 13411321301 0024360 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Admin::Person < ActiveRecord::Base
self.table_name = 'admin_people'
after_save ThinkingSphinx::RealTime.callback_for('admin/person')
end
thinking-sphinx-4.1.0/spec/internal/app/models/album.rb 0000664 0000000 0000000 00000001041 13411321301 0023055 0 ustar 00root root 0000000 0000000 # 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
after_save ThinkingSphinx::RealTime.callback_for(:album)
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
thinking-sphinx-4.1.0/spec/internal/app/models/animal.rb 0000664 0000000 0000000 00000000105 13411321301 0023216 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Animal < ActiveRecord::Base
end
thinking-sphinx-4.1.0/spec/internal/app/models/article.rb 0000664 0000000 0000000 00000000226 13411321301 0023404 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Article < ActiveRecord::Base
belongs_to :user
has_many :taggings
has_many :tags, :through => :taggings
end
thinking-sphinx-4.1.0/spec/internal/app/models/bird.rb 0000664 0000000 0000000 00000000067 13411321301 0022704 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Bird < Animal
end
thinking-sphinx-4.1.0/spec/internal/app/models/book.rb 0000664 0000000 0000000 00000000622 13411321301 0022713 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Book < ActiveRecord::Base
include ThinkingSphinx::Scopes
has_and_belongs_to_many :genres
sphinx_scope(:by_query) { |query| query }
sphinx_scope(:by_year) do |year|
{:with => {:year => year}}
end
sphinx_scope(:by_query_and_year) do |query, year|
[query, {:with => {:year =>year}}]
end
sphinx_scope(:ordered) { {:order => 'year DESC'} }
end
thinking-sphinx-4.1.0/spec/internal/app/models/car.rb 0000664 0000000 0000000 00000000227 13411321301 0022527 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Car < ActiveRecord::Base
belongs_to :manufacturer
after_save ThinkingSphinx::RealTime.callback_for(:car)
end
thinking-sphinx-4.1.0/spec/internal/app/models/categorisation.rb 0000664 0000000 0000000 00000000444 13411321301 0024776 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Categorisation < ActiveRecord::Base
belongs_to :category
belongs_to :product
after_commit :update_product
private
def update_product
product.reload
ThinkingSphinx::RealTime.callback_for(:product, [:product]).after_save self
end
end
thinking-sphinx-4.1.0/spec/internal/app/models/category.rb 0000664 0000000 0000000 00000000226 13411321301 0023576 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Category < ActiveRecord::Base
has_many :categorisations
has_many :products, :through => :categorisations
end
thinking-sphinx-4.1.0/spec/internal/app/models/city.rb 0000664 0000000 0000000 00000000155 13411321301 0022732 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class City < ActiveRecord::Base
scope :ordered, lambda { order(:name) }
end
thinking-sphinx-4.1.0/spec/internal/app/models/colour.rb 0000664 0000000 0000000 00000000126 13411321301 0023263 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Colour < ActiveRecord::Base
has_many :tees
end
thinking-sphinx-4.1.0/spec/internal/app/models/event.rb 0000664 0000000 0000000 00000000162 13411321301 0023101 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Event < ActiveRecord::Base
belongs_to :eventable, :polymorphic => true
end
thinking-sphinx-4.1.0/spec/internal/app/models/flightless_bird.rb 0000664 0000000 0000000 00000000077 13411321301 0025131 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class FlightlessBird < Bird
end
thinking-sphinx-4.1.0/spec/internal/app/models/genre.rb 0000664 0000000 0000000 00000000110 13411321301 0023051 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Genre < ActiveRecord::Base
#
end
thinking-sphinx-4.1.0/spec/internal/app/models/hardcover.rb 0000664 0000000 0000000 00000000076 13411321301 0023741 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Hardcover < Book
#
end
thinking-sphinx-4.1.0/spec/internal/app/models/mammal.rb 0000664 0000000 0000000 00000000071 13411321301 0023223 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Mammal < Animal
end
thinking-sphinx-4.1.0/spec/internal/app/models/manufacturer.rb 0000664 0000000 0000000 00000000134 13411321301 0024453 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Manufacturer < ActiveRecord::Base
has_many :cars
end
thinking-sphinx-4.1.0/spec/internal/app/models/product.rb 0000664 0000000 0000000 00000000325 13411321301 0023441 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Product < ActiveRecord::Base
has_many :categorisations
has_many :categories, :through => :categorisations
after_save ThinkingSphinx::RealTime.callback_for(:product)
end
thinking-sphinx-4.1.0/spec/internal/app/models/tag.rb 0000664 0000000 0000000 00000000203 13411321301 0022527 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Tag < ActiveRecord::Base
has_many :taggings
has_many :articles, :through => :taggings
end
thinking-sphinx-4.1.0/spec/internal/app/models/tagging.rb 0000664 0000000 0000000 00000000156 13411321301 0023403 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :article
end
thinking-sphinx-4.1.0/spec/internal/app/models/tee.rb 0000664 0000000 0000000 00000000127 13411321301 0022536 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Tee < ActiveRecord::Base
belongs_to :colour
end
thinking-sphinx-4.1.0/spec/internal/app/models/tweet.rb 0000664 0000000 0000000 00000000135 13411321301 0023110 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class Tweet < ActiveRecord::Base
self.primary_key = :id
end
thinking-sphinx-4.1.0/spec/internal/app/models/user.rb 0000664 0000000 0000000 00000000270 13411321301 0022736 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class User < ActiveRecord::Base
has_many :articles
default_scope { order(:id) }
scope :recent, lambda { where('created_at > ?', 1.week.ago) }
end
thinking-sphinx-4.1.0/spec/internal/config/ 0000775 0000000 0000000 00000000000 13411321301 0020636 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/internal/config/database.yml 0000664 0000000 0000000 00000000324 13411321301 0023124 0 ustar 00root root 0000000 0000000 test:
adapter: <%= ENV['DATABASE'] || 'mysql2' %>
database: thinking_sphinx
username: <%= ENV['DATABASE'] == 'postgresql' ? ENV['USER'] : 'root' %>
min_messages: warning
encoding: utf8
thinking-sphinx-4.1.0/spec/internal/db/ 0000775 0000000 0000000 00000000000 13411321301 0017756 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/internal/db/schema.rb 0000664 0000000 0000000 00000004757 13411321301 0021560 0 ustar 00root root 0000000 0000000 # 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
end
create_table(:articles, :force => true) do |t|
t.string :title
t.text :content
t.boolean :published
t.integer :user_id
t.timestamps null: false
end
create_table(:manufacturers, :force => true) do |t|
t.string :name
end
create_table(:cars, :force => true) do |t|
t.integer :manufacturer_id
t.string :name
end
create_table(:books, :force => true) do |t|
t.string :title
t.string :author
t.integer :year
t.string :blurb_file
t.boolean :delta, :default => true, :null => false
t.string :type, :default => 'Book', :null => false
t.timestamps null: false
end
create_table(:books_genres, :force => true, :id => false) do |t|
t.integer :book_id
t.integer :genre_id
end
create_table(:categories, :force => true) do |t|
t.string :name
end
create_table(:categorisations, :force => true) do |t|
t.integer :category_id
t.integer :product_id
end
create_table(:cities, :force => true) do |t|
t.string :name
t.float :lat
t.float :lng
end
create_table(:colours, :force => true) do |t|
t.string :name
t.timestamps null: false
end
create_table(:events, :force => true) do |t|
t.string :eventable_type
t.integer :eventable_id
end
create_table(:genres, :force => true) do |t|
t.string :name
end
create_table(:products, :force => true) do |t|
t.string :name
t.json :options if ::JSONColumn.call
end
create_table(:taggings, :force => true) do |t|
t.integer :tag_id
t.integer :article_id
t.timestamps null: false
end
create_table(:tags, :force => true) do |t|
t.string :name
t.timestamps null: false
end
create_table(:tees, :force => true) do |t|
t.integer :colour_id
t.timestamps null: false
end
create_table(:tweets, :force => true, :id => false) do |t|
t.column :id, :bigint, :null => false
t.string :text
t.timestamps null: false
end
create_table(:users, :force => true) do |t|
t.string :name
t.timestamps null: false
end
end
thinking-sphinx-4.1.0/spec/internal/tmp/ 0000775 0000000 0000000 00000000000 13411321301 0020171 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/internal/tmp/.gitkeep 0000664 0000000 0000000 00000000000 13411321301 0021610 0 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/spec_helper.rb 0000664 0000000 0000000 00000001227 13411321301 0020375 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'rubygems'
require 'bundler'
Bundler.require :default, :development
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
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
thinking-sphinx-4.1.0/spec/support/ 0000775 0000000 0000000 00000000000 13411321301 0017271 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/support/json_column.rb 0000664 0000000 0000000 00000001371 13411321301 0022146 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/support/multi_schema.rb 0000664 0000000 0000000 00000001712 13411321301 0022271 0 ustar 00root root 0000000 0000000 # 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
load Rails.root.join('db', 'schema.rb')
end
def current
connection.schema_search_path
end
def switch(schema_name)
connection.schema_search_path = %Q{"#{schema_name}"}
connection.clear_query_cache
end
private
def connection
ActiveRecord::Base.connection
end
class IndexSet < ThinkingSphinx::IndexSet
private
def indices
return super if index_names.any?
prefixed = !multi_schema.current.include?('public')
super.select { |index|
prefixed ? index.name[/_two_core$/] : index.name[/_two_core$/].nil?
}
end
def multi_schema
@multi_schema ||= MultiSchema.new
end
end
end
thinking-sphinx-4.1.0/spec/support/mysql.rb 0000664 0000000 0000000 00000001342 13411321301 0020763 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/support/sphinx_yaml_helpers.rb 0000664 0000000 0000000 00000000376 13411321301 0023701 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module SphinxYamlHelpers
def write_configuration(hash)
allow(File).to receive_messages :read => {'test' => hash}.to_yaml, :exists? => true
end
end
RSpec.configure do |config|
config.include SphinxYamlHelpers
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/ 0000775 0000000 0000000 00000000000 13411321301 0020761 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/ 0000775 0000000 0000000 00000000000 13411321301 0023572 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/association_spec.rb 0000664 0000000 0000000 00000000633 13411321301 0027447 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Association do
let(:association) { ThinkingSphinx::ActiveRecord::Association.new column }
let(:column) { double('column', :__stack => [:users], :__name => :post) }
describe '#stack' do
it "returns the column's stack and name" do
expect(association.stack).to eq([:users, :post])
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/attribute/ 0000775 0000000 0000000 00000000000 13411321301 0025575 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/attribute/type_spec.rb 0000664 0000000 0000000 00000010765 13411321301 0030126 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module ActiveRecord
class Attribute; end
end
end
require 'thinking_sphinx/errors'
require 'thinking_sphinx/active_record/attribute/type'
describe ThinkingSphinx::ActiveRecord::Attribute::Type do
let(:type) {
ThinkingSphinx::ActiveRecord::Attribute::Type.new attribute, model }
let(:attribute) { double('attribute', :columns => [column], :options => {}) }
let(:model) { double('model', :columns => [db_column]) }
let(:column) { double('column', :__name => :created_at, :string? => false,
:__stack => []) }
let(:db_column) { double('column', :name => 'created_at',
:type => :integer) }
describe '#multi?' do
let(:association) { double('association', :klass => double) }
before :each do
column.__stack << :foo
allow(model).to receive(:reflect_on_association).and_return(association)
end
it "returns true if there are has_many associations" do
allow(association).to receive(:macro).and_return(:has_many)
expect(type).to be_multi
end
it "returns true if there are has_and_belongs_to_many associations" do
allow(association).to receive(:macro).and_return(:has_and_belongs_to_many)
expect(type).to be_multi
end
it "returns false if there are no associations" do
column.__stack.clear
expect(type).not_to be_multi
end
it "returns false if there are only belongs_to associations" do
allow(association).to receive(:macro).and_return(:belongs_to)
expect(type).not_to be_multi
end
it "returns false if there are only has_one associations" do
allow(association).to receive(:macro).and_return(:has_one)
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)
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
expect(type).to be_multi
end
end
describe '#type' do
it "returns the type option provided" do
attribute.options[:type] = :datetime
expect(type.type).to eq(:datetime)
end
it "detects integer types from the database" do
allow(db_column).to receive_messages(:type => :integer, :sql_type => 'integer(11)')
expect(type.type).to eq(:integer)
end
it "detects boolean types from the database" do
allow(db_column).to receive_messages(:type => :boolean)
expect(type.type).to eq(:boolean)
end
it "detects datetime types from the database as timestamps" do
allow(db_column).to receive_messages(:type => :datetime)
expect(type.type).to eq(:timestamp)
end
it "detects date types from the database as timestamps" do
allow(db_column).to receive_messages(:type => :date)
expect(type.type).to eq(:timestamp)
end
it "detects string types from the database" do
allow(db_column).to receive_messages(:type => :string)
expect(type.type).to eq(:string)
end
it "detects text types from the database as strings" do
allow(db_column).to receive_messages(:type => :text)
expect(type.type).to eq(:string)
end
it "detects float types from the database" do
allow(db_column).to receive_messages(:type => :float)
expect(type.type).to eq(:float)
end
it "detects decimal types from the database as floats" do
allow(db_column).to receive_messages(:type => :decimal)
expect(type.type).to eq(:float)
end
it "detects big ints as big ints" do
allow(db_column).to receive_messages :type => :bigint
expect(type.type).to eq(:bigint)
end
it "detects large integers as big ints" do
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
expect(type.type).to eq(:json)
end
it "respects provided type setting" do
attribute.options[:type] = :timestamp
expect(type.type).to eq(:timestamp)
end
it 'raises an error if the database column does not exist' do
model.columns.clear
expect { type.type }.to raise_error(ThinkingSphinx::MissingColumnError)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/base_spec.rb 0000664 0000000 0000000 00000007606 13411321301 0026054 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Base do
let(:model) {
Class.new(ActiveRecord::Base) do
include ThinkingSphinx::ActiveRecord::Base
def self.name; 'Model'; end
end
}
let(:sub_model) {
Class.new(model) do
def self.name; 'SubModel'; end
end
}
describe '.facets' do
it "returns a new search object" do
expect(model.facets).to be_a(ThinkingSphinx::FacetSearch)
end
it "passes through arguments to the search object" do
expect(model.facets('pancakes').query).to eq('pancakes')
end
it "scopes the search to a given model" do
expect(model.facets('pancakes').options[:classes]).to eq([model])
end
it "merges the :classes option with the model" do
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
allow(model).to receive_messages :default_sphinx_scope => :default,
:sphinx_scopes => {:default => Proc.new { {:order => :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
allow(model).to receive_messages :default_sphinx_scope => nil,
:default => {:order => :created_at}
expect(model.facets.options[:order]).to be_nil
end
end
describe '.search' do
let(:stack) { double('stack', :call => true) }
before :each do
stub_const 'ThinkingSphinx::Middlewares::DEFAULT', stack
end
it "returns a new search object" do
expect(model.search).to be_a(ThinkingSphinx::Search)
end
it "passes through arguments to the search object" do
expect(model.search('pancakes').query).to eq('pancakes')
end
it "scopes the search to a given model" do
expect(model.search('pancakes').options[:classes]).to eq([model])
end
it "passes through options to the search object" do
expect(model.search('pancakes', populate: true).
options[:populate]).to be_truthy
end
it "should automatically populate when :populate is set to true" do
expect(stack).to receive(:call).and_return(true)
model.search('pancakes', populate: true)
end
it "merges the :classes option with the model" do
expect(model.search('pancakes', :classes => [sub_model]).
options[:classes]).to eq([sub_model, model])
end
it "respects provided middleware" do
expect(model.search(:middleware => ThinkingSphinx::Middlewares::RAW_ONLY).
options[:middleware]).to eq(ThinkingSphinx::Middlewares::RAW_ONLY)
end
it "respects provided masks" do
expect(model.search(:masks => [ThinkingSphinx::Masks::PaginationMask]).
masks).to eq([ThinkingSphinx::Masks::PaginationMask])
end
it "applies the default scope if there is one" do
allow(model).to receive_messages :default_sphinx_scope => :default,
:sphinx_scopes => {:default => Proc.new { {:order => :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
allow(model).to receive_messages :default_sphinx_scope => nil,
:default => {:order => :created_at}
expect(model.search.options[:order]).to be_nil
end
end
describe '.search_count' do
let(:search) { double('search', :options => {}, :total_entries => 12,
:populated? => false) }
before :each do
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
expect(model.search_count).to eq(search.total_entries)
end
it "scopes the search to a given model" do
model.search_count
expect(search.options[:classes]).to eq([model])
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/callbacks/ 0000775 0000000 0000000 00000000000 13411321301 0025511 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb 0000664 0000000 0000000 00000007513 13411321301 0032317 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks do
let(:callbacks) {
ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks.new instance
}
let(:instance) { double('instance', :delta? => true) }
describe '.after_destroy' do
let(:callbacks) { double('callbacks', :after_destroy => 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_destroy(instance)
end
it "invokes after_destroy on the object" do
expect(callbacks).to receive(:after_destroy)
ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks.
after_destroy(instance)
end
end
describe '#after_destroy' do
let(:index_set) { double 'index set', :to_a => [index] }
let(:index) { double('index', :name => 'foo_core',
: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_destroy
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_destroy
end
it 'does nothing if callbacks are suspended' do
ThinkingSphinx::Callbacks.suspend!
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',
: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
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb 0000664 0000000 0000000 00000011617 13411321301 0032146 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks do
let(:callbacks) {
ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks.new instance
}
let(:instance) { double('instance', :delta? => true) }
let(:config) { double('config') }
let(:processor) {
double('processor', :toggled? => true, :index => true, :delete => true)
}
before :each do
allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
[:after_commit, :before_save].each do |callback|
describe ".#{callback}" do
let(:callbacks) { double('callbacks', callback => nil) }
before :each do
allow(ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks).
to receive_messages :new => callbacks
end
it "builds an object from the instance" do
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
expect(callbacks).to receive(callback)
ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks.
send(callback, instance)
end
end
end
describe '#after_commit' do
let(:index) {
double('index', :delta? => false, :delta_processor => processor,
:type => 'plain')
}
before :each do
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
expect(processor).not_to receive(:index)
callbacks.after_commit
end
it "does not delete the instance from any index" do
expect(processor).not_to receive(:delete)
callbacks.after_commit
end
end
context 'with delta indices' do
let(:core_index) { double('index', :delta? => false, :name => 'foo_core',
:delta_processor => processor, :type => 'plain') }
let(:delta_index) { double('index', :delta? => true, :name => 'foo_delta',
:delta_processor => processor, :type => 'plain') }
before :each do
allow(ThinkingSphinx::Deltas).to receive_messages :suspended? => false
allow(config).to receive_messages :index_set_class => double(
:new => [core_index, delta_index]
)
end
it "only indexes delta indices" do
expect(processor).to receive(:index).with(delta_index)
callbacks.after_commit
end
it "does not process delta indices when deltas are suspended" do
allow(ThinkingSphinx::Deltas).to receive_messages :suspended? => true
expect(processor).not_to receive(:index)
callbacks.after_commit
end
it "deletes the instance from the core index" do
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
allow(processor).to receive_messages :toggled? => false
expect(processor).not_to receive(:index)
callbacks.after_commit
end
it "does not delete if model's delta flag is not true" do
allow(processor).to receive_messages :toggled? => false
expect(processor).not_to receive(:delete)
callbacks.after_commit
end
it "does not delete when deltas are suspended" do
allow(ThinkingSphinx::Deltas).to receive_messages :suspended? => true
expect(processor).not_to receive(:delete)
callbacks.after_commit
end
end
end
describe '#before_save' do
let(:index) {
double('index', :delta? => true, :delta_processor => processor,
:type => 'plain')
}
before :each do
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
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
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
)
expect(processor).to receive(:toggle)
callbacks.before_save
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb 0000664 0000000 0000000 00000005711 13411321301 0032335 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module ActiveRecord
module Callbacks; end
end
end
require 'active_support/core_ext/string/inflections'
require 'thinking_sphinx/callbacks'
require 'thinking_sphinx/errors'
require 'thinking_sphinx/active_record/callbacks/update_callbacks'
describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
describe '#after_update' do
let(:callbacks) {
ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks.new instance }
let(:instance) { double('instance', :class => klass, :id => 2) }
let(:klass) { double(:name => 'Article') }
let(:configuration) { double('configuration',
:settings => {'attribute_updates' => true},
:indices_for_references => [index]) }
let(:connection) { double('connection', :execute => '') }
let(:index) { double 'index', :name => 'article_core',
:sources => [source], :document_id_for_key => 3, :distributed? => false,
:type => 'plain'}
let(:source) { double('source', :attributes => []) }
before :each do
stub_const 'ThinkingSphinx::Configuration',
double(:instance => configuration)
stub_const 'ThinkingSphinx::Connection', double
stub_const 'Riddle::Query', double(:update => 'SphinxQL')
allow(ThinkingSphinx::Connection).to receive(:take).and_yield(connection)
source.attributes.replace([
double(:name => 'foo', :updateable? => true,
:columns => [double(:__name => 'foo_column')]),
double(:name => 'bar', :updateable? => true, :value_for => 7,
:columns => [double(:__name => 'bar_column')]),
double(:name => 'baz', :updateable? => false)
])
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
expect(connection).not_to receive(:execute)
callbacks.after_update
end
it "builds an update query with only updateable attributes that have changed" do
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
expect(connection).to receive(:execute).with('SphinxQL')
callbacks.after_update
end
it "doesn't care if the update fails at Sphinx's end" do
allow(connection).to receive(:execute).
and_raise(ThinkingSphinx::ConnectionError.new(''))
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
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/column_spec.rb 0000664 0000000 0000000 00000004047 13411321301 0026433 0 ustar 00root root 0000000 0000000 # 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)
expect(column.__name).to eq(:content)
end
end
describe '#__replace' do
let(:base) { [:a, :b] }
let(:replacements) { [[:a, :c], [:a, :d]] }
it "returns itself when it's a string column" do
column = ThinkingSphinx::ActiveRecord::Column.new('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)
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)
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)
expect(column.__stack).to eq([:users, :posts])
end
end
describe '#method_missing' do
let(:column) { ThinkingSphinx::ActiveRecord::Column.new(:user) }
it "shifts the current name to the stack" do
column.email
expect(column.__stack).to eq([:user])
end
it "adds the new method call as the name" do
column.email
expect(column.__name).to eq(:email)
end
it "returns itself" do
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')
expect(column).to be_a_string
end
it "is false when the name is a symbol" do
column = ThinkingSphinx::ActiveRecord::Column.new(:content)
expect(column).not_to be_a_string
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/column_sql_presenter_spec.rb 0000664 0000000 0000000 00000002515 13411321301 0031377 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/database_adapters/ 0000775 0000000 0000000 00000000000 13411321301 0027221 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb 0000664 0000000 0000000 00000001662 13411321301 0034070 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::DatabaseAdapters::AbstractAdapter do
let(:adapter) {
ThinkingSphinx::ActiveRecord::DatabaseAdapters::AbstractAdapter.new model
}
let(:model) { double('model', :connection => connection) }
let(:connection) { double('connection') }
describe '#quote' do
it "uses the model's connection to quote columns" do
expect(connection).to receive(:quote_column_name).with('foo')
adapter.quote 'foo'
end
it "returns the quoted value" do
allow(connection).to receive_messages :quote_column_name => '"foo"'
expect(adapter.quote('foo')).to eq('"foo"')
end
end
describe '#quoted_table_name' do
it "passes the method through to the model" do
expect(model).to receive(:quoted_table_name).and_return('"articles"')
expect(adapter.quoted_table_name).to eq('"articles"')
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb 0000664 0000000 0000000 00000003046 13411321301 0033430 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter do
let(:adapter) {
ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter.new(model)
}
let(:model) { double('model') }
it "returns 1 for true" do
expect(adapter.boolean_value(true)).to eq(1)
end
it "returns 0 for false" do
expect(adapter.boolean_value(false)).to eq(0)
end
describe '#cast_to_string' do
it "casts the clause to characters" do
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
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
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
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
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
expect(adapter.group_concatenate('foo', ',')).
to eq("GROUP_CONCAT(DISTINCT foo SEPARATOR ',')")
end
end
end
postgresql_adapter_spec.rb 0000664 0000000 0000000 00000003653 13411321301 0034413 0 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/database_adapters # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter do
let(:adapter) {
ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter.new(model)
}
let(:model) { double('model') }
describe '#boolean_value' do
it "returns 'TRUE' for true" do
expect(adapter.boolean_value(true)).to eq('TRUE')
end
it "returns 'FALSE' for false" do
expect(adapter.boolean_value(false)).to eq('FALSE')
end
end
describe '#cast_to_string' do
it "casts the clause to characters" do
expect(adapter.cast_to_string('foo')).to eq('foo::varchar')
end
end
describe '#cast_to_timestamp' do
it "converts to int unix timestamps" do
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
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
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
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
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
expect(adapter.group_concatenate('foo', ',')).
to eq("array_to_string(array_agg(DISTINCT foo), ',')")
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/database_adapters_spec.rb 0000664 0000000 0000000 00000011776 13411321301 0030574 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::DatabaseAdapters do
let(:model) { double('model') }
describe '.adapter_for' do
it "returns a MysqlAdapter object for :mysql" do
allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters).
to receive_messages(:adapter_type_for => :mysql)
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model)).
to be_a(
ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter
)
end
it "returns a PostgreSQLAdapter object for :postgresql" do
allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters).
to receive_messages(:adapter_type_for => :postgresql)
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model)).
to be_a(
ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter
)
end
it "instantiates using the default adapter if one is provided" do
adapter_class = double('adapter class')
adapter_instance = double('adapter instance')
ThinkingSphinx::ActiveRecord::DatabaseAdapters.default = adapter_class
allow(adapter_class).to receive_messages(:new => 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
allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters).
to receive_messages(:adapter_type_for => :sqlite)
expect {
ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model)
}.to raise_error(ThinkingSphinx::InvalidDatabaseAdapter)
end
end
describe '.adapter_type_for' do
let(:klass) { double('connection class') }
let(:connection) { double('connection', :class => klass) }
let(:model) { double('model', :connection => connection) }
it "translates a normal MySQL adapter" do
allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::MysqlAdapter')
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_type_for(model)).to eq(:mysql)
end
it "translates a MySQL2 adapter" do
allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::Mysql2Adapter')
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_type_for(model)).to eq(:mysql)
end
it "translates a normal PostgreSQL adapter" do
allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter')
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_type_for(model)).to eq(:postgresql)
end
it "translates a JDBC MySQL adapter to MySQL" do
allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
allow(connection).to receive_messages(:config => {:adapter => 'jdbcmysql'})
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_type_for(model)).to eq(:mysql)
end
it "translates a JDBC PostgreSQL adapter to PostgreSQL" do
allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
allow(connection).to receive_messages(:config => {:adapter => 'jdbcpostgresql'})
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_type_for(model)).to eq(:postgresql)
end
it "translates a JDBC adapter with MySQL connection string to MySQL" do
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'})
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_type_for(model)).to eq(:mysql)
end
it "translates a JDBC adapter with PostgresSQL connection string to PostgresSQL" do
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'})
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_type_for(model)).to eq(:postgresql)
end
it "returns other JDBC adapters without translation" do
allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
allow(connection).to receive_messages(:config => {:adapter => 'jdbcmssql'})
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_type_for(model)).to eq('jdbcmssql')
end
it "returns other unknown adapters without translation" do
allow(klass).to receive_messages(:name => 'ActiveRecord::ConnectionAdapters::FooAdapter')
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters.
adapter_type_for(model)).
to eq('ActiveRecord::ConnectionAdapters::FooAdapter')
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/field_spec.rb 0000664 0000000 0000000 00000002513 13411321301 0026215 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Field do
let(:field) { ThinkingSphinx::ActiveRecord::Field.new model, column }
let(:column) { double('column', :__name => :title, :__stack => [],
:string? => false) }
let(:model) { double('model') }
before :each do
allow(column).to receive_messages :to_a => [column]
end
describe '#columns' do
it 'returns the provided Column object' do
expect(field.columns).to eq([column])
end
it 'translates symbols to Column objects' do
expect(ThinkingSphinx::ActiveRecord::Column).to receive(:new).with(:title).
and_return(column)
ThinkingSphinx::ActiveRecord::Field.new model, :title
end
end
describe '#file?' do
it "defaults to false" do
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
expect(field).to be_file
end
end
describe '#with_attribute?' do
it "defaults to false" do
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
expect(field).to be_with_attribute
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/filter_reflection_spec.rb 0000664 0000000 0000000 00000014067 13411321301 0030640 0 ustar 00root root 0000000 0000000 # 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 => original_klass) }
let(:options) { {:polymorphic => true} }
let(:filtered_reflection) { double 'filtered reflection' }
let(:original_klass) { double }
let(:subclass) { double :include => true }
before :each do
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
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 if ActiveRecord::VERSION::STRING.to_f < 4.2
it "uses the supplied name" do
expected_reflection_arguments do |wrapper|
expect(wrapper.name).to eq('foo_bar')
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
end
it "uses the existing reflection's parent" do
expected_reflection_arguments do |wrapper|
expect(wrapper.parent).to eq(reflection.active_record)
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
end
it "removes the polymorphic setting from the options" do
expected_reflection_arguments do |wrapper|
expect(wrapper.options[:polymorphic]).to be_nil
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
end
it "adds the class name option" do
expected_reflection_arguments do |wrapper|
expect(wrapper.options[:class_name]).to eq('Bar')
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
end
it "sets the foreign key if necessary" do
expected_reflection_arguments do |wrapper|
expect(wrapper.options[:foreign_key]).to eq('baz_id')
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
end
it "respects supplied foreign keys" do
options[:foreign_key] = 'qux_id'
expected_reflection_arguments do |wrapper|
expect(wrapper.options[:foreign_key]).to eq('qux_id')
end
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
end
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
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
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
end
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
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::JoinConstraint)
ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)
end if ActiveRecord::VERSION::STRING.to_f > 5.1
it "returns the new reflection" do
expect(ThinkingSphinx::ActiveRecord::FilterReflection.call(
reflection, 'foo_bar', 'Bar'
)).to eq(filtered_reflection)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/index_spec.rb 0000664 0000000 0000000 00000014233 13411321301 0026243 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Index do
let(:index) { ThinkingSphinx::ActiveRecord::Index.new :user }
let(:config) { double('config', :settings => {},
:indices_location => 'location', :next_offset => 8) }
before :each do
allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
describe '#append_source' do
let(:model) { double('model', :primary_key => :id,
:table_exists? => true) }
let(:source) { double('source') }
before :each do
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
expect(index.sources).to receive(:<<).with(source)
index.append_source
end
it "creates the source with the index's offset" do
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
expect(index.append_source).to eq(source)
end
it "defaults to the model's primary key" do
allow(model).to receive_messages :primary_key => :sphinx_id
expect(ThinkingSphinx::ActiveRecord::SQLSource).to receive(:new).
with(model, hash_including(:primary_key => :sphinx_id)).
and_return(source)
index.append_source
end
it "uses a custom column when set" do
allow(model).to receive_messages :primary_key => :sphinx_id
expect(ThinkingSphinx::ActiveRecord::SQLSource).to receive(:new).
with(model, hash_including(:primary_key => :custom_sphinx_id)).
and_return(source)
index = ThinkingSphinx::ActiveRecord::Index.new:user,
:primary_key => :custom_sphinx_id
index.append_source
end
it "defaults to id if no primary key is set" do
allow(model).to receive_messages :primary_key => nil
expect(ThinkingSphinx::ActiveRecord::SQLSource).to receive(:new).
with(model, hash_including(:primary_key => :id)).
and_return(source)
index.append_source
end
end
describe '#delta?' do
it "defaults to false" do
expect(index).not_to be_delta
end
it "reflects the delta? option" do
index = ThinkingSphinx::ActiveRecord::Index.new :user, :delta? => true
expect(index).to be_delta
end
end
describe '#delta_processor' do
it "creates an instance of the delta processor option" do
processor = double('processor')
processor_class = double('processor class', :new => processor)
index = ThinkingSphinx::ActiveRecord::Index.new :user,
:delta_processor => processor_class
expect(index.delta_processor).to eq(processor)
end
end
describe '#docinfo' do
it "defaults to extern" do
expect(index.docinfo).to eq(:extern)
end
it "can be disabled" do
config.settings["skip_docinfo"] = true
expect(index.docinfo).to be_nil
end
end
describe '#document_id_for_key' do
it "calculates the document id based on offset and number of indices" do
allow(config).to receive_message_chain(:indices, :count).and_return(5)
allow(config).to receive_messages :next_offset => 7
expect(index.document_id_for_key(123)).to eq(622)
end
end
describe '#interpret_definition!' do
let(:block) { double('block') }
before :each do
index.definition_block = block
end
it "interprets the definition block" do
expect(ThinkingSphinx::ActiveRecord::Interpreter).to receive(:translate!).
with(index, block)
index.interpret_definition!
end
it "only interprets the definition block once" do
expect(ThinkingSphinx::ActiveRecord::Interpreter).to receive(:translate!).
once
index.interpret_definition!
index.interpret_definition!
end
end
describe '#model' do
let(:model) { double('model') }
it "translates symbol references to model class" do
allow(ActiveSupport::Inflector).to receive_messages(:constantize => model)
expect(index.model).to eq(model)
end
it "memoizes the result" do
expect(ActiveSupport::Inflector).to receive(:constantize).with('User').once.
and_return(model)
index.model
index.model
end
end
describe '#morphology' do
context 'with a render' do
before :each do
allow(FileUtils).to receive_messages :mkdir_p => true
end
it "defaults to nil" do
begin
index.render
rescue Riddle::Configuration::ConfigurationError
end
expect(index.morphology).to be_nil
end
it "reads from the settings file if provided" do
config.settings['morphology'] = 'stem_en'
begin
index.render
rescue Riddle::Configuration::ConfigurationError
end
expect(index.morphology).to eq('stem_en')
end
end
end
describe '#name' do
it "uses the core suffix by default" do
index = ThinkingSphinx::ActiveRecord::Index.new :user
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
expect(index.name).to eq('user_delta')
end
end
describe '#offset' do
before :each do
allow(config).to receive_messages :next_offset => 4
end
it "uses the next offset value from the configuration" do
expect(index.offset).to eq(4)
end
it "uses the reference to get a unique offset" do
expect(config).to receive(:next_offset).with(:user).and_return(2)
index.offset
end
end
describe '#render' do
before :each do
allow(FileUtils).to receive_messages :mkdir_p => true
end
it "interprets the provided definition" do
expect(index).to receive(:interpret_definition!).at_least(:once)
begin
index.render
rescue Riddle::Configuration::ConfigurationError
# Ignoring underlying validation error.
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/interpreter_spec.rb 0000664 0000000 0000000 00000022167 13411321301 0027504 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Interpreter do
let(:instance) {
ThinkingSphinx::ActiveRecord::Interpreter.new index, block
}
let(:model) { double('model') }
let(:index) { double('index', :append_source => source, :options => {}) }
let(:source) {
Struct.new(:attributes, :fields, :associations, :groupings, :conditions).
new([], [], [], [], [])
}
let(:block) { Proc.new { } }
before :each do
allow(ThinkingSphinx::ActiveRecord::SQLSource).to receive_messages :new => source
allow(source).to receive_messages :model => model
end
describe '.translate!' do
let(:instance) { double('interpreter', :translate! => true) }
it "creates a new interpreter instance with the given block and index" do
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
allow(ThinkingSphinx::ActiveRecord::Interpreter).to receive_messages(:new => instance)
expect(instance).to receive(:translate!)
ThinkingSphinx::ActiveRecord::Interpreter.translate! index, block
end
end
describe '#group_by' do
it "adds a source to the index" do
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
expect(index).to receive(:append_source).once.and_return(source)
instance.group_by 'lat'
instance.group_by 'lng'
end
it "appends a new grouping statement to the source" do
instance.group_by 'lat'
expect(source.groupings).to include('lat')
end
end
describe '#has' do
let(:column) { double('column') }
let(:attribute) { double('attribute') }
before :each do
allow(ThinkingSphinx::ActiveRecord::Attribute).to receive_messages :new => attribute
end
it "adds a source to the index" do
expect(index).to receive(:append_source).and_return(source)
instance.has column
end
it "only adds a single source for the given context" do
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
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
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.attributes).to include(attribute)
end
it "adds multiple attributes when passed multiple columns" do
instance.has column, column
expect(source.attributes.select { |saved_attribute|
saved_attribute == attribute
}.length).to eq(2)
end
end
describe '#indexes' do
let(:column) { double('column') }
let(:field) { double('field') }
before :each do
allow(ThinkingSphinx::ActiveRecord::Field).to receive_messages :new => field
end
it "adds a source to the index" do
expect(index).to receive(:append_source).and_return(source)
instance.indexes column
end
it "only adds a single source for the given context" do
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
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
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.fields).to include(field)
end
it "adds multiple fields when passed multiple columns" do
instance.indexes column, column
expect(source.fields.select { |saved_field|
saved_field == field
}.length).to eq(2)
end
end
describe '#join' do
let(:column) { double('column') }
let(:association) { double('association') }
before :each do
allow(ThinkingSphinx::ActiveRecord::Association).to receive_messages :new => association
end
it "adds a source to the index" do
expect(index).to receive(:append_source).and_return(source)
instance.join column
end
it "only adds a single source for the given context" do
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
expect(ThinkingSphinx::ActiveRecord::Association).to receive(:new).
with(column).and_return(association)
instance.join column
end
it "adds an association to the source" do
instance.join column
expect(source.associations).to include(association)
end
it "adds multiple fields when passed multiple columns" do
instance.join column, column
expect(source.associations.select { |saved_assoc|
saved_assoc == association
}.length).to eq(2)
end
end
describe '#method_missing' do
let(:column) { double('column') }
before :each do
allow(ThinkingSphinx::ActiveRecord::Column).to receive_messages(:new => column)
end
it "returns a new column for the given method" do
expect(instance.id).to eq(column)
end
it "should initialise the column with the method name and arguments" do
expect(ThinkingSphinx::ActiveRecord::Column).to receive(:new).
with(:users, :posts, :subject).and_return(column)
instance.users(:posts, :subject)
end
end
describe '#set_database' do
before :each do
allow(source).to receive_messages :set_database_settings => true
stub_const 'ActiveRecord::Base',
double(:configurations => {'other' => {'baz' => 'qux'}})
end
it "sends through a hash if provided" do
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
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
expect(source).to receive(:set_database_settings).with(:baz => 'qux')
instance.set_database :other
end
end
describe '#set_property' do
before :each do
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}
expect(index.options[:field_weights]).to eq({:name => 10})
end
context 'index settings' do
it "sets the provided setting" do
expect(index).to receive(:morphology=).with('stem_en')
instance.set_property :morphology => 'stem_en'
end
end
context 'source settings' do
before :each do
allow(source).to receive_messages :mysql_ssl_cert= => true
end
it "adds a source to the index" do
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
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
expect(source).to receive(:mysql_ssl_cert=).with('private.cert')
instance.set_property :mysql_ssl_cert => 'private.cert'
end
end
end
describe '#translate!' do
it "returns the block evaluated within the context of the interpreter" do
block = Proc.new {
__id__
}
interpreter = ThinkingSphinx::ActiveRecord::Interpreter.new index, block
expect(interpreter.translate!).
to eq(interpreter.__id__)
end
end
describe '#where' do
it "adds a source to the index" do
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
expect(index).to receive(:append_source).once.and_return(source)
instance.where 'id > 100'
instance.where 'id < 150'
end
it "appends a new grouping statement to the source" do
instance.where 'id > 100'
expect(source.conditions).to include('id > 100')
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/polymorpher_spec.rb 0000664 0000000 0000000 00000005734 13411321301 0027522 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::Polymorpher do
let(:polymorpher) { ThinkingSphinx::ActiveRecord::Polymorpher.new source,
column, class_names }
let(:source) { double 'Source', :model => outer, :fields => [field],
:attributes => [attribute] }
let(:column) { double 'Column', :__name => :foo, :__stack => [:a, :b],
:__path => [:a, :b, :foo] }
let(:class_names) { %w( Article Animal ) }
let(:field) { double :rebase => true }
let(:attribute) { double :rebase => true }
let(:outer) { double(
:reflect_on_association => double(:klass => inner)) }
let(:inner) { double(
:reflect_on_association => double(:klass => model)) }
let(:model) { double 'Model', :reflections => {},
:reflect_on_association => reflection }
let(:reflection) { double 'Polymorphic Reflection' }
describe '#morph!' do
let(:article_reflection) { double 'Article Reflection' }
let(:animal_reflection) { double 'Animal Reflection' }
before :each do
allow(ThinkingSphinx::ActiveRecord::FilterReflection).
to receive(:call).
and_return(article_reflection, animal_reflection)
allow(model).to receive(:reflect_on_association) do |name|
name == :foo ? reflection : nil
end
if ActiveRecord::Reflection.respond_to?(:add_reflection)
allow(ActiveRecord::Reflection).to receive :add_reflection
end
end
it "creates a new reflection for each class" do
allow(ThinkingSphinx::ActiveRecord::FilterReflection).
to receive(:call).and_call_original
expect(ThinkingSphinx::ActiveRecord::FilterReflection).
to receive(:call).
with(reflection, :foo_article, 'Article').
and_return(article_reflection)
expect(ThinkingSphinx::ActiveRecord::FilterReflection).
to receive(:call).
with(reflection, :foo_animal, 'Animal').
and_return(animal_reflection)
polymorpher.morph!
end
it "adds the new reflections to the end-of-stack model" do
if ActiveRecord::Reflection.respond_to?(:add_reflection)
expect(ActiveRecord::Reflection).to receive(:add_reflection).
with(model, :foo_article, article_reflection)
expect(ActiveRecord::Reflection).to receive(:add_reflection).
with(model, :foo_animal, animal_reflection)
polymorpher.morph!
else
polymorpher.morph!
expect(model.reflections[:foo_article]).to eq(article_reflection)
expect(model.reflections[:foo_animal]).to eq(animal_reflection)
end
end
it "rebases each field" do
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
expect(attribute).to receive(:rebase).with([:a, :b, :foo],
:to => [[:a, :b, :foo_article], [:a, :b, :foo_animal]])
polymorpher.morph!
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb 0000664 0000000 0000000 00000021714 13411321301 0031770 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::PropertySQLPresenter do
let(:adapter) { double 'adapter' }
let(:associations) { double 'associations', :alias_for => 'articles' }
let(:model) { double :column_names => ['title', 'created_at'] }
let(:path) { double :aggregate? => false, :model => model }
before :each do
allow(adapter).to receive(:quote) { |column| column }
stub_const 'Joiner::Path', double(:new => path)
end
context 'with a field' do
let(:presenter) {
ThinkingSphinx::ActiveRecord::PropertySQLPresenter.new(
field, adapter, associations
)
}
let(:field) { double('field', :name => 'title', :columns => [column],
:type => nil, :multi? => false, :source_type => nil, :model => double) }
let(:column) { double('column', :string? => false, :__stack => [],
:__name => 'title') }
describe '#to_group' do
it "returns the column name as a string" do
expect(presenter.to_group).to eq('articles.title')
end
it "gets the column's table alias from the associations object" do
allow(column).to receive_messages(:__stack => [: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
allow(path).to receive_messages :aggregate? => true
expect(presenter.to_group).to be_nil
end
it "returns nil if the field is sourced via a separate query" do
allow(field).to receive_messages :source_type => 'query'
expect(presenter.to_group).to be_nil
end
end
describe '#to_select' do
it "returns the column name as a string" do
expect(presenter.to_select).to eq('articles.title AS title')
end
it "gets the column's table alias from the associations object" do
allow(column).to receive_messages(:__stack => [: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
allow(field).to receive_messages(:name => :subject)
expect(presenter.to_select).to eq('articles.title AS subject')
end
it "groups and concatenates aggregated columns" do
allow(adapter).to receive :group_concatenate do |clause, separator|
"GROUP_CONCAT(#{clause} SEPARATOR '#{separator}')"
end
allow(path).to receive_messages :aggregate? => true
expect(presenter.to_select).
to eq("GROUP_CONCAT(articles.title SEPARATOR ' ') AS title")
end
it "concatenates multiple columns" do
allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
allow(field).to receive_messages(:columns => [column, column])
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
allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
allow(field).to receive_messages(:columns => [column, double('column', :string? => false,
:__stack => [], :__name => 'body')])
expect(presenter.to_select).
to eq("CONCAT_WS(' ', articles.title) AS title")
end
it "returns nil for query sourced fields" do
allow(field).to receive_messages :source_type => :query
expect(presenter.to_select).to be_nil
end
it "returns nil for ranged query sourced fields" do
allow(field).to receive_messages :source_type => :ranged_query
expect(presenter.to_select).to be_nil
end
end
end
context 'with an attribute' do
let(:presenter) {
ThinkingSphinx::ActiveRecord::PropertySQLPresenter.new(
attribute, adapter, associations
)
}
let(:attribute) { double('attribute', :name => 'created_at',
:columns => [column], :type => :integer, :multi? => false,
:source_type => nil, :model => double) }
let(:column) { double('column', :string? => false, :__stack => [],
:__name => 'created_at') }
before :each do
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
expect(presenter.to_group).to eq('articles.created_at')
end
it "gets the column's table alias from the associations object" do
allow(column).to receive_messages(:__stack => [: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
allow(column).to receive_messages(:string? => true)
expect(presenter.to_group).to be_nil
end
it "returns nil if the property is an aggregate" do
allow(path).to receive_messages :aggregate? => true
expect(presenter.to_group).to be_nil
end
it "returns nil if the attribute is sourced via a separate query" do
allow(attribute).to receive_messages :source_type => 'query'
expect(presenter.to_group).to be_nil
end
end
describe '#to_select' do
it "returns the column name as a string" do
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
allow(column).to receive_messages(:__stack => [: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
allow(attribute).to receive_messages(:name => :creation_timestamp)
expect(presenter.to_select).
to eq('articles.created_at AS creation_timestamp')
end
it "ensures datetime attributes are converted to timestamps" do
allow(attribute).to receive_messages :type => :timestamp
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
allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
allow(adapter).to receive :cast_to_string do |clause|
"CAST(#{clause} AS varchar)"
end
allow(attribute).to receive_messages(:columns => [column, double('column',
:string? => false, :__stack => [], :__name => 'updated_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
allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
allow(adapter).to receive :cast_to_string do |clause|
"CAST(#{clause} AS varchar)"
end
allow(attribute).to receive_messages(:columns => [column, column])
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
allow(adapter).to receive :concatenate do |clause, separator|
"CONCAT_WS('#{separator}', #{clause})"
end
allow(adapter).to receive :cast_to_string do |clause|
"CAST(#{clause} AS varchar)"
end
allow(attribute).to receive_messages :columns => [column, column], :type => :timestamp
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
allow(column).to receive_messages :__name => "COALESCE(articles.updated_at, articles.created_at)", :string? => true
allow(attribute).to receive_messages :name => 'mod_date', :columns => [column],
:type => :timestamp
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
allow(attribute).to receive_messages :source_type => :query
expect(presenter.to_select).to be_nil
end
it "returns nil for ranged query sourced attributes" do
allow(attribute).to receive_messages :source_type => :ranged_query
expect(presenter.to_select).to be_nil
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/sql_builder_spec.rb 0000664 0000000 0000000 00000045771 13411321301 0027454 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::SQLBuilder do
let(:source) { double('source', :model => model, :offset => 3,
:fields => [], :attributes => [], :disable_range? => false,
:delta_processor => nil, :conditions => [], :groupings => [],
:adapter => adapter, :associations => [], :primary_key => :id,
:options => {}, :properties => []) }
let(:model) { double('model', :connection => connection,
:descends_from_active_record? => true, :column_names => [],
:inheritance_column => 'type', :unscoped => relation,
:quoted_table_name => '`users`', :name => 'User') }
let(:connection) { double('connection') }
let(:relation) { double('relation') }
let(:config) { double('config', :indices => indices, :settings => {}) }
let(:indices) { double('indices', :count => 5) }
let(:presenter) { double('presenter', :to_select => '`name` AS `name`',
:to_group => '`name`') }
let(:adapter) { double('adapter',
:time_zone_query_pre => ['SET TIME ZONE']) }
let(:associations) { double('associations', :join_values => []) }
let(:builder) { ThinkingSphinx::ActiveRecord::SQLBuilder.new source }
before :each do
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 => ''
allow(connection).to receive(:quote_column_name) { |column| "`#{column}`"}
end
describe 'sql_query' do
before :each do
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)
expect(associations).to receive(:add_join_to).with([:user, :posts])
builder.sql_query
end
it "adds string joins directly to the relation" do
source.associations << double('association',
:to_s => 'my string', :string? => true)
expect(relation).to receive(:joins).with(['my string']).and_return(relation)
builder.sql_query
end
context 'MySQL adapter' do
before :each do
allow(source).to receive_messages :type => 'mysql'
end
it "returns the relation's query" do
allow(relation).to receive_messages :to_sql => 'SELECT * FROM people'
expect(builder.sql_query).to eq('SELECT * FROM people')
end
it "ensures results aren't from cache" do
expect(relation).to receive(:select) do |string|
expect(string).to match(/^SQL_NO_CACHE /)
relation
end
builder.sql_query
end
it "adds the document id using the offset and index count" do
expect(relation).to receive(:select) do |string|
expect(string).to match(/`users`.`id` \* 5 \+ 3 AS `id`/)
relation
end
builder.sql_query
end
it "adds each field to the SELECT clause" do
source.fields << double('field')
expect(relation).to receive(:select) do |string|
expect(string).to match(/`name` AS `name`/)
relation
end
builder.sql_query
end
it "adds each attribute to the SELECT clause" do
source.attributes << double('attribute')
allow(presenter).to receive_messages(:to_select => '`created_at` AS `created_at`')
expect(relation).to receive(:select) do |string|
expect(string).to match(/`created_at` AS `created_at`/)
relation
end
builder.sql_query
end
it "limits results to a set range" do
expect(relation).to receive(:where) do |string|
expect(string).to match(/`users`.`id` BETWEEN \$start AND \$end/)
relation
end
builder.sql_query
end
it "shouldn't limit results to a range if ranges are disabled" do
allow(source).to receive_messages :disable_range? => true
expect(relation).to receive(:where) do |string|
expect(string).not_to match(/`users`.`id` BETWEEN \$start AND \$end/)
relation
end
builder.sql_query
end
it "adds source conditions" do
source.conditions << 'created_at > NOW()'
expect(relation).to receive(:where) do |string|
expect(string).to match(/created_at > NOW()/)
relation
end
builder.sql_query
end
it "groups by the primary key" do
expect(relation).to receive(:group) do |string|
expect(string).to match(/`users`.`id`/)
relation
end
builder.sql_query
end
it "groups each field" do
source.fields << double('field')
expect(relation).to receive(:group) do |string|
expect(string).to match(/`name`/)
relation
end
builder.sql_query
end
it "groups each attribute" do
source.attributes << double('attribute')
allow(presenter).to receive_messages(:to_group => '`created_at`')
expect(relation).to receive(:group) do |string|
expect(string).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
it "orders by NULL" do
expect(relation).to receive(:order).with('NULL').and_return(relation)
builder.sql_query
end
context 'STI model' do
before :each do
model.column_names << 'type'
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
expect(relation).to receive(:group) do |string|
expect(string).to match(/`users`.`type`/)
relation
end
builder.sql_query
end
context 'with a custom inheritance column' do
before :each do
model.column_names << 'custom_type'
allow(model).to receive_messages :inheritance_column => 'custom_type'
end
it "groups by the right column" do
expect(relation).to receive(:group) do |string|
expect(string).to match(/`users`.`custom_type`/)
relation
end
builder.sql_query
end
end
end
context 'with a delta processor' do
let(:processor) { double('processor') }
before :each do
allow(source).to receive_messages :delta_processor => processor
allow(source).to receive_messages :delta? => true
end
it "filters by the provided clause" do
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
builder.sql_query
end
end
end
context 'PostgreSQL adapter' do
let(:presenter) { double('presenter', :to_select => '"name" AS "name"',
:to_group => '"name"') }
before :each do
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
allow(relation).to receive_messages :to_sql => '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
expect(relation).to receive(:select) do |string|
expect(string).to match(/"users"."id" \* 5 \+ 3 AS "id"/)
relation
end
builder.sql_query
end
it "adds each field to the SELECT clause" do
source.fields << double('field')
expect(relation).to receive(:select) do |string|
expect(string).to match(/"name" AS "name"/)
relation
end
builder.sql_query
end
it "adds each attribute to the SELECT clause" do
source.attributes << double('attribute')
allow(presenter).to receive_messages(:to_select => '"created_at" AS "created_at"')
expect(relation).to receive(:select) do |string|
expect(string).to match(/"created_at" AS "created_at"/)
relation
end
builder.sql_query
end
it "limits results to a set range" do
expect(relation).to receive(:where) do |string|
expect(string).to match(/"users"."id" BETWEEN \$start AND \$end/)
relation
end
builder.sql_query
end
it "shouldn't limit results to a range if ranges are disabled" do
allow(source).to receive_messages :disable_range? => true
expect(relation).to receive(:where) do |string|
expect(string).not_to match(/"users"."id" BETWEEN \$start AND \$end/)
relation
end
builder.sql_query
end
it "adds source conditions" do
source.conditions << 'created_at > NOW()'
expect(relation).to receive(:where) do |string|
expect(string).to match(/created_at > NOW()/)
relation
end
builder.sql_query
end
it "groups by the primary key" do
expect(relation).to receive(:group) do |string|
expect(string).to match(/"users"."id"/)
relation
end
builder.sql_query
end
it "groups each field" do
source.fields << double('field')
expect(relation).to receive(:group) do |string|
expect(string).to match(/"name"/)
relation
end
builder.sql_query
end
it "groups each attribute" do
source.attributes << double('attribute')
allow(presenter).to receive_messages(:to_group => '"created_at"')
expect(relation).to receive(:group) do |string|
expect(string).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
it "has no ORDER clause" do
expect(relation).not_to receive(:order)
builder.sql_query
end
context 'group by shortcut' do
before :each do
source.options[: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
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
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 'STI model' do
before :each do
model.column_names << 'type'
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
expect(relation).to receive(:group) do |string|
expect(string).to match(/"users"."type"/)
relation
end
builder.sql_query
end
context 'with a custom inheritance column' do
before :each do
model.column_names << 'custom_type'
allow(model).to receive_messages :inheritance_column => 'custom_type'
end
it "groups by the right column" do
expect(relation).to receive(:group) do |string|
expect(string).to match(/"users"."custom_type"/)
relation
end
builder.sql_query
end
end
end
context 'with a delta processor' do
let(:processor) { double('processor') }
before :each do
allow(source).to receive_messages :delta_processor => processor
allow(source).to receive_messages :delta? => true
end
it "filters by the provided clause" do
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
builder.sql_query
end
end
end
end
describe 'sql_query_pre' do
let(:processor) { double('processor', :reset_query => '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
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
expect(builder.sql_query_pre).to include('RESET DELTAS')
end
it "does not add a reset query if there is no delta processor" do
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
allow(source).to receive_messages :delta_processor => processor
allow(source).to receive_messages :delta? => true
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
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
expect(builder.sql_query_pre.select { |sql|
sql[/SET SESSION group_concat_max_len/]
}).to be_empty
end
it "sets the connection to use UTF-8 if required" do
source.options[:utf8?] = true
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
expect(builder.sql_query_pre).not_to include('SET UTF8')
end
it "adds a time-zone query by default" do
expect(builder.sql_query_pre).to include('SET TIME ZONE')
end
it "does not add a time-zone query if requested" do
config.settings['skip_time_zone'] = true
expect(builder.sql_query_pre).to_not include('SET TIME ZONE')
end
end
describe 'sql_query_range' do
before :each do
allow(adapter).to receive(:convert_nulls) { |string, default|
"ISNULL(#{string}, #{default})"
}
end
it "returns the relation's query" do
allow(relation).to receive_messages :to_sql => 'SELECT * FROM people'
expect(builder.sql_query_range).to eq('SELECT * FROM people')
end
it "returns nil if ranges are disabled" do
allow(source).to receive_messages :disable_range? => true
expect(builder.sql_query_range).to be_nil
end
it "selects the minimum primary key value, allowing for nulls" do
expect(relation).to receive(:select) do |string|
expect(string).to match(/ISNULL\(MIN\(`users`.`id`\), 1\)/)
relation
end
builder.sql_query_range
end
it "selects the maximum primary key value, allowing for nulls" do
expect(relation).to receive(:select) do |string|
expect(string).to match(/ISNULL\(MAX\(`users`.`id`\), 1\)/)
relation
end
builder.sql_query_range
end
it "shouldn't limit results to a range" do
expect(relation).to receive(:where) do |string|
expect(string).not_to match(/`users`.`id` BETWEEN \$start AND \$end/)
relation
end
builder.sql_query_range
end
it "does not add source conditions" do
source.conditions << 'created_at > NOW()'
expect(relation).to receive(:where) do |string|
expect(string).not_to match(/created_at > NOW()/)
relation
end
builder.sql_query_range
end
context 'with a delta processor' do
let(:processor) { double('processor') }
before :each do
allow(source).to receive_messages :delta_processor => processor
allow(source).to receive_messages :delta? => true
end
it "filters by the provided clause" do
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
builder.sql_query_range
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/active_record/sql_source_spec.rb 0000664 0000000 0000000 00000036470 13411321301 0027322 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::ActiveRecord::SQLSource do
let(:model) { double('model', :connection => connection,
:name => 'User', :column_names => [], :inheritance_column => 'type',
:primary_key => :id) }
let(:connection) {
double('connection', :instance_variable_get => db_config) }
let(:db_config) { {:host => 'localhost', :user => 'root',
:database => 'default'} }
let(:source) { ThinkingSphinx::ActiveRecord::SQLSource.new(model,
:position => 3, :primary_key => model.primary_key || :id ) }
let(:adapter) { double('adapter') }
before :each do
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
expect(ThinkingSphinx::ActiveRecord::DatabaseAdapters).
to receive(:adapter_for).with(model).and_return(adapter)
expect(source.adapter).to eq(adapter)
end
end
describe '#attributes' do
it "has the internal id attribute by default" do
expect(source.attributes.collect(&:name)).to include('sphinx_internal_id')
end
it "has the class name attribute by default" do
expect(source.attributes.collect(&:name)).to include('sphinx_internal_class')
end
it "has the internal deleted attribute by default" do
expect(source.attributes.collect(&:name)).to include('sphinx_deleted')
end
it "marks the internal class attribute as a facet" do
expect(source.attributes.detect { |attribute|
attribute.name == 'sphinx_internal_class'
}.options[:facet]).to be_truthy
end
end
describe '#delta_processor' do
let(:processor_class) { double('processor class', :try => processor) }
let(:processor) { double('processor') }
let(:source) {
ThinkingSphinx::ActiveRecord::SQLSource.new model,
: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 },
:primary_key => model.primary_key || :id
}
it "loads the processor with the adapter" do
expect(processor_class).to receive(:try).with(:new, adapter, {}).
and_return processor
source.delta_processor
end
it "returns the given processor" do
expect(source.delta_processor).to eq(processor)
end
it "passes given options to the processor" do
expect(processor_class).to receive(:try).with(:new, adapter, {:opt_key => :opt_value})
source_with_options.delta_processor
end
end
describe '#delta?' do
it "returns the given delta setting" do
source = ThinkingSphinx::ActiveRecord::SQLSource.new model,
:delta? => true,
:primary_key => model.primary_key || :id
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,
:primary_key => model.primary_key || :id
expect(source.disable_range?).to be_truthy
end
end
describe '#fields' do
it "has the internal class field by default" do
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
expect(source.fields.detect { |field|
field.name == 'sphinx_internal_class_name'
}.columns.first.__name).to eq("'User'")
end
it "uses the inheritance column if it exists for the sphinx class field" do
allow(adapter).to receive_messages :quoted_table_name => '"users"', :quote => '"type"'
allow(adapter).to receive(:convert_blank) { |clause, default|
"coalesce(nullif(#{clause}, ''), #{default})"
}
allow(model).to receive_messages :column_names => ['type'], :sti_name => 'User'
expect(source.fields.detect { |field|
field.name == 'sphinx_internal_class_name'
}.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
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, :primary_key => model.primary_key || :id
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, :primary_key => model.primary_key || :id
expect(source.offset).to eq(12)
end
end
describe '#options' do
it "defaults to having utf8? set to false" do
expect(source.options[:utf8?]).to be_falsey
end
it "sets utf8? to true if the database encoding is utf8" do
db_config[:encoding] = 'utf8'
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
describe '#render' do
let(:builder) { double('builder', :sql_query_pre => [],
:sql_query_post_index => [], :sql_query => 'query',
:sql_query_range => 'range', :sql_query_info => 'info') }
let(:config) { double('config', :settings => {}) }
let(:presenter) { double('presenter', :collection_type => :uint) }
let(:template) { double('template', :apply => true) }
before :each do
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
allow(builder).to receive_messages :sql_query => 'select * from table'
source.render
expect(source.sql_query).to eq('select * from table')
end
it "uses the builder's sql_query_range value" do
allow(builder).to receive_messages :sql_query_range => 'select 0, 10 from table'
source.render
expect(source.sql_query_range).to eq('select 0, 10 from table')
end
it "appends the builder's sql_query_pre value" do
allow(builder).to receive_messages :sql_query_pre => ['Change Setting']
source.render
expect(source.sql_query_pre).to eq(['Change Setting'])
end
it "adds fields with attributes to sql_field_string" do
source.fields << double('field', :name => 'title', :source_type => nil,
:with_attribute? => true, :file? => false, :wordcount? => false)
source.render
expect(source.sql_field_string).to include('title')
end
it "adds any joined or file fields" do
source.fields << double('field', :name => 'title', :file? => true,
:with_attribute? => false, :wordcount? => false, :source_type => nil)
source.render
expect(source.sql_file_field).to include('title')
end
it "adds wordcounted fields to sql_field_str2wordcount" do
source.fields << double('field', :name => 'title', :source_type => nil,
:with_attribute? => false, :file? => false, :wordcount? => true)
source.render
expect(source.sql_field_str2wordcount).to include('title')
end
it "adds any joined fields" do
allow(ThinkingSphinx::ActiveRecord::PropertyQuery).to receive_messages(
:new => double(:to_s => 'query for title')
)
source.fields << double('field', :name => 'title',
:source_type => :query, :with_attribute? => false, :file? => false,
:wordcount? => false)
source.render
expect(source.sql_joined_field).to include('query for title')
end
it "adds integer attributes to sql_attr_uint" do
source.attributes << double('attribute')
allow(presenter).to receive_messages :declaration => 'count', :collection_type => :uint
source.render
expect(source.sql_attr_uint).to include('count')
end
it "adds boolean attributes to sql_attr_bool" do
source.attributes << double('attribute')
allow(presenter).to receive_messages :declaration => 'published', :collection_type => :bool
source.render
expect(source.sql_attr_bool).to include('published')
end
it "adds string attributes to sql_attr_string" do
source.attributes << double('attribute')
allow(presenter).to receive_messages :declaration => 'name', :collection_type => :string
source.render
expect(source.sql_attr_string).to include('name')
end
it "adds timestamp attributes to sql_attr_timestamp" do
source.attributes << double('attribute')
allow(presenter).to receive_messages :declaration => 'created_at',
:collection_type => :timestamp
source.render
expect(source.sql_attr_timestamp).to include('created_at')
end
it "adds float attributes to sql_attr_float" do
source.attributes << double('attribute')
allow(presenter).to receive_messages :declaration => 'rating', :collection_type => :float
source.render
expect(source.sql_attr_float).to include('rating')
end
it "adds bigint attributes to sql_attr_bigint" do
source.attributes << double('attribute')
allow(presenter).to receive_messages :declaration => 'super_id', :collection_type => :bigint
source.render
expect(source.sql_attr_bigint).to include('super_id')
end
it "adds ordinal strings to sql_attr_str2ordinal" do
source.attributes << double('attribute')
allow(presenter).to receive_messages :declaration => 'name', :collection_type => :str2ordinal
source.render
expect(source.sql_attr_str2ordinal).to include('name')
end
it "adds multi-value attributes to sql_attr_multi" do
source.attributes << double('attribute')
allow(presenter).to receive_messages :declaration => 'uint tag_ids from field',
:collection_type => :multi
source.render
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')
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
expect(source.sql_attr_json).to include('json')
end
it "adds relevant settings from thinking_sphinx.yml" do
config.settings['mysql_ssl_cert'] = 'foo.cert'
config.settings['morphology'] = 'stem_en' # should be ignored
source.render
expect(source.mysql_ssl_cert).to eq('foo.cert')
end
end
describe '#set_database_settings' do
it "sets the sql_host setting from the model's database settings" do
source.set_database_settings :host => '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
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'
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'
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'
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'
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'
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
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'
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
allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter).
to receive_messages(:=== => true)
allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter).
to receive_messages(:=== => false)
expect(source.type).to eq('mysql')
end
it "is pgsql when using the PostgreSQL Adapter" do
allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter).
to receive_messages(:=== => false)
allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter).
to receive_messages(:=== => true)
expect(source.type).to eq('pgsql')
end
it "raises an exception for any other adapter" do
allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter).
to receive_messages(:=== => false)
allow(ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter).
to receive_messages(:=== => false)
expect { source.type }.to raise_error(
ThinkingSphinx::UnknownDatabaseAdapter
)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/attribute_types_spec.rb 0000664 0000000 0000000 00000002263 13411321301 0025552 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/ 0000775 0000000 0000000 00000000000 13411321301 0022562 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/clear_real_time_spec.rb 0000664 0000000 0000000 00000003004 13411321301 0027225 0 ustar 00root root 0000000 0000000 # 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_r => true,
:rm => true
allow(File).to receive_messages :exists? => 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_r).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
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/clear_sql_spec.rb 0000664 0000000 0000000 00000003411 13411321301 0026065 0 ustar 00root root 0000000 0000000 # 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_r => true, :rm => true
allow(File).to receive_messages :exists? => 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_r).with(["/path/to/indices/ts-foo.tmp"])
command.call
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/configure_spec.rb 0000664 0000000 0000000 00000001411 13411321301 0026077 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/index_real_time_spec.rb 0000664 0000000 0000000 00000001752 13411321301 0027256 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/index_sql_spec.rb 0000664 0000000 0000000 00000004563 13411321301 0026117 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/merge_and_update_spec.rb 0000664 0000000 0000000 00000007710 13411321301 0027411 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/merge_spec.rb 0000664 0000000 0000000 00000002562 13411321301 0025225 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/prepare_spec.rb 0000664 0000000 0000000 00000001103 13411321301 0025552 0 ustar 00root root 0000000 0000000 # 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'
}
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
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/start_detached_spec.rb 0000664 0000000 0000000 00000003157 13411321301 0027105 0 ustar 00root root 0000000 0000000 # 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 }
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 "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
thinking-sphinx-4.1.0/spec/thinking_sphinx/commands/stop_spec.rb 0000664 0000000 0000000 00000003370 13411321301 0025111 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/configuration/ 0000775 0000000 0000000 00000000000 13411321301 0023630 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/configuration/minimum_fields_spec.rb 0000664 0000000 0000000 00000003627 13411321301 0030200 0 ustar 00root root 0000000 0000000 # 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_a, :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 'keeps the class name fields when one index model has a 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_b1, field_b2])
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/configuration_spec.rb 0000664 0000000 0000000 00000040301 13411321301 0025165 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::Configuration do
let(:config) { ThinkingSphinx::Configuration.instance }
after :each do
ThinkingSphinx::Configuration.reset
end
describe '.instance' do
it "returns an instance of ThinkingSphinx::Configuration" do
expect(ThinkingSphinx::Configuration.instance).
to be_a(ThinkingSphinx::Configuration)
end
it "memoizes the instance" do
config = double('configuration')
expect(ThinkingSphinx::Configuration).to receive(:new).once.and_return(config)
ThinkingSphinx::Configuration.instance
ThinkingSphinx::Configuration.instance
end
end
describe '.reset' do
after :each do
config.framework = ThinkingSphinx::Frameworks.current
end
it 'does not cache settings after reset' do
allow(File).to receive_messages :exists? => true
allow(File).to receive_messages :read => {
'test' => {'foo' => 'bugs'},
'production' => {'foo' => 'bar'}
}.to_yaml
ThinkingSphinx::Configuration.reset
# Grab a new copy of the instance.
config = ThinkingSphinx::Configuration.instance
expect(config.settings['foo']).to eq('bugs')
config.framework = double :environment => 'production', :root => Pathname.new(__FILE__).join('..', '..', 'internal')
expect(config.settings['foo']).to eq('bar')
end
end
describe '#configuration_file' do
it "uses the Rails environment in the configuration file name" do
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'
expect(config.configuration_file).to eq('/path/to/foo.conf')
end
end
describe '#controller' do
it "returns an instance of Riddle::Controller" do
expect(config.controller).to be_a(Riddle::Controller)
end
it "memoizes the instance" do
expect(Riddle::Controller).to receive(:new).once.
and_return(double('controller'))
config.controller
config.controller
end
it "sets the bin path from the thinking_sphinx.yml file" do
write_configuration('bin_path' => '/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')
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
expect(config.index_paths).to include(File.join(Rails.root, 'app', 'indices'))
end
it "uses app/indices in the Rails engines" do
engine = double :engine, { :paths => { 'app/indices' =>
double(:path, { :existent => '/engine/app/indices' } )
} }
engine_class = double :instance => engine
expect(Rails::Engine).to receive(:subclasses).and_return([ engine_class ])
expect(config.index_paths).to include('/engine/app/indices')
end
end
describe '#indices_location' do
it "stores index files in db/sphinx/ENVIRONMENT" do
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'
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 local_path
FileUtils.rmdir linked_path
end
end
describe '#initialize' do
before :each do
FileUtils.rm_rf Rails.root.join('log')
end
it "sets the daemon pid file within log for the Rails app" do
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
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
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'
expect(config.indexer.mem_limit).to eq('128M')
end
it "sets searchd settings if within thinking_sphinx.yml" do
write_configuration 'workers' => 'none'
expect(config.searchd.workers).to eq('none')
end
it 'adds settings to indexer without common section' do
write_configuration 'lemmatizer_base' => 'foo'
expect(config.indexer.lemmatizer_base).to eq('foo')
end
it 'adds settings to common section if requested' do
write_configuration 'lemmatizer_base' => 'foo',
'common_sphinx_configuration' => true
expect(config.common.lemmatizer_base).to eq('foo')
end
end
describe '#next_offset' do
let(:reference) { double('reference') }
it "starts at 0" do
expect(config.next_offset(reference)).to eq(0)
end
it "increments for each new reference" do
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
expect(config.next_offset(reference)).to eq(0)
expect(config.next_offset(reference)).to eq(0)
end
end
describe '#preload_indices' do
let(:distributor) { double :reconcile => true }
before :each do
stub_const 'ThinkingSphinx::Configuration::DistributedIndices',
double(:new => distributor)
end
it "searches each index path for ruby files" do
config.index_paths.replace ['/path/to/indices', '/path/to/other/indices']
expect(Dir).to receive(:[]).with('/path/to/indices/**/*.rb').once.
and_return([])
expect(Dir).to receive(:[]).with('/path/to/other/indices/**/*.rb').once.
and_return([])
config.preload_indices
end
it "loads each file returned" do
config.index_paths.replace ['/path/to/indices']
allow(Dir).to receive_messages :[] => [
'/path/to/indices/foo_index.rb',
'/path/to/indices/bar_index.rb'
]
expect(ActiveSupport::Dependencies).to receive(:require_or_load).
with('/path/to/indices/foo_index.rb').once
expect(ActiveSupport::Dependencies).to receive(:require_or_load).
with('/path/to/indices/bar_index.rb').once
config.preload_indices
end
it "does not double-load indices" do
config.index_paths.replace ['/path/to/indices']
allow(Dir).to receive_messages :[] => [
'/path/to/indices/foo_index.rb',
'/path/to/indices/bar_index.rb'
]
expect(ActiveSupport::Dependencies).to receive(:require_or_load).
with('/path/to/indices/foo_index.rb').once
expect(ActiveSupport::Dependencies).to receive(:require_or_load).
with('/path/to/indices/bar_index.rb').once
config.preload_indices
config.preload_indices
end
it 'adds distributed indices' do
expect(distributor).to receive(:reconcile)
config.preload_indices
end
it 'does not add distributed indices if disabled' do
write_configuration('distributed_indices' => false)
expect(distributor).not_to receive(:reconcile)
config.preload_indices
end
end
describe '#render' do
before :each do
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']
expect(Dir).to receive(:[]).with('/path/to/indices/**/*.rb').once.
and_return([])
expect(Dir).to receive(:[]).with('/path/to/other/indices/**/*.rb').once.
and_return([])
config.render
end
it "loads each file returned" do
config.index_paths.replace ['/path/to/indices']
allow(Dir).to receive_messages :[] => [
'/path/to/indices/foo_index.rb',
'/path/to/indices/bar_index.rb'
]
expect(ActiveSupport::Dependencies).to receive(:require_or_load).
with('/path/to/indices/foo_index.rb').once
expect(ActiveSupport::Dependencies).to receive(:require_or_load).
with('/path/to/indices/bar_index.rb').once
config.render
end
it "does not double-load indices" do
config.index_paths.replace ['/path/to/indices']
allow(Dir).to receive_messages :[] => [
'/path/to/indices/foo_index.rb',
'/path/to/indices/bar_index.rb'
]
expect(ActiveSupport::Dependencies).to receive(:require_or_load).
with('/path/to/indices/foo_index.rb').once
expect(ActiveSupport::Dependencies).to receive(:require_or_load).
with('/path/to/indices/bar_index.rb').once
config.preload_indices
config.preload_indices
end
end
describe '#render_to_file' do
let(:file) { double('file') }
let(:output) { config.render }
before :each do
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'
expect(config).to receive(:open).with('/path/to/file.config', 'w').
and_yield(file)
expect(file).to receive(:write).with(output)
config.render_to_file
end
it "creates a directory at the binlog_path" do
allow(FileUtils).to receive_messages :mkdir_p => true
allow(config).to receive_messages :searchd => double(:binlog_path => '/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
allow(FileUtils).to receive_messages :mkdir_p => true
allow(config).to receive_messages :searchd => double(:binlog_path => '')
expect(FileUtils).not_to receive(:mkdir_p)
config.render_to_file
end
end
describe '#searchd' do
describe '#address' do
it "defaults to 127.0.0.1" do
expect(config.searchd.address).to eq('127.0.0.1')
end
it "respects the address setting" do
write_configuration('address' => '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
expect(config.searchd.mysql41).to eq(9306)
end
it "respects the port setting" do
write_configuration('port' => 9313)
expect(config.searchd.mysql41).to eq(9313)
end
it "respects the mysql41 setting" do
write_configuration('mysql41' => 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
describe '#settings' do
context 'YAML file exists' do
before :each do
allow(File).to receive_messages :exists? => true
end
it "reads from the YAML file" do
expect(File).to receive(:read).and_return('')
config.settings
end
it "uses the settings for the given environment" do
allow(File).to receive_messages :read => {
'test' => {'foo' => 'bar'},
'staging' => {'baz' => 'qux'}
}.to_yaml
allow(Rails).to receive_messages :env => 'staging'
expect(config.settings['baz']).to eq('qux')
end
it "remembers the file contents" do
expect(File).to receive(:read).and_return('')
config.settings
config.settings
end
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'
expect(config.settings.class).to eq(Hash)
end
end
context 'YAML file does not exist' do
before :each do
allow(File).to receive_messages :exists? => false
end
it "does not read the file" do
expect(File).not_to receive(:read)
config.settings
end
it "returns a hash" do
expect(config.settings.class).to eq(Hash)
end
end
end
describe '#version' do
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'
expect(config.version).to eq('2.0.4')
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/connection_spec.rb 0000664 0000000 0000000 00000004346 13411321301 0024466 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::Connection do
describe '.take' do
let(:pool) { double 'pool' }
let(:connection) { double 'connection', :base_error => StandardError }
let(:error) { ThinkingSphinx::QueryExecutionError.new 'failed' }
let(:translated_error) { ThinkingSphinx::SphinxError.new }
before :each do
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'
end
it "yields a connection from the pool" do
ThinkingSphinx::Connection.take do |c|
expect(c).to eq(connection)
end
end
it "retries errors once" do
tries = 0
expect {
ThinkingSphinx::Connection.take do |c|
tries += 1
raise error if tries < 2
end
}.not_to raise_error
end
it "retries errors twice" do
tries = 0
expect {
ThinkingSphinx::Connection.take do |c|
tries += 1
raise error if tries < 3
end
}.not_to raise_error
end
it "raises a translated error if it fails three times" do
tries = 0
expect {
ThinkingSphinx::Connection.take do |c|
tries += 1
raise error if tries < 4
end
}.to raise_error(ThinkingSphinx::SphinxError)
end
[ThinkingSphinx::SyntaxError, ThinkingSphinx::ParseError].each do |klass|
context klass.name do
let(:translated_error) { klass.new }
it "raises the error" do
expect {
ThinkingSphinx::Connection.take { |c| raise error }
}.to raise_error(klass)
end
it "does not yield the connection more than once" do
yields = 0
begin
ThinkingSphinx::Connection.take do |c|
yields += 1
raise error
end
rescue klass
#
end
expect(yields).to eq(1)
end
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/deletion_spec.rb 0000664 0000000 0000000 00000003175 13411321301 0024131 0 ustar 00root root 0000000 0000000 # 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) }
before :each do
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
expect(connection).to receive(:execute).
with('UPDATE foo_core SET sphinx_deleted = 1 WHERE id IN (14)')
ThinkingSphinx::Deletion.perform index, 7
end
it "doesn't care about Sphinx errors" do
allow(connection).to receive(:execute).
and_raise(ThinkingSphinx::ConnectionError.new(''))
expect {
ThinkingSphinx::Deletion.perform index, 7
}.not_to raise_error
end
end
context "index is real-time" do
before :each do
allow(index).to receive_messages :type => 'rt'
end
it "deletes the record to false" do
expect(connection).to receive(:execute).
with('DELETE FROM foo_core WHERE id = 14')
ThinkingSphinx::Deletion.perform index, 7
end
it "doesn't care about Sphinx errors" do
allow(connection).to receive(:execute).
and_raise(ThinkingSphinx::ConnectionError.new(''))
expect {
ThinkingSphinx::Deletion.perform index, 7
}.not_to raise_error
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/deltas/ 0000775 0000000 0000000 00000000000 13411321301 0022235 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/deltas/default_delta_spec.rb 0000664 0000000 0000000 00000007006 13411321301 0026374 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::Deltas::DefaultDelta do
let(:delta) { ThinkingSphinx::Deltas::DefaultDelta.new adapter }
let(:adapter) {
double('adapter', :quoted_table_name => 'articles', :quote => 'delta')
}
describe '#clause' do
context 'for a delta source' do
before :each do
allow(adapter).to receive_messages :boolean_value => 't'
end
it "limits results to those flagged as deltas" do
expect(delta.clause(true)).to eq("articles.delta = t")
end
end
end
describe '#delete' do
let(:connection) { double('connection', :execute => nil) }
let(:index) { double('index', :name => 'foo_core',
:document_id_for_instance => 14) }
let(:instance) { double('instance', :id => 7) }
before :each do
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
expect(connection).to receive(:execute).with('UPDATE STATEMENT')
delta.delete index, instance
end
it "builds the update query for the given index" do
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
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
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
allow(connection).to receive(:execute).
and_raise(ThinkingSphinx::ConnectionError.new(''))
expect { delta.delete index, instance }.not_to raise_error
end
end
describe '#index' do
let(:config) { double('config', :controller => controller,
:settings => {}) }
let(:controller) { double('controller') }
let(:commander) { double('commander', :call => true) }
before :each do
stub_const 'ThinkingSphinx::Commander', commander
allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
it "indexes the given index" do
expect(commander).to receive(:call).with(
:index_sql, config, :indices => ['foo_delta'], :verbose => false
)
delta.index double('index', :name => 'foo_delta')
end
end
describe '#reset_query' do
it "updates the table to set delta flags to false" do
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
describe '#toggle' do
let(:instance) { double('instance') }
it "sets instance's delta flag to true" do
expect(instance).to receive(:delta=).with(true)
delta.toggle(instance)
end
end
describe '#toggled?' do
let(:instance) { double('instance') }
it "returns the delta flag value when true" do
allow(instance).to receive_messages :delta? => true
expect(delta.toggled?(instance)).to be_truthy
end
it "returns the delta flag value when false" do
allow(instance).to receive_messages :delta? => false
expect(delta.toggled?(instance)).to be_falsey
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/deltas_spec.rb 0000664 0000000 0000000 00000004175 13411321301 0023603 0 ustar 00root root 0000000 0000000 # 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
expect(ThinkingSphinx::Deltas.processor_for(true)).
to eq(ThinkingSphinx::Deltas::DefaultDelta)
end
it "returns the class when given one" do
klass = Class.new
expect(ThinkingSphinx::Deltas.processor_for(klass)).to eq(klass)
end
it "instantiates a class from the name as a string" do
expect(ThinkingSphinx::Deltas.
processor_for('ThinkingSphinx::Deltas::DefaultDelta')).
to eq(ThinkingSphinx::Deltas::DefaultDelta)
end
end
describe '.suspend' do
let(:config) { double('config',
:indices_for_references => [core_index, delta_index]) }
let(:core_index) { double('index', :name => 'user_core',
:delta_processor => processor, :delta? => false) }
let(:delta_index) { double('index', :name => 'user_core',
:delta_processor => processor, :delta? => true) }
let(:processor) { double('processor', :index => true) }
before :each do
allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
it "executes the given block" do
variable = :foo
ThinkingSphinx::Deltas.suspend :user do
variable = :bar
end
expect(variable).to eq(:bar)
end
it "suspends deltas within the block" do
ThinkingSphinx::Deltas.suspend :user do
expect(ThinkingSphinx::Deltas).to be_suspended
end
end
it "removes the suspension after the block" do
ThinkingSphinx::Deltas.suspend :user do
#
end
expect(ThinkingSphinx::Deltas).not_to be_suspended
end
it "processes the delta indices for the given reference" do
expect(processor).to receive(:index).with(delta_index)
ThinkingSphinx::Deltas.suspend :user do
#
end
end
it "does not process the core indices for the given reference" do
expect(processor).not_to receive(:index).with(core_index)
ThinkingSphinx::Deltas.suspend :user do
#
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/errors_spec.rb 0000664 0000000 0000000 00000007130 13411321301 0023635 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::SphinxError do
describe '.new_from_mysql' do
let(:error) { double 'error', :message => 'index foo: unknown error',
:backtrace => ['foo', 'bar'] }
it "translates syntax errors" do
allow(error).to receive_messages :message => 'index foo: syntax error: something is wrong'
expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
to be_a(ThinkingSphinx::SyntaxError)
end
it "translates parse errors" do
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
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
allow(error).to receive_messages :message => 'index foo: query error: something is wrong'
expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
to be_a(ThinkingSphinx::QueryError)
end
it "translates connection errors" do
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)
end
it 'translates out-of-bounds errors' do
allow(error).to receive_messages :message => "offset out of bounds (offset=1001, max_matches=1000)"
expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
to be_a(ThinkingSphinx::OutOfBoundsError)
end
it 'prefixes the connection error message' do
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).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
allow(error).to receive_messages :message => "Communications link failure"
expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
to be_a(ThinkingSphinx::ConnectionError)
end
it 'prefixes the jdbc connection error message' do
allow(error).to receive_messages :message => "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
allow(error).to receive_messages :message => 'index foo: unknown error: something is wrong'
expect(ThinkingSphinx::SphinxError.new_from_mysql(error)).
to be_a(ThinkingSphinx::SphinxError)
end
it "keeps the original error's backtrace" do
allow(error).to receive_messages :message => 'index foo: unknown error: something is wrong'
expect(ThinkingSphinx::SphinxError.new_from_mysql(error).
backtrace).to eq(error.backtrace)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/excerpter_spec.rb 0000664 0000000 0000000 00000003246 13411321301 0024326 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::Excerpter do
let(:excerpter) { ThinkingSphinx::Excerpter.new('index', 'all words') }
let(:connection) {
double('connection', :execute => [{'snippet' => 'some highlighted words'}])
}
before :each do
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
expect(Riddle::Query).to receive(:snippets).
with('all of the words', 'index', 'all words',
ThinkingSphinx::Excerpter::DefaultOptions).
and_return('CALL SNIPPETS')
excerpter.excerpt!('all of the words')
end
it "respects the provided options" do
excerpter = ThinkingSphinx::Excerpter.new('index', 'all words',
:before_match => '', :chunk_separator => ' -- ')
expect(Riddle::Query).to receive(:snippets).
with('all of the words', 'index', 'all words',
:before_match => '', :after_match => '',
:chunk_separator => ' -- ').
and_return('CALL SNIPPETS')
excerpter.excerpt!('all of the words')
end
it "sends the snippets call to Sphinx" do
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
allow(connection).to receive_messages :execute => [{'snippet' => 'some highlighted words'}]
expect(excerpter.excerpt!('all of the words')).to eq('some highlighted words')
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/facet_search_spec.rb 0000664 0000000 0000000 00000007563 13411321301 0024742 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::FacetSearch do
let(:facet_search) { ThinkingSphinx::FacetSearch.new '', {} }
let(:batch) { double('batch', :searches => [], :populate => true) }
let(:index_set) { [] }
let(:index) { double('index', :facets => [property_a, property_b],
:name => 'foo_core') }
let(:property_a) { double('property', :name => 'price_bracket',
:multi? => false) }
let(:property_b) { double('property', :name => 'category_id',
:multi? => false) }
let(:configuration) { double 'configuration', :settings => {}, :index_set_class => double(:new => index_set) }
before :each do
stub_const 'ThinkingSphinx::BatchedSearch', double(:new => batch)
stub_const 'ThinkingSphinx::Search', DumbSearch
stub_const 'ThinkingSphinx::Middlewares::RAW_ONLY', double
stub_const 'ThinkingSphinx::Configuration',
double(:instance => configuration)
index_set << index << double('index', :facets => [], :name => 'bar_core')
end
DumbSearch = ::Struct.new(:query, :options) do
def raw
[{
'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
expect(facet_search[:price_bracket]).to eq({3 => 5})
end
end
describe '#populate' do
it "queries on each facet with a grouped search in a batch" do
facet_search.populate
expect(batch.searches.detect { |search|
search.options[:group_by] == 'price_bracket'
}).not_to be_nil
end
it "limits query for a facet to just indices that have that facet" do
facet_search.populate
expect(batch.searches.detect { |search|
search.options[:indices] == ['foo_core']
}).not_to be_nil
end
it "limits facets to the specified set" do
facet_search.options[:facets] = [:category_id]
facet_search.populate
expect(batch.searches.collect { |search|
search.options[:group_by]
}).to eq(['category_id'])
end
it "aliases the class facet from sphinx_internal_class" do
allow(property_a).to receive_messages :name => 'sphinx_internal_class'
facet_search.populate
expect(facet_search[:class]).to eq({'Foo' => 5})
end
it "uses the @groupby value for MVAs" do
allow(property_a).to receive_messages :name => 'tag_ids', :multi? => true
facet_search.populate
expect(facet_search[:tag_ids]).to eq({2 => 5})
end
[:max_matches, :limit].each do |setting|
it "sets #{setting} in each search" do
facet_search.populate
batch.searches.each { |search|
expect(search.options[setting]).to eq(1000)
}
end
it "respects configured max_matches values for #{setting}" do
configuration.settings['max_matches'] = 1234
facet_search.populate
batch.searches.each { |search|
expect(search.options[setting]).to eq(1234)
}
end
end
[:limit, :per_page].each do |setting|
it "respects #{setting} option if set" do
facet_search = ThinkingSphinx::FacetSearch.new '', {setting => 42}
facet_search.populate
batch.searches.each { |search|
expect(search.options[setting]).to eq(42)
}
end
it "allows separate #{setting} and max_matches settings to support pagination" do
configuration.settings['max_matches'] = 500
facet_search = ThinkingSphinx::FacetSearch.new '', {setting => 10}
facet_search.populate
batch.searches.each do |search|
expect(search.options[setting]).to eq(10)
expect(search.options[:max_matches]).to eq(500)
end
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/hooks/ 0000775 0000000 0000000 00000000000 13411321301 0022104 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/hooks/guard_presence_spec.rb 0000664 0000000 0000000 00000001420 13411321301 0026426 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/index_set_spec.rb 0000664 0000000 0000000 00000007364 13411321301 0024314 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx; end
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/module/delegation'
require 'thinking_sphinx/index_set'
describe ThinkingSphinx::IndexSet do
let(:set) { ThinkingSphinx::IndexSet.new options, configuration }
let(:configuration) { double('configuration', :preload_indices => true,
:indices => []) }
let(:ar_base) { double('ActiveRecord::Base') }
let(:options) { {} }
before :each do
stub_const 'ActiveRecord::Base', ar_base
end
def class_double(name, methods = {}, *superclasses)
klass = double 'class', :name => name, :class => Class
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
it "ensures the indices are loaded" do
expect(configuration).to receive(:preload_indices)
set.to_a
end
it "returns all non-distributed indices when no models or indices are specified" do
article_core = double 'index', :name => 'article_core',
:distributed? => false
user_core = double 'index', :name => 'user_core',
:distributed? => false
distributed = double 'index', :name => 'user', :distributed? => true
configuration.indices.replace [article_core, user_core, distributed]
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)
]
options[:classes] = [class_double('Article', :column_names => [])]
expect(set.to_a.length).to eq(1)
end
it "requests indices for any STI superclasses" do
configuration.indices.replace [
double(:reference => :article, :distributed? => false),
double(:reference => :opinion_article, :distributed? => false),
double(:reference => :page, :distributed? => false)
]
article = class_double('Article', :column_names => [:type])
opinion = class_double('OpinionArticle', {:column_names => [:type]},
article)
options[:classes] = [opinion]
expect(set.to_a.length).to eq(2)
end
it "does not use MTI superclasses" do
configuration.indices.replace [
double(:reference => :article, :distributed? => false),
double(:reference => :opinion_article, :distributed? => false),
double(:reference => :page, :distributed? => false)
]
article = class_double('Article', :column_names => [])
opinion = class_double('OpinionArticle', {:column_names => []}, article)
options[:classes] = [opinion]
expect(set.to_a.length).to eq(1)
end
it "uses named indices if names are provided" do
article_core = double('index', :name => 'article_core')
user_core = double('index', :name => 'user_core')
configuration.indices.replace [article_core, user_core]
options[:indices] = ['article_core']
expect(set.to_a).to eq([article_core])
end
it "selects from the full index set those with matching references" do
configuration.indices.replace [
double('index', :reference => :article, :distributed? => false),
double('index', :reference => :book, :distributed? => false),
double('index', :reference => :page, :distributed? => false)
]
options[:references] = [:book, :article]
expect(set.to_a.length).to eq(2)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/index_spec.rb 0000664 0000000 0000000 00000010717 13411321301 0023435 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::Index do
let(:configuration) { Struct.new(:indices, :settings).new([], {}) }
before :each do
allow(ThinkingSphinx::Configuration).to receive_messages :instance => configuration
end
describe '.define' do
let(:index) { double('index', :definition_block= => nil) }
context 'with ActiveRecord' do
before :each do
allow(ThinkingSphinx::ActiveRecord::Index).to receive_messages :new => index
end
it "creates an ActiveRecord index" do
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
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)
expect(configuration.indices).to include(index)
end
it "sets the block in the index" do
expect(index).to receive(:definition_block=).with instance_of(Proc)
ThinkingSphinx::Index.define(:user, :with => :active_record) do
indexes name
end
end
context 'with a delta' do
let(:delta_index) { double('delta index', :definition_block= => nil) }
let(:processor) { double('delta processor') }
before :each do
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
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
expect(ThinkingSphinx::ActiveRecord::Index).to receive(:new).
with(:user,
hash_including(:delta? => true, :delta_processor => processor)
).once.
and_return delta_index
ThinkingSphinx::Index.define :user,
:with => :active_record,
:delta => true
end
it "appends both indices to the collection" do
ThinkingSphinx::Index.define :user,
:with => :active_record,
:delta => true
expect(configuration.indices).to include(index)
expect(configuration.indices).to include(delta_index)
end
it "sets the block in the index" do
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,
:delta => true) do
indexes name
end
end
end
end
context 'with Real-Time' do
before :each do
allow(ThinkingSphinx::RealTime::Index).to receive_messages :new => index
end
it "creates a real-time index" do
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
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)
expect(configuration.indices).to include(index)
end
it "sets the block in the index" do
expect(index).to receive(:definition_block=).with instance_of(Proc)
ThinkingSphinx::Index.define(:user, :with => :real_time) do
indexes name
end
end
end
end
describe '#initialize' do
it "is fine with no defaults from settings" do
expect(ThinkingSphinx::Index.new(:user, {}).options).to eq({})
end
it "respects defaults from settings" do
configuration.settings['index_options'] = {'delta' => true}
expect(ThinkingSphinx::Index.new(:user, {}).options).to eq({:delta => true})
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/interfaces/ 0000775 0000000 0000000 00000000000 13411321301 0023104 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/interfaces/daemon_spec.rb 0000664 0000000 0000000 00000003164 13411321301 0025712 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/interfaces/real_time_spec.rb 0000664 0000000 0000000 00000006023 13411321301 0026405 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/interfaces/sql_spec.rb 0000664 0000000 0000000 00000006523 13411321301 0025250 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/masks/ 0000775 0000000 0000000 00000000000 13411321301 0022077 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/masks/pagination_mask_spec.rb 0000664 0000000 0000000 00000005535 13411321301 0026612 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Masks; end
end
require 'active_support/core_ext/object/blank'
require 'thinking_sphinx/masks/pagination_mask'
describe ThinkingSphinx::Masks::PaginationMask do
let(:search) { double('search', :options => {}, :meta => {},
:per_page => 20, :current_page => 1) }
let(:mask) { ThinkingSphinx::Masks::PaginationMask.new search }
describe '#first_page?' do
it "returns true when on the first page" do
expect(mask).to be_first_page
end
it "returns false on other pages" do
allow(search).to receive_messages :current_page => 2
expect(mask).not_to be_first_page
end
end
describe '#last_page?' do
before :each do
search.meta['total'] = '44'
end
it "is true when there's no more pages" do
allow(search).to receive_messages :current_page => 3
expect(mask).to be_last_page
end
it "is false when there's still more pages" do
expect(mask).not_to be_last_page
end
end
describe '#next_page' do
before :each do
search.meta['total'] = '44'
end
it "should return one more than the current page" do
expect(mask.next_page).to eq(2)
end
it "should return nil if on the last page" do
allow(search).to receive_messages :current_page => 3
expect(mask.next_page).to be_nil
end
end
describe '#next_page?' do
before :each do
search.meta['total'] = '44'
end
it "is true when there is a second page" do
expect(mask.next_page?).to be_truthy
end
it "is false when there's no more pages" do
allow(search).to receive_messages :current_page => 3
expect(mask.next_page?).to be_falsey
end
end
describe '#previous_page' do
before :each do
search.meta['total'] = '44'
end
it "should return one less than the current page" do
allow(search).to receive_messages :current_page => 2
expect(mask.previous_page).to eq(1)
end
it "should return nil if on the first page" do
expect(mask.previous_page).to be_nil
end
end
describe '#total_entries' do
before :each do
search.meta['total_found'] = '12'
end
it "returns the total found from the search request metadata" do
expect(mask.total_entries).to eq(12)
end
end
describe '#total_pages' do
before :each do
search.meta['total'] = '40'
search.meta['total_found'] = '44'
end
it "uses the total available from the search request metadata" do
expect(mask.total_pages).to eq(2)
end
it "should allow for custom per_page values" do
allow(search).to receive_messages :per_page => 40
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
expect(mask.total_pages).to eq(0)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/masks/scopes_mask_spec.rb 0000664 0000000 0000000 00000006715 13411321301 0025756 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Masks; end
end
require 'thinking_sphinx/masks/scopes_mask'
describe ThinkingSphinx::Masks::ScopesMask do
let(:search) { double('search', :options => {}, :per_page => 20,
:populated? => false) }
let(:mask) { ThinkingSphinx::Masks::ScopesMask.new search }
before :each do
allow(FileUtils).to receive_messages :mkdir_p => true
end
describe '#search' do
it "replaces the query if one is supplied" do
expect(search).to receive(:query=).with('bar')
mask.search('bar')
end
it "keeps the existing query when only options are offered" do
expect(search).not_to receive(:query=)
mask.search :with => {:foo => :bar}
end
it "merges conditions" do
search.options[:conditions] = {:foo => 'bar'}
mask.search :conditions => {:baz => 'qux'}
expect(search.options[:conditions]).to eq({:foo => 'bar', :baz => 'qux'})
end
it "merges filters" do
search.options[:with] = {:foo => :bar}
mask.search :with => {:baz => :qux}
expect(search.options[:with]).to eq({:foo => :bar, :baz => :qux})
end
it "merges exclusive filters" do
search.options[:without] = {:foo => :bar}
mask.search :without => {:baz => :qux}
expect(search.options[:without]).to eq({:foo => :bar, :baz => :qux})
end
it "appends excluded ids" do
search.options[:without_ids] = [1, 3]
mask.search :without_ids => [5, 7]
expect(search.options[:without_ids]).to eq([1, 3, 5, 7])
end
it "replaces the retry_stale option" do
search.options[:retry_stale] = true
mask.search :retry_stale => 6
expect(search.options[:retry_stale]).to eq(6)
end
it "returns the original search object" do
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
expect(search).to receive(:query=).with('bar')
mask.search_for_ids('bar')
end
it "keeps the existing query when only options are offered" do
expect(search).not_to receive(:query=)
mask.search_for_ids :with => {:foo => :bar}
end
it "merges conditions" do
search.options[:conditions] = {:foo => 'bar'}
mask.search_for_ids :conditions => {:baz => 'qux'}
expect(search.options[:conditions]).to eq({:foo => 'bar', :baz => 'qux'})
end
it "merges filters" do
search.options[:with] = {:foo => :bar}
mask.search_for_ids :with => {:baz => :qux}
expect(search.options[:with]).to eq({:foo => :bar, :baz => :qux})
end
it "merges exclusive filters" do
search.options[:without] = {:foo => :bar}
mask.search_for_ids :without => {:baz => :qux}
expect(search.options[:without]).to eq({:foo => :bar, :baz => :qux})
end
it "appends excluded ids" do
search.options[:without_ids] = [1, 3]
mask.search_for_ids :without_ids => [5, 7]
expect(search.options[:without_ids]).to eq([1, 3, 5, 7])
end
it "replaces the retry_stale option" do
search.options[:retry_stale] = true
mask.search_for_ids :retry_stale => 6
expect(search.options[:retry_stale]).to eq(6)
end
it "adds the ids_only option" do
mask.search_for_ids
expect(search.options[:ids_only]).to be_truthy
end
it "returns the original search object" do
expect(mask.search_for_ids.object_id).to eq(search.object_id)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/middlewares/ 0000775 0000000 0000000 00000000000 13411321301 0023261 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb 0000664 0000000 0000000 00000014147 13411321301 0031711 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Middlewares; end
class Search; end
end
require 'thinking_sphinx/middlewares/middleware'
require 'thinking_sphinx/middlewares/active_record_translator'
require 'thinking_sphinx/search/stale_ids_exception'
describe ThinkingSphinx::Middlewares::ActiveRecordTranslator do
let(:app) { double('app', :call => true) }
let(:middleware) {
ThinkingSphinx::Middlewares::ActiveRecordTranslator.new app }
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}
end
describe '#call' do
before :each do
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)
allow(model).to receive_messages :where => [instance]
context[:results] << raw_result(24, model_name)
middleware.call [context]
expect(context[:results]).to eq([instance])
end
it "only queries the model once for the given search results" do
model_name = double('article', :constantize => model)
instance_a = double('instance', :id => 24)
instance_b = double('instance', :id => 42)
context[:results] << raw_result(24, model_name)
context[:results] << raw_result(42, model_name)
expect(model).to receive(:where).once.and_return([instance_a, instance_b])
middleware.call [context]
end
it "handles multiple models" do
article_model = double('article model', :primary_key => :id)
article_name = double('article name', :constantize => article_model)
article = double('article instance', :id => 24)
user_model = double('user model', :primary_key => :id)
user_name = double('user name', :constantize => user_model)
user = double('user instance', :id => 12)
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)
expect(article_model).to receive(:where).once.and_return([article])
expect(user_model).to receive(:where).once.and_return([user])
middleware.call [context]
end
it "sorts the results according to Sphinx order, not database order" do
model_name = double('article', :constantize => model)
instance_1 = double('instance 1', :id => 1)
instance_2 = double('instance 2', :id => 2)
context[:results] << raw_result(2, model_name)
context[:results] << raw_result(1, model_name)
allow(model).to receive_messages(:where => [instance_1, instance_2])
middleware.call [context]
expect(context[:results]).to eq([instance_2, instance_1])
end
it "returns objects in database order if a SQL order clause is supplied" do
model_name = double('article', :constantize => model)
instance_1 = double('instance 1', :id => 1)
instance_2 = double('instance 2', :id => 2)
context[:results] << raw_result(2, model_name)
context[:results] << raw_result(1, model_name)
allow(model).to receive_messages(:order => model, :where => [instance_1, instance_2])
search.options[:sql] = {:order => 'name DESC'}
middleware.call [context]
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
allow(model).to receive_messages :unscoped => relation
context[:results] << raw_result(1, model_name)
end
it "passes through SQL include options to the relation" do
search.options[:sql] = {:include => :association}
expect(relation).to receive(:includes).with(:association).
and_return(relation)
middleware.call [context]
end
it "passes through SQL join options to the relation" do
search.options[:sql] = {:joins => :association}
expect(relation).to receive(:joins).with(:association).and_return(relation)
middleware.call [context]
end
it "passes through SQL order options to the relation" do
search.options[:sql] = {:order => 'name DESC'}
expect(relation).to receive(:order).with('name DESC').and_return(relation)
middleware.call [context]
end
it "passes through SQL select options to the relation" do
search.options[:sql] = {:select => :column}
expect(relation).to receive(:select).with(:column).and_return(relation)
middleware.call [context]
end
it "passes through SQL group options to the relation" do
search.options[:sql] = {:group => :column}
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
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/middlewares/geographer_spec.rb 0000664 0000000 0000000 00000006116 13411321301 0026747 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Middlewares; end
end
require 'thinking_sphinx/middlewares/middleware'
require 'thinking_sphinx/middlewares/geographer'
require 'thinking_sphinx/float_formatter'
describe ThinkingSphinx::Middlewares::Geographer do
let(:app) { double('app', :call => true) }
let(:middleware) { ThinkingSphinx::Middlewares::Geographer.new app }
let(:context) { {:sphinxql => sphinx_sql, :indices => [], :panes => []} }
let(:sphinx_sql) { double('sphinx_sql') }
let(:search) { double('search', :options => {}) }
before :each do
stub_const 'ThinkingSphinx::Panes::DistancePane', double
allow(context).to receive_messages :search => search
end
describe '#call' do
context 'no geodistance location provided' do
before :each do
search.options[:geo] = nil
end
it "doesn't add anything if :geo is nil" do
expect(sphinx_sql).not_to receive(:prepend_values)
middleware.call [context]
end
end
context 'geodistance location provided' do
before :each do
search.options[:geo] = [0.1, 0.2]
end
it "adds the geodist function when given a :geo option" do
expect(sphinx_sql).to receive(:prepend_values).
with('GEODIST(0.1, 0.2, lat, lng) AS geodist').
and_return(sphinx_sql)
middleware.call [context]
end
it "adds the distance pane" do
allow(sphinx_sql).to receive_messages :prepend_values => sphinx_sql
middleware.call [context]
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'
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)
middleware.call [context]
end
it "uses latitude if any index has that but not lat as an attribute" do
context[:indices] << double('index',
:unique_attribute_names => ['latitude'], :name => 'an_index')
expect(sphinx_sql).to receive(:prepend_values).
with('GEODIST(0.1, 0.2, latitude, lng) AS geodist').
and_return(sphinx_sql)
middleware.call [context]
end
it "uses latitude if any index has that but not lat as an attribute" do
context[:indices] << double('index',
:unique_attribute_names => ['longitude'], :name => 'an_index')
expect(sphinx_sql).to receive(:prepend_values).
with('GEODIST(0.1, 0.2, lat, longitude) AS geodist').
and_return(sphinx_sql)
middleware.call [context]
end
it "handles very small values" do
search.options[:geo] = [0.0000001, 0.00000000002]
expect(sphinx_sql).to receive(:prepend_values).
with('GEODIST(0.0000001, 0.00000000002, lat, lng) AS geodist').
and_return(sphinx_sql)
middleware.call [context]
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/middlewares/glazier_spec.rb 0000664 0000000 0000000 00000003513 13411321301 0026257 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Middlewares; end
end
require 'thinking_sphinx/middlewares/middleware'
require 'thinking_sphinx/middlewares/glazier'
describe ThinkingSphinx::Middlewares::Glazier do
let(:app) { double('app', :call => true) }
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(:raw_result) {
{'sphinx_internal_class' => 'Article', 'sphinx_internal_id' => 10} }
describe '#call' do
before :each do
stub_const 'ThinkingSphinx::Search::Glaze', double(:new => glazed_result)
allow(context).to receive_messages :search => search
end
context 'No panes provided' do
before :each do
context[:panes].clear
end
it "leaves the results as they are" do
middleware.call [context]
expect(context[:results]).to eq([result])
end
end
context 'Panes provided' do
let(:pane_class) { double('pane class') }
before :each do
context[:panes] << pane_class
end
it "replaces each result with a glazed version" do
middleware.call [context]
expect(context[:results]).to eq([glazed_result])
end
it "creates a glazed result for each result" do
expect(ThinkingSphinx::Search::Glaze).to receive(:new).
with(context, result, raw_result, [pane_class]).
and_return(glazed_result)
middleware.call [context]
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/middlewares/inquirer_spec.rb 0000664 0000000 0000000 00000004073 13411321301 0026462 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Middlewares; end
end
require 'thinking_sphinx/middlewares/middleware'
require 'thinking_sphinx/middlewares/inquirer'
describe ThinkingSphinx::Middlewares::Inquirer do
let(:app) { double('app', :call => true) }
let(:middleware) { ThinkingSphinx::Middlewares::Inquirer.new app }
let(:context) { {:sphinxql => sphinx_sql} }
let(:sphinx_sql) { double('sphinx_sql',
:to_sql => 'SELECT * FROM index') }
let(:batch_inquirer) { double('batcher', :append_query => true,
:results => [[:raw], [{'Variable_name' => 'meta', 'Value' => 'value'}]]) }
before :each do
batch_class = double
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
end
describe '#call' do
it "passes through the SphinxQL from a Riddle::Query::Select object" do
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
it "sets up the raw results" do
middleware.call [context]
expect(context[:raw]).to eq([:raw])
end
it "sets up the meta results as a hash" do
middleware.call [context]
expect(context[:meta]).to eq({'meta' => 'value'})
end
it "uses the raw values as the initial results" do
middleware.call [context]
expect(context[:results]).to eq([:raw])
end
context "with mysql2 result" do
class FakeResult
include Enumerable
def each; [{"fake" => "value"}].each { |m| yield m }; end
end
let(:batch_inquirer) { double('batcher', :append_query => true,
:results => [
FakeResult.new, [{'Variable_name' => 'meta', 'Value' => 'value'}]
])
}
it "converts the results into an array" do
middleware.call [context]
expect(context[:results]).to be_a Array
end
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/middlewares/sphinxql_spec.rb 0000664 0000000 0000000 00000031371 13411321301 0026473 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Middlewares; end
end
module ActiveRecord
class Base; end
end
class SphinxQLSubclass
end
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/inflections'
require 'thinking_sphinx/middlewares/middleware'
require 'thinking_sphinx/middlewares/sphinxql'
require 'thinking_sphinx/errors'
describe ThinkingSphinx::Middlewares::SphinxQL do
let(:app) { double('app', :call => true) }
let(:middleware) { ThinkingSphinx::Middlewares::SphinxQL.new app }
let(:context) { {} }
let(:search) { double('search', :query => '', :options => {},
:offset => 0, :per_page => 5) }
let(:index_set) { [double(:name => 'article_core', :options => {})] }
let(:sphinx_sql) { double('sphinx_sql', :from => true, :offset => true,
:limit => true, :where => true, :matching => true, :values => true) }
let(:query) { double('query') }
let(:configuration) { double('configuration', :settings => {},
index_set_class: set_class) }
let(:set_class) { double(:new => index_set) }
before :each do
stub_const 'Riddle::Query::Select', double(:new => sphinx_sql)
stub_const 'ThinkingSphinx::Search::Query', double(:new => query)
allow(context).to receive_messages :search => search, :configuration => configuration
end
describe '#call' do
it "uses the indexes for the FROM clause" do
index_set.replace [
double('index', :name => 'article_core', :options => {}),
double('index', :name => 'user_core', :options => {})
]
expect(sphinx_sql).to receive(:from).with('`article_core`', '`user_core`').
and_return(sphinx_sql)
middleware.call [context]
end
it "finds index objects for the given models and indices options" do
klass = double(:column_names => [], :inheritance_column => 'type',
:name => 'User')
search.options[:classes] = [klass]
search.options[:indices] = ['user_core']
allow(index_set.first).to receive_messages :reference => :user
expect(set_class).to receive(:new).
with(:classes => [klass], :indices => ['user_core']).
and_return(index_set)
middleware.call [context]
end
it "raises an exception if there's no matching indices" do
index_set.clear
expect {
middleware.call [context]
}.to raise_error(ThinkingSphinx::NoIndicesError)
end
it "generates a Sphinx query from the provided keyword and conditions" do
allow(search).to receive_messages :query => 'tasty'
search.options[:conditions] = {:title => 'pancakes'}
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
allow(query).to receive_messages :to_s => 'waffles'
expect(sphinx_sql).to receive(:matching).with('waffles')
middleware.call [context]
end
it "requests a starred query if the :star option is set to true" do
search.options[:star] = true
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
expect(ThinkingSphinx::Search::Query).to receive(:new) do |query, conditions, star|
expect(conditions[:sphinx_internal_class_name]).to be_nil
query
end
middleware.call [context]
end
it "doesn't append a field condition if all classes match index references" do
model = double('model', :connection => double,
:ancestors => [ActiveRecord::Base], :name => 'Animal')
allow(index_set.first).to receive_messages :reference => :animal
search.options[:classes] = [model]
expect(ThinkingSphinx::Search::Query).to receive(:new) do |query, conditions, star|
expect(conditions[:sphinx_internal_class_name]).to be_nil
query
end
middleware.call [context]
end
it "appends field conditions for the class when searching on subclasses" do
db_connection = double('db connection', :select_values => [],
:schema_cache => double('cache', :table_exists? => false))
supermodel = Class.new(ActiveRecord::Base) do
def self.name; 'Cat'; end
def self.inheritance_column; 'type'; end
end
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
allow(submodel).to receive_messages :connection => db_connection, :column_names => ['type'],
:descendants => []
allow(index_set.first).to receive_messages :reference => :cat
search.options[:classes] = [submodel]
expect(ThinkingSphinx::Search::Query).to receive(:new).with(anything,
hash_including(:sphinx_internal_class_name => '(Lion)'), anything).
and_return(query)
middleware.call [context]
end
it "quotes namespaced models in the class name condition" do
db_connection = double('db connection', :select_values => [],
:schema_cache => double('cache', :table_exists? => false))
supermodel = Class.new(ActiveRecord::Base) do
def self.name; 'Animals::Cat'; end
def self.inheritance_column; 'type'; end
end
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
allow(submodel).to receive_messages :connection => db_connection, :column_names => ['type'],
:descendants => []
allow(index_set.first).to receive_messages :reference => :"animals/cat"
search.options[:classes] = [submodel]
expect(ThinkingSphinx::Search::Query).to receive(:new).with(anything,
hash_including(:sphinx_internal_class_name => '("Animals::Lion")'), anything).
and_return(query)
middleware.call [context]
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')
allow(index_set.first).to receive_messages :reference => :animal
search.options[:classes] = [model]
search.options[:skip_sti] = true
expect(model.connection).not_to receive(:select_values)
middleware.call [context]
end
it "ignores blank subclasses" do
db_connection = double('db connection', :select_values => [''],
:schema_cache => double('cache', :table_exists? => false))
supermodel = Class.new(ActiveRecord::Base) do
def self.name; 'Cat'; end
def self.inheritance_column; 'type'; end
end
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
allow(submodel).to receive_messages :connection => db_connection, :column_names => ['type'],
:descendants => []
allow(index_set.first).to receive_messages :reference => :cat
search.options[:classes] = [submodel]
expect { middleware.call [context] }.to_not raise_error
end
it "filters out deleted values by default" do
expect(sphinx_sql).to receive(:where).with(:sphinx_deleted => false).
and_return(sphinx_sql)
middleware.call [context]
end
it "appends boolean attribute filters to the query" do
search.options[:with] = {:visible => true}
expect(sphinx_sql).to receive(:where).with(hash_including(:visible => true)).
and_return(sphinx_sql)
middleware.call [context]
end
it "appends exclusive filters to the query" do
search.options[:without] = {:tag_ids => [2, 4, 8]}
expect(sphinx_sql).to receive(:where_not).
with(hash_including(:tag_ids => [2, 4, 8])).and_return(sphinx_sql)
middleware.call [context]
end
it "appends the without_ids option as an exclusive filter" do
search.options[:without_ids] = [1, 4, 9]
expect(sphinx_sql).to receive(:where_not).
with(hash_including(:sphinx_internal_id => [1, 4, 9])).
and_return(sphinx_sql)
middleware.call [context]
end
it "appends MVA matches with all values" do
search.options[:with_all] = {:tag_ids => [1, 7]}
expect(sphinx_sql).to receive(:where_all).
with(:tag_ids => [1, 7]).and_return(sphinx_sql)
middleware.call [context]
end
it "appends MVA matches without all of the given values" do
search.options[:without_all] = {:tag_ids => [1, 7]}
expect(sphinx_sql).to receive(:where_not_all).
with(:tag_ids => [1, 7]).and_return(sphinx_sql)
middleware.call [context]
end
it "appends order clauses to the query" do
search.options[:order] = 'created_at ASC'
expect(sphinx_sql).to receive(:order_by).with('created_at ASC').
and_return(sphinx_sql)
middleware.call [context]
end
it "presumes attributes given as symbols should be sorted ascendingly" do
search.options[:order] = :updated_at
expect(sphinx_sql).to receive(:order_by).with('updated_at ASC').
and_return(sphinx_sql)
middleware.call [context]
end
it "appends a group by clause to the query" do
search.options[:group_by] = :foreign_id
allow(search).to receive_messages :masks => []
allow(sphinx_sql).to receive_messages :values => sphinx_sql
expect(sphinx_sql).to receive(:group_by).with('foreign_id').
and_return(sphinx_sql)
middleware.call [context]
end
it "appends a sort within group clause to the query" do
search.options[:order_group_by] = :title
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
allow(search).to receive_messages :offset => 50
expect(sphinx_sql).to receive(:offset).with(50).and_return(sphinx_sql)
middleware.call [context]
end
it "uses the provided limit" do
allow(search).to receive_messages :per_page => 24
expect(sphinx_sql).to receive(:limit).with(24).and_return(sphinx_sql)
middleware.call [context]
end
it "adds the provided select statement" do
search.options[:select] = 'foo as bar'
expect(sphinx_sql).to receive(:values).with('foo as bar').
and_return(sphinx_sql)
middleware.call [context]
end
it "adds the provided group-best count" do
search.options[:group_best] = 5
expect(sphinx_sql).to receive(:group_best).with(5).and_return(sphinx_sql)
middleware.call [context]
end
it "adds the provided having clause" do
search.options[:having] = 'foo > 1'
expect(sphinx_sql).to receive(:having).with('foo > 1').and_return(sphinx_sql)
middleware.call [context]
end
it "uses any provided field weights" do
search.options[:field_weights] = {:title => 3}
expect(sphinx_sql).to receive(:with_options) do |options|
expect(options[:field_weights]).to eq({:title => 3})
sphinx_sql
end
middleware.call [context]
end
it "uses index-defined field weights if they're available" do
index_set.first.options[:field_weights] = {:title => 3}
expect(sphinx_sql).to receive(:with_options).with(
hash_including(:field_weights => {:title => 3})
).and_return(sphinx_sql)
middleware.call [context]
end
it "uses index-defined max matches if it's available" do
index_set.first.options[:max_matches] = 100
expect(sphinx_sql).to receive(:with_options).with(
hash_including(:max_matches => 100)
).and_return(sphinx_sql)
middleware.call [context]
end
it "uses configuration-level max matches if set" do
configuration.settings['max_matches'] = 120
expect(sphinx_sql).to receive(:with_options).with(
hash_including(:max_matches => 120)
).and_return(sphinx_sql)
middleware.call [context]
end
it "uses any given ranker option" do
search.options[:ranker] = 'proximity'
expect(sphinx_sql).to receive(:with_options) do |options|
expect(options[:ranker]).to eq('proximity')
sphinx_sql
end
middleware.call [context]
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb 0000664 0000000 0000000 00000002733 13411321301 0030075 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Middlewares; end
class Search; end
end
require 'thinking_sphinx/middlewares/middleware'
require 'thinking_sphinx/middlewares/stale_id_checker'
require 'thinking_sphinx/search/stale_ids_exception'
describe ThinkingSphinx::Middlewares::StaleIdChecker do
let(:app) { double('app') }
let(:middleware) { ThinkingSphinx::Middlewares::StaleIdChecker.new app }
let(:context) { {:raw => [], :results => []} }
let(:model) { double('model') }
def raw_result(id, model_name)
{'sphinx_internal_id' => id, 'sphinx_internal_class' => model_name}
end
describe '#call' do
it 'passes the call on if there are no nil results' do
context[:raw] << raw_result(24, 'Article')
context[:raw] << raw_result(42, 'Article')
context[:results] << double('instance', :id => 24)
context[:results] << double('instance', :id => 42)
expect(app).to receive(:call)
middleware.call [context]
end
it "raises a stale id exception if ActiveRecord doesn't return ids" do
context[:raw] << raw_result(24, 'Article')
context[:raw] << raw_result(42, 'Article')
context[:results] << double('instance', :id => 24)
context[:results] << nil
expect {
middleware.call [context]
}.to raise_error(ThinkingSphinx::Search::StaleIdsException) { |err|
expect(err.ids).to eq([42])
expect(err.context).to eq(context)
}
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb 0000664 0000000 0000000 00000006601 13411321301 0027754 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Middlewares; end
class Search; end
end
require 'thinking_sphinx/middlewares/middleware'
require 'thinking_sphinx/middlewares/stale_id_filter'
require 'thinking_sphinx/search/stale_ids_exception'
describe ThinkingSphinx::Middlewares::StaleIdFilter do
let(:app) { double('app', :call => true) }
let(:middleware) { ThinkingSphinx::Middlewares::StaleIdFilter.new app }
let(:context) { {:raw => [], :results => []} }
let(:search) { double('search', :options => {}) }
describe '#call' do
before :each do
allow(context).to receive_messages :search => search
end
context 'one stale ids exception' do
before :each do
allow(app).to receive(:call) do
@calls ||= 0
@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]
expect(search.options[:without_ids]).to eq([12])
end
it "respects existing without_ids filters" do
search.options[:without_ids] = [11]
middleware.call [context]
expect(search.options[:without_ids]).to eq([11, 12])
end
end
context 'two stale ids exceptions' do
before :each do
allow(app).to receive(:call) do
@calls ||= 0
@calls += 1
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]
expect(search.options[:without_ids]).to eq([12, 13])
end
it "respects existing without_ids filters" do
search.options[:without_ids] = [11]
middleware.call [context]
expect(search.options[:without_ids]).to eq([11, 12, 13])
end
end
context 'three stale ids exceptions' do
before :each do
allow(app).to receive(:call) do
@calls ||= 0
@calls += 1
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
expect {
middleware.call [context]
}.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
thinking-sphinx-4.1.0/spec/thinking_sphinx/middlewares/valid_options_spec.rb 0000664 0000000 0000000 00000002245 13411321301 0027475 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/panes/ 0000775 0000000 0000000 00000000000 13411321301 0022067 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/panes/attributes_pane_spec.rb 0000664 0000000 0000000 00000001057 13411321301 0026622 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Panes; end
end
require 'thinking_sphinx/panes/attributes_pane'
describe ThinkingSphinx::Panes::AttributesPane do
let(:pane) {
ThinkingSphinx::Panes::AttributesPane.new context, object, raw }
let(:context) { double('context') }
let(:object) { double('object') }
let(:raw) { {} }
describe '#sphinx_attributes' do
it "returns the object's sphinx attributes by default" do
raw['foo'] = 24
expect(pane.sphinx_attributes).to eq({'foo' => 24})
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/panes/distance_pane_spec.rb 0000664 0000000 0000000 00000001735 13411321301 0026231 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Panes; end
end
require 'thinking_sphinx/panes/distance_pane'
describe ThinkingSphinx::Panes::DistancePane do
let(:pane) {
ThinkingSphinx::Panes::DistancePane.new context, object, raw }
let(:context) { double('context') }
let(:object) { double('object') }
let(:raw) { {} }
describe '#distance' do
it "returns the object's geodistance attribute by default" do
raw['geodist'] = 123.45
expect(pane.distance).to eq(123.45)
end
it "converts string geodistances to floats" do
raw['geodist'] = '123.450'
expect(pane.distance).to eq(123.45)
end
end
describe '#geodist' do
it "returns the object's geodistance attribute by default" do
raw['geodist'] = 123.45
expect(pane.geodist).to eq(123.45)
end
it "converts string geodistances to floats" do
raw['geodist'] = '123.450'
expect(pane.geodist).to eq(123.45)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/panes/excerpts_pane_spec.rb 0000664 0000000 0000000 00000003107 13411321301 0026267 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Panes; end
end
require 'thinking_sphinx/panes/excerpts_pane'
describe ThinkingSphinx::Panes::ExcerptsPane do
let(:pane) {
ThinkingSphinx::Panes::ExcerptsPane.new context, object, raw }
let(:context) { {:indices => [double(:name => 'foo_core')]} }
let(:object) { double('object') }
let(:raw) { {} }
let(:search) { double('search', :query => 'foo', :options => {}) }
before :each do
allow(context).to receive_messages :search => search
end
describe '#excerpts' do
let(:excerpter) { double('excerpter') }
let(:excerpts) { double('excerpts object') }
before :each do
stub_const 'ThinkingSphinx::Excerpter', double(:new => excerpter)
allow(ThinkingSphinx::Panes::ExcerptsPane::Excerpts).to receive_messages :new => excerpts
end
it "returns an excerpt glazing" do
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'}
expect(ThinkingSphinx::Excerpter).to receive(:new).
with('alpha', 'foo bar', anything).and_return(excerpter)
pane.excerpts
end
it "passes through excerpts options" do
search.options[:excerpts] = {:before_match => 'foo'}
expect(ThinkingSphinx::Excerpter).to receive(:new).
with(anything, anything, :before_match => 'foo').and_return(excerpter)
pane.excerpts
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/panes/weight_pane_spec.rb 0000664 0000000 0000000 00000000772 13411321301 0025726 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
module Panes; end
end
require 'thinking_sphinx/panes/weight_pane'
describe ThinkingSphinx::Panes::WeightPane do
let(:pane) { ThinkingSphinx::Panes::WeightPane.new context, object, raw }
let(:context) { double('context') }
let(:object) { double('object') }
let(:raw) { {} }
describe '#weight' do
it "returns the object's weight by default" do
raw["weight()"] = 101
expect(pane.weight).to eq(101)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/rake_interface_spec.rb 0000664 0000000 0000000 00000001655 13411321301 0025271 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::RakeInterface do
let(:interface) { ThinkingSphinx::RakeInterface.new }
let(:commander) { double :call => nil }
before :each do
stub_const 'ThinkingSphinx::Commander', commander
end
describe '#configure' do
it 'sends the configure command' do
expect(commander).to receive(:call).
with(:configure, anything, {:verbose => true})
interface.configure
end
end
describe '#daemon' do
it 'returns a daemon interface' do
expect(interface.daemon.class).to eq(ThinkingSphinx::Interfaces::Daemon)
end
end
describe '#rt' do
it 'returns a real-time interface' do
expect(interface.rt.class).to eq(ThinkingSphinx::Interfaces::RealTime)
end
end
describe '#sql' do
it 'returns an SQL interface' do
expect(interface.sql.class).to eq(ThinkingSphinx::Interfaces::SQL)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/ 0000775 0000000 0000000 00000000000 13411321301 0022722 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/attribute_spec.rb 0000664 0000000 0000000 00000003703 13411321301 0026267 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::RealTime::Attribute do
let(:attribute) { ThinkingSphinx::RealTime::Attribute.new column }
let(:column) { double('column', :__name => :created_at, :__stack => []) }
describe '#name' do
it "uses the provided option by default" do
attribute = ThinkingSphinx::RealTime::Attribute.new column, :as => :foo
expect(attribute.name).to eq('foo')
end
it "falls back to the column's name" do
expect(attribute.name).to eq('created_at')
end
end
describe '#translate' do
let(:klass) { Struct.new(:name, :parent) }
let(:object) { klass.new 'the object name', parent }
let(:parent) { klass.new 'the parent name', nil }
it "returns the column's name if it's a string" do
allow(column).to receive_messages :__name => 'value'
expect(attribute.translate(object)).to eq('value')
end
it "returns the column's name if it's an integer" do
allow(column).to receive_messages :__name => 404
expect(attribute.translate(object)).to eq(404)
end
it "returns the object's method matching the column's name" do
allow(object).to receive_messages :created_at => 'a time'
expect(attribute.translate(object)).to eq('a time')
end
it "uses the column's stack to navigate through the object tree" do
allow(column).to receive_messages :__name => :name, :__stack => [:parent]
expect(attribute.translate(object)).to eq('the parent name')
end
it "returns zero if any element in the object tree is nil" do
allow(column).to receive_messages :__name => :name, :__stack => [:parent]
object.parent = nil
expect(attribute.translate(object)).to be_zero
end
end
describe '#type' do
it "returns the given type option" do
attribute = ThinkingSphinx::RealTime::Attribute.new column,
:type => :string
expect(attribute.type).to eq(:string)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/callbacks/ 0000775 0000000 0000000 00000000000 13411321301 0024641 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb 0000664 0000000 0000000 00000020471 13411321301 0032144 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks do
let(:callbacks) {
ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks.new :article
}
let(:instance) { double('instance', :id => 12, :persisted? => true) }
let(:config) { double('config', :indices_for_references => [index],
:settings => {}) }
let(:index) { double('index', :name => 'my_index', :is_a? => true,
:document_id_for_key => 123, :fields => [], :attributes => [],
:conditions => [], :primary_key => :id) }
let(:connection) { double('connection', :execute => true) }
before :each do
allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
allow(ThinkingSphinx::Connection).to receive_message_chain(:pool, :take).and_yield connection
end
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') }
let(:attribute) { double('attribute', :name => 'created_at',
:translate => time) }
before :each do
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
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
expect(insert).to receive(:replace!).and_return(insert)
callbacks.after_save instance
end
it "sends the insert through to the server" do
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(
:article, [:user]
)
}
let(:instance) { double('instance', :id => 12, :user => user) }
let(:user) { double('user', :id => 13, :persisted? => true) }
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_save 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_save instance
end
it "translates values for the user object" do
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
let(:callbacks) {
ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks.new(
:article, [:readers]
)
}
let(:instance) { double('instance', :id => 12,
:readers => [user_a, user_b]) }
let(:user_a) { double('user', :id => 13, :persisted? => true) }
let(:user_b) { double('user', :id => 14, :persisted? => true) }
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_save 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_save 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_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
let(:callbacks) {
ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks.new(
:article
) { |object| object.readers }
}
let(:instance) { double('instance', :id => 12,
:readers => [user_a, user_b]) }
let(:user_a) { double('user', :id => 13, :persisted? => true) }
let(:user_b) { double('user', :id => 14, :persisted? => true) }
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_save 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_save 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_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
thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/field_spec.rb 0000664 0000000 0000000 00000004052 13411321301 0025345 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::RealTime::Field do
let(:field) { ThinkingSphinx::RealTime::Field.new column }
let(:column) { double('column', :__name => :created_at, :__stack => []) }
describe '#column' do
it 'returns the provided Column object' do
expect(field.column).to eq(column)
end
it 'translates symbols to Column objects' do
expect(ThinkingSphinx::ActiveRecord::Column).to receive(:new).with(:title).
and_return(column)
ThinkingSphinx::RealTime::Field.new :title
end
end
describe '#name' do
it "uses the provided option by default" do
field = ThinkingSphinx::RealTime::Field.new column, :as => :foo
expect(field.name).to eq('foo')
end
it "falls back to the column's name" do
expect(field.name).to eq('created_at')
end
end
describe '#translate' do
let(:klass) { Struct.new(:name, :parent) }
let(:object) { klass.new 'the object name', parent }
let(:parent) { klass.new 'the parent name', nil }
it "returns the column's name if it's a string" do
allow(column).to receive_messages :__name => 'value'
expect(field.translate(object)).to eq('value')
end
it "returns the column's name as a string if it's an integer" do
allow(column).to receive_messages :__name => 404
expect(field.translate(object)).to eq('404')
end
it "returns the object's method matching the column's name" do
allow(object).to receive_messages :created_at => 'a time'
expect(field.translate(object)).to eq('a time')
end
it "uses the column's stack to navigate through the object tree" do
allow(column).to receive_messages :__name => :name, :__stack => [:parent]
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
allow(column).to receive_messages :__name => :name, :__stack => [:parent]
object.parent = nil
expect(field.translate(object)).to eq('')
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/index_spec.rb 0000664 0000000 0000000 00000011400 13411321301 0025364 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::RealTime::Index do
let(:index) { ThinkingSphinx::RealTime::Index.new :user }
let(:config) { double('config', :settings => {},
:indices_location => 'location', :next_offset => 8) }
before :each do
allow(ThinkingSphinx::Configuration).to receive_messages :instance => config
end
describe '#attributes' do
it "has the internal id attribute by default" do
expect(index.attributes.collect(&:name)).to include('sphinx_internal_id')
end
it "has the class name attribute by default" do
expect(index.attributes.collect(&:name)).to include('sphinx_internal_class')
end
it "has the internal deleted attribute by default" do
expect(index.attributes.collect(&:name)).to include('sphinx_deleted')
end
end
describe '#delta?' do
it "always returns false" do
expect(index).not_to be_delta
end
end
describe '#docinfo' do
it "defaults to extern" do
expect(index.docinfo).to eq(:extern)
end
it "can be disabled" do
config.settings["skip_docinfo"] = true
expect(index.docinfo).to be_nil
end
end
describe '#document_id_for_key' do
it "calculates the document id based on offset and number of indices" do
allow(config).to receive_message_chain(:indices, :count).and_return(5)
allow(config).to receive_messages :next_offset => 7
expect(index.document_id_for_key(123)).to eq(622)
end
end
describe '#fields' do
it "has the internal class field by default" do
expect(index.fields.collect(&:name)).to include('sphinx_internal_class_name')
end
end
describe '#interpret_definition!' do
let(:block) { double('block') }
before :each do
index.definition_block = block
end
it "interprets the definition block" do
expect(ThinkingSphinx::RealTime::Interpreter).to receive(:translate!).
with(index, block)
index.interpret_definition!
end
it "only interprets the definition block once" do
expect(ThinkingSphinx::RealTime::Interpreter).to receive(:translate!).
once
index.interpret_definition!
index.interpret_definition!
end
end
describe '#model' do
let(:model) { double('model', :primary_key => :id) }
it "translates symbol references to model class" do
allow(ActiveSupport::Inflector).to receive_messages(:constantize => model)
expect(index.model).to eq(model)
end
it "memoizes the result" do
expect(ActiveSupport::Inflector).to receive(:constantize).with('User').once.
and_return(model)
index.model
index.model
end
end
describe '#morphology' do
before :each do
skip
end
context 'with a render' do
it "defaults to nil" do
begin
index.render
rescue Riddle::Configuration::ConfigurationError
end
expect(index.morphology).to be_nil
end
it "reads from the settings file if provided" do
config.settings['morphology'] = 'stem_en'
begin
index.render
rescue Riddle::Configuration::ConfigurationError
end
expect(index.morphology).to eq('stem_en')
end
end
end
describe '#name' do
it "always uses the core suffix" do
index = ThinkingSphinx::RealTime::Index.new :user
expect(index.name).to eq('user_core')
end
end
describe '#offset' do
before :each do
allow(config).to receive_messages :next_offset => 4
end
it "uses the next offset value from the configuration" do
expect(index.offset).to eq(4)
end
it "uses the reference to get a unique offset" do
expect(config).to receive(:next_offset).with(:user).and_return(2)
index.offset
end
end
describe '#render' do
before :each do
allow(FileUtils).to receive_messages :mkdir_p => true
end
it "interprets the provided definition" do
expect(index).to receive(:interpret_definition!).at_least(:once)
begin
index.render
rescue Riddle::Configuration::ConfigurationError
# Ignoring underlying validation error.
end
end
end
describe '#scope' do
let(:model) { double('model', :primary_key => :id) }
it "returns the model by default" do
allow(ActiveSupport::Inflector).to receive_messages(:constantize => model)
expect(index.scope).to eq(model)
end
it "returns the evaluated scope if provided" do
index.scope = lambda { :foo }
expect(index.scope).to eq(:foo)
end
end
describe '#unique_attribute_names' do
it "returns all attribute names" do
expect(index.unique_attribute_names).to eq([
'sphinx_internal_id', 'sphinx_internal_class', 'sphinx_deleted'
])
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/interpreter_spec.rb 0000664 0000000 0000000 00000013326 13411321301 0026631 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::RealTime::Interpreter do
let(:instance) {
ThinkingSphinx::RealTime::Interpreter.new index, block
}
let(:model) { double('model') }
let(:index) { Struct.new(:attributes, :fields, :options).new([], [], {}) }
let(:block) { Proc.new { } }
describe '.translate!' do
let(:instance) { double('interpreter', :translate! => true) }
it "creates a new interpreter instance with the given block and index" do
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
allow(ThinkingSphinx::RealTime::Interpreter).to receive_messages(:new => instance)
expect(instance).to receive(:translate!)
ThinkingSphinx::RealTime::Interpreter.translate! index, block
end
end
describe '#has' do
let(:column) { double('column') }
let(:attribute) { double('attribute') }
before :each do
allow(ThinkingSphinx::RealTime::Attribute).to receive_messages :new => attribute
end
it "creates a new attribute with the provided column" do
expect(ThinkingSphinx::RealTime::Attribute).to receive(:new).
with(column, {}).and_return(attribute)
instance.has column
end
it "passes through options to the attribute" do
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.attributes).to include(attribute)
end
it "adds multiple attributes when passed multiple columns" do
instance.has column, column
expect(index.attributes.select { |saved_attribute|
saved_attribute == attribute
}.length).to eq(2)
end
end
describe '#indexes' do
let(:column) { double('column') }
let(:field) { double('field') }
before :each do
allow(ThinkingSphinx::RealTime::Field).to receive_messages :new => field
end
it "creates a new field with the provided column" do
expect(ThinkingSphinx::RealTime::Field).to receive(:new).
with(column, {}).and_return(field)
instance.indexes column
end
it "passes through options to the field" do
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.fields).to include(field)
end
it "adds multiple fields when passed multiple columns" do
instance.indexes column, column
expect(index.fields.select { |saved_field|
saved_field == field
}.length).to eq(2)
end
context 'sortable' do
let(:attribute) { double('attribute') }
before :each do
allow(ThinkingSphinx::RealTime::Attribute).to receive_messages :new => attribute
allow(column).to receive_messages :__name => :col
end
it "adds the _sort suffix to the field's name" do
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
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
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.attributes).to include(attribute)
end
end
end
describe '#method_missing' do
let(:column) { double('column') }
before :each do
allow(ThinkingSphinx::ActiveRecord::Column).to receive_messages(:new => column)
end
it "returns a new column for the given method" do
expect(instance.id).to eq(column)
end
it "should initialise the column with the method name and arguments" do
expect(ThinkingSphinx::ActiveRecord::Column).to receive(:new).
with(:users, :posts, :subject).and_return(column)
instance.users(:posts, :subject)
end
end
describe '#scope' do
it "passes the scope block through to the index" do
expect(index).to receive(:scope=).with(instance_of(Proc))
instance.scope { :foo }
end
end
describe '#set_property' do
before :each do
allow(index.class).to receive_messages :settings => [:morphology]
end
it 'saves other settings as index options' do
instance.set_property :field_weights => {:name => 10}
expect(index.options[:field_weights]).to eq({:name => 10})
end
context 'index settings' do
it "sets the provided setting" do
expect(index).to receive(:morphology=).with('stem_en')
instance.set_property :morphology => 'stem_en'
end
end
end
describe '#translate!' do
it "returns the block evaluated within the context of the interpreter" do
block = Proc.new {
__id__
}
interpreter = ThinkingSphinx::RealTime::Interpreter.new index, block
expect(interpreter.translate!).
to eq(interpreter.__id__)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/transcribe_instance_spec.rb 0000664 0000000 0000000 00000002205 13411321301 0030300 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/transcriber_spec.rb 0000664 0000000 0000000 00000006503 13411321301 0026603 0 ustar 00root root 0000000 0000000 # 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')] }
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 "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
thinking-sphinx-4.1.0/spec/thinking_sphinx/real_time/translator_spec.rb 0000664 0000000 0000000 00000000772 13411321301 0026460 0 ustar 00root root 0000000 0000000 # 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
thinking-sphinx-4.1.0/spec/thinking_sphinx/scopes_spec.rb 0000664 0000000 0000000 00000002233 13411321301 0023614 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::Scopes do
let(:model) {
Class.new do
include ThinkingSphinx::Scopes
def self.search(query = nil, options = {})
ThinkingSphinx::Search.new(query, options)
end
end
}
describe '#method_missing' do
before :each do
model.sphinx_scopes[:foo] = Proc.new { {:with => {:foo => :bar}} }
end
it "creates new search" do
expect(model.foo.class).to eq(ThinkingSphinx::Search)
end
it "passes block result to constructor" do
expect(model.foo.options[:with]).to eq({:foo => :bar})
end
it "passes non-scopes through to the standard method error call" do
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 }
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
expect(model.default_sphinx_scope).to eq(:foo)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/search/ 0000775 0000000 0000000 00000000000 13411321301 0022226 5 ustar 00root root 0000000 0000000 thinking-sphinx-4.1.0/spec/thinking_sphinx/search/glaze_spec.rb 0000664 0000000 0000000 00000004270 13411321301 0024672 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
class Search; end
end
require 'thinking_sphinx/search/glaze'
describe ThinkingSphinx::Search::Glaze do
let(:glaze) { ThinkingSphinx::Search::Glaze.new context, object, raw, [] }
let(:object) { double('object') }
let(:raw) { {} }
let(:context) { {} }
describe '#!=' do
it "is true for objects that don't match" do
expect(glaze != double('foo')).to be_truthy
end
it "is false when the underlying object is a match" do
expect(glaze != object).to be_falsey
end
end
describe '#method_missing' do
let(:glaze) {
ThinkingSphinx::Search::Glaze.new context, object, raw, [klass, klass] }
let(:klass) { double('pane class') }
let(:pane_one) { double('pane one', :foo => 'one') }
let(:pane_two) { double('pane two', :foo => 'two', :bar => 'two') }
before :each do
allow(klass).to receive(:new).and_return(pane_one, pane_two)
end
it "respects objects existing methods" do
allow(object).to receive_messages :foo => 'original'
expect(glaze.foo).to eq('original')
end
it "uses the first pane that responds to the method" do
expect(glaze.foo).to eq('one')
expect(glaze.bar).to eq('two')
end
it "raises the method missing error otherwise" do
allow(object).to receive_messages :respond_to? => false
allow(object).to receive(:baz).and_raise(NoMethodError)
expect { glaze.baz }.to raise_error(NoMethodError)
end
end
describe '#respond_to?' do
it "responds to underlying object methods" do
allow(object).to receive_messages :foo => 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]
expect(glaze.respond_to?(:bar)).to be_truthy
end
it "does not to respond to methods that don't exist" do
expect(glaze.respond_to?(:something)).to be_falsey
end
end
describe '#unglazed' do
it "returns the original object" do
expect(glaze.unglazed).to eq(object)
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/search/query_spec.rb 0000664 0000000 0000000 00000005032 13411321301 0024732 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ThinkingSphinx
class Search; end
end
require 'active_support/core_ext/object/blank'
require './lib/thinking_sphinx/search/query'
describe ThinkingSphinx::Search::Query do
before :each do
stub_const 'ThinkingSphinx::Query', double(wildcard: '')
end
describe '#to_s' do
it "passes through the keyword as provided" do
query = ThinkingSphinx::Search::Query.new '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'
expect(query.to_s).to eq('@title pancakes')
end
it "combines both keywords and conditions" do
query = ThinkingSphinx::Search::Query.new 'tasty', :title => 'pancakes'
expect(query.to_s).to eq('tasty @title pancakes')
end
it "automatically stars keywords if requested" do
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
expect(ThinkingSphinx::Query).to receive(:wildcard).with('pan', true).
and_return('*pan*')
ThinkingSphinx::Search::Query.new('', {:title => 'pan'}, true).to_s
end
it "does not star the sphinx_internal_class field keyword" do
query = ThinkingSphinx::Search::Query.new '',
{:sphinx_internal_class_name => 'article'}, true
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
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 => ''
expect(query.to_s).to eq('')
end
it "handles nil queries" do
query = ThinkingSphinx::Search::Query.new nil, {}
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
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
thinking-sphinx-4.1.0/spec/thinking_sphinx/search_spec.rb 0000664 0000000 0000000 00000012451 13411321301 0023570 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx::Search do
let(:search) { ThinkingSphinx::Search.new }
let(:context) { {:results => []} }
let(:stack) { double('stack', :call => true) }
let(:pagination_mask_methods) do
[:first_page?,
:last_page?,
:next_page,
:next_page?,
:page,
:per,
:previous_page,
:total_entries,
:total_count,
:count,
:total_pages,
:page_count,
:num_pages]
end
let(:scopes_mask_methods) do
[:facets,
:search,
:search_for_ids]
end
let(:group_enumerator_mask_methods) do
[:each_with_count,
:each_with_group,
:each_with_group_and_count]
end
before :each do
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
expect(search.current_page).to eq(1)
end
it "should handle string page values" do
expect(ThinkingSphinx::Search.new(:page => '2').current_page).to eq(2)
end
it "should handle empty string page values" do
expect(ThinkingSphinx::Search.new(:page => '').current_page).to eq(1)
end
it "should return the requested page" do
expect(ThinkingSphinx::Search.new(:page => 10).current_page).to eq(10)
end
end
describe '#empty?' do
it "returns false if there is anything in the data set" do
context[:results] << double
expect(search).not_to be_empty
end
it "returns true if the data set is empty" do
context[:results].clear
expect(search).to be_empty
end
end
describe '#initialize' do
it "lazily loads by default" do
expect(stack).not_to receive(:call)
ThinkingSphinx::Search.new
end
it "should automatically populate when :populate is set to true" do
expect(stack).to receive(:call).and_return(true)
ThinkingSphinx::Search.new(:populate => true)
end
end
describe '#offset' do
it "should default to 0" do
expect(search.offset).to eq(0)
end
it "should increase by the per_page value for each page in" do
expect(ThinkingSphinx::Search.new(:per_page => 25, :page => 2).offset).
to eq(25)
end
it "should prioritise explicit :offset over calculated if given" do
expect(ThinkingSphinx::Search.new(:offset => 5).offset).to eq(5)
end
end
describe '#page' do
it "sets the current page" do
search.page(3)
expect(search.current_page).to eq(3)
end
it "returns the search object" do
expect(search.page(2)).to eq(search)
end
end
describe '#per' do
it "sets the current per_page value" do
search.per(29)
expect(search.per_page).to eq(29)
end
it "returns the search object" do
expect(search.per(29)).to eq(search)
end
end
describe '#per_page' do
it "defaults to 20" do
expect(search.per_page).to eq(20)
end
it "is set as part of the search options" do
expect(ThinkingSphinx::Search.new(:per_page => 10).per_page).to eq(10)
end
it "should prioritise :limit over :per_page if given" do
expect(ThinkingSphinx::Search.new(:per_page => 30, :limit => 40).per_page).
to eq(40)
end
it "should allow for string arguments" do
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)
expect(search.per_page).to eq(24)
end
end
describe '#populate' do
it "runs the middleware" do
expect(stack).to receive(:call).with([context]).and_return(true)
search.populate
end
it "does not retrieve results twice" do
expect(stack).to receive(:call).with([context]).once.and_return(true)
search.populate
search.populate
end
end
describe '#respond_to?' do
it "should respond to Array methods" do
expect(search.respond_to?(:each)).to be_truthy
end
it "should respond to Search methods" do
expect(search.respond_to?(:per_page)).to be_truthy
end
it "should return true for methods delegated to pagination mask by method_missing" do
pagination_mask_methods.each do |method|
expect(search).to respond_to method
end
end
it "should return true for methods delegated to scopes mask by method_missing" do
scopes_mask_methods.each do |method|
expect(search).to respond_to method
end
end
it "should return true for methods delegated to group enumerators mask by method_missing" do
group_enumerator_mask_methods.each do |method|
expect(search).to respond_to method
end
end
end
describe '#to_a' do
it "returns each of the standard ActiveRecord objects" do
unglazed = double('unglazed instance')
glazed = double('glazed instance', :unglazed => unglazed)
context[:results] << glazed
expect(search.to_a.first.__id__).to eq(unglazed.__id__)
end
end
it "correctly handles access to methods delegated to masks through 'method' call" do
[
pagination_mask_methods,
scopes_mask_methods,
group_enumerator_mask_methods
].flatten.each do |method|
expect { search.method method }.to_not raise_exception
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx/wildcard_spec.rb 0000664 0000000 0000000 00000003072 13411321301 0024113 0 ustar 00root root 0000000 0000000 # encoding: utf-8
# frozen_string_literal: true
module ThinkingSphinx; end
require './lib/thinking_sphinx/wildcard'
describe ThinkingSphinx::Wildcard do
describe '.call' do
it "does not star quorum operators" do
expect(ThinkingSphinx::Wildcard.call("foo/3")).to eq("*foo*/3")
end
it "does not star proximity operators or quoted strings" do
expect(ThinkingSphinx::Wildcard.call(%q{"hello world"~3})).
to eq(%q{"hello world"~3})
end
it "treats slashes as a separator when starring" do
expect(ThinkingSphinx::Wildcard.call("a\\/c")).to eq("*a*\\/*c*")
end
it "separates escaping from the end of words" do
expect(ThinkingSphinx::Wildcard.call("\\(913\\)")).to eq("\\(*913*\\)")
end
it "ignores escaped slashes" do
expect(ThinkingSphinx::Wildcard.call("\\/\\/pan")).to eq("\\/\\/*pan*")
end
it "does not star manually provided field tags" do
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
expect(ThinkingSphinx::Wildcard.call("@(title, body) pan")).
to eq("@(title, body) *pan*")
end
it "handles nil queries" do
expect(ThinkingSphinx::Wildcard.call(nil)).to eq('')
end
it "handles unicode values" do
expect(ThinkingSphinx::Wildcard.call('älytön')).to eq('*älytön*')
end
end
end
thinking-sphinx-4.1.0/spec/thinking_sphinx_spec.rb 0000664 0000000 0000000 00000002210 13411321301 0022313 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ThinkingSphinx do
describe '.count' do
let(:search) { double('search', :total_entries => 23, :populated? => false,
:options => {}) }
before :each do
allow(ThinkingSphinx::Search).to receive_messages :new => search
end
it "returns the total entries of the search object" do
expect(ThinkingSphinx.count).to eq(search.total_entries)
end
it "passes through the given query and options" do
expect(ThinkingSphinx::Search).to receive(:new).with('foo', :bar => :baz).
and_return(search)
ThinkingSphinx.count('foo', :bar => :baz)
end
end
describe '.search' do
let(:search) { double('search') }
before :each do
allow(ThinkingSphinx::Search).to receive_messages :new => search
end
it "returns a new search object" do
expect(ThinkingSphinx.search).to eq(search)
end
it "passes through the given query and options" do
expect(ThinkingSphinx::Search).to receive(:new).with('foo', :bar => :baz).
and_return(search)
ThinkingSphinx.search('foo', :bar => :baz)
end
end
end
thinking-sphinx-4.1.0/thinking-sphinx.gemspec 0000664 0000000 0000000 00000002736 13411321301 0021322 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
# -*- encoding: utf-8 -*-
$:.push File.expand_path('../lib', __FILE__)
Gem::Specification.new do |s|
s.name = 'thinking-sphinx'
s.version = '4.1.0'
s.platform = Gem::Platform::RUBY
s.authors = ["Pat Allan"]
s.email = ["pat@freelancing-gods.com"]
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|
File.basename(f)
}
s.require_paths = ['lib']
s.add_runtime_dependency 'activerecord', '>= 3.1.0'
s.add_runtime_dependency 'builder', '>= 2.1.2'
s.add_runtime_dependency 'joiner', '>= 0.2.0'
s.add_runtime_dependency 'middleware', '>= 0.1.0'
s.add_runtime_dependency 'innertube', '>= 1.0.2'
s.add_runtime_dependency 'riddle', '~> 2.3'
s.add_development_dependency 'appraisal', '~> 1.0.2'
s.add_development_dependency 'combustion', '~> 0.8.0'
s.add_development_dependency 'database_cleaner', '~> 1.6.0'
s.add_development_dependency 'rspec', '~> 3.7.0'
s.add_development_dependency 'rspec-retry', '~> 0.5.6'
end