# encoding: utf-8 require 'spec_helper' shared_examples_for 'model with search syntax' do describe '#search' do it 'should find records by given words' do match = subject.create!(:name => 'Abraham') no_match = subject.create!(:name => 'Elizabath') subject.search('Abraham').to_a.should == [match] end it 'should not find stale text after fields were updated (bugfix)' do match = subject.create!(:name => 'Abraham') no_match = subject.create!(:name => 'Elizabath') match.update_attributes!(:name => 'Johnny') subject.search('Abraham').to_a.should be_empty subject.search('Johnny').to_a.should == [match] end it 'should AND multiple words' do match = subject.create!(:name => 'Abraham Lincoln') no_match = subject.create!(:name => 'Abraham') subject.search('Abraham Lincoln').to_a.should == [match] end it 'should find records by phrases' do match = subject.create!(:name => 'Abraham Lincoln') no_match = subject.create!(:name => 'Abraham John Lincoln') subject.search('"Abraham Lincoln"').to_a.should == [match] end it 'should find records by qualified fields' do match = subject.create!(:name => 'foo@bar.com', :email => 'foo@bar.com') no_match = subject.create!(:name => 'foo@bar.com', :email => 'bam@baz.com') subject.search('email:foo@bar.com').to_a.should == [match] end it 'should find no records if a nonexistent qualifier is used' do subject.create!(:name => 'someuser', :email => 'foo@bar.com') subject.search('nonexistent_qualifier:someuser email:foo@bar.com').to_a.should == [] end it 'should allow phrases as values for qualified field queries' do match = subject.create!(:name => 'Foo Bar', :city => 'Foo Bar') no_match = subject.create!(:name => 'Foo Bar', :city => 'Bar Foo') subject.search('city:"Foo Bar"').to_a.should == [match] end it 'should allow to mix multiple types of tokens in a single query' do match = subject.create!(:name => 'Abraham', :city => 'Foohausen') no_match = subject.create!(:name => 'Abraham', :city => 'Barhausen') subject.search('Foo city:Foohausen').to_a.should == [match] end it 'should not find records from another model' do match = subject.create!(:name => 'Abraham') Recipe.create!(:name => 'Abraham') subject.search('Abraham').to_a.should == [match] end it 'should find words where one letter is separated from other letters by a period' do match = subject.create!(:name => 'E.ONNNEN') subject.search('E.ONNNEN').to_a.should == [match] end it 'should find words where one letter is separated from other letters by a semicolon' do match = subject.create!(:name => 'E;ONNNEN') subject.search('E;ONNNEN') subject.search('E;ONNNEN').to_a.should == [match] end it 'should distinguish between "Baden" and "Baden-Baden" (bugfix)' do match = subject.create!(:city => 'Baden-Baden') no_match = subject.create!(:city => 'Baden') subject.search('Baden-Baden').to_a.should == [match] end it 'should handle umlauts and special characters' do match = subject.create!(:city => 'púlvérìsätëûr') subject.search('púlvérìsätëûr').to_a.should == [match] end context 'with excludes' do it 'should exclude words with prefix - (minus)' do match = subject.create!(:name => 'Sunny Flower') no_match = subject.create!(:name => 'Sunny Power') no_match2 = subject.create!(:name => 'Absolutly no match') subject.search('Sunny -Power').to_a.should == [match] end it 'should exclude phrases with prefix - (minus)' do match = subject.create!(:name => 'Buch Tastatur Schreibtisch') no_match = subject.create!(:name => 'Buch Schreibtisch Tastatur') no_match2 = subject.create!(:name => 'Absolutly no match') subject.search('Buch -"Schreibtisch Tastatur"').to_a.should == [match] end it 'should exclude qualified fields with prefix - (minus)' do match = subject.create!(:name => 'Abraham', :city => 'Foohausen') no_match = subject.create!(:name => 'Abraham', :city => 'Barhausen') no_match2 = subject.create!(:name => 'Absolutly no match') subject.search('Abraham -city:Barhausen').to_a.should == [match] end it 'should work if the query only contains excluded words' do match = subject.create!(:name => 'Sunny Flower') no_match = subject.create!(:name => 'Sunny Power') subject.search('-Power').to_a.should == [match] end it 'should work if the query only contains excluded phrases' do match = subject.create!(:name => 'Buch Tastatur Schreibtisch') no_match = subject.create!(:name => 'Buch Schreibtisch Tastatur') subject.search('-"Schreibtisch Tastatur"').to_a.should == [match] end it 'should work if the query only contains excluded qualified fields' do match = subject.create!(:name => 'Abraham', :city => 'Foohausen') no_match = subject.create!(:name => 'Abraham', :city => 'Barhausen') subject.search('-city:Barhausen').to_a.should == [match] end it 'respects an existing scope chain when there are only excluded tokens (bugfix)' do match = subject.create!(:name => 'Abraham', :city => 'Foohausen') no_match = subject.create!(:name => 'Abraham', :city => 'Barhausen') subject.scoped(:conditions => { :name => 'Abraham' }).search('-Barhausen').to_a.should == [match] end it 'should work if there are fields contained in the search that are NULL (needs NOT COALESCE in syntax#search)' do match = subject.create!(:name => 'Sunny Flower', :city => nil, :email => nil) no_match = subject.create!(:name => 'Sunny Power', :city => nil, :email => nil) no_match2 = subject.create!(:name => 'Absolutly no match') subject.search('Sunny -Power').to_a.should == [match] end it 'should work if search_by contains a join (bugfix)' do category1 = Recipe::Category.create!(:name => 'Rice') category2 = Recipe::Category.create!(:name => 'Barbecue') match = Recipe.create!(:name => 'Martini Chicken', :category => category1) no_match = Recipe.create!(:name => 'Barbecue Chicken', :category => category2) Recipe.search('Chicken -category:Barbecue').to_a.should == [match] end it 'should work when search_by uses SQL-Regexes which need to be "and"ed together by syntax#build_exclude_scope (bugfix)' do match = subject.create!(:name => 'Sunny Flower', :city => "Flower") no_match = subject.create!(:name => 'Sunny Power', :city => "Power") subject.search('-name_and_city_regex:Power').to_a.should == [match] end end context 'when the given query is blank' do it 'returns all records' do match = subject.create! subject.search('').to_a.should == [match] end it 'respects an existing scope chain' do match = subject.create!(:name => 'Abraham') no_match = subject.create!(:name => 'Elizabath') subject.scoped(:conditions => { :name => 'Abraham' }).search('').to_a.should == [match] end end end describe '.search_syntax' do it "should return the model's syntax definition when called without a block" do subject.search_syntax.should be_a(Dusen::Syntax) end it 'should be callable multiple times, appending additional syntax' do subject.search_syntax.fields.keys.should =~ ['text', 'email', 'city', 'role', 'name_and_city_regex'] end end describe '.where_like' do it 'matches a record if a word appears in any of the given columns' do match1 = subject.create!(:name => 'word', :city => 'XXXX') match2 = subject.create!(:name => 'XXXX', :city => 'word') no_match = subject.create!(:name => 'XXXX', :city => 'XXXX') subject.where_like([:name, :city] => 'word').to_a.should =~ [match1, match2] end it 'matches a record if it contains all the given words' do match1 = subject.create!(:city => 'word1 word2') match2 = subject.create!(:city => 'word2 word1') no_match = subject.create!(:city => 'word1') subject.where_like(:city => ['word1', 'word2']).to_a.should =~ [match1, match2] end describe 'with :negate option' do it 'rejects a record if a word appears in any of the given columns' do no_match1 = subject.create!(:name => 'word', :city => 'XXXX') no_match2 = subject.create!(:name => 'XXXX', :city => 'word') match = subject.create!(:name => 'XXXX', :city => 'XXXX') subject.where_like({ [:name, :city] => 'word' }, :negate => true).to_a.should =~ [match] end it 'rejects a record if it matches at least one of the given words' do no_match1 = subject.create!(:city => 'word1') no_match2 = subject.create!(:city => 'word2') match = subject.create!(:city => 'word3') subject.where_like({ :city => ['word1', 'word2'] }, :negate => true).to_a.should =~ [match] end it "doesn't match NULL values" do no_match = subject.create!(:city => nil) match = subject.create!(:city => 'word3') subject.where_like({ :city => ['word1'] }, :negate => true).to_a.should =~ [match] end end end end describe ActiveRecord::Base do describe 'for a model without an associated FULLTEXT table' do subject { User::WithoutFulltext } it_should_behave_like 'model with search syntax' end describe 'for a model with an associated FULLTEXT table' do subject { User::WithFulltext } it_should_behave_like 'model with search syntax' it 'should be shadowed by a Dusen::ActiveRecord::SearchText, which is created, updated and destroyed with the record' do user = User::WithFulltext.create!(:name => 'name', :email => 'email', :city => 'city') User::WithFulltext.index_search_texts search_texts = Dusen::ActiveRecord::SearchText.all search_texts.size.should == 1 search_texts[0].words.should == 'name email city' search_texts[0].should_not be_stale user.reload user.update_attributes!(:email => 'changed_email') search_texts = Dusen::ActiveRecord::SearchText.all search_texts.size.should == 1 search_texts[0].words.should == 'name email city' search_texts[0].should be_stale User::WithFulltext.index_search_texts search_texts = Dusen::ActiveRecord::SearchText.all search_texts.size.should == 1 search_texts[0].words.should == 'name changed_email city' search_texts[0].should_not be_stale user.destroy search_texts = Dusen::ActiveRecord::SearchText.all search_texts.size.should be_zero end describe 'indexing fields from associated records' do it 'should allow to index fields from an associated record' do category = Recipe::Category.create!(:name => 'Rice') recipe = Recipe.create!(:name => 'Martini Chicken', :category => category) recipe.ingredients.create!(:name => 'Paprica') recipe.ingredients.create!(:name => 'Tomatoes') Recipe.search('Rice').to_a.should == [recipe] Recipe.search('Martini').to_a.should == [recipe] Recipe.search('Paprica').to_a.should == [recipe] Recipe.search('Tomatoes').to_a.should == [recipe] end context 'if the associated model has a .part_of_search_text_for directive' do it 'should automatically reindex itself when an associated record changes' do category = Recipe::Category.create!(:name => 'Rice') recipe = category.recipes.create!(:name => 'Martini Chicken') ingredient = recipe.ingredients.create!(:name => 'Paprica') category.update_attributes!(:name => 'Noodles') ingredient.update_attributes!(:name => 'Onions') Recipe.search('Noodles').to_a.should == [recipe] Recipe.search('Onion').to_a.should == [recipe] category.reload category.destroy Recipe.search('Noodles').to_a.should be_empty end #it 'should automatically reindex both containers if the container changes' do # recipe1 = Recipe.create!(:name => 'Martini Chicken') # recipe2 = Recipe.create!(:name => 'Whiskey Chicken') # ingredient = recipe1.ingredients.create!(:name => 'Paprica') # Recipe.search('Paprica').to_a.should == [recipe1] # ingredient.update_attributes!(:recipe => recipe2) # Recipe.search('Paprica').to_a.should == [recipe2] #end end end describe '#search_text' do it 'should return the currently indexable words for this record' do category = Recipe::Category.create!(:name => 'Rice') recipe = Recipe.create!(:name => 'Martini Chicken', :category => category) recipe.search_text.should == "Martini Chicken Rice" end end end end