thinking-sphinx-3.1.4/0000755000004100000410000000000012556214551014677 5ustar www-datawww-datathinking-sphinx-3.1.4/Rakefile0000644000004100000410000000074512556214551016352 0ustar www-datawww-datarequire '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-3.1.4/Gemfile0000644000004100000410000000043712556214551016176 0ustar www-datawww-datasource 'https://rubygems.org' gemspec gem 'mysql2', '~> 0.3.12b4', :platform => :ruby gem 'pg', '~> 0.16.0', :platform => :ruby gem 'activerecord-jdbcmysql-adapter', '~> 1.3.4', :platform => :jruby gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3.4', :platform => :jruby thinking-sphinx-3.1.4/spec/0000755000004100000410000000000012556214551015631 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/0000755000004100000410000000000012556214551021035 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/scopes_spec.rb0000644000004100000410000000215412556214551023672 0ustar www-datawww-datarequire '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 model.foo.class.should == ThinkingSphinx::Search end it "passes block result to constructor" do model.foo.options[:with].should == {:foo => :bar} end it "passes non-scopes through to the standard method error call" do lambda { model.bar }.should raise_error(NoMethodError) end end describe '#sphinx_scope' do it "saves the given block with a name" do model.sphinx_scope(:foo) { 27 } model.sphinx_scopes[:foo].call.should == 27 end end describe '#default_sphinx_scope' do it "gets and sets the default scope depending on the argument" do model.default_sphinx_scope :foo model.default_sphinx_scope.should == :foo end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/index_set_spec.rb0000644000004100000410000000564012556214551024363 0ustar www-datawww-datamodule 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, *superclasses) klass = double 'class', :name => name, :class => Class klass.stub :ancestors => ([klass] + superclasses + [ar_base]) klass end describe '#to_a' do it "ensures the indices are loaded" do configuration.should_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] set.to_a.should == [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')] set.to_a.length.should == 1 end it "requests indices for any superclasses" do configuration.indices.replace [ double(:reference => :article, :distributed? => false), double(:reference => :opinion_article, :distributed? => false), double(:reference => :page, :distributed? => false) ] options[:classes] = [ class_double('OpinionArticle', class_double('Article')) ] set.to_a.length.should == 2 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'] set.to_a.should == [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] set.to_a.length.should == 2 end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/connection_spec.rb0000644000004100000410000000422412556214551024535 0ustar www-datawww-datarequire '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 ThinkingSphinx::Connection.stub :pool => pool ThinkingSphinx::SphinxError.stub :new_from_mysql => translated_error pool.stub(: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| c.should == connection end end it "retries errors once" do tries = 0 lambda { ThinkingSphinx::Connection.take do |c| tries += 1 raise error if tries < 2 end }.should_not raise_error end it "retries errors twice" do tries = 0 lambda { ThinkingSphinx::Connection.take do |c| tries += 1 raise error if tries < 3 end }.should_not raise_error end it "raises a translated error if it fails three times" do tries = 0 lambda { ThinkingSphinx::Connection.take do |c| tries += 1 raise error if tries < 4 end }.should 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 lambda { ThinkingSphinx::Connection.take { |c| raise error } }.should 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 yields.should == 1 end end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/search_spec.rb0000644000004100000410000001217312556214551023645 0ustar www-datawww-datarequire '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 ThinkingSphinx::Search::Context.stub :new => context stub_const 'ThinkingSphinx::Middlewares::DEFAULT', stack end describe '#current_page' do it "should return 1 by default" do search.current_page.should == 1 end it "should handle string page values" do ThinkingSphinx::Search.new(:page => '2').current_page.should == 2 end it "should handle empty string page values" do ThinkingSphinx::Search.new(:page => '').current_page.should == 1 end it "should return the requested page" do ThinkingSphinx::Search.new(:page => 10).current_page.should == 10 end end describe '#empty?' do it "returns false if there is anything in the data set" do context[:results] << double search.should_not be_empty end it "returns true if the data set is empty" do context[:results].clear search.should be_empty end end describe '#initialize' do it "lazily loads by default" do stack.should_not_receive(:call) ThinkingSphinx::Search.new end it "should automatically populate when :populate is set to true" do stack.should_receive(:call).and_return(true) ThinkingSphinx::Search.new(:populate => true) end end describe '#offset' do it "should default to 0" do search.offset.should == 0 end it "should increase by the per_page value for each page in" do ThinkingSphinx::Search.new(:per_page => 25, :page => 2).offset. should == 25 end it "should prioritise explicit :offset over calculated if given" do ThinkingSphinx::Search.new(:offset => 5).offset.should == 5 end end describe '#page' do it "sets the current page" do search.page(3) search.current_page.should == 3 end it "returns the search object" do search.page(2).should == search end end describe '#per' do it "sets the current per_page value" do search.per(29) search.per_page.should == 29 end it "returns the search object" do search.per(29).should == search end end describe '#per_page' do it "defaults to 20" do search.per_page.should == 20 end it "is set as part of the search options" do ThinkingSphinx::Search.new(:per_page => 10).per_page.should == 10 end it "should prioritise :limit over :per_page if given" do ThinkingSphinx::Search.new(:per_page => 30, :limit => 40).per_page. should == 40 end it "should allow for string arguments" do ThinkingSphinx::Search.new(:per_page => '10').per_page.should == 10 end it "allows setting of the per_page value" do search.per_page(24) search.per_page.should == 24 end end describe '#populate' do it "runs the middleware" do stack.should_receive(:call).with([context]).and_return(true) search.populate end it "does not retrieve results twice" do stack.should_receive(:call).with([context]).once.and_return(true) search.populate search.populate end end describe '#respond_to?' do it "should respond to Array methods" do search.respond_to?(:each).should be_true end it "should respond to Search methods" do search.respond_to?(:per_page).should be_true 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 search.to_a.first.__id__.should == 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-3.1.4/spec/thinking_sphinx/masks/0000755000004100000410000000000012556214551022153 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/masks/pagination_mask_spec.rb0000644000004100000410000000517112556214551026662 0ustar www-datawww-datamodule 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 mask.should be_first_page end it "returns false on other pages" do search.stub :current_page => 2 mask.should_not 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 search.stub :current_page => 3 mask.should be_last_page end it "is false when there's still more pages" do mask.should_not 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 mask.next_page.should == 2 end it "should return nil if on the last page" do search.stub :current_page => 3 mask.next_page.should 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 mask.next_page?.should be_true end it "is false when there's no more pages" do search.stub :current_page => 3 mask.next_page?.should be_false end end describe '#previous_page' do before :each do search.meta['total'] = '44' end it "should return one less than the current page" do search.stub :current_page => 2 mask.previous_page.should == 1 end it "should return nil if on the first page" do mask.previous_page.should 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 mask.total_entries.should == 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 mask.total_pages.should == 2 end it "should allow for custom per_page values" do search.stub :per_page => 40 mask.total_pages.should == 1 end it "should return 0 if there is no index and therefore no results" do search.meta.clear mask.total_pages.should == 0 end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/masks/scopes_mask_spec.rb0000644000004100000410000000650612556214551026030 0ustar www-datawww-datamodule 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 FileUtils.stub :mkdir_p => true end describe '#search' do it "replaces the query if one is supplied" do search.should_receive(:query=).with('bar') mask.search('bar') end it "keeps the existing query when only options are offered" do search.should_not_receive(:query=) mask.search :with => {:foo => :bar} end it "merges conditions" do search.options[:conditions] = {:foo => 'bar'} mask.search :conditions => {:baz => 'qux'} search.options[:conditions].should == {:foo => 'bar', :baz => 'qux'} end it "merges filters" do search.options[:with] = {:foo => :bar} mask.search :with => {:baz => :qux} search.options[:with].should == {:foo => :bar, :baz => :qux} end it "merges exclusive filters" do search.options[:without] = {:foo => :bar} mask.search :without => {:baz => :qux} search.options[:without].should == {:foo => :bar, :baz => :qux} end it "appends excluded ids" do search.options[:without_ids] = [1, 3] mask.search :without_ids => [5, 7] search.options[:without_ids].should == [1, 3, 5, 7] end it "replaces the retry_stale option" do search.options[:retry_stale] = true mask.search :retry_stale => 6 search.options[:retry_stale].should == 6 end it "returns the original search object" do mask.search.object_id.should == search.object_id end end describe '#search_for_ids' do it "replaces the query if one is supplied" do search.should_receive(:query=).with('bar') mask.search_for_ids('bar') end it "keeps the existing query when only options are offered" do search.should_not_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'} search.options[:conditions].should == {:foo => 'bar', :baz => 'qux'} end it "merges filters" do search.options[:with] = {:foo => :bar} mask.search_for_ids :with => {:baz => :qux} search.options[:with].should == {:foo => :bar, :baz => :qux} end it "merges exclusive filters" do search.options[:without] = {:foo => :bar} mask.search_for_ids :without => {:baz => :qux} search.options[:without].should == {:foo => :bar, :baz => :qux} end it "appends excluded ids" do search.options[:without_ids] = [1, 3] mask.search_for_ids :without_ids => [5, 7] search.options[:without_ids].should == [1, 3, 5, 7] end it "replaces the retry_stale option" do search.options[:retry_stale] = true mask.search_for_ids :retry_stale => 6 search.options[:retry_stale].should == 6 end it "adds the ids_only option" do mask.search_for_ids search.options[:ids_only].should be_true end it "returns the original search object" do mask.search_for_ids.object_id.should == search.object_id end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/0000755000004100000410000000000012556214551023646 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/association_spec.rb0000644000004100000410000000056712556214551027531 0ustar www-datawww-datarequire '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 association.stack.should == [:users, :post] end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/field_spec.rb0000644000004100000410000000237612556214551026300 0ustar www-datawww-datarequire '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 column.stub! :to_a => [column] end describe '#columns' do it 'returns the provided Column object' do field.columns.should == [column] end it 'translates symbols to Column objects' do ThinkingSphinx::ActiveRecord::Column.should_receive(:new).with(:title). and_return(column) ThinkingSphinx::ActiveRecord::Field.new model, :title end end describe '#file?' do it "defaults to false" do field.should_not be_file end it "is true if file option is set" do field = ThinkingSphinx::ActiveRecord::Field.new model, column, :file => true field.should be_file end end describe '#with_attribute?' do it "defaults to false" do field.should_not be_with_attribute end it "is true if the field is sortable" do field = ThinkingSphinx::ActiveRecord::Field.new model, column, :sortable => true field.should be_with_attribute end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/callbacks/0000755000004100000410000000000012556214551025565 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb0000644000004100000410000000331712556214551032371 0ustar www-datawww-datarequire '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 ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks. stub :new => callbacks end it "builds an object from the instance" do ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks. should_receive(:new).with(instance).and_return(callbacks) ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks. after_destroy(instance) end it "invokes after_destroy on the object" do callbacks.should_receive(:after_destroy) 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 ThinkingSphinx::IndexSet.stub :new => index_set end it "performs the deletion for the index and instance" do ThinkingSphinx::Deletion.should_receive(:perform).with(index, 7) callbacks.after_destroy end it "doesn't do anything if the instance is a new record" do instance.stub :new_record? => true ThinkingSphinx::Deletion.should_not_receive(:perform) callbacks.after_destroy end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb0000644000004100000410000000763612556214551032230 0ustar www-datawww-datarequire '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 ThinkingSphinx::Configuration.stub :instance => config end [:after_commit, :before_save].each do |callback| describe ".#{callback}" do let(:callbacks) { double('callbacks', callback => nil) } before :each do ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks. stub :new => callbacks end it "builds an object from the instance" do ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks. should_receive(:new).with(instance).and_return(callbacks) ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks. send(callback, instance) end it "invokes #{callback} on the object" do callbacks.should_receive(callback) ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks. send(callback, instance) end end end describe '#after_commit' do let(:index) { double('index', :delta? => false, :delta_processor => processor) } before :each do config.stub :index_set_class => double(:new => [index]) end context 'without delta indices' do it "does not fire a delta index when no delta indices" do processor.should_not_receive(:index) callbacks.after_commit end it "does not delete the instance from any index" do processor.should_not_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) } let(:delta_index) { double('index', :delta? => true, :name => 'foo_delta', :delta_processor => processor) } before :each do ThinkingSphinx::Deltas.stub :suspended? => false config.stub :index_set_class => double( :new => [core_index, delta_index] ) end it "only indexes delta indices" do processor.should_receive(:index).with(delta_index) callbacks.after_commit end it "does not process delta indices when deltas are suspended" do ThinkingSphinx::Deltas.stub :suspended? => true processor.should_not_receive(:index) callbacks.after_commit end it "deletes the instance from the core index" do processor.should_receive(:delete).with(core_index, instance) callbacks.after_commit end it "does not index if model's delta flag is not true" do processor.stub :toggled? => false processor.should_not_receive(:index) callbacks.after_commit end it "does not delete if model's delta flag is not true" do processor.stub :toggled? => false processor.should_not_receive(:delete) callbacks.after_commit end it "does not delete when deltas are suspended" do ThinkingSphinx::Deltas.stub :suspended? => true processor.should_not_receive(:delete) callbacks.after_commit end end end describe '#before_save' do let(:index) { double('index', :delta? => true, :delta_processor => processor) } before :each do config.stub :index_set_class => double(:new => [index]) end it "sets delta to true if there are delta indices" do processor.should_receive(:toggle).with(instance) callbacks.before_save end it "does not try to set delta to true if there are no delta indices" do index.stub :delta? => false processor.should_not_receive(:toggle) callbacks.before_save end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb0000644000004100000410000000505312556214551032410 0ustar www-datawww-datamodule 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') ThinkingSphinx::Connection.stub(: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) ]) instance.stub :changed => ['bar_column', 'baz'], :bar_column => 7 end it "does not send any updates to Sphinx if updates are disabled" do configuration.settings['attribute_updates'] = false connection.should_not_receive(:execute) callbacks.after_update end it "builds an update query with only updateable attributes that have changed" do Riddle::Query.should_receive(:update). with('article_core', 3, 'bar' => 7).and_return('SphinxQL') callbacks.after_update end it "sends the update query through to Sphinx" do connection.should_receive(:execute).with('SphinxQL') callbacks.after_update end it "doesn't care if the update fails at Sphinx's end" do connection.stub(:execute). and_raise(ThinkingSphinx::ConnectionError.new('')) lambda { callbacks.after_update }.should_not raise_error end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/polymorpher_spec.rb0000644000004100000410000000554012556214551027571 0ustar www-datawww-datarequire '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 ThinkingSphinx::ActiveRecord::FilterReflection. stub(:call). and_return(article_reflection, animal_reflection) model.stub(:reflect_on_association) do |name| name == :foo ? reflection : nil end if ActiveRecord::Reflection.respond_to?(:add_reflection) ActiveRecord::Reflection.stub :add_reflection end end it "creates a new reflection for each class" do ThinkingSphinx::ActiveRecord::FilterReflection. unstub :call ThinkingSphinx::ActiveRecord::FilterReflection. should_receive(:call). with(reflection, :foo_article, 'Article'). and_return(article_reflection) ThinkingSphinx::ActiveRecord::FilterReflection. should_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) ActiveRecord::Reflection.should_receive(:add_reflection). with(model, :foo_article, article_reflection) ActiveRecord::Reflection.should_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 field.should_receive(:rebase).with([:a, :b, :foo], :to => [[:a, :b, :foo_article], [:a, :b, :foo_animal]]) polymorpher.morph! end it "rebases each attribute" do attribute.should_receive(:rebase).with([:a, :b, :foo], :to => [[:a, :b, :foo_article], [:a, :b, :foo_animal]]) polymorpher.morph! end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/attribute/0000755000004100000410000000000012556214551025651 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/attribute/type_spec.rb0000644000004100000410000000771712556214551030205 0ustar www-datawww-datamodule 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 model.stub :reflect_on_association => association end it "returns true if there are has_many associations" do association.stub :macro => :has_many type.should be_multi end it "returns true if there are has_and_belongs_to_many associations" do association.stub :macro => :has_and_belongs_to_many type.should be_multi end it "returns false if there are no associations" do column.__stack.clear type.should_not be_multi end it "returns false if there are only belongs_to associations" do association.stub :macro => :belongs_to type.should_not be_multi end it "returns false if there are only has_one associations" do association.stub :macro => :has_one type.should_not be_multi end it "returns true if deeper associations have many" do column.__stack << :bar deep_association = double(:klass => double, :macro => :has_many) association.stub :macro => :belongs_to, :klass => double(:reflect_on_association => deep_association) type.should be_multi end it "respects the provided setting" do attribute.options[:multi] = true type.should be_multi end end describe '#type' do it "returns the type option provided" do attribute.options[:type] = :datetime type.type.should == :datetime end it "detects integer types from the database" do db_column.stub!(:type => :integer, :sql_type => 'integer(11)') type.type.should == :integer end it "detects boolean types from the database" do db_column.stub!(:type => :boolean) type.type.should == :boolean end it "detects datetime types from the database as timestamps" do db_column.stub!(:type => :datetime) type.type.should == :timestamp end it "detects date types from the database as timestamps" do db_column.stub!(:type => :date) type.type.should == :timestamp end it "detects string types from the database" do db_column.stub!(:type => :string) type.type.should == :string end it "detects text types from the database as strings" do db_column.stub!(:type => :text) type.type.should == :string end it "detects float types from the database" do db_column.stub!(:type => :float) type.type.should == :float end it "detects decimal types from the database as floats" do db_column.stub!(:type => :decimal) type.type.should == :float end it "detects big ints as big ints" do db_column.stub :type => :bigint type.type.should == :bigint end it "detects large integers as big ints" do db_column.stub :type => :integer, :sql_type => 'bigint(20)' type.type.should == :bigint end it "detects JSON" do db_column.stub :type => :json type.type.should == :json end it "respects provided type setting" do attribute.options[:type] = :timestamp type.type.should == :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-3.1.4/spec/thinking_sphinx/active_record/interpreter_spec.rb0000644000004100000410000002132612556214551027554 0ustar www-datawww-datarequire '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 ThinkingSphinx::ActiveRecord::SQLSource.stub! :new => source source.stub :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 ThinkingSphinx::ActiveRecord::Interpreter.should_receive(:new). with(index, block).and_return(instance) ThinkingSphinx::ActiveRecord::Interpreter.translate! index, block end it "calls translate! on the instance" do ThinkingSphinx::ActiveRecord::Interpreter.stub!(:new => instance) instance.should_receive(:translate!) ThinkingSphinx::ActiveRecord::Interpreter.translate! index, block end end describe '#group_by' do it "adds a source to the index" do index.should_receive(:append_source).and_return(source) instance.group_by 'lat' end it "only adds a single source for the given context" do index.should_receive(:append_source).once.and_return(source) instance.group_by 'lat' instance.group_by 'lng' end it "appends a new grouping statement to the source" do instance.group_by 'lat' source.groupings.should include('lat') end end describe '#has' do let(:column) { double('column') } let(:attribute) { double('attribute') } before :each do ThinkingSphinx::ActiveRecord::Attribute.stub! :new => attribute end it "adds a source to the index" do index.should_receive(:append_source).and_return(source) instance.has column end it "only adds a single source for the given context" do index.should_receive(:append_source).once.and_return(source) instance.has column instance.has column end it "creates a new attribute with the provided column" do ThinkingSphinx::ActiveRecord::Attribute.should_receive(:new). with(model, column, {}).and_return(attribute) instance.has column end it "passes through options to the attribute" do ThinkingSphinx::ActiveRecord::Attribute.should_receive(:new). with(model, column, :as => :other_name).and_return(attribute) instance.has column, :as => :other_name end it "adds an attribute to the source" do instance.has column source.attributes.should include(attribute) end it "adds multiple attributes when passed multiple columns" do instance.has column, column source.attributes.select { |saved_attribute| saved_attribute == attribute }.length.should == 2 end end describe '#indexes' do let(:column) { double('column') } let(:field) { double('field') } before :each do ThinkingSphinx::ActiveRecord::Field.stub! :new => field end it "adds a source to the index" do index.should_receive(:append_source).and_return(source) instance.indexes column end it "only adds a single source for the given context" do index.should_receive(:append_source).once.and_return(source) instance.indexes column instance.indexes column end it "creates a new field with the provided column" do ThinkingSphinx::ActiveRecord::Field.should_receive(:new). with(model, column, {}).and_return(field) instance.indexes column end it "passes through options to the field" do ThinkingSphinx::ActiveRecord::Field.should_receive(:new). with(model, column, :as => :other_name).and_return(field) instance.indexes column, :as => :other_name end it "adds a field to the source" do instance.indexes column source.fields.should include(field) end it "adds multiple fields when passed multiple columns" do instance.indexes column, column source.fields.select { |saved_field| saved_field == field }.length.should == 2 end end describe '#join' do let(:column) { double('column') } let(:association) { double('association') } before :each do ThinkingSphinx::ActiveRecord::Association.stub! :new => association end it "adds a source to the index" do index.should_receive(:append_source).and_return(source) instance.join column end it "only adds a single source for the given context" do index.should_receive(:append_source).once.and_return(source) instance.join column instance.join column end it "creates a new association with the provided column" do ThinkingSphinx::ActiveRecord::Association.should_receive(:new). with(column).and_return(association) instance.join column end it "adds an association to the source" do instance.join column source.associations.should include(association) end it "adds multiple fields when passed multiple columns" do instance.join column, column source.associations.select { |saved_assoc| saved_assoc == association }.length.should == 2 end end describe '#method_missing' do let(:column) { double('column') } before :each do ThinkingSphinx::ActiveRecord::Column.stub!(:new => column) end it "returns a new column for the given method" do instance.id.should == column end it "should initialise the column with the method name and arguments" do ThinkingSphinx::ActiveRecord::Column.should_receive(:new). with(:users, :posts, :subject).and_return(column) instance.users(:posts, :subject) end end describe '#set_database' do before :each do source.stub :set_database_settings => true stub_const 'ActiveRecord::Base', double(:configurations => {'other' => {'baz' => 'qux'}}) end it "sends through a hash if provided" do source.should_receive(:set_database_settings).with(:foo => :bar) instance.set_database :foo => :bar end it "finds the environment settings if given a string key" do source.should_receive(:set_database_settings).with(:baz => 'qux') instance.set_database 'other' end it "finds the environment settings if given a symbol key" do source.should_receive(:set_database_settings).with(:baz => 'qux') instance.set_database :other end end describe '#set_property' do before :each do index.class.stub :settings => [:morphology] source.class.stub :settings => [:mysql_ssl_cert] end it 'saves other settings as index options' do instance.set_property :field_weights => {:name => 10} index.options[:field_weights].should == {:name => 10} end context 'index settings' do it "sets the provided setting" do index.should_receive(:morphology=).with('stem_en') instance.set_property :morphology => 'stem_en' end end context 'source settings' do before :each do source.stub :mysql_ssl_cert= => true end it "adds a source to the index" do index.should_receive(:append_source).and_return(source) instance.set_property :mysql_ssl_cert => 'private.cert' end it "only adds a single source for the given context" do index.should_receive(:append_source).once.and_return(source) instance.set_property :mysql_ssl_cert => 'private.cert' instance.set_property :mysql_ssl_cert => 'private.cert' end it "sets the provided setting" do source.should_receive(:mysql_ssl_cert=).with('private.cert') 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 interpreter.translate!. should == interpreter.__id__ end end describe '#where' do it "adds a source to the index" do index.should_receive(:append_source).and_return(source) instance.where 'id > 100' end it "only adds a single source for the given context" do index.should_receive(:append_source).once.and_return(source) instance.where 'id > 100' instance.where 'id < 150' end it "appends a new grouping statement to the source" do instance.where 'id > 100' source.conditions.should include('id > 100') end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/base_spec.rb0000644000004100000410000000721512556214551026124 0ustar www-datawww-datarequire '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 model.facets.should be_a(ThinkingSphinx::FacetSearch) end it "passes through arguments to the search object" do model.facets('pancakes').query.should == 'pancakes' end it "scopes the search to a given model" do model.facets('pancakes').options[:classes].should == [model] end it "merges the :classes option with the model" do model.facets('pancakes', :classes => [sub_model]). options[:classes].should == [sub_model, model] end it "applies the default scope if there is one" do model.stub :default_sphinx_scope => :default, :sphinx_scopes => {:default => Proc.new { {:order => :created_at} }} model.facets.options[:order].should == :created_at end it "does not apply a default scope if one is not set" do model.stub :default_sphinx_scope => nil, :default => {:order => :created_at} model.facets.options[:order].should 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 model.search.should be_a(ThinkingSphinx::Search) end it "passes through arguments to the search object" do model.search('pancakes').query.should == 'pancakes' end it "scopes the search to a given model" do model.search('pancakes').options[:classes].should == [model] end it "passes through options to the search object" do model.search('pancakes', populate: true). options[:populate].should be_true end it "should automatically populate when :populate is set to true" do stack.should_receive(:call).and_return(true) model.search('pancakes', populate: true) end it "merges the :classes option with the model" do model.search('pancakes', :classes => [sub_model]). options[:classes].should == [sub_model, model] end it "respects provided middleware" do model.search(:middleware => ThinkingSphinx::Middlewares::RAW_ONLY). options[:middleware].should == ThinkingSphinx::Middlewares::RAW_ONLY end it "respects provided masks" do model.search(:masks => [ThinkingSphinx::Masks::PaginationMask]). masks.should == [ThinkingSphinx::Masks::PaginationMask] end it "applies the default scope if there is one" do model.stub :default_sphinx_scope => :default, :sphinx_scopes => {:default => Proc.new { {:order => :created_at} }} model.search.options[:order].should == :created_at end it "does not apply a default scope if one is not set" do model.stub :default_sphinx_scope => nil, :default => {:order => :created_at} model.search.options[:order].should be_nil end end describe '.search_count' do let(:search) { double('search', :options => {}, :total_entries => 12, :populated? => false) } before :each do ThinkingSphinx.stub :search => search FileUtils.stub :mkdir_p => true end it "returns the search object's total entries count" do model.search_count.should == search.total_entries end it "scopes the search to a given model" do model.search_count search.options[:classes].should == [model] end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/filter_reflection_spec.rb0000644000004100000410000001352212556214551030707 0ustar www-datawww-datarequire 'spec_helper' describe ThinkingSphinx::ActiveRecord::FilterReflection do describe '.call' do let(:reflection) { double('Reflection', :macro => :has_some, :options => options, :active_record => double, :name => 'baz', :foreign_type => :foo_type, :class => reflection_klass) } let(:options) { {:polymorphic => true} } let(:filtered_reflection) { double 'filtered reflection' } let(:reflection_klass) { double :new => filtered_reflection, :instance_method => initialize_method } let(:initialize_method) { double :arity => 4 } before :each do reflection.active_record.stub_chain(:connection, :quote_column_name). and_return('"foo_type"') end it "uses the existing reflection's macro" do reflection_klass.should_receive(:new). with(:has_some, anything, anything, anything) ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end unless defined?(ActiveRecord::Reflection::MacroReflection) it "uses the supplied name" do if defined?(ActiveRecord::Reflection::MacroReflection) reflection_klass.should_receive(:new). with('foo_bar', anything, anything, anything) else reflection_klass.should_receive(:new). with(anything, 'foo_bar', anything, anything) end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end it "uses the existing reflection's parent" do if defined?(ActiveRecord::Reflection::MacroReflection) reflection_klass.should_receive(:new). with(anything, anything, anything, reflection.active_record) else reflection_klass.should_receive(:new). with(anything, anything, anything, reflection.active_record) end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end it "removes the polymorphic setting from the options" do if defined?(ActiveRecord::Reflection::MacroReflection) reflection_klass.should_receive(:new) do |name, scope, options, parent| options[:polymorphic].should be_nil end else reflection_klass.should_receive(:new) do |macro, name, options, parent| options[:polymorphic].should be_nil end end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end it "adds the class name option" do if defined?(ActiveRecord::Reflection::MacroReflection) reflection_klass.should_receive(:new) do |name, scope, options, parent| options[:class_name].should == 'Bar' end else reflection_klass.should_receive(:new) do |macro, name, options, parent| options[:class_name].should == 'Bar' end end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end it "sets the foreign key if necessary" do if defined?(ActiveRecord::Reflection::MacroReflection) reflection_klass.should_receive(:new) do |name, scope, options, parent| options[:foreign_key].should == 'baz_id' end else reflection_klass.should_receive(:new) do |macro, name, options, parent| options[:foreign_key].should == 'baz_id' end end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end it "respects supplied foreign keys" do options[:foreign_key] = 'qux_id' if defined?(ActiveRecord::Reflection::MacroReflection) reflection_klass.should_receive(:new) do |name, scope, options, parent| options[:foreign_key].should == 'qux_id' end else reflection_klass.should_receive(:new) do |macro, name, options, parent| options[:foreign_key].should == 'qux_id' end end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end it "sets conditions if there are none" do reflection_klass.should_receive(:new) do |macro, name, options, parent| options[:conditions].should == "::ts_join_alias::.\"foo_type\" = 'Bar'" end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end unless defined?(ActiveRecord::Reflection::MacroReflection) it "appends to the conditions array" do options[:conditions] = ['existing'] reflection_klass.should_receive(:new) do |macro, name, options, parent| options[:conditions].should == ['existing', "::ts_join_alias::.\"foo_type\" = 'Bar'"] end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end unless defined?(ActiveRecord::Reflection::MacroReflection) it "extends the conditions hash" do options[:conditions] = {:x => :y} reflection_klass.should_receive(:new) do |macro, name, options, parent| options[:conditions].should == {:x => :y, :foo_type => 'Bar'} end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end unless defined?(ActiveRecord::Reflection::MacroReflection) it "appends to the conditions string" do options[:conditions] = 'existing' reflection_klass.should_receive(:new) do |macro, name, options, parent| options[:conditions].should == "existing AND ::ts_join_alias::.\"foo_type\" = 'Bar'" end ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ) end unless defined?(ActiveRecord::Reflection::MacroReflection) it "returns the new reflection" do ThinkingSphinx::ActiveRecord::FilterReflection.call( reflection, 'foo_bar', 'Bar' ).should == filtered_reflection end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb0000644000004100000410000002024212556214551032037 0ustar www-datawww-datarequire '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 adapter.stub(: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 presenter.to_group.should == 'articles.title' end it "gets the column's table alias from the associations object" do column.stub!(:__stack => [:users, :posts]) associations.should_receive(:alias_for).with([:users, :posts]). and_return('posts') presenter.to_group end it "returns nil if the property is an aggregate" do path.stub! :aggregate? => true presenter.to_group.should be_nil end it "returns nil if the field is sourced via a separate query" do field.stub :source_type => 'query' presenter.to_group.should be_nil end end describe '#to_select' do it "returns the column name as a string" do presenter.to_select.should == 'articles.title AS title' end it "gets the column's table alias from the associations object" do column.stub!(:__stack => [:users, :posts]) associations.should_receive(:alias_for).with([:users, :posts]). and_return('posts') presenter.to_select end it "returns the column name with an alias when provided" do field.stub!(:name => :subject) presenter.to_select.should == 'articles.title AS subject' end it "groups and concatenates aggregated columns" do adapter.stub :group_concatenate do |clause, separator| "GROUP_CONCAT(#{clause} SEPARATOR '#{separator}')" end path.stub! :aggregate? => true presenter.to_select. should == "GROUP_CONCAT(articles.title SEPARATOR ' ') AS title" end it "concatenates multiple columns" do adapter.stub :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end field.stub!(:columns => [column, column]) presenter.to_select. should == "CONCAT_WS(' ', articles.title, articles.title) AS title" end it "does not include columns that don't exist" do adapter.stub :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end field.stub!(:columns => [column, double('column', :string? => false, :__stack => [], :__name => 'body')]) presenter.to_select. should == "CONCAT_WS(' ', articles.title) AS title" end it "returns nil for query sourced fields" do field.stub :source_type => :query presenter.to_select.should be_nil end it "returns nil for ranged query sourced fields" do field.stub :source_type => :ranged_query presenter.to_select.should 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 adapter.stub :cast_to_timestamp do |clause| "UNIX_TIMESTAMP(#{clause})" end end describe '#to_group' do it "returns the column name as a string" do presenter.to_group.should == 'articles.created_at' end it "gets the column's table alias from the associations object" do column.stub!(:__stack => [:users, :posts]) associations.should_receive(:alias_for).with([:users, :posts]). and_return('posts') presenter.to_group end it "returns nil if the column is a string" do column.stub!(:string? => true) presenter.to_group.should be_nil end it "returns nil if the property is an aggregate" do path.stub! :aggregate? => true presenter.to_group.should be_nil end it "returns nil if the attribute is sourced via a separate query" do attribute.stub :source_type => 'query' presenter.to_group.should be_nil end end describe '#to_select' do it "returns the column name as a string" do presenter.to_select.should == 'articles.created_at AS created_at' end it "gets the column's table alias from the associations object" do column.stub!(:__stack => [:users, :posts]) associations.should_receive(:alias_for).with([:users, :posts]). and_return('posts') presenter.to_select end it "returns the column name with an alias when provided" do attribute.stub!(:name => :creation_timestamp) presenter.to_select. should == 'articles.created_at AS creation_timestamp' end it "ensures datetime attributes are converted to timestamps" do attribute.stub :type => :timestamp presenter.to_select. should == 'UNIX_TIMESTAMP(articles.created_at) AS created_at' end it "does not include columns that don't exist" do adapter.stub :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end adapter.stub :cast_to_string do |clause| "CAST(#{clause} AS varchar)" end attribute.stub!(:columns => [column, double('column', :string? => false, :__stack => [], :__name => 'updated_at')]) presenter.to_select.should == "CONCAT_WS(',', CAST(articles.created_at AS varchar)) AS created_at" end it "casts and concatenates multiple columns for attributes" do adapter.stub :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end adapter.stub :cast_to_string do |clause| "CAST(#{clause} AS varchar)" end attribute.stub!(:columns => [column, column]) presenter.to_select.should == "CONCAT_WS(',', CAST(articles.created_at AS varchar), CAST(articles.created_at AS varchar)) AS created_at" end it "double-casts and concatenates multiple columns for timestamp attributes" do adapter.stub :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end adapter.stub :cast_to_string do |clause| "CAST(#{clause} AS varchar)" end attribute.stub :columns => [column, column], :type => :timestamp presenter.to_select.should == "CONCAT_WS(',', CAST(UNIX_TIMESTAMP(articles.created_at) AS varchar), CAST(UNIX_TIMESTAMP(articles.created_at) AS varchar)) AS created_at" end it "does not split attribute clause for timestamp casting if it looks like a function call" do column.stub :__name => "COALESCE(articles.updated_at, articles.created_at)", :string? => true attribute.stub :name => 'mod_date', :columns => [column], :type => :timestamp presenter.to_select.should == "UNIX_TIMESTAMP(COALESCE(articles.updated_at, articles.created_at)) AS mod_date" end it "returns nil for query sourced attributes" do attribute.stub :source_type => :query presenter.to_select.should be_nil end it "returns nil for ranged query sourced attributes" do attribute.stub :source_type => :ranged_query presenter.to_select.should be_nil end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/column_spec.rb0000644000004100000410000000373012556214551026505 0ustar www-datawww-datarequire 'spec_helper' describe ThinkingSphinx::ActiveRecord::Column do describe '#__name' do it "returns the top item" do column = ThinkingSphinx::ActiveRecord::Column.new(:content) column.__name.should == :content 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') column.__replace(base, replacements).collect(&:__path). should == [['foo']] end it "returns itself when the base of the stack does not match" do column = ThinkingSphinx::ActiveRecord::Column.new(:b, :c) column.__replace(base, replacements).collect(&:__path). should == [[:b, :c]] end it "returns an array of new columns " do column = ThinkingSphinx::ActiveRecord::Column.new(:a, :b, :e) column.__replace(base, replacements).collect(&:__path). should == [[:a, :c, :e], [:a, :d, :e]] end end describe '#__stack' do it "returns all but the top item" do column = ThinkingSphinx::ActiveRecord::Column.new(:users, :posts, :id) column.__stack.should == [:users, :posts] end end describe '#method_missing' do let(:column) { ThinkingSphinx::ActiveRecord::Column.new(:user) } it "shifts the current name to the stack" do column.email column.__stack.should == [:user] end it "adds the new method call as the name" do column.email column.__name.should == :email end it "returns itself" do column.email.should == column end end describe '#string?' do it "is true when the name is a string" do column = ThinkingSphinx::ActiveRecord::Column.new('content') column.should be_a_string end it "is false when the name is a symbol" do column = ThinkingSphinx::ActiveRecord::Column.new(:content) column.should_not be_a_string end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/index_spec.rb0000644000004100000410000001307112556214551026316 0ustar www-datawww-datarequire 'spec_helper' describe ThinkingSphinx::ActiveRecord::Index do let(:index) { ThinkingSphinx::ActiveRecord::Index.new :user } let(:indices_path) { double('indices path', :join => '') } let(:config) { double('config', :settings => {}, :indices_location => indices_path, :next_offset => 8) } before :each do ThinkingSphinx::Configuration.stub :instance => config end describe '#append_source' do let(:model) { double('model', :primary_key => :id) } let(:source) { double('source') } before :each do ActiveSupport::Inflector.stub(:constantize => model) ThinkingSphinx::ActiveRecord::SQLSource.stub :new => source config.stub :next_offset => 17 end it "adds a source to the index" do index.sources.should_receive(:<<).with(source) index.append_source end it "creates the source with the index's offset" do ThinkingSphinx::ActiveRecord::SQLSource.should_receive(:new). with(model, hash_including(:offset => 17)).and_return(source) index.append_source end it "returns the new source" do index.append_source.should == source end it "defaults to the model's primary key" do model.stub :primary_key => :sphinx_id ThinkingSphinx::ActiveRecord::SQLSource.should_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 model.stub :primary_key => :sphinx_id ThinkingSphinx::ActiveRecord::SQLSource.should_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 model.stub :primary_key => nil ThinkingSphinx::ActiveRecord::SQLSource.should_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 index.should_not be_delta end it "reflects the delta? option" do index = ThinkingSphinx::ActiveRecord::Index.new :user, :delta? => true index.should 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 index.delta_processor.should == processor end end describe '#document_id_for_key' do it "calculates the document id based on offset and number of indices" do config.stub_chain(:indices, :count).and_return(5) config.stub :next_offset => 7 index.document_id_for_key(123).should == 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 ThinkingSphinx::ActiveRecord::Interpreter.should_receive(:translate!). with(index, block) index.interpret_definition! end it "only interprets the definition block once" do ThinkingSphinx::ActiveRecord::Interpreter.should_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 ActiveSupport::Inflector.stub(:constantize => model) index.model.should == model end it "memoizes the result" do ActiveSupport::Inflector.should_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 FileUtils.stub :mkdir_p => true end it "defaults to nil" do begin index.render rescue Riddle::Configuration::ConfigurationError end index.morphology.should 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 index.morphology.should == 'stem_en' end end end describe '#name' do it "uses the core suffix by default" do index = ThinkingSphinx::ActiveRecord::Index.new :user index.name.should == 'user_core' end it "uses the delta suffix when delta? is true" do index = ThinkingSphinx::ActiveRecord::Index.new :user, :delta? => true index.name.should == 'user_delta' end end describe '#offset' do before :each do config.stub :next_offset => 4 end it "uses the next offset value from the configuration" do index.offset.should == 4 end it "uses the reference to get a unique offset" do config.should_receive(:next_offset).with(:user).and_return(2) index.offset end end describe '#render' do before :each do FileUtils.stub :mkdir_p => true end it "interprets the provided definition" do index.should_receive(:interpret_definition!).at_least(:once) begin index.render rescue Riddle::Configuration::ConfigurationError # Ignoring underlying validation error. end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/database_adapters/0000755000004100000410000000000012556214551027275 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb0000644000004100000410000000273712556214551033512 0ustar www-datawww-datarequire '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 adapter.boolean_value(true).should == 1 end it "returns 0 for false" do adapter.boolean_value(false).should == 0 end describe '#cast_to_string' do it "casts the clause to characters" do adapter.cast_to_string('foo').should == "CAST(foo AS char)" end end describe '#cast_to_timestamp' do it "converts to unix timestamps" do adapter.cast_to_timestamp('created_at'). should == 'UNIX_TIMESTAMP(created_at)' end end describe '#concatenate' do it "concatenates with the given separator" do adapter.concatenate('foo, bar, baz', ','). should == "CONCAT_WS(',', foo, bar, baz)" end end describe '#convert_nulls' do it "translates arguments to an IFNULL SQL call" do adapter.convert_nulls('id', 5).should == 'IFNULL(id, 5)' end end describe '#convert_blank' do it "translates arguments to a COALESCE NULLIF SQL call" do adapter.convert_blank('id', 5).should == "COALESCE(NULLIF(id, ''), 5)" end end describe '#group_concatenate' do it "group concatenates the clause with the given separator" do adapter.group_concatenate('foo', ','). should == "GROUP_CONCAT(DISTINCT foo SEPARATOR ',')" end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb0000644000004100000410000000155312556214551034143 0ustar www-datawww-datarequire '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 connection.should_receive(:quote_column_name).with('foo') adapter.quote 'foo' end it "returns the quoted value" do connection.stub :quote_column_name => '"foo"' adapter.quote('foo').should == '"foo"' end end describe '#quoted_table_name' do it "passes the method through to the model" do model.should_receive(:quoted_table_name).and_return('"articles"') adapter.quoted_table_name.should == '"articles"' end end end ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootthinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rbthinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.r0000644000004100000410000000353712556214551034405 0ustar www-datawww-datarequire '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 adapter.boolean_value(true).should == 'TRUE' end it "returns 'FALSE' for false" do adapter.boolean_value(false).should == 'FALSE' end end describe '#cast_to_string' do it "casts the clause to characters" do adapter.cast_to_string('foo').should == 'foo::varchar' end end describe '#cast_to_timestamp' do it "converts to int unix timestamps" do adapter.cast_to_timestamp('created_at'). should == 'extract(epoch from created_at)::int' end it "converts to bigint unix timestamps" do ThinkingSphinx::Configuration.instance.settings['64bit_timestamps'] = true adapter.cast_to_timestamp('created_at'). should == 'extract(epoch from created_at)::bigint' end end describe '#concatenate' do it "concatenates with the given separator" do adapter.concatenate('foo, bar, baz', ','). should == "COALESCE(foo, '') || ',' || COALESCE(bar, '') || ',' || COALESCE(baz, '')" end end describe '#convert_nulls' do it "translates arguments to a COALESCE SQL call" do adapter.convert_nulls('id', 5).should == 'COALESCE(id, 5)' end end describe '#convert_blank' do it "translates arguments to a COALESCE NULLIF SQL call" do adapter.convert_blank('id', 5).should == "COALESCE(NULLIF(id, ''), 5)" end end describe '#group_concatenate' do it "group concatenates the clause with the given separator" do adapter.group_concatenate('foo', ','). should == "array_to_string(array_agg(DISTINCT foo), ',')" end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/database_adapters_spec.rb0000644000004100000410000001076612556214551030646 0ustar www-datawww-datarequire 'spec_helper' describe ThinkingSphinx::ActiveRecord::DatabaseAdapters do let(:model) { double('model') } describe '.adapter_for' do it "returns a MysqlAdapter object for :mysql" do ThinkingSphinx::ActiveRecord::DatabaseAdapters. stub(:adapter_type_for => :mysql) ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model). should be_a( ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter ) end it "returns a PostgreSQLAdapter object for :postgresql" do ThinkingSphinx::ActiveRecord::DatabaseAdapters. stub(:adapter_type_for => :postgresql) ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model). should 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 adapter_class.stub!(:new => adapter_instance) ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model). should == adapter_instance ThinkingSphinx::ActiveRecord::DatabaseAdapters.default = nil end it "raises an exception for other responses" do ThinkingSphinx::ActiveRecord::DatabaseAdapters. stub(:adapter_type_for => :sqlite) lambda { ThinkingSphinx::ActiveRecord::DatabaseAdapters.adapter_for(model) }.should raise_error 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 klass.stub(:name => 'ActiveRecord::ConnectionAdapters::MysqlAdapter') ThinkingSphinx::ActiveRecord::DatabaseAdapters. adapter_type_for(model).should == :mysql end it "translates a MySQL2 adapter" do klass.stub(:name => 'ActiveRecord::ConnectionAdapters::Mysql2Adapter') ThinkingSphinx::ActiveRecord::DatabaseAdapters. adapter_type_for(model).should == :mysql end it "translates a normal PostgreSQL adapter" do klass.stub(:name => 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter') ThinkingSphinx::ActiveRecord::DatabaseAdapters. adapter_type_for(model).should == :postgresql end it "translates a JDBC MySQL adapter to MySQL" do klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter') connection.stub(:config => {:adapter => 'jdbcmysql'}) ThinkingSphinx::ActiveRecord::DatabaseAdapters. adapter_type_for(model).should == :mysql end it "translates a JDBC PostgreSQL adapter to PostgreSQL" do klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter') connection.stub(:config => {:adapter => 'jdbcpostgresql'}) ThinkingSphinx::ActiveRecord::DatabaseAdapters. adapter_type_for(model).should == :postgresql end it "translates a JDBC adapter with MySQL connection string to MySQL" do klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter') connection.stub(:config => {:adapter => 'jdbc', :url => 'jdbc:mysql://127.0.0.1:3306/sphinx'}) ThinkingSphinx::ActiveRecord::DatabaseAdapters. adapter_type_for(model).should == :mysql end it "translates a JDBC adapter with PostgresSQL connection string to PostgresSQL" do klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter') connection.stub(:config => {:adapter => 'jdbc', :url => 'jdbc:postgresql://127.0.0.1:3306/sphinx'}) ThinkingSphinx::ActiveRecord::DatabaseAdapters. adapter_type_for(model).should == :postgresql end it "returns other JDBC adapters without translation" do klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter') connection.stub(:config => {:adapter => 'jdbcmssql'}) ThinkingSphinx::ActiveRecord::DatabaseAdapters. adapter_type_for(model).should == 'jdbcmssql' end it "returns other unknown adapters without translation" do klass.stub(:name => 'ActiveRecord::ConnectionAdapters::FooAdapter') ThinkingSphinx::ActiveRecord::DatabaseAdapters. adapter_type_for(model). should == 'ActiveRecord::ConnectionAdapters::FooAdapter' end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/sql_builder_spec.rb0000644000004100000410000004421312556214551027516 0ustar www-datawww-datarequire '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 => {}) } 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 ThinkingSphinx::Configuration.stub! :instance => config ThinkingSphinx::ActiveRecord::PropertySQLPresenter.stub! :new => presenter Joiner::Joins.stub! :new => associations relation.stub! :select => relation, :where => relation, :group => relation, :order => relation, :joins => relation, :to_sql => '' connection.stub!(:quote_column_name) { |column| "`#{column}`"} end describe 'sql_query' do before :each do source.stub! :type => 'mysql' end it "adds source associations to the joins of the query" do source.associations << double('association', :stack => [:user, :posts], :string? => false) associations.should_receive(:add_join_to).with([:user, :posts]) builder.sql_query end it "adds string joins directly to the relation" do source.associations << double('association', :to_s => 'my string', :string? => true) relation.should_receive(:joins).with(['my string']).and_return(relation) builder.sql_query end context 'MySQL adapter' do before :each do source.stub! :type => 'mysql' end it "returns the relation's query" do relation.stub! :to_sql => 'SELECT * FROM people' builder.sql_query.should == 'SELECT * FROM people' end it "ensures results aren't from cache" do relation.should_receive(:select) do |string| string.should match(/^SQL_NO_CACHE /) relation end builder.sql_query end it "adds the document id using the offset and index count" do relation.should_receive(:select) do |string| string.should match(/`users`.`id` \* 5 \+ 3 AS `id`/) relation end builder.sql_query end it "adds each field to the SELECT clause" do source.fields << double('field') relation.should_receive(:select) do |string| string.should match(/`name` AS `name`/) relation end builder.sql_query end it "adds each attribute to the SELECT clause" do source.attributes << double('attribute') presenter.stub!(:to_select => '`created_at` AS `created_at`') relation.should_receive(:select) do |string| string.should match(/`created_at` AS `created_at`/) relation end builder.sql_query end it "limits results to a set range" do relation.should_receive(:where) do |string| string.should 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 source.stub! :disable_range? => true relation.should_receive(:where) do |string| string.should_not match(/`users`.`id` BETWEEN \$start AND \$end/) relation end builder.sql_query end it "adds source conditions" do source.conditions << 'created_at > NOW()' relation.should_receive(:where) do |string| string.should match(/created_at > NOW()/) relation end builder.sql_query end it "groups by the primary key" do relation.should_receive(:group) do |string| string.should match(/`users`.`id`/) relation end builder.sql_query end it "groups each field" do source.fields << double('field') relation.should_receive(:group) do |string| string.should match(/`name`/) relation end builder.sql_query end it "groups each attribute" do source.attributes << double('attribute') presenter.stub!(:to_group => '`created_at`') relation.should_receive(:group) do |string| string.should match(/`created_at`/) relation end builder.sql_query end it "groups by source groupings" do source.groupings << '`latitude`' relation.should_receive(:group) do |string| string.should match(/`latitude`/) relation end builder.sql_query end it "orders by NULL" do relation.should_receive(:order).with('NULL').and_return(relation) builder.sql_query end context 'STI model' do before :each do model.column_names << 'type' model.stub! :descends_from_active_record? => false model.stub! :store_full_sti_class => true end it "groups by the inheritance column" do relation.should_receive(:group) do |string| string.should match(/`users`.`type`/) relation end builder.sql_query end context 'with a custom inheritance column' do before :each do model.column_names << 'custom_type' model.stub :inheritance_column => 'custom_type' end it "groups by the right column" do relation.should_receive(:group) do |string| string.should 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 source.stub! :delta_processor => processor source.stub! :delta? => true end it "filters by the provided clause" do processor.should_receive(:clause).with(true).and_return('`delta` = 1') relation.should_receive(:where) do |string| string.should match(/`delta` = 1/) 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 source.stub! :type => 'pgsql' model.stub! :quoted_table_name => '"users"' connection.stub!(:quote_column_name) { |column| "\"#{column}\""} end it "returns the relation's query" do relation.stub! :to_sql => 'SELECT * FROM people' builder.sql_query.should == 'SELECT * FROM people' end it "adds the document id using the offset and index count" do relation.should_receive(:select) do |string| string.should match(/"users"."id" \* 5 \+ 3 AS "id"/) relation end builder.sql_query end it "adds each field to the SELECT clause" do source.fields << double('field') relation.should_receive(:select) do |string| string.should match(/"name" AS "name"/) relation end builder.sql_query end it "adds each attribute to the SELECT clause" do source.attributes << double('attribute') presenter.stub!(:to_select => '"created_at" AS "created_at"') relation.should_receive(:select) do |string| string.should match(/"created_at" AS "created_at"/) relation end builder.sql_query end it "limits results to a set range" do relation.should_receive(:where) do |string| string.should 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 source.stub! :disable_range? => true relation.should_receive(:where) do |string| string.should_not match(/"users"."id" BETWEEN \$start AND \$end/) relation end builder.sql_query end it "adds source conditions" do source.conditions << 'created_at > NOW()' relation.should_receive(:where) do |string| string.should match(/created_at > NOW()/) relation end builder.sql_query end it "groups by the primary key" do relation.should_receive(:group) do |string| string.should match(/"users"."id"/) relation end builder.sql_query end it "groups each field" do source.fields << double('field') relation.should_receive(:group) do |string| string.should match(/"name"/) relation end builder.sql_query end it "groups each attribute" do source.attributes << double('attribute') presenter.stub!(:to_group => '"created_at"') relation.should_receive(:group) do |string| string.should match(/"created_at"/) relation end builder.sql_query end it "groups by source groupings" do source.groupings << '"latitude"' relation.should_receive(:group) do |string| string.should match(/"latitude"/) relation end builder.sql_query end it "has no ORDER clause" do relation.should_not_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 relation.should_receive(:group) do |string| string.should match(/"users"."id"/) relation end builder.sql_query end it "does not group by fields" do source.fields << double('field') relation.should_receive(:group) do |string| string.should_not match(/"name"/) relation end builder.sql_query end it "does not group by attributes" do source.attributes << double('attribute') presenter.stub!(:to_group => '"created_at"') relation.should_receive(:group) do |string| string.should_not match(/"created_at"/) relation end builder.sql_query end it "groups by source groupings" do source.groupings << '"latitude"' relation.should_receive(:group) do |string| string.should 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 relation.should_receive(:group) do |string| string.should match(/"users"."id"/) relation end builder.sql_query end it "does not group by fields" do source.fields << double('field') relation.should_receive(:group) do |string| string.should_not match(/"name"/) relation end builder.sql_query end it "does not group by attributes" do source.attributes << double('attribute') presenter.stub!(:to_group => '"created_at"') relation.should_receive(:group) do |string| string.should_not match(/"created_at"/) relation end builder.sql_query end it "groups by source groupings" do source.groupings << '"latitude"' relation.should_receive(:group) do |string| string.should match(/"latitude"/) relation end builder.sql_query end end context 'STI model' do before :each do model.column_names << 'type' model.stub! :descends_from_active_record? => false model.stub! :store_full_sti_class => true end it "groups by the inheritance column" do relation.should_receive(:group) do |string| string.should match(/"users"."type"/) relation end builder.sql_query end context 'with a custom inheritance column' do before :each do model.column_names << 'custom_type' model.stub :inheritance_column => 'custom_type' end it "groups by the right column" do relation.should_receive(:group) do |string| string.should 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 source.stub! :delta_processor => processor source.stub! :delta? => true end it "filters by the provided clause" do processor.should_receive(:clause).with(true).and_return('"delta" = 1') relation.should_receive(:where) do |string| string.should match(/"delta" = 1/) relation end builder.sql_query end end end end describe 'sql_query_post_index' do let(:processor) { double('processor', :reset_query => 'RESET DELTAS') } it "adds a reset delta query if there is a delta processor and this is the core source" do source.stub :delta_processor => processor, :delta? => false builder.sql_query_post_index.should include('RESET DELTAS') end it "adds no reset delta query if there is a delta processor and this is the delta source" do source.stub :delta_processor => processor, :delta? => true builder.sql_query_post_index.should_not include('RESET DELTAS') end end describe 'sql_query_pre' do let(:processor) { double('processor', :reset_query => 'RESET DELTAS') } before :each do source.stub :options => {}, :delta_processor => nil, :delta? => false adapter.stub :utf8_query_pre => ['SET UTF8'] end it "does not add a reset query if there is no delta processor" do builder.sql_query_pre.should_not include('RESET DELTAS') end it "does not add a reset query if this is a delta source" do source.stub :delta_processor => processor source.stub :delta? => true builder.sql_query_pre.should_not include('RESET DELTAS') end it "sets the group_concat_max_len value if set" do source.options[:group_concat_max_len] = 123 builder.sql_query_pre. should include('SET SESSION group_concat_max_len = 123') end it "does not set the group_concat_max_len if not provided" do source.options[:group_concat_max_len] = nil builder.sql_query_pre.select { |sql| sql[/SET SESSION group_concat_max_len/] }.should be_empty end it "sets the connection to use UTF-8 if required" do source.options[:utf8?] = true builder.sql_query_pre.should include('SET UTF8') end it "does not set the connection to use UTF-8 if not required" do source.options[:utf8?] = false builder.sql_query_pre.should_not include('SET UTF8') 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 adapter.stub!(:convert_nulls) { |string, default| "ISNULL(#{string}, #{default})" } end it "returns the relation's query" do relation.stub! :to_sql => 'SELECT * FROM people' builder.sql_query_range.should == 'SELECT * FROM people' end it "returns nil if ranges are disabled" do source.stub! :disable_range? => true builder.sql_query_range.should be_nil end it "selects the minimum primary key value, allowing for nulls" do relation.should_receive(:select) do |string| string.should match(/ISNULL\(MIN\(`users`.`id`\), 1\)/) relation end builder.sql_query_range end it "selects the maximum primary key value, allowing for nulls" do relation.should_receive(:select) do |string| string.should match(/ISNULL\(MAX\(`users`.`id`\), 1\)/) relation end builder.sql_query_range end it "shouldn't limit results to a range" do relation.should_receive(:where) do |string| string.should_not match(/`users`.`id` BETWEEN \$start AND \$end/) relation end builder.sql_query_range end it "does not add source conditions" do source.conditions << 'created_at > NOW()' relation.should_receive(:where) do |string| string.should_not match(/created_at > NOW()/) relation end builder.sql_query_range end context 'with a delta processor' do let(:processor) { double('processor') } before :each do source.stub! :delta_processor => processor source.stub! :delta? => true end it "filters by the provided clause" do processor.should_receive(:clause).with(true).and_return('`delta` = 1') relation.should_receive(:where) do |string| string.should match(/`delta` = 1/) relation end builder.sql_query_range end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/active_record/sql_source_spec.rb0000644000004100000410000003167612556214551027401 0ustar www-datawww-datarequire '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) } let(:adapter) { double('adapter') } before :each do ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter. stub!(:=== => true) ThinkingSphinx::ActiveRecord::DatabaseAdapters. stub!(:adapter_for => adapter) end describe '#adapter' do it "returns a database adapter for the model" do ThinkingSphinx::ActiveRecord::DatabaseAdapters. should_receive(:adapter_for).with(model).and_return(adapter) source.adapter.should == adapter end end describe '#attributes' do it "has the internal id attribute by default" do source.attributes.collect(&:name).should include('sphinx_internal_id') end it "has the class name attribute by default" do source.attributes.collect(&:name).should include('sphinx_internal_class') end it "has the internal deleted attribute by default" do source.attributes.collect(&:name).should include('sphinx_deleted') end it "marks the internal class attribute as a facet" do source.attributes.detect { |attribute| attribute.name == 'sphinx_internal_class' }.options[:facet].should be_true 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 } let(:source_with_options) { ThinkingSphinx::ActiveRecord::SQLSource.new model, :delta_processor => processor_class, :delta_options => { :opt_key => :opt_value } } it "loads the processor with the adapter" do processor_class.should_receive(:try).with(:new, adapter, {}). and_return processor source.delta_processor end it "returns the given processor" do source.delta_processor.should == processor end it "passes given options to the processor" do processor_class.should_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 source.should 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 source.disable_range?.should be_true end end describe '#fields' do it "has the internal class field by default" do source.fields.collect(&:name). should include('sphinx_internal_class_name') end it "sets the sphinx class field to use a string of the class name" do source.fields.detect { |field| field.name == 'sphinx_internal_class_name' }.columns.first.__name.should == "'User'" end it "uses the inheritance column if it exists for the sphinx class field" do adapter.stub :quoted_table_name => '"users"', :quote => '"type"' adapter.stub(:convert_blank) { |clause, default| "coalesce(nullif(#{clause}, ''), #{default})" } model.stub :column_names => ['type'], :sti_name => 'User' source.fields.detect { |field| field.name == 'sphinx_internal_class_name' }.columns.first.__name. should == "coalesce(nullif(\"users\".\"type\", ''), 'User')" end end describe '#name' do it "defaults to the model name downcased with the given position" do source.name.should == 'user_3' end it "allows for custom names, but adds the position suffix" do source = ThinkingSphinx::ActiveRecord::SQLSource.new model, :name => 'people', :position => 2 source.name.should == 'people_2' end end describe '#offset' do it "returns the given offset" do source = ThinkingSphinx::ActiveRecord::SQLSource.new model, :offset => 12 source.offset.should == 12 end end describe '#options' do it "defaults to having utf8? set to false" do source.options[:utf8?].should be_false end it "sets utf8? to true if the database encoding is utf8" do db_config[:encoding] = 'utf8' source.options[:utf8?].should be_true 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 ThinkingSphinx::ActiveRecord::SQLBuilder.stub! :new => builder ThinkingSphinx::ActiveRecord::Attribute::SphinxPresenter.stub :new => presenter ThinkingSphinx::ActiveRecord::SQLSource::Template.stub :new => template ThinkingSphinx::Configuration.stub :instance => config end it "uses the builder's sql_query value" do builder.stub! :sql_query => 'select * from table' source.render source.sql_query.should == 'select * from table' end it "uses the builder's sql_query_range value" do builder.stub! :sql_query_range => 'select 0, 10 from table' source.render source.sql_query_range.should == 'select 0, 10 from table' end it "appends the builder's sql_query_pre value" do builder.stub! :sql_query_pre => ['Change Setting'] source.render source.sql_query_pre.should == ['Change Setting'] end it "appends the builder's sql_query_post_index value" do builder.stub! :sql_query_post_index => ['RESET DELTAS'] source.render source.sql_query_post_index.should include('RESET DELTAS') 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 source.sql_field_string.should 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 source.sql_file_field.should 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 source.sql_field_str2wordcount.should include('title') end it "adds any joined fields" do ThinkingSphinx::ActiveRecord::PropertyQuery.stub( :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 source.sql_joined_field.should include('query for title') end it "adds integer attributes to sql_attr_uint" do source.attributes << double('attribute') presenter.stub :declaration => 'count', :collection_type => :uint source.render source.sql_attr_uint.should include('count') end it "adds boolean attributes to sql_attr_bool" do source.attributes << double('attribute') presenter.stub :declaration => 'published', :collection_type => :bool source.render source.sql_attr_bool.should include('published') end it "adds string attributes to sql_attr_string" do source.attributes << double('attribute') presenter.stub :declaration => 'name', :collection_type => :string source.render source.sql_attr_string.should include('name') end it "adds timestamp attributes to sql_attr_timestamp" do source.attributes << double('attribute') presenter.stub :declaration => 'created_at', :collection_type => :timestamp source.render source.sql_attr_timestamp.should include('created_at') end it "adds float attributes to sql_attr_float" do source.attributes << double('attribute') presenter.stub :declaration => 'rating', :collection_type => :float source.render source.sql_attr_float.should include('rating') end it "adds bigint attributes to sql_attr_bigint" do source.attributes << double('attribute') presenter.stub :declaration => 'super_id', :collection_type => :bigint source.render source.sql_attr_bigint.should include('super_id') end it "adds ordinal strings to sql_attr_str2ordinal" do source.attributes << double('attribute') presenter.stub :declaration => 'name', :collection_type => :str2ordinal source.render source.sql_attr_str2ordinal.should include('name') end it "adds multi-value attributes to sql_attr_multi" do source.attributes << double('attribute') presenter.stub :declaration => 'uint tag_ids from field', :collection_type => :multi source.render source.sql_attr_multi.should include('uint tag_ids from field') end it "adds word count attributes to sql_attr_str2wordcount" do source.attributes << double('attribute') presenter.stub :declaration => 'name', :collection_type => :str2wordcount source.render source.sql_attr_str2wordcount.should include('name') end it "adds json attributes to sql_attr_json" do source.attributes << double('attribute') presenter.stub :declaration => 'json', :collection_type => :json source.render source.sql_attr_json.should 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 source.mysql_ssl_cert.should == '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' source.sql_host.should == '12.34.56.78' end it "defaults sql_host to localhost if the model has no host" do source.set_database_settings :host => nil source.sql_host.should == 'localhost' end it "sets the sql_user setting from the model's database settings" do source.set_database_settings :username => 'pat' source.sql_user.should == 'pat' end it "uses the user setting if username is not set in the model" do source.set_database_settings :username => nil, :user => 'pat' source.sql_user.should == 'pat' end it "sets the sql_pass setting from the model's database settings" do source.set_database_settings :password => 'swordfish' source.sql_pass.should == 'swordfish' end it "escapes hashes in the password for sql_pass" do source.set_database_settings :password => 'sword#fish' source.sql_pass.should == 'sword\#fish' end it "sets the sql_db setting from the model's database settings" do source.set_database_settings :database => 'rails_app' source.sql_db.should == 'rails_app' end it "sets the sql_port setting from the model's database settings" do source.set_database_settings :port => 5432 source.sql_port.should == 5432 end it "sets the sql_sock setting from the model's database settings" do source.set_database_settings :socket => '/unix/socket' source.sql_sock.should == '/unix/socket' end end describe '#type' do it "is mysql when using the MySQL Adapter" do ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter. stub!(:=== => true) ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter. stub!(:=== => false) source.type.should == 'mysql' end it "is pgsql when using the PostgreSQL Adapter" do ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter. stub!(:=== => false) ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter. stub!(:=== => true) source.type.should == 'pgsql' end it "raises an exception for any other adapter" do ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter. stub!(:=== => false) ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter. stub!(:=== => false) lambda { source.type }.should raise_error end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/search/0000755000004100000410000000000012556214551022302 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/search/glaze_spec.rb0000644000004100000410000000402512556214551024744 0ustar www-datawww-datamodule 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 (glaze != double('foo')).should be_true end it "is false when the underlying object is a match" do (glaze != object).should be_false 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 klass.stub(:new).and_return(pane_one, pane_two) end it "respects objects existing methods" do object.stub :foo => 'original' glaze.foo.should == 'original' end it "uses the first pane that responds to the method" do glaze.foo.should == 'one' glaze.bar.should == 'two' end it "raises the method missing error otherwise" do object.stub :respond_to? => false object.stub(:baz).and_raise(NoMethodError) lambda { glaze.baz }.should raise_error(NoMethodError) end end describe '#respond_to?' do it "responds to underlying object methods" do object.stub :foo => true glaze.respond_to?(:foo).should be_true end it "responds to underlying pane methods" do pane = double('Pane Class', :new => double('pane', :bar => true)) glaze = ThinkingSphinx::Search::Glaze.new context, object, raw, [pane] glaze.respond_to?(:bar).should be_true end it "does not to respond to methods that don't exist" do glaze.respond_to?(:something).should be_false end end describe '#unglazed' do it "returns the original object" do glaze.unglazed.should == object end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/search/query_spec.rb0000644000004100000410000000435512556214551025015 0ustar www-datawww-datamodule 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' query.to_s.should == 'pancakes' end it "pairs fields and keywords for given conditions" do query = ThinkingSphinx::Search::Query.new '', :title => 'pancakes' query.to_s.should == '@title pancakes' end it "combines both keywords and conditions" do query = ThinkingSphinx::Search::Query.new 'tasty', :title => 'pancakes' query.to_s.should == 'tasty @title pancakes' end it "automatically stars keywords if requested" do ThinkingSphinx::Query.should_receive(:wildcard).with('cake', true). and_return('*cake*') ThinkingSphinx::Search::Query.new('cake', {}, true).to_s end it "automatically stars condition keywords if requested" do ThinkingSphinx::Query.should_receive(:wildcard).with('pan', true). 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 query.to_s.should == '@sphinx_internal_class_name article' end it "handles null values by removing them from the conditions hash" do query = ThinkingSphinx::Search::Query.new '', :title => nil query.to_s.should == '' end it "handles empty string values by removing them from the conditions hash" do query = ThinkingSphinx::Search::Query.new '', :title => '' query.to_s.should == '' end it "handles nil queries" do query = ThinkingSphinx::Search::Query.new nil, {} query.to_s.should == '' end it "allows mixing of blank and non-blank conditions" do query = ThinkingSphinx::Search::Query.new 'tasty', :title => 'pancakes', :ingredients => nil query.to_s.should == 'tasty @title pancakes' end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/errors_spec.rb0000644000004100000410000000461212556214551023713 0ustar www-datawww-datarequire '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 error.stub :message => 'index foo: syntax error: something is wrong' ThinkingSphinx::SphinxError.new_from_mysql(error). should be_a(ThinkingSphinx::SyntaxError) end it "translates parse errors" do error.stub :message => 'index foo: parse error: something is wrong' ThinkingSphinx::SphinxError.new_from_mysql(error). should be_a(ThinkingSphinx::ParseError) end it "translates query errors" do error.stub :message => 'index foo: query error: something is wrong' ThinkingSphinx::SphinxError.new_from_mysql(error). should be_a(ThinkingSphinx::QueryError) end it "translates connection errors" do error.stub :message => "Can't connect to MySQL server on '127.0.0.1' (61)" ThinkingSphinx::SphinxError.new_from_mysql(error). should be_a(ThinkingSphinx::ConnectionError) end it 'prefixes the connection error message' do error.stub :message => "Can't connect to MySQL server on '127.0.0.1' (61)" ThinkingSphinx::SphinxError.new_from_mysql(error).message. should == "Error connecting to Sphinx via the MySQL protocol. Can't connect to MySQL server on '127.0.0.1' (61)" end it "translates jdbc connection errors" do error.stub :message => "Communications link failure" ThinkingSphinx::SphinxError.new_from_mysql(error). should be_a(ThinkingSphinx::ConnectionError) end it 'prefixes the jdbc connection error message' do error.stub :message => "Communications link failure" ThinkingSphinx::SphinxError.new_from_mysql(error).message. should == "Error connecting to Sphinx via the MySQL protocol. Communications link failure" end it "defaults to sphinx errors" do error.stub :message => 'index foo: unknown error: something is wrong' ThinkingSphinx::SphinxError.new_from_mysql(error). should be_a(ThinkingSphinx::SphinxError) end it "keeps the original error's backtrace" do error.stub :message => 'index foo: unknown error: something is wrong' ThinkingSphinx::SphinxError.new_from_mysql(error). backtrace.should == error.backtrace end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/rake_interface_spec.rb0000644000004100000410000001451712556214551025346 0ustar www-datawww-datarequire 'spec_helper' describe ThinkingSphinx::RakeInterface do let(:configuration) { double('configuration', :controller => controller) } let(:interface) { ThinkingSphinx::RakeInterface.new } before :each do ThinkingSphinx::Configuration.stub :instance => configuration interface.stub(:puts => nil) end describe '#clear_all' do let(:controller) { double 'controller' } before :each do configuration.stub( :indices_location => '/path/to/indices', :searchd => double(:binlog_path => '/path/to/binlog') ) FileUtils.stub :rm_r => true File.stub :exists? => true end it "removes the directory for the index files" do FileUtils.should_receive(:rm_r).with('/path/to/indices') interface.clear_all end it "removes the directory for the binlog files" do FileUtils.should_receive(:rm_r).with('/path/to/binlog') interface.clear_all end end describe '#clear_real_time' do let(:controller) { double 'controller' } let(:index) { double(:type => 'rt', :render => true, :path => '/path/to/my/index') } before :each do configuration.stub( :indices => [double(:type => 'plain'), index], :searchd => double(:binlog_path => '/path/to/binlog') ) Dir.stub :[] => ['foo.a', 'foo.b'] FileUtils.stub :rm_r => true, :rm => true File.stub :exists? => true end it 'finds each file for real-time indices' do Dir.should_receive(:[]).with('/path/to/my/index.*').and_return([]) interface.clear_real_time end it "removes each file for real-time indices" do FileUtils.should_receive(:rm).with('foo.a') FileUtils.should_receive(:rm).with('foo.b') interface.clear_real_time end it "removes the directory for the binlog files" do FileUtils.should_receive(:rm_r).with('/path/to/binlog') interface.clear_real_time end end describe '#configure' do let(:controller) { double('controller') } before :each do configuration.stub( :configuration_file => '/path/to/foo.conf', :render_to_file => true ) end it "renders the configuration to a file" do configuration.should_receive(:render_to_file) interface.configure end it "prints a message stating the file is being generated" do interface.should_receive(:puts). with('Generating configuration to /path/to/foo.conf') interface.configure end end describe '#index' do let(:controller) { double('controller', :index => true) } before :each do ThinkingSphinx.stub :before_index_hooks => [] configuration.stub( :configuration_file => '/path/to/foo.conf', :render_to_file => true, :indices_location => '/path/to/indices' ) FileUtils.stub :mkdir_p => true end it "renders the configuration to a file by default" do configuration.should_receive(:render_to_file) interface.index end it "does not render the configuration if requested" do configuration.should_not_receive(:render_to_file) interface.index false end it "creates the directory for the index files" do FileUtils.should_receive(:mkdir_p).with('/path/to/indices') interface.index end it "calls all registered hooks" do called = false ThinkingSphinx.before_index_hooks << Proc.new { called = true } interface.index called.should be_true end it "indexes all indices verbosely" do controller.should_receive(:index).with(:verbose => true) interface.index end it "does not index verbosely if requested" do controller.should_receive(:index).with(:verbose => false) interface.index true, false end end describe '#start' do let(:controller) { double('controller', :start => true, :pid => 101) } before :each do controller.stub(:running?).and_return(false, true) configuration.stub :indices_location => 'my/index/files' FileUtils.stub :mkdir_p => true end it "creates the index files directory" do FileUtils.should_receive(:mkdir_p).with('my/index/files') interface.start end it "starts the daemon" do controller.should_receive(:start) interface.start end it "raises an error if the daemon is already running" do controller.stub :running? => true lambda { interface.start }.should raise_error(RuntimeError) end it "prints a success message if the daemon has started" do controller.stub(:running?).and_return(false, true) interface.should_receive(:puts). with('Started searchd successfully (pid: 101).') interface.start end it "prints a failure message if the daemon does not start" do controller.stub(:running?).and_return(false, false) interface.should_receive(:puts). with('Failed to start searchd. Check the log files for more information.') interface.start end end describe '#stop' do let(:controller) { double('controller', :stop => true, :pid => 101) } before :each do controller.stub :running? => true end it "prints a message if the daemon is not already running" do controller.stub :running? => false interface.should_receive(:puts).with('searchd is not currently running.') interface.stop end it "stops the daemon" do controller.should_receive(:stop) interface.stop end it "prints a message informing the daemon has stopped" do interface.should_receive(:puts).with('Stopped searchd daemon (pid: 101).') interface.stop end it "should retry stopping the daemon until it stops" do controller.should_receive(:stop).twice.and_return(false, true) interface.stop end end describe '#status' do let(:controller) { double('controller') } it "reports when the daemon is running" do controller.stub :running? => true interface.should_receive(:puts). with('The Sphinx daemon searchd is currently running.') interface.status end it "reports when the daemon is not running" do controller.stub :running? => false interface.should_receive(:puts). with('The Sphinx daemon searchd is not currently running.') interface.status end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/deltas/0000755000004100000410000000000012556214551022311 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/deltas/default_delta_spec.rb0000644000004100000410000000620012556214551026443 0ustar www-datawww-datarequire '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 adapter.stub :boolean_value => 't' end it "limits results to those flagged as deltas" do delta.clause(true).should == "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_key => 14) } let(:instance) { double('instance', :id => 7) } before :each do ThinkingSphinx::Connection.stub(:take).and_yield(connection) Riddle::Query.stub :update => 'UPDATE STATEMENT' end it "updates the deleted flag to false" do connection.should_receive(:execute).with('UPDATE STATEMENT') delta.delete index, instance end it "builds the update query for the given index" do Riddle::Query.should_receive(:update). with('foo_core', anything, anything).and_return('') delta.delete index, instance end it "builds the update query for the sphinx document id" do Riddle::Query.should_receive(:update). with(anything, 14, anything).and_return('') delta.delete index, instance end it "builds the update query for setting sphinx_deleted to true" do Riddle::Query.should_receive(:update). with(anything, anything, :sphinx_deleted => true).and_return('') delta.delete index, instance end it "doesn't care about Sphinx errors" do connection.stub(:execute). and_raise(ThinkingSphinx::ConnectionError.new('')) lambda { delta.delete index, instance }.should_not raise_error end end describe '#index' do let(:config) { double('config', :controller => controller, :settings => {}) } let(:controller) { double('controller') } before :each do ThinkingSphinx::Configuration.stub :instance => config end it "indexes the given index" do controller.should_receive(:index).with('foo_delta', :verbose => true) delta.index double('index', :name => 'foo_delta') end end describe '#reset_query' do it "updates the table to set delta flags to false" do adapter.stub(:boolean_value) { |value| value ? 't' : 'f' } delta.reset_query. should == 'UPDATE articles SET delta = f WHERE delta = t' end end describe '#toggle' do let(:instance) { double('instance') } it "sets instance's delta flag to true" do instance.should_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 instance.stub! :delta? => true delta.toggled?(instance).should be_true end it "returns the delta flag value when false" do instance.stub! :delta? => false delta.toggled?(instance).should be_false end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/real_time/0000755000004100000410000000000012556214551022776 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/real_time/field_spec.rb0000644000004100000410000000356112556214551025425 0ustar www-datawww-datarequire '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 field.column.should == column end it 'translates symbols to Column objects' do ThinkingSphinx::ActiveRecord::Column.should_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 field.name.should == 'foo' end it "falls back to the column's name" do field.name.should == '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 column.stub :__name => 'value' field.translate(object).should == 'value' end it "returns the column's name as a string if it's an integer" do column.stub :__name => 404 field.translate(object).should == '404' end it "returns the object's method matching the column's name" do object.stub :created_at => 'a time' field.translate(object).should == 'a time' end it "uses the column's stack to navigate through the object tree" do column.stub :__name => :name, :__stack => [:parent] field.translate(object).should == 'the parent name' end it "returns a blank string if any element in the object tree is nil" do column.stub :__name => :name, :__stack => [:parent] object.parent = nil field.translate(object).should == '' end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/real_time/callbacks/0000755000004100000410000000000012556214551024715 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb0000644000004100000410000001234012556214551032214 0ustar www-datawww-datarequire '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 => []) } let(:connection) { double('connection', :execute => true) } before :each do ThinkingSphinx::Configuration.stub :instance => config ThinkingSphinx::Connection.stub_chain(:pool, :take).and_yield connection end describe '#after_save' 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 ThinkingSphinx::Configuration.stub :instance => config Riddle::Query::Insert.stub :new => insert insert.stub :replace! => insert index.stub :fields => [field], :attributes => [attribute] end it "creates an insert statement with all fields and attributes" do Riddle::Query::Insert.should_receive(:new). with('my_index', ['id', 'name', 'created_at'], [123, 'Foo', time]). and_return(insert) callbacks.after_save instance end it "switches the insert to a replace statement" do insert.should_receive(:replace!).and_return(insert) callbacks.after_save instance end it "sends the insert through to the server" do connection.should_receive(:execute).with('REPLACE INTO my_index') callbacks.after_save 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 Riddle::Query::Insert.should_receive(:new). with('my_index', ['id', 'name', 'created_at'], [123, 'Foo', time]). and_return(insert) callbacks.after_save instance end it "gets the document id for the user object" do index.should_receive(:document_id_for_key).with(13).and_return(123) callbacks.after_save instance end it "translates values for the user object" do field.should_receive(:translate).with(user).and_return('Foo') callbacks.after_save 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 Riddle::Query::Insert.should_receive(:new).twice. with('my_index', ['id', 'name', 'created_at'], [123, 'Foo', time]). and_return(insert) callbacks.after_save instance end it "gets the document id for each reader" do index.should_receive(:document_id_for_key).with(13).and_return(123) index.should_receive(:document_id_for_key).with(14).and_return(123) callbacks.after_save instance end it "translates values for each reader" do field.should_receive(:translate).with(user_a).and_return('Foo') field.should_receive(:translate).with(user_b).and_return('Foo') callbacks.after_save 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 Riddle::Query::Insert.should_receive(:new).twice. with('my_index', ['id', 'name', 'created_at'], [123, 'Foo', time]). and_return(insert) callbacks.after_save instance end it "gets the document id for each reader" do index.should_receive(:document_id_for_key).with(13).and_return(123) index.should_receive(:document_id_for_key).with(14).and_return(123) callbacks.after_save instance end it "translates values for each reader" do field.should_receive(:translate).with(user_a).and_return('Foo') field.should_receive(:translate).with(user_b).and_return('Foo') callbacks.after_save instance end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/real_time/attribute_spec.rb0000644000004100000410000000341712556214551026345 0ustar www-datawww-datarequire '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 attribute.name.should == 'foo' end it "falls back to the column's name" do attribute.name.should == '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 column.stub :__name => 'value' attribute.translate(object).should == 'value' end it "returns the column's name if it's an integer" do column.stub :__name => 404 attribute.translate(object).should == 404 end it "returns the object's method matching the column's name" do object.stub :created_at => 'a time' attribute.translate(object).should == 'a time' end it "uses the column's stack to navigate through the object tree" do column.stub :__name => :name, :__stack => [:parent] attribute.translate(object).should == 'the parent name' end it "returns zero if any element in the object tree is nil" do column.stub :__name => :name, :__stack => [:parent] object.parent = nil attribute.translate(object).should be_zero end end describe '#type' do it "returns the given type option" do attribute = ThinkingSphinx::RealTime::Attribute.new column, :type => :string attribute.type.should == :string end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/real_time/interpreter_spec.rb0000644000004100000410000001271512556214551026706 0ustar www-datawww-datarequire '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 ThinkingSphinx::RealTime::Interpreter.should_receive(:new). with(index, block).and_return(instance) ThinkingSphinx::RealTime::Interpreter.translate! index, block end it "calls translate! on the instance" do ThinkingSphinx::RealTime::Interpreter.stub!(:new => instance) instance.should_receive(:translate!) ThinkingSphinx::RealTime::Interpreter.translate! index, block end end describe '#has' do let(:column) { double('column') } let(:attribute) { double('attribute') } before :each do ThinkingSphinx::RealTime::Attribute.stub! :new => attribute end it "creates a new attribute with the provided column" do ThinkingSphinx::RealTime::Attribute.should_receive(:new). with(column, {}).and_return(attribute) instance.has column end it "passes through options to the attribute" do ThinkingSphinx::RealTime::Attribute.should_receive(:new). with(column, :as => :other_name).and_return(attribute) instance.has column, :as => :other_name end it "adds an attribute to the index" do instance.has column index.attributes.should include(attribute) end it "adds multiple attributes when passed multiple columns" do instance.has column, column index.attributes.select { |saved_attribute| saved_attribute == attribute }.length.should == 2 end end describe '#indexes' do let(:column) { double('column') } let(:field) { double('field') } before :each do ThinkingSphinx::RealTime::Field.stub! :new => field end it "creates a new field with the provided column" do ThinkingSphinx::RealTime::Field.should_receive(:new). with(column, {}).and_return(field) instance.indexes column end it "passes through options to the field" do ThinkingSphinx::RealTime::Field.should_receive(:new). with(column, :as => :other_name).and_return(field) instance.indexes column, :as => :other_name end it "adds a field to the index" do instance.indexes column index.fields.should include(field) end it "adds multiple fields when passed multiple columns" do instance.indexes column, column index.fields.select { |saved_field| saved_field == field }.length.should == 2 end context 'sortable' do let(:attribute) { double('attribute') } before :each do ThinkingSphinx::RealTime::Attribute.stub! :new => attribute column.stub :__name => :col end it "adds the _sort suffix to the field's name" do ThinkingSphinx::RealTime::Attribute.should_receive(:new). with(column, :as => :col_sort, :type => :string). and_return(attribute) instance.indexes column, :sortable => true end it "respects given aliases" do ThinkingSphinx::RealTime::Attribute.should_receive(:new). with(column, :as => :other_sort, :type => :string). and_return(attribute) instance.indexes column, :sortable => true, :as => :other end it "respects symbols instead of columns" do ThinkingSphinx::RealTime::Attribute.should_receive(:new). with(:title, :as => :title_sort, :type => :string). and_return(attribute) instance.indexes :title, :sortable => true end it "adds an attribute to the index" do instance.indexes column, :sortable => true index.attributes.should include(attribute) end end end describe '#method_missing' do let(:column) { double('column') } before :each do ThinkingSphinx::ActiveRecord::Column.stub!(:new => column) end it "returns a new column for the given method" do instance.id.should == column end it "should initialise the column with the method name and arguments" do ThinkingSphinx::ActiveRecord::Column.should_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 index.should_receive(:scope=).with(instance_of(Proc)) instance.scope { :foo } end end describe '#set_property' do before :each do index.class.stub :settings => [:morphology] end it 'saves other settings as index options' do instance.set_property :field_weights => {:name => 10} index.options[:field_weights].should == {:name => 10} end context 'index settings' do it "sets the provided setting" do index.should_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 interpreter.translate!. should == interpreter.__id__ end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/real_time/index_spec.rb0000644000004100000410000001044112556214551025444 0ustar www-datawww-datarequire 'spec_helper' describe ThinkingSphinx::RealTime::Index do let(:index) { ThinkingSphinx::RealTime::Index.new :user } let(:indices_path) { double('indices path', :join => '') } let(:config) { double('config', :settings => {}, :indices_location => indices_path, :next_offset => 8) } before :each do ThinkingSphinx::Configuration.stub :instance => config end describe '#attributes' do it "has the internal id attribute by default" do index.attributes.collect(&:name).should include('sphinx_internal_id') end it "has the class name attribute by default" do index.attributes.collect(&:name).should include('sphinx_internal_class') end it "has the internal deleted attribute by default" do index.attributes.collect(&:name).should include('sphinx_deleted') end end describe '#delta?' do it "always returns false" do index.should_not be_delta end end describe '#document_id_for_key' do it "calculates the document id based on offset and number of indices" do config.stub_chain(:indices, :count).and_return(5) config.stub :next_offset => 7 index.document_id_for_key(123).should == 622 end end describe '#fields' do it "has the internal class field by default" do index.fields.collect(&:name).should include('sphinx_internal_class_name') end end describe '#interpret_definition!' do let(:block) { double('block') } before :each do index.definition_block = block end it "interprets the definition block" do ThinkingSphinx::RealTime::Interpreter.should_receive(:translate!). with(index, block) index.interpret_definition! end it "only interprets the definition block once" do ThinkingSphinx::RealTime::Interpreter.should_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 ActiveSupport::Inflector.stub(:constantize => model) index.model.should == model end it "memoizes the result" do ActiveSupport::Inflector.should_receive(:constantize).with('User').once. and_return(model) index.model index.model end end describe '#morphology' do before :each do pending end context 'with a render' do it "defaults to nil" do begin index.render rescue Riddle::Configuration::ConfigurationError end index.morphology.should 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 index.morphology.should == 'stem_en' end end end describe '#name' do it "always uses the core suffix" do index = ThinkingSphinx::RealTime::Index.new :user index.name.should == 'user_core' end end describe '#offset' do before :each do config.stub :next_offset => 4 end it "uses the next offset value from the configuration" do index.offset.should == 4 end it "uses the reference to get a unique offset" do config.should_receive(:next_offset).with(:user).and_return(2) index.offset end end describe '#render' do before :each do FileUtils.stub :mkdir_p => true end it "interprets the provided definition" do index.should_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') } it "returns the model by default" do ActiveSupport::Inflector.stub(:constantize => model) index.scope.should == model end it "returns the evaluated scope if provided" do index.scope = lambda { :foo } index.scope.should == :foo end end describe '#unique_attribute_names' do it "returns all attribute names" do index.unique_attribute_names.should == [ 'sphinx_internal_id', 'sphinx_internal_class', 'sphinx_deleted' ] end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/deletion_spec.rb0000644000004100000410000000302012556214551024172 0ustar www-datawww-datarequire '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 ThinkingSphinx::Connection.stub(:take).and_yield(connection) Riddle::Query.stub :update => 'UPDATE STATEMENT' end context 'index is SQL-backed' do it "updates the deleted flag to false" do connection.should_receive(:execute).with <<-SQL UPDATE foo_core SET sphinx_deleted = 1 WHERE id IN (14) SQL ThinkingSphinx::Deletion.perform index, 7 end it "doesn't care about Sphinx errors" do connection.stub(:execute). and_raise(ThinkingSphinx::ConnectionError.new('')) lambda { ThinkingSphinx::Deletion.perform index, 7 }.should_not raise_error end end context "index is real-time" do before :each do index.stub :type => 'rt' end it "deletes the record to false" do connection.should_receive(:execute). with('DELETE FROM foo_core WHERE id = 14') ThinkingSphinx::Deletion.perform index, 7 end it "doesn't care about Sphinx errors" do connection.stub(:execute). and_raise(ThinkingSphinx::ConnectionError.new('')) lambda { ThinkingSphinx::Deletion.perform index, 7 }.should_not raise_error end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/excerpter_spec.rb0000644000004100000410000000307512556214551024402 0ustar www-datawww-datarequire '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 ThinkingSphinx::Connection.stub(:take).and_yield(connection) Riddle::Query.stub :snippets => 'CALL SNIPPETS' end describe '#excerpt!' do it "generates a snippets call" do Riddle::Query.should_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 => ' -- ') Riddle::Query.should_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 connection.should_receive(:execute).with('CALL SNIPPETS'). and_return([{'snippet' => ''}]) excerpter.excerpt!('all of the words') end it "returns the first value returned by Sphinx" do connection.stub :execute => [{'snippet' => 'some highlighted words'}] excerpter.excerpt!('all of the words').should == 'some highlighted words' end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/configuration_spec.rb0000644000004100000410000002720412556214551025250 0ustar www-datawww-datarequire '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 ThinkingSphinx::Configuration.instance. should be_a(ThinkingSphinx::Configuration) end it "memoizes the instance" do config = double('configuration') ThinkingSphinx::Configuration.should_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 File.stub :exists? => true File.stub :read => { 'test' => {'foo' => 'bugs'}, 'production' => {'foo' => 'bar'} }.to_yaml ThinkingSphinx::Configuration.reset # Grab a new copy of the instance. config = ThinkingSphinx::Configuration.instance config.settings['foo'].should == 'bugs' config.framework = double :environment => 'production', :root => Pathname.new(__FILE__).join('..', '..', 'internal') config.settings['foo'].should == 'bar' end end describe '#configuration_file' do it "uses the Rails environment in the configuration file name" do config.configuration_file. should == File.join(Rails.root, 'config', 'test.sphinx.conf') end it "respects provided settings" do write_configuration 'configuration_file' => '/path/to/foo.conf' config.configuration_file.should == '/path/to/foo.conf' end end describe '#controller' do it "returns an instance of Riddle::Controller" do config.controller.should be_a(Riddle::Controller) end it "memoizes the instance" do Riddle::Controller.should_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/') config.controller.bin_path.should == '/foo/bar/bin/' end it "appends a backslash to the bin_path if appropriate" do write_configuration('bin_path' => '/foo/bar/bin') config.controller.bin_path.should == '/foo/bar/bin/' end end describe '#index_paths' do it "uses app/indices in the Rails app by default" do config.index_paths.should include(File.join(Rails.root, 'app', 'indices')) 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 Rails::Engine.should_receive(:subclasses).and_return([ engine_class ]) config.index_paths.should include('/engine/app/indices') end end describe '#indices_location' do it "stores index files in db/sphinx/ENVIRONMENT" do config.indices_location. should == File.join(Rails.root, 'db', 'sphinx', 'test') end it "respects provided settings" do write_configuration 'indices_location' => '/my/index/files' config.indices_location.should == '/my/index/files' 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 config.searchd.pid_file. should == File.join(Rails.root, 'log', 'test.sphinx.pid') end it "sets the daemon log within log for the Rails app" do config.searchd.log. should == File.join(Rails.root, 'log', 'test.searchd.log') end it "sets the query log within log for the Rails app" do config.searchd.query_log. should == File.join(Rails.root, 'log', 'test.searchd.query.log') end it "sets indexer settings if within thinking_sphinx.yml" do write_configuration 'mem_limit' => '128M' config.indexer.mem_limit.should == '128M' end it "sets searchd settings if within thinking_sphinx.yml" do write_configuration 'workers' => 'none' config.searchd.workers.should == '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 config.next_offset(reference).should == 0 end it "increments for each new reference" do config.next_offset(double('reference')).should == 0 config.next_offset(double('reference')).should == 1 config.next_offset(double('reference')).should == 2 end it "doesn't increment for recorded references" do config.next_offset(reference).should == 0 config.next_offset(reference).should == 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'] Dir.should_receive(:[]).with('/path/to/indices/**/*.rb').once. and_return([]) Dir.should_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'] Dir.stub! :[] => [ '/path/to/indices/foo_index.rb', '/path/to/indices/bar_index.rb' ] ActiveSupport::Dependencies.should_receive(:require_or_load). with('/path/to/indices/foo_index.rb').once ActiveSupport::Dependencies.should_receive(:require_or_load). with('/path/to/indices/bar_index.rb').once config.preload_indices end it "does not double-load indices" do config.index_paths.replace ['/path/to/indices'] Dir.stub! :[] => [ '/path/to/indices/foo_index.rb', '/path/to/indices/bar_index.rb' ] ActiveSupport::Dependencies.should_receive(:require_or_load). with('/path/to/indices/foo_index.rb').once ActiveSupport::Dependencies.should_receive(:require_or_load). with('/path/to/indices/bar_index.rb').once config.preload_indices config.preload_indices end it 'adds distributed indices' do distributor.should_receive(:reconcile) config.preload_indices end it 'does not add distributed indices if disabled' do write_configuration('distributed_indices' => false) distributor.should_not_receive(:reconcile) config.preload_indices end end describe '#render' do before :each do config.searchd.stub! :render => 'searchd { }' end it "searches each index path for ruby files" do config.index_paths.replace ['/path/to/indices', '/path/to/other/indices'] Dir.should_receive(:[]).with('/path/to/indices/**/*.rb').once. and_return([]) Dir.should_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'] Dir.stub! :[] => [ '/path/to/indices/foo_index.rb', '/path/to/indices/bar_index.rb' ] ActiveSupport::Dependencies.should_receive(:require_or_load). with('/path/to/indices/foo_index.rb').once ActiveSupport::Dependencies.should_receive(:require_or_load). with('/path/to/indices/bar_index.rb').once config.render end it "does not double-load indices" do config.index_paths.replace ['/path/to/indices'] Dir.stub! :[] => [ '/path/to/indices/foo_index.rb', '/path/to/indices/bar_index.rb' ] ActiveSupport::Dependencies.should_receive(:require_or_load). with('/path/to/indices/foo_index.rb').once ActiveSupport::Dependencies.should_receive(:require_or_load). with('/path/to/indices/bar_index.rb').once config.preload_indices config.preload_indices end end describe '#render_to_file' do let(:file) { double('file') } let(:output) { config.render } before :each do config.searchd.stub! :render => 'searchd { }' end it "writes the rendered configuration to the file" do config.configuration_file = '/path/to/file.config' config.should_receive(:open).with('/path/to/file.config', 'w'). and_yield(file) file.should_receive(:write).with(output) config.render_to_file end it "creates a directory at the binlog_path" do FileUtils.stub :mkdir_p => true config.stub :searchd => double(:binlog_path => '/path/to/binlog') FileUtils.should_receive(:mkdir_p).with('/path/to/binlog') config.render_to_file end it "skips creating a directory when the binlog_path is blank" do FileUtils.stub :mkdir_p => true config.stub :searchd => double(:binlog_path => '') FileUtils.should_not_receive(:mkdir_p) config.render_to_file end end describe '#searchd' do describe '#address' do it "defaults to 127.0.0.1" do config.searchd.address.should == '127.0.0.1' end it "respects the address setting" do write_configuration('address' => '10.11.12.13') config.searchd.address.should == '10.11.12.13' end end describe '#mysql41' do it "defaults to 9306" do config.searchd.mysql41.should == 9306 end it "respects the port setting" do write_configuration('port' => 9313) config.searchd.mysql41.should == 9313 end it "respects the mysql41 setting" do write_configuration('mysql41' => 9307) config.searchd.mysql41.should == 9307 end end end describe '#settings' do context 'YAML file exists' do before :each do File.stub :exists? => true end it "reads from the YAML file" do File.should_receive(:read).and_return('') config.settings end it "uses the settings for the given environment" do File.stub :read => { 'test' => {'foo' => 'bar'}, 'staging' => {'baz' => 'qux'} }.to_yaml Rails.stub :env => 'staging' config.settings['baz'].should == 'qux' end it "remembers the file contents" do File.should_receive(:read).and_return('') config.settings config.settings end it "returns an empty hash when no settings for the environment exist" do File.stub :read => {'test' => {'foo' => 'bar'}}.to_yaml Rails.stub :env => 'staging' config.settings.should == {} end end context 'YAML file does not exist' do before :each do File.stub :exists? => false end it "does not read the file" do File.should_not_receive(:read) config.settings end it "returns an empty hash" do config.settings.should == {} end end end describe '#version' do it "defaults to 2.1.4" do config.version.should == '2.1.4' end it "respects supplied YAML versions" do write_configuration 'version' => '2.0.4' config.version.should == '2.0.4' end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/wildcard_spec.rb0000644000004100000410000000250612556214551024170 0ustar www-datawww-data# encoding: utf-8 module ThinkingSphinx; end require './lib/thinking_sphinx/wildcard' describe ThinkingSphinx::Wildcard do describe '.call' do it "does not star quorum operators" do ThinkingSphinx::Wildcard.call("foo/3").should == "*foo*/3" end it "does not star proximity operators or quoted strings" do ThinkingSphinx::Wildcard.call(%q{"hello world"~3}). should == %q{"hello world"~3} end it "treats slashes as a separator when starring" do ThinkingSphinx::Wildcard.call("a\\/c").should == "*a*\\/*c*" end it "separates escaping from the end of words" do ThinkingSphinx::Wildcard.call("\\(913\\)").should == "\\(*913*\\)" end it "ignores escaped slashes" do ThinkingSphinx::Wildcard.call("\\/\\/pan").should == "\\/\\/*pan*" end it "does not star manually provided field tags" do ThinkingSphinx::Wildcard.call("@title pan").should == "@title *pan*" end it "does not star manually provided arrays of field tags" do ThinkingSphinx::Wildcard.call("@(title, body) pan"). should == "@(title, body) *pan*" end it "handles nil queries" do ThinkingSphinx::Wildcard.call(nil).should == '' end it "handles unicode values" do ThinkingSphinx::Wildcard.call('älytön').should == '*älytön*' end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/index_spec.rb0000644000004100000410000001035112556214551023503 0ustar www-datawww-datarequire 'spec_helper' describe ThinkingSphinx::Index do let(:configuration) { Struct.new(:indices, :settings).new([], {}) } before :each do ThinkingSphinx::Configuration.stub :instance => configuration end describe '.define' do let(:index) { double('index', :definition_block= => nil) } context 'with ActiveRecord' do before :each do ThinkingSphinx::ActiveRecord::Index.stub :new => index end it "creates an ActiveRecord index" do ThinkingSphinx::ActiveRecord::Index.should_receive(:new). with(:user, :with => :active_record).and_return index ThinkingSphinx::Index.define(:user, :with => :active_record) end it "returns the ActiveRecord index" do ThinkingSphinx::Index.define(:user, :with => :active_record). should == [index] end it "adds the index to the collection of indices" do ThinkingSphinx::Index.define(:user, :with => :active_record) configuration.indices.should include(index) end it "sets the block in the index" do index.should_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 ThinkingSphinx::Deltas.stub :processor_for => processor ThinkingSphinx::ActiveRecord::Index.stub(:new). and_return(index, delta_index) end it "creates two indices with delta settings" do ThinkingSphinx::ActiveRecord::Index.unstub :new ThinkingSphinx::ActiveRecord::Index.should_receive(:new). with(:user, hash_including(:delta? => false, :delta_processor => processor) ).once. and_return index ThinkingSphinx::ActiveRecord::Index.should_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 configuration.indices.should include(index) configuration.indices.should include(delta_index) end it "sets the block in the index" do index.should_receive(:definition_block=).with instance_of(Proc) delta_index.should_receive(:definition_block=).with instance_of(Proc) ThinkingSphinx::Index.define(:user, :with => :active_record, :delta => true) do indexes name end end end end context 'with Real-Time' do before :each do ThinkingSphinx::RealTime::Index.stub :new => index end it "creates a real-time index" do ThinkingSphinx::RealTime::Index.should_receive(:new). with(:user, :with => :real_time).and_return index ThinkingSphinx::Index.define(:user, :with => :real_time) end it "returns the ActiveRecord index" do ThinkingSphinx::Index.define(:user, :with => :real_time). should == [index] end it "adds the index to the collection of indices" do ThinkingSphinx::Index.define(:user, :with => :real_time) configuration.indices.should include(index) end it "sets the block in the index" do index.should_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 ThinkingSphinx::Index.new(:user, {}).options.should == {} end it "respects defaults from settings" do configuration.settings['index_options'] = {'delta' => true} ThinkingSphinx::Index.new(:user, {}).options.should == {:delta => true} end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/facet_search_spec.rb0000644000004100000410000000754512556214551025016 0ustar www-datawww-datarequire '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, ThinkingSphinx::SphinxQL.count[:column] => 5, ThinkingSphinx::SphinxQL.group_by[:column] => 2 }] end end describe '#[]' do it "populates facet results" do facet_search[:price_bracket].should == {3 => 5} end end describe '#populate' do it "queries on each facet with a grouped search in a batch" do facet_search.populate batch.searches.detect { |search| search.options[:group_by] == 'price_bracket' }.should_not be_nil end it "limits query for a facet to just indices that have that facet" do facet_search.populate batch.searches.detect { |search| search.options[:indices] == ['foo_core'] }.should_not be_nil end it "limits facets to the specified set" do facet_search.options[:facets] = [:category_id] facet_search.populate batch.searches.collect { |search| search.options[:group_by] }.should == ['category_id'] end it "aliases the class facet from sphinx_internal_class" do property_a.stub :name => 'sphinx_internal_class' facet_search.populate facet_search[:class].should == {'Foo' => 5} end it "uses the @groupby value for MVAs" do property_a.stub :name => 'tag_ids', :multi? => true facet_search.populate facet_search[:tag_ids].should == {2 => 5} end [:max_matches, :limit].each do |setting| it "sets #{setting} in each search" do facet_search.populate batch.searches.each { |search| search.options[setting].should == 1000 } end it "respects configured max_matches values for #{setting}" do configuration.settings['max_matches'] = 1234 facet_search.populate batch.searches.each { |search| search.options[setting].should == 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| search.options[setting].should == 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| search.options[setting].should == 10 search.options[:max_matches].should == 500 end end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/middlewares/0000755000004100000410000000000012556214551023335 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb0000644000004100000410000001170612556214551031763 0ustar www-datawww-datamodule 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 => {}) } def raw_result(id, model_name) {'sphinx_internal_id' => id, 'sphinx_internal_class' => model_name} end describe '#call' do before :each do context.stub :search => search model.stub :unscoped => model end it "translates records to ActiveRecord objects" do model_name = double('article', :constantize => model) instance = double('instance', :id => 24) model.stub :where => [instance] context[:results] << raw_result(24, model_name) middleware.call [context] context[:results].should == [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) model.should_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) article_model.stub :unscoped => article_model user_model.stub :unscoped => user_model context[:results] << raw_result(24, article_name) context[:results] << raw_result(12, user_name) article_model.should_receive(:where).once.and_return([article]) user_model.should_receive(:where).once.and_return([user]) 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) model.stub(:where => [instance_1, instance_2]) middleware.call [context] context[:results].should == [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) model.stub(:order => model, :where => [instance_1, instance_2]) search.options[:sql] = {:order => 'name DESC'} middleware.call [context] context[:results].should == [instance_1, instance_2] end context 'SQL options' do let(:relation) { double('relation', :where => []) } before :each do model.stub :unscoped => relation model_name = double('article', :constantize => model) context[:results] << raw_result(1, model_name) end it "passes through SQL include options to the relation" do search.options[:sql] = {:include => :association} relation.should_receive(:includes).with(:association). and_return(relation) middleware.call [context] end it "passes through SQL join options to the relation" do search.options[:sql] = {:joins => :association} relation.should_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'} relation.should_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} relation.should_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} relation.should_receive(:group).with(:column).and_return(relation) middleware.call [context] end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/middlewares/sphinxql_spec.rb0000644000004100000410000003032112556214551026541 0ustar www-datawww-datamodule 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' require 'thinking_sphinx/sphinxql' 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) context.stub :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 => {}) ] sphinx_sql.should_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'] index_set.first.stub :reference => :user set_class.should_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 search.stub :query => 'tasty' search.options[:conditions] = {:title => 'pancakes'} ThinkingSphinx::Search::Query.should_receive(:new). with('tasty', {:title => 'pancakes'}, anything).and_return(query) middleware.call [context] end it "matches on the generated query" do query.stub :to_s => 'waffles' sphinx_sql.should_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 ThinkingSphinx::Search::Query.should_receive(:new). with(anything, anything, true).and_return(query) middleware.call [context] end it "doesn't append a field condition by default" do ThinkingSphinx::Search::Query.should_receive(:new) do |query, conditions, star| conditions[:sphinx_internal_class_name].should be_nil 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') index_set.first.stub :reference => :animal search.options[:classes] = [model] ThinkingSphinx::Search::Query.should_receive(:new) do |query, conditions, star| conditions[:sphinx_internal_class_name].should 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 supermodel.stub :connection => db_connection, :column_names => ['type'] submodel = Class.new(supermodel) do def self.name; 'Lion'; end def self.inheritance_column; 'type'; end def self.table_name; 'cats'; end end submodel.stub :connection => db_connection, :column_names => ['type'], :descendants => [] index_set.first.stub :reference => :cat search.options[:classes] = [submodel] ThinkingSphinx::Search::Query.should_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 supermodel.stub :connection => db_connection, :column_names => ['type'] submodel = Class.new(supermodel) do def self.name; 'Animals::Lion'; end def self.inheritance_column; 'type'; end def self.table_name; 'cats'; end end submodel.stub :connection => db_connection, :column_names => ['type'], :descendants => [] index_set.first.stub :reference => :"animals/cat" search.options[:classes] = [submodel] ThinkingSphinx::Search::Query.should_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') index_set.first.stub :reference => :animal search.options[:classes] = [model] search.options[:skip_sti] = true model.connection.should_not_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 supermodel.stub :connection => db_connection, :column_names => ['type'] submodel = Class.new(supermodel) do def self.name; 'Lion'; end def self.inheritance_column; 'type'; end def self.table_name; 'cats'; end end submodel.stub :connection => db_connection, :column_names => ['type'], :descendants => [] index_set.first.stub :reference => :cat search.options[:classes] = [submodel] expect { middleware.call [context] }.to_not raise_error end it "filters out deleted values by default" do sphinx_sql.should_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} sphinx_sql.should_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]} sphinx_sql.should_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] sphinx_sql.should_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]} sphinx_sql.should_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]} sphinx_sql.should_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' sphinx_sql.should_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 sphinx_sql.should_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 search.stub :masks => [] sphinx_sql.stub :values => sphinx_sql sphinx_sql.should_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 sphinx_sql.should_receive(:order_within_group_by).with('title ASC'). and_return(sphinx_sql) middleware.call [context] end it "uses the provided offset" do search.stub :offset => 50 sphinx_sql.should_receive(:offset).with(50).and_return(sphinx_sql) middleware.call [context] end it "uses the provided limit" do search.stub :per_page => 24 sphinx_sql.should_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' sphinx_sql.should_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 sphinx_sql.should_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' sphinx_sql.should_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} sphinx_sql.should_receive(:with_options) do |options| options[:field_weights].should == {: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} sphinx_sql.should_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 sphinx_sql.should_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 sphinx_sql.should_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' sphinx_sql.should_receive(:with_options) do |options| options[:ranker].should == 'proximity' sphinx_sql end middleware.call [context] end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/middlewares/glazier_spec.rb0000644000004100000410000000341012556214551026327 0ustar www-datawww-datamodule 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) context.stub :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] context[:results].should == [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] context[:results].should == [glazed_result] end it "creates a glazed result for each result" do ThinkingSphinx::Search::Glaze.should_receive(:new). with(context, result, raw_result, [pane_class]). and_return(glazed_result) middleware.call [context] end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb0000644000004100000410000000261412556214551030147 0ustar www-datawww-datamodule 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) app.should_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 lambda { middleware.call [context] }.should raise_error(ThinkingSphinx::Search::StaleIdsException) { |err| err.ids.should == [42] } end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb0000644000004100000410000000476512556214551030041 0ustar www-datawww-datamodule 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 context.stub :search => search end context 'one stale ids exception' do before :each do app.stub(:call) do @calls ||= 0 @calls += 1 raise ThinkingSphinx::Search::StaleIdsException, [12] if @calls == 1 end end it "appends the ids to the without_ids filter" do middleware.call [context] search.options[:without_ids].should == [12] end it "respects existing without_ids filters" do search.options[:without_ids] = [11] middleware.call [context] search.options[:without_ids].should == [11, 12] end end context 'two stale ids exceptions' do before :each do app.stub(:call) do @calls ||= 0 @calls += 1 raise ThinkingSphinx::Search::StaleIdsException, [12] if @calls == 1 raise ThinkingSphinx::Search::StaleIdsException, [13] if @calls == 2 end end it "appends the ids to the without_ids filter" do middleware.call [context] search.options[:without_ids].should == [12, 13] end it "respects existing without_ids filters" do search.options[:without_ids] = [11] middleware.call [context] search.options[:without_ids].should == [11, 12, 13] end end context 'three stale ids exceptions' do before :each do app.stub(:call) do @calls ||= 0 @calls += 1 raise ThinkingSphinx::Search::StaleIdsException, [12] if @calls == 1 raise ThinkingSphinx::Search::StaleIdsException, [13] if @calls == 2 raise ThinkingSphinx::Search::StaleIdsException, [14] if @calls == 3 end end it "raises the final stale ids exceptions" do lambda { middleware.call [context] }.should raise_error(ThinkingSphinx::Search::StaleIdsException) { |err| err.ids.should == [14] } end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/middlewares/geographer_spec.rb0000644000004100000410000000574712556214551027034 0ustar www-datawww-datamodule 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 context.stub :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 sphinx_sql.should_not_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 sphinx_sql.should_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 sphinx_sql.stub :prepend_values => sphinx_sql middleware.call [context] context[:panes].should include(ThinkingSphinx::Panes::DistancePane) end it "respects :latitude_attr and :longitude_attr options" do search.options[:latitude_attr] = 'side_to_side' search.options[:longitude_attr] = 'up_or_down' sphinx_sql.should_receive(:prepend_values). 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') sphinx_sql.should_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') sphinx_sql.should_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] sphinx_sql.should_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-3.1.4/spec/thinking_sphinx/middlewares/inquirer_spec.rb0000644000004100000410000000376412556214551026544 0ustar www-datawww-datamodule 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 batch_class.stub(: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 batch_inquirer.should_receive(:append_query).with('SELECT * FROM index') batch_inquirer.should_receive(:append_query).with('SHOW META') middleware.call [context] end it "sets up the raw results" do middleware.call [context] context[:raw].should == [:raw] end it "sets up the meta results as a hash" do middleware.call [context] context[:meta].should == {'meta' => 'value'} end it "uses the raw values as the initial results" do middleware.call [context] context[:results].should == [: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] context[:results].should be_a Array end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/deltas_spec.rb0000644000004100000410000000404412556214551023652 0ustar www-datawww-datarequire 'spec_helper' describe ThinkingSphinx::Deltas do describe '.processor_for' do it "returns the default processor class when given true" do ThinkingSphinx::Deltas.processor_for(true). should == ThinkingSphinx::Deltas::DefaultDelta end it "returns the class when given one" do klass = Class.new ThinkingSphinx::Deltas.processor_for(klass).should == klass end it "instantiates a class from the name as a string" do ThinkingSphinx::Deltas. processor_for('ThinkingSphinx::Deltas::DefaultDelta'). should == 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 ThinkingSphinx::Configuration.stub :instance => config end it "executes the given block" do variable = :foo ThinkingSphinx::Deltas.suspend :user do variable = :bar end variable.should == :bar end it "suspends deltas within the block" do ThinkingSphinx::Deltas.suspend :user do ThinkingSphinx::Deltas.should be_suspended end end it "removes the suspension after the block" do ThinkingSphinx::Deltas.suspend :user do # end ThinkingSphinx::Deltas.should_not be_suspended end it "processes the delta indices for the given reference" do processor.should_receive(:index).with(delta_index) ThinkingSphinx::Deltas.suspend :user do # end end it "does not process the core indices for the given reference" do processor.should_not_receive(:index).with(core_index) ThinkingSphinx::Deltas.suspend :user do # end end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/panes/0000755000004100000410000000000012556214551022143 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/thinking_sphinx/panes/weight_pane_spec.rb0000644000004100000410000000076412556214551026003 0ustar www-datawww-datamodule 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[ThinkingSphinx::SphinxQL.weight[:column]] = 101 pane.weight.should == 101 end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/panes/excerpts_pane_spec.rb0000644000004100000410000000275712556214551026355 0ustar www-datawww-datamodule 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 context.stub :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) ThinkingSphinx::Panes::ExcerptsPane::Excerpts.stub :new => excerpts end it "returns an excerpt glazing" do pane.excerpts.should == excerpts end it "creates an excerpter with the first index and the query and conditions values" do context[:indices] = [double(:name => 'alpha'), double(:name => 'beta')] context.search.options[:conditions] = {:baz => 'bar'} ThinkingSphinx::Excerpter.should_receive(:new). with('alpha', 'foo bar', anything).and_return(excerpter) pane.excerpts end it "passes through excerpts options" do search.options[:excerpts] = {:before_match => 'foo'} ThinkingSphinx::Excerpter.should_receive(:new). with(anything, anything, :before_match => 'foo').and_return(excerpter) pane.excerpts end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/panes/attributes_pane_spec.rb0000644000004100000410000000101312556214551026666 0ustar www-datawww-datamodule 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 pane.sphinx_attributes.should == {'foo' => 24} end end end thinking-sphinx-3.1.4/spec/thinking_sphinx/panes/distance_pane_spec.rb0000644000004100000410000000165212556214551026303 0ustar www-datawww-datamodule 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 pane.distance.should == 123.45 end it "converts string geodistances to floats" do raw['geodist'] = '123.450' pane.distance.should == 123.45 end end describe '#geodist' do it "returns the object's geodistance attribute by default" do raw['geodist'] = 123.45 pane.geodist.should == 123.45 end it "converts string geodistances to floats" do raw['geodist'] = '123.450' pane.geodist.should == 123.45 end end end thinking-sphinx-3.1.4/spec/spec_helper.rb0000644000004100000410000000067612556214551020460 0ustar www-datawww-datarequire 'rubygems' require 'bundler' Bundler.require :default, :development root = File.expand_path File.dirname(__FILE__) require "#{root}/support/multi_schema" require 'thinking_sphinx/railtie' Combustion.initialize! :active_record Dir["#{root}/support/**/*.rb"].each { |file| require file } RSpec.configure do |config| # enable filtering for examples config.filter_run :wip => nil config.run_all_when_everything_filtered = true end thinking-sphinx-3.1.4/spec/internal/0000755000004100000410000000000012556214551017445 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/internal/db/0000755000004100000410000000000012556214551020032 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/internal/db/schema.rb0000644000004100000410000000433112556214551021620 0ustar www-datawww-dataActiveRecord::Schema.define do create_table(:admin_people, :force => true) do |t| t.string :name t.timestamps 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 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-3.1.4/spec/internal/config/0000755000004100000410000000000012556214551020712 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/internal/config/database.yml0000644000004100000410000000032412556214551023200 0ustar www-datawww-datatest: adapter: <%= ENV['DATABASE'] || 'mysql2' %> database: thinking_sphinx username: <%= ENV['DATABASE'] == 'postgresql' ? ENV['USER'] : 'root' %> min_messages: warning encoding: utf8 thinking-sphinx-3.1.4/spec/internal/app/0000755000004100000410000000000012556214551020225 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/internal/app/indices/0000755000004100000410000000000012556214551021643 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/internal/app/indices/product_index.rb0000644000004100000410000000110312556214551025032 0ustar www-datawww-datamulti_schema = MultiSchema.new ThinkingSphinx::Index.define :product, :with => :real_time do indexes name, :sortable => true has category_ids, :type => :integer, :multi => true end if multi_schema.active? multi_schema.create 'thinking_sphinx' ThinkingSphinx::Index.define(:product, :name => :product_two, :offset_as => :product_two, :with => :real_time ) do 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-3.1.4/spec/internal/app/indices/book_index.rb0000644000004100000410000000037312556214551024314 0ustar www-datawww-dataThinkingSphinx::Index.define :book, :with => :active_record, :delta => true do indexes title, :sortable => true indexes author, :facet => true indexes [title, author], :as => :info indexes blurb_file, :file => true has year, created_at end thinking-sphinx-3.1.4/spec/internal/app/indices/city_index.rb0000644000004100000410000000035712556214551024334 0ustar www-datawww-dataThinkingSphinx::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-3.1.4/spec/internal/app/indices/user_index.rb0000644000004100000410000000027512556214551024341 0ustar www-datawww-dataThinkingSphinx::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-3.1.4/spec/internal/app/indices/tee_index.rb0000644000004100000410000000016612556214551024137 0ustar www-datawww-dataThinkingSphinx::Index.define :tee, :with => :active_record do index colour.name has colour_id, :facet => true end thinking-sphinx-3.1.4/spec/internal/app/indices/bird_index.rb0000644000004100000410000000014112556214551024273 0ustar www-datawww-dataFlightlessBird ThinkingSphinx::Index.define :bird, :with => :active_record do indexes name end thinking-sphinx-3.1.4/spec/internal/app/indices/car_index.rb0000644000004100000410000000021212556214551024117 0ustar www-datawww-dataThinkingSphinx::Index.define :car, :with => :real_time do indexes name, :sortable => true has manufacturer_id, :type => :integer end thinking-sphinx-3.1.4/spec/internal/app/indices/admin_person_index.rb0000644000004100000410000000013312556214551026032 0ustar www-datawww-dataThinkingSphinx::Index.define 'admin/person', :with => :active_record do indexes name end thinking-sphinx-3.1.4/spec/internal/app/indices/article_index.rb0000644000004100000410000000122312556214551025000 0ustar www-datawww-dataThinkingSphinx::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 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 set_property :morphology => 'stem_en' end thinking-sphinx-3.1.4/spec/internal/app/indices/animal_index.rb0000644000004100000410000000012412556214551024615 0ustar www-datawww-dataThinkingSphinx::Index.define :animal, :with => :active_record do indexes name end thinking-sphinx-3.1.4/spec/internal/app/models/0000755000004100000410000000000012556214551021510 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/internal/app/models/colour.rb0000644000004100000410000000006712556214551023343 0ustar www-datawww-dataclass Colour < ActiveRecord::Base has_many :tees end thinking-sphinx-3.1.4/spec/internal/app/models/product.rb0000644000004100000410000000026612556214551023521 0ustar www-datawww-dataclass Product < ActiveRecord::Base has_many :categorisations has_many :categories, :through => :categorisations after_save ThinkingSphinx::RealTime.callback_for(:product) end thinking-sphinx-3.1.4/spec/internal/app/models/manufacturer.rb0000644000004100000410000000007512556214551024533 0ustar www-datawww-dataclass Manufacturer < ActiveRecord::Base has_many :cars end thinking-sphinx-3.1.4/spec/internal/app/models/genre.rb0000644000004100000410000000005112556214551023131 0ustar www-datawww-dataclass Genre < ActiveRecord::Base # end thinking-sphinx-3.1.4/spec/internal/app/models/admin/0000755000004100000410000000000012556214551022600 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/internal/app/models/admin/person.rb0000644000004100000410000000012012556214551024424 0ustar www-datawww-dataclass Admin::Person < ActiveRecord::Base self.table_name = 'admin_people' end thinking-sphinx-3.1.4/spec/internal/app/models/tagging.rb0000644000004100000410000000011712556214551023454 0ustar www-datawww-dataclass Tagging < ActiveRecord::Base belongs_to :tag belongs_to :article end thinking-sphinx-3.1.4/spec/internal/app/models/mammal.rb0000644000004100000410000000003212556214551023274 0ustar www-datawww-dataclass Mammal < Animal end thinking-sphinx-3.1.4/spec/internal/app/models/bird.rb0000644000004100000410000000003012556214551022746 0ustar www-datawww-dataclass Bird < Animal end thinking-sphinx-3.1.4/spec/internal/app/models/category.rb0000644000004100000410000000016712556214551023656 0ustar www-datawww-dataclass Category < ActiveRecord::Base has_many :categorisations has_many :products, :through => :categorisations end thinking-sphinx-3.1.4/spec/internal/app/models/book.rb0000644000004100000410000000056312556214551022773 0ustar www-datawww-dataclass 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-3.1.4/spec/internal/app/models/hardcover.rb0000644000004100000410000000003712556214551024012 0ustar www-datawww-dataclass Hardcover < Book # end thinking-sphinx-3.1.4/spec/internal/app/models/tee.rb0000644000004100000410000000007012556214551022607 0ustar www-datawww-dataclass Tee < ActiveRecord::Base belongs_to :colour end thinking-sphinx-3.1.4/spec/internal/app/models/categorisation.rb0000644000004100000410000000024512556214551025051 0ustar www-datawww-dataclass Categorisation < ActiveRecord::Base belongs_to :category belongs_to :product after_save ThinkingSphinx::RealTime.callback_for(:product, [:product]) end thinking-sphinx-3.1.4/spec/internal/app/models/user.rb0000644000004100000410000000023112556214551023007 0ustar www-datawww-dataclass User < ActiveRecord::Base has_many :articles default_scope { order(:id) } scope :recent, lambda { where('created_at > ?', 1.week.ago) } end thinking-sphinx-3.1.4/spec/internal/app/models/event.rb0000644000004100000410000000012312556214551023152 0ustar www-datawww-dataclass Event < ActiveRecord::Base belongs_to :eventable, :polymorphic => true end thinking-sphinx-3.1.4/spec/internal/app/models/tag.rb0000644000004100000410000000014412556214551022607 0ustar www-datawww-dataclass Tag < ActiveRecord::Base has_many :taggings has_many :articles, :through => :taggings end thinking-sphinx-3.1.4/spec/internal/app/models/city.rb0000644000004100000410000000011612556214551023003 0ustar www-datawww-dataclass City < ActiveRecord::Base scope :ordered, lambda { order(:name) } end thinking-sphinx-3.1.4/spec/internal/app/models/flightless_bird.rb0000644000004100000410000000004012556214551025173 0ustar www-datawww-dataclass FlightlessBird < Bird end thinking-sphinx-3.1.4/spec/internal/app/models/car.rb0000644000004100000410000000017012556214551022600 0ustar www-datawww-dataclass Car < ActiveRecord::Base belongs_to :manufacturer after_save ThinkingSphinx::RealTime.callback_for(:car) end thinking-sphinx-3.1.4/spec/internal/app/models/article.rb0000644000004100000410000000016712556214551023464 0ustar www-datawww-dataclass Article < ActiveRecord::Base belongs_to :user has_many :taggings has_many :tags, :through => :taggings end thinking-sphinx-3.1.4/spec/internal/app/models/tweet.rb0000644000004100000410000000007612556214551023170 0ustar www-datawww-dataclass Tweet < ActiveRecord::Base self.primary_key = :id end thinking-sphinx-3.1.4/spec/internal/app/models/animal.rb0000644000004100000410000000004612556214551023276 0ustar www-datawww-dataclass Animal < ActiveRecord::Base end thinking-sphinx-3.1.4/spec/internal/tmp/0000755000004100000410000000000012556214551020245 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/internal/tmp/.gitkeep0000644000004100000410000000000012556214551021664 0ustar www-datawww-datathinking-sphinx-3.1.4/spec/fixtures/0000755000004100000410000000000012556214551017502 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/fixtures/database.yml0000644000004100000410000000010312556214551021763 0ustar www-datawww-datausername: root password: host: localhost database: thinking_sphinx thinking-sphinx-3.1.4/spec/thinking_sphinx_spec.rb0000644000004100000410000000177712556214551022410 0ustar www-datawww-datarequire 'spec_helper' describe ThinkingSphinx do describe '.count' do let(:search) { double('search', :total_entries => 23) } before :each do ThinkingSphinx::Search.stub :new => search end it "returns the total entries of the search object" do ThinkingSphinx.count.should == search.total_entries end it "passes through the given query and options" do ThinkingSphinx::Search.should_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 ThinkingSphinx::Search.stub :new => search end it "returns a new search object" do ThinkingSphinx.search.should == search end it "passes through the given query and options" do ThinkingSphinx::Search.should_receive(:new).with('foo', :bar => :baz). and_return(search) ThinkingSphinx.search('foo', :bar => :baz) end end end thinking-sphinx-3.1.4/spec/support/0000755000004100000410000000000012556214551017345 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/support/multi_schema.rb0000644000004100000410000000165312556214551022351 0ustar www-datawww-dataclass MultiSchema def active? ENV['DATABASE'] == 'postgresql' end def create(schema_name) unless connection.schema_exists? schema_name connection.execute %Q{CREATE SCHEMA "#{schema_name}"} end switch schema_name silence_stream(STDOUT) { load Rails.root.join('db', 'schema.rb') } 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-3.1.4/spec/support/sphinx_yaml_helpers.rb0000644000004100000410000000031112556214551023742 0ustar www-datawww-datamodule SphinxYamlHelpers def write_configuration(hash) File.stub :read => {'test' => hash}.to_yaml, :exists? => true end end RSpec.configure do |config| config.include SphinxYamlHelpers end thinking-sphinx-3.1.4/spec/acceptance/0000755000004100000410000000000012556214551017717 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/acceptance/index_options_spec.rb0000644000004100000410000000744012556214551024145 0ustar www-datawww-datarequire '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 index.send("#{type}_fields").should be_nil end it "sets min_#{type}_len" do index.send("min_#{type}_len").should == 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 index.send("#{type}_fields").should == 'title' end it "sets min_#{type}_len" do index.send("min_#{type}_len").should == 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 index.sources.length.should == 2 end it "treats each source as separate" do index.sources.first.fields.length.should == 2 index.sources.last.fields.length.should == 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 index.sources.first.sql_field_str2wordcount.should == ['title'] end it "declares wordcount attributes" do index.sources.first.sql_attr_str2wordcount.should == ['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 index.sources.first.sql_range_step.should == 5 end it "allows for source options" do index.sources.first.disable_range?.should be_true end it "respects sql_query_pre values" do index.sources.first.sql_query_pre.should 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 index.min_infix_len.should == 1 end it "prioritises index-level source options" do index.sources.first.sql_range_step.should == 20 end it "keeps index-level options prioritised when rendered again" do index.render index.min_infix_len.should == 1 end it "keeps index-level options prioritised when rendered again" do index.render index.sources.first.sql_range_step.should == 20 end end end thinking-sphinx-3.1.4/spec/acceptance/searching_with_sti_spec.rb0000644000004100000410000000423212556214551025134 0ustar www-datawww-datarequire '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 Animal.search(:indices => ['animal_core']).to_a.should == [platypus, duck] end it "limits results based on subclasses" do platypus = Animal.create :name => 'Platypus' duck = Bird.create :name => 'Duck' index Bird.search(:indices => ['animal_core']).to_a.should == [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 Bird.search(:indices => ['animal_core']).to_a.should == [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 FlightlessBird.search(:indices => ['animal_core']).to_a.should == [emu] end it "filters out sibling subclasses" do platypus = Animal.create :name => 'Platypus' duck = Bird.create :name => 'Duck' otter = Mammal.create :name => 'Otter' index Bird.search(:indices => ['animal_core']).to_a.should == [duck] end it "obeys :classes if supplied" do platypus = Animal.create :name => 'Platypus' duck = Bird.create :name => 'Duck' emu = FlightlessBird.create :name => 'Emu' index Bird.search( :indices => ['animal_core'], :skip_sti => true, :classes => [Bird, FlightlessBird] ).to_a.should == [duck, emu] end it 'finds root objects when type is blank' do animal = Animal.create :name => 'Animal', type: '' index Animal.search(:indices => ['animal_core']).to_a.should == [animal] end it 'allows for indices on mid-hierarchy classes' do duck = Bird.create :name => 'Duck' emu = FlightlessBird.create :name => 'Emu' index Bird.search(:indices => ['bird_core']).to_a.should == [duck, emu] end end thinking-sphinx-3.1.4/spec/acceptance/excerpts_spec.rb0000644000004100000410000000266012556214551023117 0ustar www-datawww-data# encoding: utf-8 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 search.first.excerpts.title. should == '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 search.first.excerpts.title. should == 'Война и миръ' 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 search.first.excerpts.title. should == '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 search.first.excerpts.title. should == 'Something' end end thinking-sphinx-3.1.4/spec/acceptance/search_for_just_ids_spec.rb0000644000004100000410000000106212556214551025274 0ustar www-datawww-datarequire '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 Article.search_for_ids('pancakes').to_a.should == [pancakes.id] end it "works across the global context" do article = Article.create! :title => 'Pancakes' book = Book.create! :title => 'American Gods' index ThinkingSphinx.search_for_ids.to_a.should =~ [article.id, book.id] end end thinking-sphinx-3.1.4/spec/acceptance/sql_deltas_spec.rb0000644000004100000410000000244312556214551023414 0ustar www-datawww-datarequire '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 Book.search('Terry Pratchett').to_a.should == [guards] men = Book.create( :title => 'Men At Arms', :author => 'Terry Pratchett' ) sleep 0.25 Book.search('Terry Pratchett').to_a.should == [guards, men] end it "automatically indexes updated records" do book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett' index Book.search('Harry').to_a.should == [book] book.reload.update_attributes(:author => 'Terry Pratchett') sleep 0.25 Book.search('Terry').to_a.should == [book] end it "does not match on old values" do book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett' index Book.search('Harry').to_a.should == [book] book.reload.update_attributes(:author => 'Terry Pratchett') sleep 0.25 Book.search('Harry').should be_empty end it "automatically indexes new records of subclasses" do book = Hardcover.create( :title => 'American Gods', :author => 'Neil Gaiman' ) sleep 0.25 Book.search('Gaiman').to_a.should == [book] end end thinking-sphinx-3.1.4/spec/acceptance/facets_spec.rb0000644000004100000410000000714712556214551022534 0ustar www-datawww-data# encoding: utf-8 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 Tee.facets.to_hash[:colour_id].should == { 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 article_count = ENV['SPHINX_VERSION'].try(:[], /2.0.\d/) ? 2 : 1 ThinkingSphinx.facets.to_hash[:class].should == { 'Tee' => 2, 'City' => 1, 'Article' => article_count } 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 Book.facets.to_hash[:author].should == { '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 User.facets.to_hash[:tag_ids].should == { 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 Tee.facets.for(:colour_id => blue.id).to_a.should == [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 User.facets.for(:tag_ids => waffles.id).to_a.should == [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 Book.facets.for(:author => 'Neil Gaiman').to_a.should == [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| facet.should == expectations[calls].first hash.should == expectations[calls].last calls += 1 end end end thinking-sphinx-3.1.4/spec/acceptance/big_integers_spec.rb0000644000004100000410000000330012556214551023713 0ustar www-datawww-datarequire '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 large_index.sources.first.attributes.detect { |attribute| attribute.name == 'sphinx_internal_id' }.type.should == :bigint small_index.sources.first.attributes.detect { |attribute| attribute.name == 'sphinx_internal_id' }.type.should == :bigint real_time_index.attributes.detect { |attribute| attribute.name == 'sphinx_internal_id' }.type.should == :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-3.1.4/spec/acceptance/indexing_spec.rb0000644000004100000410000000201612556214551023062 0ustar www-datawww-datarequire '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' Article.search.should 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' Article.search.should_not 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') File.exist?(file).should be_false end end thinking-sphinx-3.1.4/spec/acceptance/searching_across_models_spec.rb0000644000004100000410000000204012556214551026132 0ustar www-datawww-datarequire 'acceptance/spec_helper' describe 'Searching across models', :live => true do it "returns results" do article = Article.create! :title => 'Pancakes' index ThinkingSphinx.search.first.should == 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' articles.should include(pancakes) articles.should_not include(waffles) end it "handles results from different models" do article = Article.create! :title => 'Pancakes' book = Book.create! :title => 'American Gods' index ThinkingSphinx.search.to_a.should =~ [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 ThinkingSphinx.search(:classes => [User, Article]).to_a. should =~ [article, user] end end thinking-sphinx-3.1.4/spec/acceptance/specifying_sql_spec.rb0000644000004100000410000004205612556214551024304 0ustar www-datawww-datarequire '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 index.sources.first.sql_query.should 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 query.should match(/LEFT OUTER JOIN .users./) query.should 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 query.should match(/LEFT OUTER JOIN .taggings./) query.should 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 query.should 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 query.should 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 query.should 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 index.sources.first.sql_attr_multi.should == ['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 query.should 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 query.should 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 query.should match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/) query.should match(/LEFT OUTER JOIN .books. ON .books.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Book'/) query.should match(/.articles.\..title., .books.\..title./) end 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 query.should match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/) query.should_not match(/articles\..title., users\..title./) query.should match(/.articles.\..title./) end 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 query.should match(/LEFT OUTER JOIN .articles. ON .articles.\..id. = .events.\..eventable_id. AND .events.\..eventable_type. = 'Article'/) query.should match(/LEFT OUTER JOIN .users. ON .users.\..id. = .articles.\..user_id./) query.should match(/.users.\..name./) end end if ActiveRecord::VERSION::MAJOR > 3 describe 'separate queries for MVAs' do 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+/) declaration.should == 'uint tag_ids from query' query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)$/) 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+/) declaration.should == 'uint tag_ids from query' query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.taggings.\..article_id. IS NOT NULL\)\s?$/) 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+/) declaration.should == 'uint tag_ids from query' query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.taggings.\..article_id. IS NOT NULL\)\s?$/) 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+/) declaration.should == 'uint tag_ids from query' query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.articles.\..user_id. IS NOT NULL\)\s?$/) 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+/) declaration.should == 'uint genre_ids from query' query.should match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .books_genres.\..genre_id. AS .genre_ids. FROM .books_genres.\s?$/) end 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+/) declaration.should == 'uint tag_ids from ranged-query' query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)$/) range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/) 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+/) declaration.should == 'uint tag_ids from ranged-query' query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)$/) range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/) 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+/) declaration.should == 'uint genre_ids from ranged-query' query.should match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .books_genres.\..genre_id. AS .genre_ids. FROM .books_genres. WHERE \(.books_genres.\..book_id. BETWEEN \$start AND \$end\)$/) range.should match(/^SELECT MIN\(.books_genres.\..book_id.\), MAX\(.books_genres.\..book_id.\) FROM .books_genres.$/) end 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+/) declaration.should == 'uint tag_ids from query' query.should == '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+/) declaration.should == 'uint tag_ids from ranged-query' query.should == 'My Custom SQL Query' range.should == '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+/) declaration.should == 'uint tag_ids from query' query.should == "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+/) declaration.should == 'tags from query' query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC\s?$/) 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+/) declaration.should == 'tags from query' query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC\s?$/) 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+/) declaration.should == 'tags from query' query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.articles.\..user_id. IS NOT NULL\)\s? ORDER BY .articles.\..user_id. ASC\s?$/) 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+/) declaration.should == 'tags from ranged-query' query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC$/) range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/) 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+/) declaration.should == 'tags from query' query.should == '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+/) declaration.should == 'tags from ranged-query' query.should == 'My Custom SQL Query' range.should == '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+/) declaration.should == 'tags from query' query.should == "My Custom\\\nSQL Query" end end thinking-sphinx-3.1.4/spec/acceptance/grouping_by_attributes_spec.rb0000644000004100000410000000456012556214551026055 0ustar www-datawww-datarequire '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 Book.search(:group_by => :year).to_a.should == [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 Book.search(:group_by => :year, :order_group_by => 'title ASC').to_a. should == [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 book.should == expectation.first count.should == 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 book.should == expectation.first group.should == 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 book.should == expectation[0] group.should == expectation[1] count.should == expectation[2] end end end thinking-sphinx-3.1.4/spec/acceptance/spec_helper.rb0000644000004100000410000000075612556214551022545 0ustar www-datawww-datarequire 'spec_helper' root = File.expand_path File.dirname(__FILE__) Dir["#{root}/support/**/*.rb"].each { |file| require file } if ENV['SPHINX_VERSION'].try :[], /2.0.\d/ ThinkingSphinx::SphinxQL.variables! ThinkingSphinx::Middlewares::DEFAULT.insert_after( ThinkingSphinx::Middlewares::Inquirer, ThinkingSphinx::Middlewares::UTF8 ) ThinkingSphinx::Middlewares::RAW_ONLY.insert_after( ThinkingSphinx::Middlewares::Inquirer, ThinkingSphinx::Middlewares::UTF8 ) end thinking-sphinx-3.1.4/spec/acceptance/searching_across_schemas_spec.rb0000644000004100000410000000256612556214551026307 0ustar www-datawww-datarequire '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' Product.search('Jekyll', :retry_stale => false).to_a.should == [jekyll] Product.search(:retry_stale => false).to_a.should == [jekyll] multi_schema.switch :thinking_sphinx hyde = Product.create name: 'Mister Hyde' Product.search('Jekyll', :retry_stale => false).to_a.should == [] Product.search('Hyde', :retry_stale => false).to_a.should == [hyde] Product.search(:retry_stale => false).to_a.should == [hyde] multi_schema.switch :public Product.search('Jekyll', :retry_stale => false).to_a.should == [jekyll] Product.search(:retry_stale => false).to_a.should == [jekyll] Product.search('Hyde', :retry_stale => false).to_a.should == [] Product.search( :middleware => ThinkingSphinx::Middlewares::RAW_ONLY, :indices => ['product_core', 'product_two_core'] ).to_a.length.should == 2 end end if multi_schema.active? thinking-sphinx-3.1.4/spec/acceptance/searching_with_filters_spec.rb0000644000004100000410000001143212556214551026005 0ustar www-datawww-datarequire '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 Article.search(:with => {:published => true}).to_a.should == [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 Book.search(:with => {:year => [2001, 2005]}).to_a.should == [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 Book.search(:with => {:created_at => 6.days.ago..2.days.ago}).to_a. should == [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 Article.search(:without => {:published => true}).to_a.should == [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 Book.search(:without => {:year => [2001, 2005]}).to_a.should == [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 Article.search( :with => {:taggings_at => 1.days.ago..1.day.from_now} ).to_a.should == [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 Article.search( :with => {:taggings_at => 2.minutes.ago..Time.zone.now} ).to_a.should == [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]} articles.to_a.should == [pancakes] end it "limits results with MVAs that don't contain all the given values" do # Matching results may have some of the given values, but cannot have all # of them. Certainly an edge case. pending "SphinxQL doesn't yet support OR in its WHERE clause" 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]} articles.to_a.should == [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]} products.to_a.should == [pancakes] end end thinking-sphinx-3.1.4/spec/acceptance/attribute_access_spec.rb0000644000004100000410000000233312556214551024603 0ustar www-datawww-datarequire '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 search.first.sphinx_attributes['year'].should == 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 => "*, #{ThinkingSphinx::SphinxQL.weight[:select]}" search.context[:panes] << ThinkingSphinx::Panes::WeightPane search.first.weight.should == 2500 end it "can enumerate with the weight" do gods = Book.create! :title => 'American Gods', :year => 2001 index search = Book.search 'gods', :select => "*, #{ThinkingSphinx::SphinxQL.weight[:select]}" search.masks << ThinkingSphinx::Masks::WeightEnumeratorMask expectations = [[gods, 2500]] search.each_with_weight do |result, weight| expectation = expectations.shift result.should == expectation.first weight.should == expectation.last end end end thinking-sphinx-3.1.4/spec/acceptance/geosearching_spec.rb0000644000004100000410000000431012556214551023712 0ustar www-datawww-datarequire '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 City.search(:geo => [-0.616241, 2.602712], :order => 'geodist ASC'). to_a.should == [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 City.search( :geo => [-0.616241, 2.602712], :with => {:geodist => 0.0..470_000.0} ).to_a.should == [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['SPHINX_VERSION'].try :[], /2.2.\d/ expected = {:mysql => 249907.171875, :postgresql => 249912.03125} else expected = {:mysql => 250326.906250, :postgresql => 250331.234375} end if ActiveRecord::Base.configurations['test']['adapter'][/postgres/] cities.first.geodist.should == expected[:postgresql] else # mysql cities.first.geodist.should == 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 City.search( :geo => [-0.616241, 2.602712], :with => {:geodist => 0.0..470_000.0}, :select => "*, geodist as custom_weight" ).to_a.should == [mel, syd] end end thinking-sphinx-3.1.4/spec/acceptance/sorting_search_results_spec.rb0000644000004100000410000000337012556214551026054 0ustar www-datawww-datarequire '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 Book.search(:order => 'year ASC').to_a.should == [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 Book.search(:order => :year).to_a.should == [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 Book.search(:order => :title).to_a.should == [gods, boys, grave] end it "sorts by a given sortable field with real-time indices" do widgets = Product.create! :name => 'Widgets' gadgets = Product.create! :name => 'Gadgets' Product.search(:order => "name_sort ASC").to_a.should == [gadgets, widgets] 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 Book.search( :select => '*, year MOD 2004 as mod_year', :order => 'mod_year ASC' ).to_a.should == [boys, grave, gods] end end thinking-sphinx-3.1.4/spec/acceptance/real_time_updates_spec.rb0000644000004100000410000000101012556214551024734 0ustar www-datawww-datarequire 'acceptance/spec_helper' describe 'Updates to records in real-time indices', :live => true do it "handles fields with unicode nulls" do product = Product.create! :name => "Widget \u0000" Product.search.first.should == product end it "handles attributes for sortable fields accordingly" do product = Product.create! :name => 'Red Fish' product.update_attributes :name => 'Blue Fish' Product.search('blue fish', :indices => ['product_core']).to_a. should == [product] end end thinking-sphinx-3.1.4/spec/acceptance/search_counts_spec.rb0000644000004100000410000000071612556214551024122 0ustar www-datawww-datarequire '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 Article.search_count.should == 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 ThinkingSphinx.count.should == 5 end end thinking-sphinx-3.1.4/spec/acceptance/searching_on_fields_spec.rb0000644000004100000410000000351012556214551025242 0ustar www-datawww-datarequire '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'} articles.should include(pancakes) articles.should_not 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 Article.search(:conditions => {:user => 'pat'}).first.should == 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 Article.search('waffles', :conditions => {:title => 'pancakes'}).to_a. should == [pancakes] end it "returns results with matches from concatenated columns in a field" do book = Book.create! :title => 'Night Watch', :author => 'Terry Pratchett' index Book.search(:conditions => {:info => 'Night Pratchett'}).to_a. should == [book] end it "handles NULLs in concatenated fields" do book = Book.create! :title => 'Night Watch' index Book.search(:conditions => {:info => 'Night Watch'}).to_a.should == [book] 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 Book.search('cyberpunk').to_a.should == [book] end end thinking-sphinx-3.1.4/spec/acceptance/remove_deleted_records_spec.rb0000644000004100000410000000220412556214551025760 0ustar www-datawww-datarequire '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 Article.search('pancakes').should_not be_empty pancakes.destroy Article.search('pancakes').should be_empty end it "will catch stale records deleted without callbacks being fired" do pancakes = Article.create! :title => 'Pancakes' index Article.search('pancakes').should_not be_empty Article.connection.execute "DELETE FROM articles WHERE id = #{pancakes.id}" Article.search('pancakes').should be_empty end it "removes records from real-time index results" do product = Product.create! :name => 'Shiny' Product.search('Shiny', :indices => ['product_core']).to_a. should == [product] product.destroy Product.search_for_ids('Shiny', :indices => ['product_core']). should be_empty 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-3.1.4/spec/acceptance/suspended_deltas_spec.rb0000644000004100000410000000266112556214551024611 0ustar www-datawww-datarequire '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 Book.search('Harry').to_a.should == [book] ThinkingSphinx::Deltas.suspend :book do book.reload.update_attributes(:author => 'Terry Pratchett') sleep 0.25 Book.search('Terry').to_a.should == [] end sleep 0.25 Book.search('Terry').to_a.should == [book] end it "returns core records even though they are no longer valid" do book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett' index Book.search('Harry').to_a.should == [book] ThinkingSphinx::Deltas.suspend :book do book.reload.update_attributes(:author => 'Terry Pratchett') sleep 0.25 Book.search('Terry').to_a.should == [] end sleep 0.25 Book.search('Harry').to_a.should == [book] end it "marks core records as deleted" do book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett' index Book.search('Harry').to_a.should == [book] ThinkingSphinx::Deltas.suspend_and_update :book do book.reload.update_attributes(:author => 'Terry Pratchett') sleep 0.25 Book.search('Terry').to_a.should == [] end sleep 0.25 Book.search('Harry').to_a.should be_empty end end thinking-sphinx-3.1.4/spec/acceptance/attribute_updates_spec.rb0000644000004100000410000000070712556214551025012 0ustar www-datawww-datarequire '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 Article.search('pancakes', :with => {:published => true}).should be_empty article.published = true article.save Article.search('pancakes', :with => {:published => true}).to_a .should == [article] end end thinking-sphinx-3.1.4/spec/acceptance/paginating_search_results_spec.rb0000644000004100000410000000115712556214551026511 0ustar www-datawww-datarequire '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 Article.search.total_entries.should == 21 end it "paginates the result set by default" do 21.times { |number| Article.create :title => "Article #{number}" } index Article.search.length.should == 20 end it "tracks the number of pages" do 21.times { |number| Article.create :title => "Article #{number}" } index Article.search.total_pages.should == 2 end end thinking-sphinx-3.1.4/spec/acceptance/searching_within_a_model_spec.rb0000644000004100000410000000457112556214551026272 0ustar www-datawww-data# encoding: UTF-8 require 'acceptance/spec_helper' describe 'Searching within a model', :live => true do it "returns results" do article = Article.create! :title => 'Pancakes' index Article.search.first.should == 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' articles.should include(pancakes) articles.should_not include(waffles) end it "handles unicode characters" do istanbul = City.create! :name => 'İstanbul' index City.search('İstanbul').to_a.should == [istanbul] end it "will star provided queries on request" do article = Article.create! :title => 'Pancakes' index Article.search('cake', :star => true).first.should == article end it "allows for searching on specific indices" do article = Article.create :title => 'Pancakes' index articles = Article.search('pancake', :indices => ['stemmed_article_core']) articles.to_a.should == [article] end it "allows for searching on distributed indices" do article = Article.create :title => 'Pancakes' index articles = Article.search('pancake', :indices => ['article']) articles.to_a.should == [article] end it "can search on namespaced models" do person = Admin::Person.create :name => 'James Bond' index Admin::Person.search('Bond').to_a.should == [person] end it "raises an error if searching through an ActiveRecord scope" do lambda { City.ordered.search }.should raise_error(ThinkingSphinx::MixedScopesError) end it "does not raise an error when searching with a default ActiveRecord scope" do lambda { User.search }.should_not raise_error(ThinkingSphinx::MixedScopesError) end it "raises an error when searching with default and applied AR scopes" do lambda { User.recent.search }.should raise_error(ThinkingSphinx::MixedScopesError) end it "raises an error if the model has no indices defined" do lambda { Category.search.to_a }.should raise_error(ThinkingSphinx::NoIndicesError) end end describe 'Searching within a model with a realtime index', :live => true do it "returns results" do product = Product.create! :name => 'Widget' Product.search.first.should == product end end thinking-sphinx-3.1.4/spec/acceptance/support/0000755000004100000410000000000012556214551021433 5ustar www-datawww-datathinking-sphinx-3.1.4/spec/acceptance/support/sphinx_helpers.rb0000644000004100000410000000121112556214551025006 0ustar www-datawww-datamodule 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 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 end thinking-sphinx-3.1.4/spec/acceptance/support/database_cleaner.rb0000644000004100000410000000035412556214551025217 0ustar www-datawww-dataRSpec.configure do |config| config.before(:suite) do DatabaseCleaner.strategy = :truncation end config.after(:each) do if example.example_group_instance.class.metadata[:live] DatabaseCleaner.clean end end end thinking-sphinx-3.1.4/spec/acceptance/support/sphinx_controller.rb0000644000004100000410000000172712556214551025543 0ustar www-datawww-dataclass 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 if ENV['SPHINX_VERSION'].try :[], /2.0.\d/ ThinkingSphinx::Configuration.instance.settings['utf8'] = false end 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 end def stop config.controller.stop end def index(*indices) config.controller.index *indices end private def config ThinkingSphinx::Configuration.instance end end thinking-sphinx-3.1.4/spec/acceptance/association_scoping_spec.rb0000644000004100000410000000436612556214551025325 0ustar www-datawww-datarequire '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 pat.articles.search('Guide').to_a.should == [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 pat.articles.search_for_ids('Guide').to_a.should == [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 porsche.cars.search('Spyder').to_a.should == [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 porsche.cars.search_for_ids('Spyder').to_a.should == [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 flat.products.search('Low').to_a.should == [pancakes] end end end thinking-sphinx-3.1.4/spec/acceptance/sphinx_scopes_spec.rb0000644000004100000410000000506212556214551024146 0ustar www-datawww-datarequire '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 Book.by_year(2009).to_a.should == [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 Book.by_query_and_year('Graveyard', 2009).to_a.should == [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 Book.by_year(2001..2005).ordered.to_a.should == [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 Book.by_year(2001).by_query_and_year('Graveyard', 2009).to_a. should == [grave] end it "allows further search calls on scopes" do gaiman = Book.create! :title => 'American Gods' pratchett = Book.create! :title => 'Small Gods' index Book.by_query('Gods').search('Small').to_a.should == [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 Book.by_query('Gods').facets.to_hash[:author].should == { '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 Book.by_query('gods').count.should == 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-3.1.4/spec/acceptance/batch_searching_spec.rb0000644000004100000410000000122012556214551024355 0ustar www-datawww-datarequire '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 batch.searches.first.should include(pancakes) batch.searches.first.should_not include(waffles) batch.searches.last.should include(waffles) batch.searches.last.should_not include(pancakes) end end thinking-sphinx-3.1.4/.travis.yml0000644000004100000410000000153112556214551017010 0ustar www-datawww-datalanguage: ruby rvm: - 1.9.3 - 2.0.0 - 2.1.0 - jruby-19mode before_install: - 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" - bundle exec appraisal install script: bundle exec appraisal rspec env: - DATABASE=mysql2 SPHINX_BIN=/usr/local/sphinx-2.0.10/bin/ SPHINX_VERSION=2.0.10 - DATABASE=postgresql SPHINX_BIN=/usr/local/sphinx-2.0.10/bin/ SPHINX_VERSION=2.0.10 - DATABASE=mysql2 SPHINX_BIN=/usr/local/sphinx-2.1.9/bin/ SPHINX_VERSION=2.1.9 - DATABASE=postgresql SPHINX_BIN=/usr/local/sphinx-2.1.9/bin/ SPHINX_VERSION=2.1.9 - DATABASE=mysql2 SPHINX_BIN=/usr/local/sphinx-2.2.6/bin/ SPHINX_VERSION=2.2.6 - DATABASE=postgresql SPHINX_BIN=/usr/local/sphinx-2.2.6/bin/ SPHINX_VERSION=2.2.6 thinking-sphinx-3.1.4/lib/0000755000004100000410000000000012556214551015445 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking-sphinx.rb0000644000004100000410000000003212556214551021107 0ustar www-datawww-datarequire 'thinking_sphinx' thinking-sphinx-3.1.4/lib/thinking_sphinx/0000755000004100000410000000000012556214551020651 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/active_record.rb0000644000004100000410000000253512556214551024014 0ustar www-datawww-datarequire 'active_record' require 'joiner' module ThinkingSphinx::ActiveRecord module Callbacks; 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/filter_reflection' 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/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' thinking-sphinx-3.1.4/lib/thinking_sphinx/excerpter.rb0000644000004100000410000000171512556214551023203 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/distributed.rb0000644000004100000410000000013012556214551023512 0ustar www-datawww-datamodule ThinkingSphinx::Distributed # end require 'thinking_sphinx/distributed/index' thinking-sphinx-3.1.4/lib/thinking_sphinx/batched_search.rb0000644000004100000410000000063512556214551024121 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/index.rb0000644000004100000410000000245712556214551022315 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/configuration.rb0000644000004100000410000001111612556214551024045 0ustar www-datawww-datarequire 'pathname' class ThinkingSphinx::Configuration < Riddle::Configuration attr_accessor :configuration_file, :indices_location, :version attr_reader :index_paths attr_writer :controller, :index_set_class delegate :environment, :to => :framework def initialize super setup 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 = ThinkingSphinx::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 setup framework end def engine_index_paths return [] unless defined?(Rails) engine_indice_paths.flatten.compact 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 index_set_class @index_set_class ||= ThinkingSphinx::IndexSet 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 return if @preloaded_indices index_paths.each do |path| Dir["#{path}/**/*.rb"].sort.each do |file| ActiveSupport::Dependencies.require_or_load file end end if settings['distributed_indices'].nil? || settings['distributed_indices'] ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile end @preloaded_indices = true end def render preload_indices ThinkingSphinx::Configuration::ConsistentIds.new(indices).reconcile ThinkingSphinx::Configuration::MinimumFields.new(indices).reconcile super end def render_to_file FileUtils.mkdir_p searchd.binlog_path unless searchd.binlog_path.blank? open(configuration_file, 'w') { |file| file.write render } end def settings @settings ||= File.exists?(settings_file) ? settings_to_hash : {} end private def configure_searchd configure_searchd_log_files searchd.binlog_path = tmp_path.join('binlog', environment).to_s searchd.address = settings['address'].presence || Defaults::ADDRESS searchd.mysql41 = settings['mysql41'] || settings['port'] || Defaults::PORT searchd.workers = 'threads' searchd.mysql_version_string = '5.5.21' if RUBY_PLATFORM == 'java' end def configure_searchd_log_files searchd.pid_file = log_root.join("#{environment}.sphinx.pid").to_s searchd.log = log_root.join("#{environment}.searchd.log").to_s searchd.query_log = log_root.join("#{environment}.searchd.query.log").to_s end def log_root real_path 'log' end def framework_root Pathname.new(framework.root) end def real_path(*arguments) path = framework_root.join(*arguments) path.exist? ? path.realpath : path end def settings_to_hash contents = YAML.load(ERB.new(File.read(settings_file)).result) contents && contents[environment] || {} end def settings_file framework_root.join 'config', 'thinking_sphinx.yml' end def setup @settings = nil @configuration_file = settings['configuration_file'] || framework_root.join( 'config', "#{environment}.sphinx.conf" ).to_s @index_paths = engine_index_paths + [framework_root.join('app', 'indices').to_s] @indices_location = settings['indices_location'] || framework_root.join( 'db', 'sphinx', environment ).to_s @version = settings['version'] || '2.1.4' if settings['common_sphinx_configuration'] common.common_sphinx_configuration = true indexer.common_sphinx_configuration = true end configure_searchd apply_sphinx_settings! @offsets = {} end def tmp_path real_path 'tmp' end def sphinx_sections sections = [indexer, searchd] sections.unshift common if settings['common_sphinx_configuration'] sections end def apply_sphinx_settings! sphinx_sections.each do |object| settings.each do |key, value| next unless object.class.settings.include?(key.to_sym) object.send("#{key}=", value) end end end end require 'thinking_sphinx/configuration/consistent_ids' require 'thinking_sphinx/configuration/defaults' require 'thinking_sphinx/configuration/distributed_indices' require 'thinking_sphinx/configuration/minimum_fields' thinking-sphinx-3.1.4/lib/thinking_sphinx/core/0000755000004100000410000000000012556214551021601 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/core/index.rb0000644000004100000410000000315112556214551023235 0ustar www-datawww-datamodule 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 @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_key(key) key * config.indices.count + offset end def interpret_definition! 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 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-3.1.4/lib/thinking_sphinx/core/property.rb0000644000004100000410000000021112556214551024004 0ustar www-datawww-datamodule ThinkingSphinx::Core::Property def facet? options[:facet] end def multi? false end def type nil end end thinking-sphinx-3.1.4/lib/thinking_sphinx/core/field.rb0000644000004100000410000000020212556214551023203 0ustar www-datawww-datamodule ThinkingSphinx::Core::Field def infixing? options[:infixes] end def prefixing? options[:prefixes] end end thinking-sphinx-3.1.4/lib/thinking_sphinx/core/interpreter.rb0000644000004100000410000000100512556214551024465 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/core/settings.rb0000644000004100000410000000036612556214551023773 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/panes.rb0000644000004100000410000000033612556214551022306 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/sinatra.rb0000644000004100000410000000016412556214551022640 0ustar www-datawww-datarequire 'thinking_sphinx' ActiveSupport.on_load :active_record do include ThinkingSphinx::ActiveRecord::Base end thinking-sphinx-3.1.4/lib/thinking_sphinx/connection.rb0000644000004100000410000001021612556214551023335 0ustar www-datawww-datamodule ThinkingSphinx::Connection MAXIMUM_RETRIES = 3 def self.new configuration = ThinkingSphinx::Configuration.instance # If you use localhost, MySQL insists on a socket connection, but Sphinx # requires a TCP connection. Using 127.0.0.1 fixes that. address = configuration.searchd.address || '127.0.0.1' address = '127.0.0.1' if address == 'localhost' options = { :host => address, :port => configuration.searchd.mysql41, :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 class Client def close client.close unless ThinkingSphinx::Connection.persistent? end def execute(statement) query(statement).first end def query_all(*statements) query *statements end private def close_and_clear client.close @client = nil end def query(*statements) results_for *statements rescue => error message = "#{error.message} - #{statements.join('; ')}" wrapper = ThinkingSphinx::QueryExecutionError.new message wrapper.statement = statements.join('; ') raise wrapper ensure close_and_clear unless ThinkingSphinx::Connection.persistent? end end class MRI < Client def initialize(options) @options = options end def base_error Mysql2::Error end private attr_reader :options def client @client ||= Mysql2::Client.new({ :flags => Mysql2::Client::MULTI_STATEMENTS }.merge(options)) rescue base_error => error raise ThinkingSphinx::SphinxError.new_from_mysql error end def results_for(*statements) results = [client.query(statements.join('; '))] results << client.store_result while client.next_result results end end class JRuby < Client attr_reader :address, :options def initialize(options) @address = "jdbc:mysql://#{options[:host]}:#{options[:port]}/?allowMultiQueries=true" @options = options end def base_error Java::JavaSql::SQLException end private def client @client ||= java.sql.DriverManager.getConnection address, options[:username], options[:password] rescue base_error => error raise ThinkingSphinx::SphinxError.new_from_mysql error end def results_for(*statements) statement = client.createStatement statement.execute statements.join('; ') results = [set_to_array(statement.getResultSet)] results << set_to_array(statement.getResultSet) while statement.getMoreResults results.compact end def set_to_array(set) return nil if set.nil? meta = set.meta_data rows = [] while set.next rows << (1..meta.column_count).inject({}) do |row, index| name = meta.column_name index row[name] = set.get_object(index) row end end rows end end end thinking-sphinx-3.1.4/lib/thinking_sphinx/errors.rb0000644000004100000410000000263312556214551022516 0ustar www-datawww-dataclass ThinkingSphinx::SphinxError < StandardError attr_accessor :statement def self.new_from_mysql(error) case error.message when /parse error/ 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/ replacement = ThinkingSphinx::ConnectionError.new( "Error connecting to Sphinx via the MySQL protocol. #{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::SyntaxError < ThinkingSphinx::QueryError end class ThinkingSphinx::ParseError < 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 thinking-sphinx-3.1.4/lib/thinking_sphinx/float_formatter.rb0000644000004100000410000000102312556214551024362 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/index_set.rb0000644000004100000410000000276612556214551023173 0ustar www-datawww-dataclass ThinkingSphinx::IndexSet include Enumerable 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 ||= 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 references options[:references] || classes_and_ancestors.collect { |klass| klass.name.underscore.to_sym } end def references_specified? options[:references] && options[:references].any? end end thinking-sphinx-3.1.4/lib/thinking_sphinx/masks/0000755000004100000410000000000012556214551021767 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/masks/weight_enumerator_mask.rb0000644000004100000410000000053612556214551027063 0ustar www-datawww-dataclass 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[ThinkingSphinx::SphinxQL.weight[:column]] end end end thinking-sphinx-3.1.4/lib/thinking_sphinx/masks/scopes_mask.rb0000644000004100000410000000233012556214551024621 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/masks/group_enumerators_mask.rb0000644000004100000410000000135612556214551027114 0ustar www-datawww-dataclass 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[ThinkingSphinx::SphinxQL.count[:column]] end end def each_with_group(&block) @search.raw.each_with_index do |row, index| yield @search[index], row[ThinkingSphinx::SphinxQL.group_by[:column]] end end def each_with_group_and_count(&block) @search.raw.each_with_index do |row, index| yield @search[index], row[ThinkingSphinx::SphinxQL.group_by[:column]], row[ThinkingSphinx::SphinxQL.count[:column]] end end end thinking-sphinx-3.1.4/lib/thinking_sphinx/masks/pagination_mask.rb0000644000004100000410000000211212556214551025454 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/facet_search.rb0000644000004100000410000000561512556214551023614 0ustar www-datawww-dataclass 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] || '*'), "#{ThinkingSphinx::SphinxQL.group_by[:select]}", "#{ThinkingSphinx::SphinxQL.count[:select]}" ].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-3.1.4/lib/thinking_sphinx/distributed/0000755000004100000410000000000012556214551023173 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/distributed/index.rb0000644000004100000410000000057712556214551024640 0ustar www-datawww-dataclass 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 end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/0000755000004100000410000000000012556214551023462 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/index.rb0000644000004100000410000000262612556214551025124 0ustar www-datawww-dataclass 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 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 fields sources.collect(&:fields).flatten 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 => @options[:primary_key] || model.primary_key || :id } end end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/polymorpher.rb0000644000004100000410000000271712556214551026376 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/filter_reflection.rb0000644000004100000410000000406412556214551027512 0ustar www-datawww-dataclass ThinkingSphinx::ActiveRecord::FilterReflection attr_reader :reflection, :class_name delegate :foreign_type, :active_record, :to => :reflection def self.call(reflection, name, class_name) filter = new(reflection, class_name) klass = reflection.class arity = klass.instance_method(:initialize).arity if defined?(ActiveRecord::Reflection::MacroReflection) && arity == 4 klass.new name, filter.scope, filter.options, reflection.active_record elsif reflection.respond_to?(:scope) klass.new reflection.macro, name, filter.scope, filter.options, reflection.active_record else klass.new reflection.macro, name, filter.options, reflection.active_record end end def initialize(reflection, class_name) @reflection, @class_name = reflection, class_name @options = reflection.options.clone end def options @options.delete :polymorphic @options[:class_name] = class_name @options[:foreign_key] ||= "#{reflection.name}_id" @options[:foreign_type] = reflection.foreign_type if reflection.respond_to?(:scope) @options[:sphinx_internal_filtered] = true return @options end case @options[:conditions] when nil @options[:conditions] = condition when Array @options[:conditions] << condition when Hash @options[:conditions].merge!(reflection.foreign_type => @options[:class_name]) else @options[:conditions] << " AND #{condition}" end @options end def scope if ::Joiner::Joins.instance_methods.include?(:join_association_class) return nil end lambda { |association| reflection = association.reflection klass = reflection.class_name.constantize where( association.parent.aliased_table_name.to_sym => {reflection.foreign_type => klass.base_class.name} ) } end private def condition "::ts_join_alias::.#{quoted_foreign_type} = '#{class_name}'" end def quoted_foreign_type active_record.connection.quote_column_name foreign_type end end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/column.rb0000644000004100000410000000120212556214551025277 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/join_association.rb0000644000004100000410000000062212556214551027342 0ustar www-datawww-dataclass 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.name) ) if reflection.options[:sphinx_internal_filtered] constraint end end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/database_adapters.rb0000644000004100000410000000301212556214551027432 0ustar www-datawww-datamodule 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 "Invalid Database 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-3.1.4/lib/thinking_sphinx/active_record/column_sql_presenter.rb0000644000004100000410000000166612556214551030263 0ustar www-datawww-dataclass 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? ? adapter.quote(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[/[`"]/].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-3.1.4/lib/thinking_sphinx/active_record/sql_source.rb0000644000004100000410000000752412556214551026176 0ustar www-datawww-datamodule ThinkingSphinx module ActiveRecord class SQLSource < Riddle::Configuration::SQLSource include ThinkingSphinx::Core::Settings attr_reader :model, :database_settings, :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 @database_settings = model.connection.instance_variable_get(:@config).clone @options = { :utf8? => (@database_settings[:encoding] == '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 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] end def type @type ||= case adapter when DatabaseAdapters::MySQLAdapter 'mysql' when DatabaseAdapters::PostgreSQLAdapter 'pgsql' else raise "Unknown Adapter 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 @sql_query_post_index += builder.sql_query_post_index end def config ThinkingSphinx::Configuration.instance 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 def properties fields + attributes end end end end require 'thinking_sphinx/active_record/sql_source/template' thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/callbacks/0000755000004100000410000000000012556214551025401 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb0000644000004100000410000000064512556214551031174 0ustar www-datawww-dataclass ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks < ThinkingSphinx::Callbacks callbacks :after_destroy def after_destroy return if instance.new_record? indices.each { |index| ThinkingSphinx::Deletion.perform index, instance.id } end private def indices ThinkingSphinx::Configuration.instance.index_set_class.new( :classes => [instance.class] ).to_a end end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb0000644000004100000410000000271312556214551031212 0ustar www-datawww-dataclass ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks < ThinkingSphinx::Callbacks callbacks :after_update def after_update return unless 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 instance.changed.include?(attribute.columns.first.__name.to_s) hash[attribute.name] = attribute.value_for(instance) end hash end 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 instance.class.name.underscore.to_sym 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-3.1.4/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb0000644000004100000410000000205112556214551031014 0ustar www-datawww-dataclass ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks < ThinkingSphinx::Callbacks callbacks :after_commit, :before_save def after_commit return unless delta_indices? && processors.any? { |processor| processor.toggled?(instance) } && !ThinkingSphinx::Deltas.suspended? 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 delta_indices? 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] end def processors delta_indices.collect &:delta_processor end end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/association.rb0000644000004100000410000000036412556214551026326 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/property.rb0000644000004100000410000000122112556214551025667 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/sql_source/0000755000004100000410000000000012556214551025641 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/sql_source/template.rb0000644000004100000410000000253112556214551030002 0ustar www-datawww-dataclass 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.model.primary_key.to_sym end end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/association_proxy/0000755000004100000410000000000012556214551027237 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb0000644000004100000410000000153512556214551033276 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb0000644000004100000410000000171512556214551033122 0ustar www-datawww-dataclass 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( *@association.klass.name.underscore.to_sym ).reject &:distributed? end end def reflection_target target = @association.reflection target = target.through_reflection if target.through_reflection target end end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/simple_many_query.rb0000644000004100000410000000173112556214551027553 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/attribute/0000755000004100000410000000000012556214551025465 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/attribute/values.rb0000644000004100000410000000056712556214551027321 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb0000644000004100000410000000172612556214551031420 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/attribute/type.rb0000644000004100000410000000364612556214551027004 0ustar www-datawww-dataclass 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? database_column.type == :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, "column #{column_name} does not exist" if database_column.nil? return :bigint if big_integer? case database_column.type when :datetime, :date :timestamp when :text :string when :decimal :float else database_column.type end end end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/association_proxy.rb0000644000004100000410000000166512556214551027574 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/active_record/sql_builder.rb0000644000004100000410000000564012556214551026321 0ustar www-datawww-datamodule 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 def sql_query_post_index return [] unless delta_processor && !source.delta? [delta_processor.reset_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 ||= begin joins = Joiner::Joins.new model if joins.respond_to?(:join_association_class) joins.join_association_class = ThinkingSphinx::ActiveRecord::JoinAssociation end source.associations.reject(&:string?).each do |association| joins.add_join_to association.stack end joins end 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-3.1.4/lib/thinking_sphinx/active_record/log_subscriber.rb0000644000004100000410000000106712556214551027017 0ustar www-datawww-dataclass 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 end ThinkingSphinx::ActiveRecord::LogSubscriber.attach_to :thinking_sphinx thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/field.rb0000644000004100000410000000043412556214551025073 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/sql_builder/0000755000004100000410000000000012556214551025767 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb0000644000004100000410000000073612556214551031304 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/active_record/sql_builder/statement.rb0000644000004100000410000000674412556214551030333 0ustar www-datawww-datarequire '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-3.1.4/lib/thinking_sphinx/active_record/sql_builder/query.rb0000644000004100000410000000164112556214551027463 0ustar www-datawww-datamodule 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_session scope_by_utf8 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 method_missing(*args, &block) report.send *args, &block end end end end thinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/base.rb0000644000004100000410000000337212556214551024726 0ustar www-datawww-datamodule 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(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-3.1.4/lib/thinking_sphinx/active_record/database_adapters/0000755000004100000410000000000012556214551027111 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb0000644000004100000410000000206512556214551033344 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb0000644000004100000410000000044512556214551032744 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb0000644000004100000410000000162712556214551032311 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/interpreter.rb0000644000004100000410000000356312556214551026361 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/property_sql_presenter.rb0000644000004100000410000000362712556214551030651 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/property_query.rb0000644000004100000410000000666312556214551027133 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/active_record/attribute.rb0000644000004100000410000000113512556214551026012 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/masks.rb0000644000004100000410000000036012556214551022313 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/controller.rb0000644000004100000410000000041312556214551023357 0ustar www-datawww-dataclass ThinkingSphinx::Controller < Riddle::Controller def index(*indices) options = indices.extract_options! indices << '--all' if indices.empty? ThinkingSphinx::Guard::Files.call(indices) do |names| super(*(names + [options])) end end end thinking-sphinx-3.1.4/lib/thinking_sphinx/search/0000755000004100000410000000000012556214551022116 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/search/batch_inquirer.rb0000644000004100000410000000055212556214551025444 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/search/context.rb0000644000004100000410000000070412556214551024130 0ustar www-datawww-dataclass 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 end thinking-sphinx-3.1.4/lib/thinking_sphinx/search/stale_ids_exception.rb0000644000004100000410000000034512556214551026472 0ustar www-datawww-dataclass ThinkingSphinx::Search::StaleIdsException < StandardError attr_reader :ids def initialize(ids) @ids = ids end def message "Record IDs found by Sphinx but not by ActiveRecord : #{ids.join(', ')}" end end thinking-sphinx-3.1.4/lib/thinking_sphinx/search/query.rb0000644000004100000410000000121512556214551023607 0ustar www-datawww-data# encoding: utf-8 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? "@#{key} #{star_keyword conditions[key], key}" }.join(' ')).strip end private 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-3.1.4/lib/thinking_sphinx/search/merger.rb0000644000004100000410000000150312556214551023723 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/search/glaze.rb0000644000004100000410000000150512556214551023546 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/logger.rb0000644000004100000410000000032112556214551022451 0ustar www-datawww-dataclass ThinkingSphinx::Logger def self.log(notification, message, &block) ActiveSupport::Notifications.instrument( "#{notification}.thinking_sphinx", notification => message, &block ) end end thinking-sphinx-3.1.4/lib/thinking_sphinx/configuration/0000755000004100000410000000000012556214551023520 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/configuration/consistent_ids.rb0000644000004100000410000000124712556214551027101 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/configuration/defaults.rb0000644000004100000410000000015312556214551025653 0ustar www-datawww-datamodule ThinkingSphinx::Configuration::Defaults ADDRESS = '127.0.0.1' PORT = 9306 PANES = [] end thinking-sphinx-3.1.4/lib/thinking_sphinx/configuration/minimum_fields.rb0000644000004100000410000000117012556214551027045 0ustar www-datawww-dataclass ThinkingSphinx::Configuration::MinimumFields def initialize(indices) @indices = indices end def reconcile return unless no_inheritance_columns? sources.each do |source| source.fields.delete_if do |field| field.name == 'sphinx_internal_class_name' end end end private attr_reader :indices def no_inheritance_columns? indices.select { |index| index.model.column_names.include?(index.model.inheritance_column) }.empty? end def sources @sources ||= @indices.select { |index| index.respond_to?(:sources) }.collect(&:sources).flatten end end thinking-sphinx-3.1.4/lib/thinking_sphinx/configuration/distributed_indices.rb0000644000004100000410000000113212556214551030062 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/frameworks/0000755000004100000410000000000012556214551023031 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/frameworks/plain.rb0000644000004100000410000000024312556214551024460 0ustar www-datawww-dataclass ThinkingSphinx::Frameworks::Plain attr_accessor :environment, :root def initialize @environment = 'production' @root = Dir.pwd end end thinking-sphinx-3.1.4/lib/thinking_sphinx/frameworks/rails.rb0000644000004100000410000000016312556214551024470 0ustar www-datawww-dataclass ThinkingSphinx::Frameworks::Rails def environment Rails.env end def root Rails.root end end thinking-sphinx-3.1.4/lib/thinking_sphinx/query.rb0000644000004100000410000000030212556214551022336 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/test.rb0000644000004100000410000000200312556214551022150 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/deltas/0000755000004100000410000000000012556214551022125 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/deltas/default_delta.rb0000644000004100000410000000217212556214551025251 0ustar www-datawww-dataclass 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_key(instance.id) ).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-3.1.4/lib/thinking_sphinx/deltas/index_job.rb0000644000004100000410000000051612556214551024415 0ustar www-datawww-dataclass ThinkingSphinx::Deltas::IndexJob def initialize(index_name) @index_name = index_name end def perform configuration.controller.index @index_name, :verbose => !configuration.settings['quiet_deltas'] end private def configuration @configuration ||= ThinkingSphinx::Configuration.instance end end thinking-sphinx-3.1.4/lib/thinking_sphinx/deltas/delete_job.rb0000644000004100000410000000067312556214551024554 0ustar www-datawww-dataclass ThinkingSphinx::Deltas::DeleteJob def initialize(index_name, document_id) @index_name, @document_id = index_name, document_id end def perform ThinkingSphinx::Connection.take do |connection| connection.execute Riddle::Query.update( @index_name, @document_id, :sphinx_deleted => true ) end rescue ThinkingSphinx::ConnectionError => error # This isn't vital, so don't raise the error. end end thinking-sphinx-3.1.4/lib/thinking_sphinx/capistrano/0000755000004100000410000000000012556214551023014 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/capistrano/v3.rb0000644000004100000410000000520112556214551023667 0ustar www-datawww-datanamespace :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-3.1.4/lib/thinking_sphinx/capistrano/v2.rb0000644000004100000410000000355412556214551023677 0ustar www-datawww-dataCapistrano::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-3.1.4/lib/thinking_sphinx/real_time/0000755000004100000410000000000012556214551022612 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/real_time/index.rb0000644000004100000410000000333112556214551024246 0ustar www-datawww-dataclass ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex include ThinkingSphinx::Core::Index attr_writer :fields, :attributes, :conditions, :scope def initialize(reference, options = {}) @fields = [] @attributes = [] @conditions = [] Template.new(self).apply super reference, options 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 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-3.1.4/lib/thinking_sphinx/real_time/populator.rb0000644000004100000410000000170312556214551025165 0ustar www-datawww-dataclass ThinkingSphinx::RealTime::Populator def self.populate(index) new(index).populate end def initialize(index) @index = index end def populate(&block) instrument 'start_populating' remove_files scope.find_each do |instance| transcriber.copy instance instrument 'populated', :instance => instance end controller.rotate instrument 'finish_populating' end private attr_reader :index delegate :controller, :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 remove_files Dir["#{index.path}*"].each { |file| FileUtils.rm file } end def transcriber @transcriber ||= ThinkingSphinx::RealTime::Transcriber.new index end end thinking-sphinx-3.1.4/lib/thinking_sphinx/real_time/index/0000755000004100000410000000000012556214551023721 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/real_time/index/template.rb0000644000004100000410000000154712556214551026070 0ustar www-datawww-dataclass 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 :id, :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 end thinking-sphinx-3.1.4/lib/thinking_sphinx/real_time/callbacks/0000755000004100000410000000000012556214551024531 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb0000644000004100000410000000213212556214551031014 0ustar www-datawww-dataclass ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks def initialize(reference, path = [], &block) @reference, @path, @block = reference, path, block end def after_save(instance) return unless real_time_indices? && callbacks_enabled? real_time_indices.each do |index| objects_for(instance).each do |object| ThinkingSphinx::RealTime::Transcriber.new(index).copy object end end 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 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-3.1.4/lib/thinking_sphinx/real_time/property.rb0000644000004100000410000000115212556214551025022 0ustar www-datawww-dataclass 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) return @column.__name unless @column.__name.is_a?(Symbol) base = @column.__stack.inject(object) { |base, node| base.try(node) } base = base.try(@column.__name) base.is_a?(String) ? base.gsub("\u0000", '') : base end end thinking-sphinx-3.1.4/lib/thinking_sphinx/real_time/field.rb0000644000004100000410000000026512556214551024225 0ustar www-datawww-dataclass ThinkingSphinx::RealTime::Field < ThinkingSphinx::RealTime::Property include ThinkingSphinx::Core::Field def translate(object) Array(super || '').join(' ') end end thinking-sphinx-3.1.4/lib/thinking_sphinx/real_time/transcriber.rb0000644000004100000410000000176712556214551025470 0ustar www-datawww-dataclass ThinkingSphinx::RealTime::Transcriber def initialize(index) @index = index end def copy(instance) return unless instance.persisted? && copy?(instance) columns, values = ['id'], [index.document_id_for_key(instance.id)] (index.fields + index.attributes).each do |property| columns << property.name values << property.translate(instance) end 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 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 end thinking-sphinx-3.1.4/lib/thinking_sphinx/real_time/interpreter.rb0000644000004100000410000000244212556214551025504 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/real_time/attribute.rb0000644000004100000410000000043612556214551025145 0ustar www-datawww-dataclass ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property def multi? @options[:multi] end def type @options[:type] end def translate(object) super || default_value end private def default_value type == :string ? '' : 0 end end thinking-sphinx-3.1.4/lib/thinking_sphinx/deltas.rb0000644000004100000410000000211012556214551022444 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/middlewares.rb0000644000004100000410000000153312556214551023500 0ustar www-datawww-datamodule ThinkingSphinx::Middlewares; end %w[middleware active_record_translator geographer glazier ids_only inquirer sphinxql stale_id_checker stale_id_filter utf8].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 = [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-3.1.4/lib/thinking_sphinx/deletion.rb0000644000004100000410000000212312556214551022777 0ustar www-datawww-dataclass 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) ThinkingSphinx::Connection.take do |connection| connection.execute statement end end class RealtimeDeletion < ThinkingSphinx::Deletion def perform execute Riddle::Query::Delete.new(name, document_ids_for_keys).to_sql 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-3.1.4/lib/thinking_sphinx/scopes.rb0000644000004100000410000000113612556214551022473 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/callbacks.rb0000644000004100000410000000046712556214551023124 0ustar www-datawww-dataclass 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 initialize(instance) @instance = instance end end thinking-sphinx-3.1.4/lib/thinking_sphinx/guard/0000755000004100000410000000000012556214551021753 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/guard/files.rb0000644000004100000410000000131112556214551023376 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/guard/file.rb0000644000004100000410000000057412556214551023225 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/core.rb0000644000004100000410000000035212556214551022126 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/rake_interface.rb0000644000004100000410000000416012556214551024141 0ustar www-datawww-dataclass ThinkingSphinx::RakeInterface def clear_all [ configuration.indices_location, configuration.searchd.binlog_path ].each do |path| FileUtils.rm_r(path) if File.exists?(path) end end def clear_real_time indices = configuration.indices.select { |index| index.type == 'rt' } indices.each do |index| index.render Dir["#{index.path}.*"].each { |path| FileUtils.rm path } end path = configuration.searchd.binlog_path FileUtils.rm_r(path) if File.exists?(path) end def configure puts "Generating configuration to #{configuration.configuration_file}" configuration.render_to_file end def generate indices = configuration.indices.select { |index| index.type == 'rt' } indices.each do |index| ThinkingSphinx::RealTime::Populator.populate index end end def index(reconfigure = true, verbose = true) configure if reconfigure FileUtils.mkdir_p configuration.indices_location ThinkingSphinx.before_index_hooks.each { |hook| hook.call } controller.index :verbose => verbose end def prepare configuration.preload_indices configuration.render FileUtils.mkdir_p configuration.indices_location end def start raise RuntimeError, 'searchd is already running' if controller.running? FileUtils.mkdir_p configuration.indices_location controller.start if controller.running? puts "Started searchd successfully (pid: #{controller.pid})." else puts "Failed to start searchd. Check the log files for more information." end end def status if controller.running? puts "The Sphinx daemon searchd is currently running." else puts "The Sphinx daemon searchd is not currently running." end end def stop unless controller.running? puts 'searchd is not currently running.' and return end pid = controller.pid until controller.stop do sleep(0.5) end puts "Stopped searchd daemon (pid: #{pid})." end private delegate :controller, :to => :configuration def configuration ThinkingSphinx::Configuration.instance end end thinking-sphinx-3.1.4/lib/thinking_sphinx/real_time.rb0000644000004100000410000000107612556214551023143 0ustar www-datawww-datamodule ThinkingSphinx::RealTime module Callbacks # end def self.callback_for(reference, path = [], &block) Callbacks::RealTimeCallbacks.new reference, 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/transcriber' require 'thinking_sphinx/real_time/callbacks/real_time_callbacks' thinking-sphinx-3.1.4/lib/thinking_sphinx/facet.rb0000644000004100000410000000112712556214551022261 0ustar www-datawww-dataclass 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[ThinkingSphinx::SphinxQL.count[:column]] hash } end private def group_column @properties.any?(&:multi?) ? ThinkingSphinx::SphinxQL.group_by[:column] : name end def use_field? @properties.any? { |property| property.type.nil? || property.type == :string } end end thinking-sphinx-3.1.4/lib/thinking_sphinx/subscribers/0000755000004100000410000000000012556214551023177 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/subscribers/populator_subscriber.rb0000644000004100000410000000133612556214551027777 0ustar www-datawww-dataclass 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 start_populating(event) puts "Generating index files for #{event.payload[:index].name}" end def populated(event) print '.' end def finish_populating(event) print "\n" end end ThinkingSphinx::Subscribers::PopulatorSubscriber.attach_to( 'thinking_sphinx.real_time' ) thinking-sphinx-3.1.4/lib/thinking_sphinx/guard.rb0000644000004100000410000000016112556214551022276 0ustar www-datawww-datamodule ThinkingSphinx::Guard # end require 'thinking_sphinx/guard/file' require 'thinking_sphinx/guard/files' thinking-sphinx-3.1.4/lib/thinking_sphinx/search.rb0000644000004100000410000000636312556214551022453 0ustar www-datawww-dataclass 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 ) 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 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 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-3.1.4/lib/thinking_sphinx/capistrano.rb0000644000004100000410000000033112556214551023336 0ustar www-datawww-dataif 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-3.1.4/lib/thinking_sphinx/utf8.rb0000644000004100000410000000037212556214551022066 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/wildcard.rb0000644000004100000410000000174512556214551022776 0ustar www-datawww-dataclass 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 == '@' || 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-3.1.4/lib/thinking_sphinx/middlewares/0000755000004100000410000000000012556214551023151 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/middlewares/stale_id_checker.rb0000644000004100000410000000157512556214551026756 0ustar www-datawww-dataclass 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, stale_ids 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-3.1.4/lib/thinking_sphinx/middlewares/inquirer.rb0000644000004100000410000000245212556214551025337 0ustar www-datawww-dataclass 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] = raw_results 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-3.1.4/lib/thinking_sphinx/middlewares/active_record_translator.rb0000644000004100000410000000373112556214551030564 0ustar www-datawww-dataclass ThinkingSphinx::Middlewares::ActiveRecordTranslator < 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 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 model_names @model_names ||= context[:results].collect { |row| row['sphinx_internal_class'] }.uniq 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.id == row['sphinx_internal_id'] } end def results_for_models @results_for_models ||= model_names.inject({}) do |hash, name| model = name.constantize hash[name] = model_relation_with_sql_options(model.unscoped).where( model.primary_key => ids_for_model(name) ) hash end end def model_relation_with_sql_options(relation) relation = relation.includes sql_options[:include] if sql_options[:include] relation = relation.joins sql_options[:joins] if sql_options[:joins] relation = relation.order sql_options[:order] if sql_options[:order] relation = relation.select sql_options[:select] if sql_options[:select] relation = relation.group sql_options[:group] if sql_options[:group] relation end def sql_options context.search.options[:sql] || {} end end end thinking-sphinx-3.1.4/lib/thinking_sphinx/middlewares/glazier.rb0000644000004100000410000000137512556214551025141 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/middlewares/geographer.rb0000644000004100000410000000400612556214551025621 0ustar www-datawww-datarequire '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-3.1.4/lib/thinking_sphinx/middlewares/middleware.rb0000644000004100000410000000020612556214551025611 0ustar www-datawww-dataclass ThinkingSphinx::Middlewares::Middleware def initialize(app) @app = app end private attr_reader :app, :context end thinking-sphinx-3.1.4/lib/thinking_sphinx/middlewares/utf8.rb0000644000004100000410000000112512556214551024363 0ustar www-datawww-dataclass ThinkingSphinx::Middlewares::UTF8 < ThinkingSphinx::Middlewares::Middleware def call(contexts) contexts.each do |context| context[:results].each { |row| update_row row } update_row context[:meta] end unless encoded? app.call contexts end private def encoded? ThinkingSphinx::Configuration.instance.settings['utf8'].nil? || ThinkingSphinx::Configuration.instance.settings['utf8'] end def update_row(row) row.each do |key, value| next unless value.is_a?(String) row[key] = ThinkingSphinx::UTF8.encode value end end end thinking-sphinx-3.1.4/lib/thinking_sphinx/middlewares/stale_id_filter.rb0000644000004100000410000000171412556214551026632 0ustar www-datawww-dataclass 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 ThinkingSphinx::Logger.log :message, log_message @retries -= 1 and retry end end private def append_stale_ids(ids) @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-3.1.4/lib/thinking_sphinx/middlewares/ids_only.rb0000644000004100000410000000043312556214551025316 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/middlewares/sphinxql.rb0000644000004100000410000001436112556214551025351 0ustar www-datawww-dataclass 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] 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| klass.name.underscore.to_sym }.sort end def inheritance_column_select(klass) <<-SQL SELECT DISTINCT #{klass.inheritance_column} FROM #{klass.table_name} SQL end def exclusive_filters @exclusive_filters ||= (options[:without] || {}).tap do |without| without[:sphinx_internal_id] = options[:without_ids] if options[:without_ids].present? 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] ||= ['*', "#{ThinkingSphinx::SphinxQL.group_by[:select]}", "#{ThinkingSphinx::SphinxQL.count[:select]}" ].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-3.1.4/lib/thinking_sphinx/railtie.rb0000644000004100000410000000045412556214551022632 0ustar www-datawww-dataclass ThinkingSphinx::Railtie < Rails::Railtie initializer 'thinking_sphinx.initialisation' do if defined?(ActiveRecord::Base) ActiveRecord::Base.send :include, ThinkingSphinx::ActiveRecord::Base end end rake_tasks do load File.expand_path('../tasks.rb', __FILE__) end end thinking-sphinx-3.1.4/lib/thinking_sphinx/tasks.rb0000644000004100000410000000256412556214551022332 0ustar www-datawww-datanamespace :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 => :environment do interface.index( ENV['INDEX_ONLY'] != 'true', !Rake.application.options.silent ) end desc 'Clear out Sphinx files' task :clear => :environment do interface.clear_all end desc 'Clear out real-time index files' task :clear_rt => :environment do interface.clear_real_time end desc 'Generate fresh index files for real-time indices' task :generate => :environment do interface.prepare interface.generate end desc 'Stop Sphinx, index and then restart Sphinx' task :rebuild => [:stop, :clear, :index, :start] desc 'Stop Sphinx, clear files, reconfigure, start Sphinx, generate files' task :regenerate => [:stop, :clear_rt, :configure, :start, :generate] desc 'Restart the Sphinx daemon' task :restart => [:stop, :start] desc 'Start the Sphinx daemon' task :start => :environment do interface.start end desc 'Stop the Sphinx daemon' task :stop => :environment do interface.stop end desc 'Determine whether Sphinx is running' task :status => :environment do interface.status end def interface @interface ||= ThinkingSphinx::RakeInterface.new end end thinking-sphinx-3.1.4/lib/thinking_sphinx/panes/0000755000004100000410000000000012556214551021757 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking_sphinx/panes/distance_pane.rb0000644000004100000410000000030712556214551025101 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/panes/attributes_pane.rb0000644000004100000410000000022412556214551025473 0ustar www-datawww-dataclass ThinkingSphinx::Panes::AttributesPane def initialize(context, object, raw) @raw = raw end def sphinx_attributes @raw end end thinking-sphinx-3.1.4/lib/thinking_sphinx/panes/excerpts_pane.rb0000644000004100000410000000167012556214551025150 0ustar www-datawww-dataclass 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-3.1.4/lib/thinking_sphinx/panes/weight_pane.rb0000644000004100000410000000025712556214551024602 0ustar www-datawww-dataclass ThinkingSphinx::Panes::WeightPane def initialize(context, object, raw) @raw = raw end def weight @raw[ThinkingSphinx::SphinxQL.weight[:column]] end end thinking-sphinx-3.1.4/lib/thinking_sphinx/frameworks.rb0000644000004100000410000000040212556214551023352 0ustar www-datawww-datamodule 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-3.1.4/lib/thinking_sphinx/sphinxql.rb0000644000004100000410000000132112556214551023041 0ustar www-datawww-datamodule ThinkingSphinx::SphinxQL mattr_accessor :weight, :group_by, :count def self.functions! self.weight = {:select => 'weight()', :column => 'weight()'} self.group_by = { :select => 'groupby() AS sphinx_internal_group', :column => 'sphinx_internal_group' } self.count = { :select => 'id AS sphinx_document_id, count(DISTINCT sphinx_document_id) AS sphinx_internal_count', :column => 'sphinx_internal_count' } end def self.variables! self.weight = {:select => '@weight', :column => '@weight'} self.group_by = {:select => '@groupby', :column => '@groupby'} self.count = {:select => '@count', :column => '@count'} end self.functions! end thinking-sphinx-3.1.4/lib/thinking/0000755000004100000410000000000012556214551017260 5ustar www-datawww-datathinking-sphinx-3.1.4/lib/thinking/sphinx.rb0000644000004100000410000000003212556214551021111 0ustar www-datawww-datarequire 'thinking_sphinx' thinking-sphinx-3.1.4/lib/thinking_sphinx.rb0000644000004100000410000000427112556214551021202 0ustar www-datawww-dataif 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 def self.count(query = '', options = {}) search(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 = [] module Subscribers; end end # Core require 'thinking_sphinx/batched_search' require 'thinking_sphinx/callbacks' require 'thinking_sphinx/core' require 'thinking_sphinx/configuration' require 'thinking_sphinx/connection' require 'thinking_sphinx/controller' 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/index' require 'thinking_sphinx/index_set' 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/sphinxql' 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) thinking-sphinx-3.1.4/gemfiles/0000755000004100000410000000000012556214551016472 5ustar www-datawww-datathinking-sphinx-3.1.4/gemfiles/rails_4_1.gemfile0000644000004100000410000000054212556214551021602 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "mysql2", "~> 0.3.12b4", :platform => :ruby gem "pg", "~> 0.16.0", :platform => :ruby gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform => :jruby gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform => :jruby gem "rails", "~> 4.1.8" gemspec :path => "../" thinking-sphinx-3.1.4/gemfiles/rails_4_2.gemfile0000644000004100000410000000054212556214551021603 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "mysql2", "~> 0.3.12b4", :platform => :ruby gem "pg", "~> 0.16.0", :platform => :ruby gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform => :jruby gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform => :jruby gem "rails", "~> 4.2.0" gemspec :path => "../" thinking-sphinx-3.1.4/gemfiles/.gitignore0000644000004100000410000000001612556214551020457 0ustar www-datawww-data*.gemfile.lockthinking-sphinx-3.1.4/gemfiles/rails_3_2.gemfile0000644000004100000410000000054312556214551021603 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "mysql2", "~> 0.3.12b4", :platform => :ruby gem "pg", "~> 0.16.0", :platform => :ruby gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform => :jruby gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform => :jruby gem "rails", "~> 3.2.21" gemspec :path => "../" thinking-sphinx-3.1.4/gemfiles/rails_4_0.gemfile0000644000004100000410000000054312556214551021602 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "mysql2", "~> 0.3.12b4", :platform => :ruby gem "pg", "~> 0.16.0", :platform => :ruby gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform => :jruby gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform => :jruby gem "rails", "~> 4.0.12" gemspec :path => "../" thinking-sphinx-3.1.4/metadata.yml0000644000004100000410000005307012556214551017207 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: thinking-sphinx version: !ruby/object:Gem::Version version: 3.1.4 platform: ruby authors: - Pat Allan autorequire: bindir: bin cert_chain: [] date: 2015-06-01 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activerecord requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.1.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.1.0 - !ruby/object:Gem::Dependency name: builder requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 2.1.2 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 2.1.2 - !ruby/object:Gem::Dependency name: joiner requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.2.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.2.0 - !ruby/object:Gem::Dependency name: middleware requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.1.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.1.0 - !ruby/object:Gem::Dependency name: innertube requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.0.2 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.0.2 - !ruby/object:Gem::Dependency name: riddle requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.5.11 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.5.11 - !ruby/object:Gem::Dependency name: appraisal requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 1.0.2 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 1.0.2 - !ruby/object:Gem::Dependency name: combustion requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.4.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.4.0 - !ruby/object:Gem::Dependency name: database_cleaner requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 1.2.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 1.2.0 - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 2.13.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 2.13.0 description: An intelligent layer for ActiveRecord (via Rails and Sinatra) for the Sphinx full-text search tool. email: - pat@freelancing-gods.com executables: [] extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".travis.yml" - Appraisals - Gemfile - HISTORY - LICENCE - README.textile - Rakefile - gemfiles/.gitignore - gemfiles/rails_3_2.gemfile - gemfiles/rails_4_0.gemfile - gemfiles/rails_4_1.gemfile - gemfiles/rails_4_2.gemfile - lib/thinking-sphinx.rb - lib/thinking/sphinx.rb - lib/thinking_sphinx.rb - lib/thinking_sphinx/active_record.rb - lib/thinking_sphinx/active_record/association.rb - lib/thinking_sphinx/active_record/association_proxy.rb - lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb - lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb - lib/thinking_sphinx/active_record/attribute.rb - lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb - lib/thinking_sphinx/active_record/attribute/type.rb - lib/thinking_sphinx/active_record/attribute/values.rb - lib/thinking_sphinx/active_record/base.rb - lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb - lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb - lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb - lib/thinking_sphinx/active_record/column.rb - lib/thinking_sphinx/active_record/column_sql_presenter.rb - lib/thinking_sphinx/active_record/database_adapters.rb - lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb - lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb - lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb - lib/thinking_sphinx/active_record/field.rb - lib/thinking_sphinx/active_record/filter_reflection.rb - lib/thinking_sphinx/active_record/index.rb - lib/thinking_sphinx/active_record/interpreter.rb - lib/thinking_sphinx/active_record/join_association.rb - lib/thinking_sphinx/active_record/log_subscriber.rb - lib/thinking_sphinx/active_record/polymorpher.rb - lib/thinking_sphinx/active_record/property.rb - lib/thinking_sphinx/active_record/property_query.rb - lib/thinking_sphinx/active_record/property_sql_presenter.rb - lib/thinking_sphinx/active_record/simple_many_query.rb - lib/thinking_sphinx/active_record/sql_builder.rb - lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb - lib/thinking_sphinx/active_record/sql_builder/query.rb - lib/thinking_sphinx/active_record/sql_builder/statement.rb - lib/thinking_sphinx/active_record/sql_source.rb - lib/thinking_sphinx/active_record/sql_source/template.rb - lib/thinking_sphinx/batched_search.rb - lib/thinking_sphinx/callbacks.rb - lib/thinking_sphinx/capistrano.rb - lib/thinking_sphinx/capistrano/v2.rb - lib/thinking_sphinx/capistrano/v3.rb - lib/thinking_sphinx/configuration.rb - lib/thinking_sphinx/configuration/consistent_ids.rb - lib/thinking_sphinx/configuration/defaults.rb - lib/thinking_sphinx/configuration/distributed_indices.rb - lib/thinking_sphinx/configuration/minimum_fields.rb - lib/thinking_sphinx/connection.rb - lib/thinking_sphinx/controller.rb - lib/thinking_sphinx/core.rb - lib/thinking_sphinx/core/field.rb - lib/thinking_sphinx/core/index.rb - lib/thinking_sphinx/core/interpreter.rb - lib/thinking_sphinx/core/property.rb - lib/thinking_sphinx/core/settings.rb - lib/thinking_sphinx/deletion.rb - lib/thinking_sphinx/deltas.rb - lib/thinking_sphinx/deltas/default_delta.rb - lib/thinking_sphinx/deltas/delete_job.rb - lib/thinking_sphinx/deltas/index_job.rb - lib/thinking_sphinx/distributed.rb - lib/thinking_sphinx/distributed/index.rb - lib/thinking_sphinx/errors.rb - lib/thinking_sphinx/excerpter.rb - lib/thinking_sphinx/facet.rb - lib/thinking_sphinx/facet_search.rb - lib/thinking_sphinx/float_formatter.rb - lib/thinking_sphinx/frameworks.rb - lib/thinking_sphinx/frameworks/plain.rb - lib/thinking_sphinx/frameworks/rails.rb - lib/thinking_sphinx/guard.rb - lib/thinking_sphinx/guard/file.rb - lib/thinking_sphinx/guard/files.rb - lib/thinking_sphinx/index.rb - lib/thinking_sphinx/index_set.rb - lib/thinking_sphinx/logger.rb - lib/thinking_sphinx/masks.rb - lib/thinking_sphinx/masks/group_enumerators_mask.rb - lib/thinking_sphinx/masks/pagination_mask.rb - lib/thinking_sphinx/masks/scopes_mask.rb - lib/thinking_sphinx/masks/weight_enumerator_mask.rb - lib/thinking_sphinx/middlewares.rb - lib/thinking_sphinx/middlewares/active_record_translator.rb - lib/thinking_sphinx/middlewares/geographer.rb - lib/thinking_sphinx/middlewares/glazier.rb - lib/thinking_sphinx/middlewares/ids_only.rb - lib/thinking_sphinx/middlewares/inquirer.rb - lib/thinking_sphinx/middlewares/middleware.rb - lib/thinking_sphinx/middlewares/sphinxql.rb - lib/thinking_sphinx/middlewares/stale_id_checker.rb - lib/thinking_sphinx/middlewares/stale_id_filter.rb - lib/thinking_sphinx/middlewares/utf8.rb - lib/thinking_sphinx/panes.rb - lib/thinking_sphinx/panes/attributes_pane.rb - lib/thinking_sphinx/panes/distance_pane.rb - lib/thinking_sphinx/panes/excerpts_pane.rb - lib/thinking_sphinx/panes/weight_pane.rb - lib/thinking_sphinx/query.rb - lib/thinking_sphinx/railtie.rb - lib/thinking_sphinx/rake_interface.rb - lib/thinking_sphinx/real_time.rb - lib/thinking_sphinx/real_time/attribute.rb - lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb - lib/thinking_sphinx/real_time/field.rb - lib/thinking_sphinx/real_time/index.rb - lib/thinking_sphinx/real_time/index/template.rb - lib/thinking_sphinx/real_time/interpreter.rb - lib/thinking_sphinx/real_time/populator.rb - lib/thinking_sphinx/real_time/property.rb - lib/thinking_sphinx/real_time/transcriber.rb - lib/thinking_sphinx/scopes.rb - lib/thinking_sphinx/search.rb - lib/thinking_sphinx/search/batch_inquirer.rb - lib/thinking_sphinx/search/context.rb - lib/thinking_sphinx/search/glaze.rb - lib/thinking_sphinx/search/merger.rb - lib/thinking_sphinx/search/query.rb - lib/thinking_sphinx/search/stale_ids_exception.rb - lib/thinking_sphinx/sinatra.rb - lib/thinking_sphinx/sphinxql.rb - lib/thinking_sphinx/subscribers/populator_subscriber.rb - lib/thinking_sphinx/tasks.rb - lib/thinking_sphinx/test.rb - lib/thinking_sphinx/utf8.rb - lib/thinking_sphinx/wildcard.rb - spec/acceptance/association_scoping_spec.rb - spec/acceptance/attribute_access_spec.rb - spec/acceptance/attribute_updates_spec.rb - spec/acceptance/batch_searching_spec.rb - spec/acceptance/big_integers_spec.rb - spec/acceptance/excerpts_spec.rb - spec/acceptance/facets_spec.rb - spec/acceptance/geosearching_spec.rb - spec/acceptance/grouping_by_attributes_spec.rb - spec/acceptance/index_options_spec.rb - spec/acceptance/indexing_spec.rb - spec/acceptance/paginating_search_results_spec.rb - spec/acceptance/real_time_updates_spec.rb - spec/acceptance/remove_deleted_records_spec.rb - spec/acceptance/search_counts_spec.rb - spec/acceptance/search_for_just_ids_spec.rb - spec/acceptance/searching_across_models_spec.rb - spec/acceptance/searching_across_schemas_spec.rb - spec/acceptance/searching_on_fields_spec.rb - spec/acceptance/searching_with_filters_spec.rb - spec/acceptance/searching_with_sti_spec.rb - spec/acceptance/searching_within_a_model_spec.rb - spec/acceptance/sorting_search_results_spec.rb - spec/acceptance/spec_helper.rb - spec/acceptance/specifying_sql_spec.rb - spec/acceptance/sphinx_scopes_spec.rb - spec/acceptance/sql_deltas_spec.rb - spec/acceptance/support/database_cleaner.rb - spec/acceptance/support/sphinx_controller.rb - spec/acceptance/support/sphinx_helpers.rb - spec/acceptance/suspended_deltas_spec.rb - spec/fixtures/database.yml - spec/internal/app/indices/admin_person_index.rb - spec/internal/app/indices/animal_index.rb - spec/internal/app/indices/article_index.rb - spec/internal/app/indices/bird_index.rb - spec/internal/app/indices/book_index.rb - spec/internal/app/indices/car_index.rb - spec/internal/app/indices/city_index.rb - spec/internal/app/indices/product_index.rb - spec/internal/app/indices/tee_index.rb - spec/internal/app/indices/user_index.rb - spec/internal/app/models/admin/person.rb - spec/internal/app/models/animal.rb - spec/internal/app/models/article.rb - spec/internal/app/models/bird.rb - spec/internal/app/models/book.rb - spec/internal/app/models/car.rb - spec/internal/app/models/categorisation.rb - spec/internal/app/models/category.rb - spec/internal/app/models/city.rb - spec/internal/app/models/colour.rb - spec/internal/app/models/event.rb - spec/internal/app/models/flightless_bird.rb - spec/internal/app/models/genre.rb - spec/internal/app/models/hardcover.rb - spec/internal/app/models/mammal.rb - spec/internal/app/models/manufacturer.rb - spec/internal/app/models/product.rb - spec/internal/app/models/tag.rb - spec/internal/app/models/tagging.rb - spec/internal/app/models/tee.rb - spec/internal/app/models/tweet.rb - spec/internal/app/models/user.rb - spec/internal/config/database.yml - spec/internal/db/schema.rb - spec/internal/tmp/.gitkeep - spec/spec_helper.rb - spec/support/multi_schema.rb - spec/support/sphinx_yaml_helpers.rb - spec/thinking_sphinx/active_record/association_spec.rb - spec/thinking_sphinx/active_record/attribute/type_spec.rb - spec/thinking_sphinx/active_record/base_spec.rb - spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb - spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb - spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb - spec/thinking_sphinx/active_record/column_spec.rb - spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb - spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb - spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb - spec/thinking_sphinx/active_record/database_adapters_spec.rb - spec/thinking_sphinx/active_record/field_spec.rb - spec/thinking_sphinx/active_record/filter_reflection_spec.rb - spec/thinking_sphinx/active_record/index_spec.rb - spec/thinking_sphinx/active_record/interpreter_spec.rb - spec/thinking_sphinx/active_record/polymorpher_spec.rb - spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb - spec/thinking_sphinx/active_record/sql_builder_spec.rb - spec/thinking_sphinx/active_record/sql_source_spec.rb - spec/thinking_sphinx/configuration_spec.rb - spec/thinking_sphinx/connection_spec.rb - spec/thinking_sphinx/deletion_spec.rb - spec/thinking_sphinx/deltas/default_delta_spec.rb - spec/thinking_sphinx/deltas_spec.rb - spec/thinking_sphinx/errors_spec.rb - spec/thinking_sphinx/excerpter_spec.rb - spec/thinking_sphinx/facet_search_spec.rb - spec/thinking_sphinx/index_set_spec.rb - spec/thinking_sphinx/index_spec.rb - spec/thinking_sphinx/masks/pagination_mask_spec.rb - spec/thinking_sphinx/masks/scopes_mask_spec.rb - spec/thinking_sphinx/middlewares/active_record_translator_spec.rb - spec/thinking_sphinx/middlewares/geographer_spec.rb - spec/thinking_sphinx/middlewares/glazier_spec.rb - spec/thinking_sphinx/middlewares/inquirer_spec.rb - spec/thinking_sphinx/middlewares/sphinxql_spec.rb - spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb - spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb - spec/thinking_sphinx/panes/attributes_pane_spec.rb - spec/thinking_sphinx/panes/distance_pane_spec.rb - spec/thinking_sphinx/panes/excerpts_pane_spec.rb - spec/thinking_sphinx/panes/weight_pane_spec.rb - spec/thinking_sphinx/rake_interface_spec.rb - spec/thinking_sphinx/real_time/attribute_spec.rb - spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb - spec/thinking_sphinx/real_time/field_spec.rb - spec/thinking_sphinx/real_time/index_spec.rb - spec/thinking_sphinx/real_time/interpreter_spec.rb - spec/thinking_sphinx/scopes_spec.rb - spec/thinking_sphinx/search/glaze_spec.rb - spec/thinking_sphinx/search/query_spec.rb - spec/thinking_sphinx/search_spec.rb - spec/thinking_sphinx/wildcard_spec.rb - spec/thinking_sphinx_spec.rb - thinking-sphinx.gemspec homepage: https://pat.github.io/thinking-sphinx/ licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: thinking-sphinx rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: A smart wrapper over Sphinx for ActiveRecord test_files: - spec/acceptance/association_scoping_spec.rb - spec/acceptance/attribute_access_spec.rb - spec/acceptance/attribute_updates_spec.rb - spec/acceptance/batch_searching_spec.rb - spec/acceptance/big_integers_spec.rb - spec/acceptance/excerpts_spec.rb - spec/acceptance/facets_spec.rb - spec/acceptance/geosearching_spec.rb - spec/acceptance/grouping_by_attributes_spec.rb - spec/acceptance/index_options_spec.rb - spec/acceptance/indexing_spec.rb - spec/acceptance/paginating_search_results_spec.rb - spec/acceptance/real_time_updates_spec.rb - spec/acceptance/remove_deleted_records_spec.rb - spec/acceptance/search_counts_spec.rb - spec/acceptance/search_for_just_ids_spec.rb - spec/acceptance/searching_across_models_spec.rb - spec/acceptance/searching_across_schemas_spec.rb - spec/acceptance/searching_on_fields_spec.rb - spec/acceptance/searching_with_filters_spec.rb - spec/acceptance/searching_with_sti_spec.rb - spec/acceptance/searching_within_a_model_spec.rb - spec/acceptance/sorting_search_results_spec.rb - spec/acceptance/spec_helper.rb - spec/acceptance/specifying_sql_spec.rb - spec/acceptance/sphinx_scopes_spec.rb - spec/acceptance/sql_deltas_spec.rb - spec/acceptance/support/database_cleaner.rb - spec/acceptance/support/sphinx_controller.rb - spec/acceptance/support/sphinx_helpers.rb - spec/acceptance/suspended_deltas_spec.rb - spec/fixtures/database.yml - spec/internal/app/indices/admin_person_index.rb - spec/internal/app/indices/animal_index.rb - spec/internal/app/indices/article_index.rb - spec/internal/app/indices/bird_index.rb - spec/internal/app/indices/book_index.rb - spec/internal/app/indices/car_index.rb - spec/internal/app/indices/city_index.rb - spec/internal/app/indices/product_index.rb - spec/internal/app/indices/tee_index.rb - spec/internal/app/indices/user_index.rb - spec/internal/app/models/admin/person.rb - spec/internal/app/models/animal.rb - spec/internal/app/models/article.rb - spec/internal/app/models/bird.rb - spec/internal/app/models/book.rb - spec/internal/app/models/car.rb - spec/internal/app/models/categorisation.rb - spec/internal/app/models/category.rb - spec/internal/app/models/city.rb - spec/internal/app/models/colour.rb - spec/internal/app/models/event.rb - spec/internal/app/models/flightless_bird.rb - spec/internal/app/models/genre.rb - spec/internal/app/models/hardcover.rb - spec/internal/app/models/mammal.rb - spec/internal/app/models/manufacturer.rb - spec/internal/app/models/product.rb - spec/internal/app/models/tag.rb - spec/internal/app/models/tagging.rb - spec/internal/app/models/tee.rb - spec/internal/app/models/tweet.rb - spec/internal/app/models/user.rb - spec/internal/config/database.yml - spec/internal/db/schema.rb - spec/internal/tmp/.gitkeep - spec/spec_helper.rb - spec/support/multi_schema.rb - spec/support/sphinx_yaml_helpers.rb - spec/thinking_sphinx/active_record/association_spec.rb - spec/thinking_sphinx/active_record/attribute/type_spec.rb - spec/thinking_sphinx/active_record/base_spec.rb - spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb - spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb - spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb - spec/thinking_sphinx/active_record/column_spec.rb - spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb - spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb - spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb - spec/thinking_sphinx/active_record/database_adapters_spec.rb - spec/thinking_sphinx/active_record/field_spec.rb - spec/thinking_sphinx/active_record/filter_reflection_spec.rb - spec/thinking_sphinx/active_record/index_spec.rb - spec/thinking_sphinx/active_record/interpreter_spec.rb - spec/thinking_sphinx/active_record/polymorpher_spec.rb - spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb - spec/thinking_sphinx/active_record/sql_builder_spec.rb - spec/thinking_sphinx/active_record/sql_source_spec.rb - spec/thinking_sphinx/configuration_spec.rb - spec/thinking_sphinx/connection_spec.rb - spec/thinking_sphinx/deletion_spec.rb - spec/thinking_sphinx/deltas/default_delta_spec.rb - spec/thinking_sphinx/deltas_spec.rb - spec/thinking_sphinx/errors_spec.rb - spec/thinking_sphinx/excerpter_spec.rb - spec/thinking_sphinx/facet_search_spec.rb - spec/thinking_sphinx/index_set_spec.rb - spec/thinking_sphinx/index_spec.rb - spec/thinking_sphinx/masks/pagination_mask_spec.rb - spec/thinking_sphinx/masks/scopes_mask_spec.rb - spec/thinking_sphinx/middlewares/active_record_translator_spec.rb - spec/thinking_sphinx/middlewares/geographer_spec.rb - spec/thinking_sphinx/middlewares/glazier_spec.rb - spec/thinking_sphinx/middlewares/inquirer_spec.rb - spec/thinking_sphinx/middlewares/sphinxql_spec.rb - spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb - spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb - spec/thinking_sphinx/panes/attributes_pane_spec.rb - spec/thinking_sphinx/panes/distance_pane_spec.rb - spec/thinking_sphinx/panes/excerpts_pane_spec.rb - spec/thinking_sphinx/panes/weight_pane_spec.rb - spec/thinking_sphinx/rake_interface_spec.rb - spec/thinking_sphinx/real_time/attribute_spec.rb - spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb - spec/thinking_sphinx/real_time/field_spec.rb - spec/thinking_sphinx/real_time/index_spec.rb - spec/thinking_sphinx/real_time/interpreter_spec.rb - spec/thinking_sphinx/scopes_spec.rb - spec/thinking_sphinx/search/glaze_spec.rb - spec/thinking_sphinx/search/query_spec.rb - spec/thinking_sphinx/search_spec.rb - spec/thinking_sphinx/wildcard_spec.rb - spec/thinking_sphinx_spec.rb thinking-sphinx-3.1.4/.gitignore0000644000004100000410000000030212556214551016662 0ustar www-datawww-data*.gem .bundle .rbx Gemfile.lock *.sublime-* pkg/* spec/internal/config/test.sphinx.conf spec/internal/db/sphinx spec/internal/log/*.log !spec/internal/tmp/.gitkeep spec/internal/tmp/* tmp _site thinking-sphinx-3.1.4/README.textile0000644000004100000410000001142212556214551017234 0ustar www-datawww-datah1. Thinking Sphinx Thinking Sphinx is a library for connecting ActiveRecord to the Sphinx full-text search tool, and integrates closely with Rails (but also works with other Ruby web frameworks). The current release is v3.1.4. h2. Upgrading Please refer to the release notes for any changes you need to make when upgrading: * "v3.1.4":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.4 * "v3.1.3":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.3 * "v3.1.2":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.2 * "v3.1.1":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.1 * "v3.1.0":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.0 * "v3.0.6":https://github.com/pat/thinking-sphinx/releases/tag/v3.0.6 If you're upgrading from pre-v3, then the documentation has "pretty extensive notes":http://pat.github.io/thinking-sphinx/upgrading.html on what's changed. 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.18', :platform => :ruby
gem 'jdbc-mysql',      '~> 5.1.35', :platform => :jruby
gem 'thinking-sphinx', '~> 3.1.4'
The MySQL gems mentioned are required for connecting to Sphinx, so please include it even when you're using PostgreSQL for your database. You'll also need to install Sphinx - this is covered in "the extended documentation":http://pat.github.io/thinking-sphinx/installing_sphinx.html. h2. Usage Begin by reading the "quick-start guide":http://pat.github.io/thinking-sphinx/quickstart.html, and beyond that, "the documentation":http://pat.github.io/thinking-sphinx/ should serve you pretty well. h3. Extending with Middleware, Glazes and Panes These are covered in "a blog post":http://freelancing-gods.com/posts/rewriting_thinking_sphinx_middleware_glazes_and_panes. h2. Requirements h3. Sphinx Thinking Sphinx v3 is currently built for Sphinx 2.0.5 or newer, and releases since v3.1.0 expect Sphinx 2.1.2 or newer by default. h3. 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 (v1.9.3 or newer) or JRuby (1.7.9 or newer). I'm open to patches to improve Rubinius support (if required - it may work with it right now). JRuby is only supported as of Thinking Sphinx v3.1.0, and requires Sphinx 2.1.2 or newer. h3. Database Versions MySQL 5.x and Postgres 8.4 or better are supported. h2. Contributing Please note that this project now 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-2015, Thinking Sphinx is developed and maintained by Pat Allan, and is released under the open MIT Licence. Many thanks to "all who have contributed patches":https://github.com/pat/thinking-sphinx/contributors. thinking-sphinx-3.1.4/HISTORY0000644000004100000410000005007712556214551015774 0ustar www-datawww-data2015-06-01: 3.1.4 * [FIX] Kaminari expects prev_page to be available. * [CHANGE] Add a contributor code of conduct. * [FEATURE] Add JSON as a Sphinx type for attributes (Daniel Vandersluis). * [CHANGE] Remove polymorphic association and HABTM query support (when related to Thinking Sphinx) when ActiveRecord 3.2 is involved. * [FIX] Don't try to delete guard files if they don't exist (@exAspArk). * [FEATURE] minimal_group_by? can now be set in config/thinking_sphinx.yml to automatically apply to all index definitions. * [FIX] Handle database settings reliably, now that ActiveRecord 4.2 uses strings all the time. * [FIX] More consistent with escaping table names. * [CHANGE] Remove default charset_type - no longer required for Sphinx 2.2. * [FIX] Bug fix for association creation (with polymophic fields/attributes). * [CHANGE] Removing sql_query_info setting, as it's no longer used by Sphinx (nor is it actually used by Thinking Sphinx). 2015-01-21: 3.1.3 * [CHANGE] Log excerpt SphinxQL queries just like the search queries. * [CHANGE] Load Railtie if Rails::Railtie is defined, instead of just Rails (Andrew Cone). * [CHANGE] Convert raw Sphinx results to an array when querying (Bryan Ricker). * [FIX] Generate de-polymorphised associations properly for Rails 4.2 * [FIX] Use reflect_on_association instead of reflections, to stick to the public ActiveRecord::Base API. * [FIX] Don't load ActiveRecord early - fixes a warning in Rails 4.2. * [FEATURE] Allow for custom offset references with the :offset_as option - thus one model across many schemas with Apartment can be treated differently. * [FEATURE] Allow for custom IndexSet classes. * [FIX] Don't double-up on STI filtering, already handled by Rails. * [CHANGE] Add bigint support for real-time indices, and use bigints for the sphinx_internal_id attribute (mapped to model primary keys) (Chance Downs). 2014-11-04: 3.1.2 * [CHANGE] regenerate task now only deletes index files for real-time indices. * [CHANGE] Raise an exception when a populated search query is modified (as it can't be requeried). * [FEATURE] Allow for custom paths for index files using :path option in the ThinkingSphinx::Index.define call. * [FIX] Ensure indexing guard files are removed when an exception is raised (Bobby Uhlenbrock). * [FIX] Don't update real-time indices for objects that are not persisted (Chance Downs). * [FEATURE] Allow the binlog path to be an empty string (Bobby Uhlenbrock). * [FIX] Use STI base class for polymorphic association replacements. * [FIX] Convert database setting keys to symbols for consistency with Rails (@dimko). * [FIX] Field weights and other search options are now respected from set_property. * [CHANGE] Log indices that aren't processed due to guard files existing. * [FEATURE] Add status task to report on whether Sphinx is running. * [FIX] Models with more than one index have correct facet counts (using Sphinx 2.1.x or newer). * [FEATURE] Real-time index callbacks can take a block for dynamic scoping. * [FIX] Some association fixes for Rails 4.1. * [CHANGE] Paginate records by 1000 results at a time when flagging as deleted. * [CHANGE] Default the Capistrano TS Rails environment to use rails_env, and then fall back to stage. * [CHANGE] rebuild task uses clear between stopping the daemon and indexing. * [FIX] Clear connections when raising connection errors. * [FEATURE] Allow casting of document ids pre-offset as bigints (via big_documents_id option). 2014-04-22: 3.1.1 * [CHANGE] Include full statements when query execution errors are raised (uglier, but more useful when debugging). * [FEATURE] Allow for common section in generated Sphinx configuration files for Sphinx 2.2.x (disabled by default, though) (Trevor Smith). * [FEATURE] Basic support for HABTM associations and MVAs with query/ranged-query sources. * [CHANGE] Connection error messages now mention Sphinx, instead of just MySQL. * [FIX] Don't apply attribute-only updates to real-time indices. * [FIX] Don't instantiate blank strings (via inheritance type columns) as constants. * [FIX] Don't presume all indices for a model have delta pairs, even if one does. * [CHANGE] Raise an exception when a referenced column does not exist. * [CHANGE] Capistrano tasks use thinking_sphinx_rails_env (defaults to standard environment) (Robert Coleman). * [FIX] Always use connection options for connection information. * [FIX] respond_to? works reliably with masks (Konstantin Burnaev). * [FEATURE] Real-time indices callbacks can be disabled (useful for unit tests). * [FEATURE] ThinkingSphinx::Test has a clear method and no-index option for starting for real-time setups. * [FIX] Avoid null values in MVA query/ranged-query sources. * [CHANGE] Alias group and count columns for easier referencing in other clauses. * [FEATURE] Allow disabling of distributed indices. * [FIX] Don't send unicode null characters to real-time Sphinx indices. * [FIX] :populate option is now respected for single-model searches. * [FIX] :thinking_sphinx_roles is now used consistently in Capistrano v3 tasks. * [CHANGE] Log real-time index updates (Demian Ferreiro). * [FIX] Only expand log directory if it exists. * [FIX] Handle JDBC connection errors appropriately (Adam Hutchison). * [FIX] Fixing wildcarding of Unicode strings. * [CHANGE] All indices now respond to a public attributes method. * [FIX] Improved handling of association searches with real-time indices, including via has_many :though associations (Rob Anderton). 2014-01-11: 3.1.0 * [CHANGE] Updating Riddle requirement to >= 1.5.10. * [CHANGE] Extracting join generation into its own gem: Joiner. * [FEATURE] Support for Capistrano v3 (Alexander Tipugin). * [FEATURE] JRuby support (with Sphinx 2.1 or newer). * [CHANGE] Geodist calculation is now prepended to the SELECT statement, so it can be referred to by other dynamic attributes. * [FIX] Indices will be detected in Rails engines upon configuration. * [FEATURE] Support for Sphinx 2.2.x's HAVING and GROUP N BY SphinxQL options. * [FEATURE] Adding max_predicted_time search option (Sphinx 2.2.x). * [FEATURE] Wildcard/starring can be applied directly to strings using ThinkingSphinx::Query.wildcard('pancakes'), and escaping via ThinkingSphinx::Query.escape('pancakes'). * [CHANGE] Auto-wildcard/starring (via :star => true) now treats escaped characters as word separators. * [FEATURE] Capistrano recipe now includes tasks for realtime indices. * [CHANGE] Capistrano recipe no longer automatically adds thinking_sphinx:index and thinking_sphinx:start to be run after deploy:cold. * [CHANGE] UTF-8 forced encoding is now disabled by default (in line with Sphinx 2.1.x). * [CHANGE] Sphinx functions are now the default, instead of the legacy special variables (in line with Sphinx 2.1.x). * [CHANGE] Rails 3.1 is no longer supported. * [CHANGE] MRI 1.9.2 is no longer supported. * [FIX] Destroy callbacks are ignored for non-persisted objects. * [FEATURE] :group option within :sql options in a search call is passed through to the underlying ActiveRecord relation (Siarhei Hanchuk). * [FIX] Blank STI values are converted to the parent class in Sphinx index data (Jonathan Greenberg). * [CHANGE] Insist on at least * for SphinxQL SELECT statements. * [FIX] Track indices on parent STI models when marking documents as deleted. * [FEATURE] Persistent connections can be disabled if you wish. * [FIX] Separate per_page/max_matches values are respected in facet searches (Timo Virkkala). * [FIX] Don't split function calls when casting timestamps (Timo Virkalla). * [FEATURE] Track what's being indexed, and don't double-up while indexing is running. Single indices (e.g. deltas) can be processed while a full index is happening, though. * [FEATURE] Pass through :delta_options to delta processors (Timo Virkalla). * [FEATURE] All delta records can have their core pairs marked as deleted after a suspended delta (use ThinkingSphinx::Deltas.suspend_and_update instead of ThinkingSphinx::Deltas.suspend). * [CHANGE] Reset the delta column to true after core indexing is completed, instead of before, and don't filter out delta records from the core source. * [FEATURE] Set custom database settings within the index definition, using the set_database method. A more sane approach with multiple databases. * [CHANGE] Provide a distributed index per model that covers both core and delta indices. 2013-10-20: 3.0.6 * [FEATURE] Raise an error if no indices match the search criteria (Bryan Ricker). * [FEATURE] skip_time_zone setting is now available per environment via config/thinking_sphinx.yml to avoid the sql_query_pre time zone command. * [CHANGE] Updating Riddle dependency to be >= 1.5.9. * [FEATURE] Added new search options in Sphinx 2.1.x. * [FEATURE] Added ability to disable UTF-8 forced encoding, now that Sphinx 2.1.2 returns UTF-8 strings by default. This will be disabled by default in Thinking Sphinx 3.1.0. * [FEATURE] Added ability to switch between Sphinx special variables and the equivalent functions. Sphinx 2.1.x requires the latter, and that behaviour will become the default in Sphinx 3.1.0. * [FIX] Cast every column to a timestamp for timestamp attributes with multiple columns. * [CHANGE] Separated directory preparation from data generation for real-time index (re)generation tasks. * [CHANGE] Have tests index UTF-8 characters where appropriate (Pedro Cunha). * [FIX] Don't use Sphinx ordering if SQL order option is supplied to a search. * [CHANGE] Always use DISTINCT in group concatenation. * [CHANGE] Sphinx connection failures now have their own class, ThinkingSphinx::ConnectionError, instead of the standard Mysql2::Error. * [FIX] Custom middleware and mask options now function correctly with model-scoped searches. * [FEATURE] Adding search_for_ids on scoped search calls. * [CHANGE] Don't clobber custom :select options for facet searches (Timo Virkkala). * [CHANGE] Automatically load Riddle's Sphinx 2.0.5 compatability changes. * [FIX] Suspended deltas now no longer update core indices as well. * [CHANGE] Realtime fields and attributes now accept symbols as well as column objects, and fields can be sortable (with a _sort prefix for the matching attribute). * [FEATURE] MySQL users can enable a minimal GROUP BY statement, to speed up queries: set_property :minimal_group_by? => true. * [CHANGE] Insist on the log directory existing, to ensure correct behaviour for symlinked paths. (Michael Pearson). * [FIX] Use alphabetical ordering for index paths consistently (@grin). * [FIX] Convert very small floats to fixed format for geo-searches. * [CHANGE] Rake's silent mode is respected for indexing (@endoscient). 2013-08-26: 3.0.5 * [CHANGE] Updating Riddle dependency to be >= 1.5.8. * [FEATURE] Allow scoping of real-time index models. * [CHANGE] Real-time index population presentation and logic are now separated. * [CHANGE] Using the connection pool for update callbacks, excerpts, deletions. * [FIX] Respect existing sql_query_range/sql_query_info settings. * [CHANGE] Don't add the sphinx_internal_class_name unless STI models are indexed. * [FIX] Don't add select clauses or joins to sql_query if they're for query/ranged-query properties. * [CHANGE] Use Mysql2's reconnect option and have it turned on by default. * [FIX] Set database timezones as part of the indexing process. * [CHANGE] Improved auto-starring with escaped characters. * [FIX] Chaining scopes with just options works again. 2013-07-09: 3.0.4 * [CHANGE] Updating Riddle dependency to be >= 1.5.7. * [FEATURE] ts:regenerate rake task for rebuilding Sphinx when realtime indices are involved. * [FEATURE] ts:clear task removes all Sphinx index and binlog files. * [CHANGE] Glaze now responds to respond_to? (@groe). * [FEATURE] Facet search calls now respect the limit option (which otherwise defaults to max_matches) (Demian Ferreiro). * [FEATURE] Excerpts words can be overwritten with the words option (@groe). * [FIX] Empty queries with the star option set to true are handled gracefully. * [CHANGE] Deleted ActiveRecord objects are deleted in realtime indices as well. * [CHANGE] Realtime callbacks are no longer automatically added, but they're now more flexible (for association situations). * [CHANGE] Cleaning and refactoring so Code Climate ranks this as A-level code (Philip Arndt, Shevaun Coker, Garrett Heinlen). * [FIX] Excerpts are now wildcard-friendly. * [FIX] Facet searches now use max_matches value (with a default of 1000) to ensure as many results as possible are returned. * [CHANGE] Exceptions raised when communicating with Sphinx are now mentioned in the logs when queries are retried (instead of STDOUT). * [CHANGE] Excerpts now use just the query and standard conditions, instead of parsing Sphinx's keyword metadata (which had model names in it). * [FIX] The settings cache is now cleared when the configuration singleton is reset (Pedro Cunha). * [FEATURE] The :facets option can be used in facet searches to limit which facets are queried. * [FIX] Escaped @'s in queries are considered part of each word, instead of word separators. * [FIX] Internal class name conditions are ignored with auto-starred queries. * [FEATURE] A separate role can be set for Sphinx actions with Capistrano (Andrey Chernih). * [FIX] RDoc doesn't like constant hierarchies split over multiple lines. * [CHANGE] Get database connection details from ActiveRecord::Base, not each model, as this is where changes are reflected. * [CHANGE] Default Sphinx scopes are applied to new facet searches. * [FEATURE] Facet searches can now be called from Sphinx scopes. 2013-05-07: 3.0.3 * [CHANGE] Updating Riddle dependency to be >= 1.5.6 * [FEATURE] INDEX_ONLY environment flag is passed through when invoked through Capistrano (Demian Ferreiro). * [FEATURE] use_64_bit option returns as cast_to_timestamp instead (Denis Abushaev). * [FIX] Update to association handling for Rails/ActiveRecord 4.0.0.rc1. * [CHANGE] Delta jobs get common classes to allow third-party delta behaviours to leverage Thinking Sphinx. * [FEATURE] Collection of hooks (lambdas) that get called before indexing. Useful for delta libraries. * [FIX] Cast and concatenate multi-column attributes correctly. * [FIX] Don't load fields or attributes when building a real-time index - otherwise the index is translated before it has a chance to be built. * [CHANGE] Raise ThinkingSphinx::MixedScopesError if a search is called through an ActiveRecord scope. * [FIX] Default search panes are cloned for each search. * [FIX] Index-level settings (via set_property) are now applied consistently after global settings (in thinking_sphinx.yml). * [FIX] All string values returned from Sphinx are now properly converted to UTF8. * [CHANGE] GroupEnumeratorsMask is now a default mask, as masks need to be in place before search results are populated/the middleware is called (and previously it was being added within a middleware call). * [FIX] The default search masks are now cloned for each search, instead of referring to the constant (and potentially modifying it often). * [CHANGE] The current_page method is now a part of ThinkingSphinx::Search, as it is used when populating results. 2013-03-23: 3.0.2 * [CHANGE] per_page now accepts an optional paging limit, to match WillPaginate's behaviour. If none is supplied, it just returns the page size. * [FEATURE] Ruby 2.0 support. * [FEATURE] Rails 4.0.0 beta1 support. * [FIX] :utf8? option within index definitions is now supported, and defaults to true if the database configuration's encoding is set to 'utf8'. * [FIX] indices_location and configuration_file values in thinking_sphinx.yml will be applied to the configuration. * [CHANGE] Strings and regular expressions in ThinkingSphinx::Search::Query are now treated as UTF-8. * [FIX] Primary keys that are not 'id' now work correctly. * [CHANGE] Setting a custom framework will rebuild the core configuration around its provided settings (path and environment). * [CHANGE] Search masks don't rely on respond_to?, and so Object/Kernel methods are passed through to the underlying array instead. * [FIX] Search options specified in index definitions and thinking_sphinx.yml are now used in search requests (eg: max_matches, field_weights). * [FEATURE] Indexes defined in app/indices in engines are now loaded (Antonio Tapiador del Dujo). * [FIX] Custom association conditions are no longer presumed to be an array. * [CHANGE] Empty search conditions are now ignored, instead of being appended with no value (Nicholas Klick). * [CHANGE] Custom conditions are no longer added to the sql_query_range value, as they may involve associations. * [FIX] Capistrano tasks use the correct ts rake task prefix (David Celis). * [FEATURE] Query errors are classified as such, instead of getting the base SphinxError. 2013-02-04: 3.0.1 * [FEATURE] Provide Capistrano deployment tasks (David Celis). * [FEATURE] Allow specifying of Sphinx version. Is only useful for Flying Sphinx purposes at this point - has no impact on Riddle or Sphinx. * [FEATURE] Support new JDBC configuration style (when JDBC can be used) (Kyle Stevens). * [FIX] Referring to associations via polymorphic associations in an index definition now works. * [FEATURE] Mysql2::Errors are wrapped as ThinkingSphinx::SphinxErrors, with subclasses of SyntaxError and ParseError used appropriately. Syntax and parse errors do not prompt a retry on a new connection. * [CHANGE] Use connection pool for search queries. If a query fails, it will be retried on a new connection before raising if necessary. * [CHANGE] Glaze always passes methods through to the underlying ActiveRecord::Base object if they don't exist on any of the panes. * [FIX] Don't override foreign keys for polymorphic association replacements. * [FIX] Quote namespaced model names in class field condition. * [FEATURE] Polymorphic associations can be used within index definitions when the appropriate classes are set out. * [FEATURE] Allow custom strings for SQL joins in index definitions. * [FIX] New lines are maintained and escaped in custom source queries. * [FIX] Subclasses of indexed models fire delta callbacks properly. * [FIX] Thinking Sphinx can be loaded via thinking/sphinx, to satisfy Bundler. * [FEATURE] indexer and searchd settings are added to the appropriate objects from config/thinking_sphinx.yml (@ygelfand). * [FIX] New lines are maintained and escaped in sql_query values. 2013-01-02: 3.0.0 * [CHANGE] Updating Riddle dependency to 1.5.4. * [FIX] Respect source options as well as underlying settings via the set_property method in index definitions. * [FIX] Load real-time index definitions when listing fields, attributes, and/or conditions. * [CHANGE] UTF-8 is now the default charset again (as it was in earlier Thinking Sphinx versions). * [FEATURE] Initial realtime index support, including the ts:generate task for building index datasets. Sphinx 2.0.6 is required. * [CHANGE] Removing ts:version rake task. * [FEATURE] SphinxQL connection pooling via the Innertube gem. 2012-12-22: 3.0.0.rc * [FEATURE] Source type support (query and ranged query) for both attributes and fields. Custom SQL strings can be supplied as well. * [FEATURE] Wordcount attributes and fields now supported. * [FEATURE] Support for Sinatra and other non-Rails frameworks. * [FEATURE] A sphinx scope can be defined as the default. * [FEATURE] An index can have multiple sources, by using define_source within the index definition. * [FEATURE] sanitize_sql is available within an index definition. * [FEATURE] Providing :prefixes => true or :infixes => true as an option when declaring a field means just the noted fields have infixes/prefixes applied. * [FEATURE] ThinkingSphinx::Search#query_time returns the time Sphinx took to make the query. * [FEATURE] Namespaced model support. * [FEATURE] Default settings for index definition arguments can be set in config/thinking_sphinx.yml. * [FIX] Correctly escape nulls in inheritance column (Darcy Laycock). * [FIX] Use ThinkingSphinx::Configuration#render_to_file instead of ThinkingSphinx::Configuration#build in test helpers (Darcy Laycock). * [FIX] Suppressing delta output in test helpers now works (Darcy Laycock). * [FEATURE] A custom Riddle/Sphinx controller can be supplied. Useful for Flying Sphinx to have an API layer over Sphinx commands, without needing custom gems for different Thinking Sphinx/Flying Sphinx combinations. 2012-10-06: 3.0.0.pre * First pre-release. Not quite feature complete, but the important stuff is certainly covered. See the README for more the finer details. thinking-sphinx-3.1.4/Appraisals0000644000004100000410000000033612556214551016723 0ustar www-datawww-dataappraise 'rails_3_2' do gem 'rails', '~> 3.2.21' end appraise 'rails_4_0' do gem 'rails', '~> 4.0.12' end appraise 'rails_4_1' do gem 'rails', '~> 4.1.8' end appraise 'rails_4_2' do gem 'rails', '~> 4.2.0' end thinking-sphinx-3.1.4/LICENCE0000644000004100000410000000203512556214551015664 0ustar www-datawww-dataCopyright (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-3.1.4/thinking-sphinx.gemspec0000644000004100000410000000260512556214551021371 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path('../lib', __FILE__) Gem::Specification.new do |s| s.name = 'thinking-sphinx' s.version = '3.1.4' 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', '>= 1.5.11' s.add_development_dependency 'appraisal', '~> 1.0.2' s.add_development_dependency 'combustion', '~> 0.4.0' s.add_development_dependency 'database_cleaner', '~> 1.2.0' s.add_development_dependency 'rspec', '~> 2.13.0' end