From 5b86e640b00d79e779d8c6b5a5b73a7a00136f29 Mon Sep 17 00:00:00 2001 From: Matchu Date: Sat, 15 May 2010 12:43:54 -0400 Subject: [PATCH] negate search conditions --- Gemfile | 2 + app/models/item.rb | 18 ++- spec/models/item_spec.rb | 238 +++++++++++++++++++++++---------------- 3 files changed, 158 insertions(+), 100 deletions(-) diff --git a/Gemfile b/Gemfile index db0d47f2..afe78ac3 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,8 @@ source 'http://rubygems.org' gem 'rails', '3.0.0.beta2' +gem 'arel', :git => 'http://github.com/ernie/arel.git' + gem 'sqlite3-ruby', :require => 'sqlite3' group :test do diff --git a/app/models/item.rb b/app/models/item.rb index f5523e1d..644c565d 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -23,6 +23,8 @@ class Item < ActiveRecord::Base in_phrase = !in_phrase elsif c == ':' && !in_phrase query_conditions.last.to_property! + elsif c == '-' && !in_phrase && query_conditions.last.empty? + query_conditions.last.negate! else query_conditions.last << c end @@ -35,26 +37,34 @@ class Item < ActiveRecord::Base private class Condition < String - attr_reader :property - def to_property! @property = self.clone self.replace '' end + def negate! + @negative = true + end + def narrow(scope) + items = Table(:objects) if @property == 'species' species = Species.find_by_name(self) # TODO: add a many-to-many table to handle this relationship - scope.where('species_support_ids = ? OR species_support_ids LIKE ? OR species_support_ids LIKE ? OR species_support_ids LIKE ?', + condition = items[:species_support_ids].matches_any( species.id, "#{species.id},%", "%,#{species.id},%", "%,#{species.id}" ) else - scope.where('name LIKE :matcher OR description LIKE :matcher', :matcher => "%#{self}%") + matcher = "%#{self}%" + condition = items[:name].matches(matcher).or( + items[:description].matches(matcher) + ) end + condition = condition.not if @negative + scope.where(condition) end def inspect diff --git a/spec/models/item_spec.rb b/spec/models/item_spec.rb index 449419cc..c6bd3b10 100644 --- a/spec/models/item_spec.rb +++ b/spec/models/item_spec.rb @@ -1,105 +1,151 @@ require 'spec_helper' describe Item do - specify "should accept string or array for species_support_ids" do - items = [ - Factory.build(:item, :species_support_ids => '1,2,3'), - Factory.build(:item, :species_support_ids => [1,2,3]) - ] - items.each { |i| i.species_support_ids.should == [1,2,3] } - end - - specify "class should search name for word" do - query_should 'blue', - :return => [ - 'A Hat That is Blue', - 'Blue Hat', - 'Blueish Hat', - 'Very Blue Hat' - ], - :not_return => [ - 'Green Hat', - 'Red Hat' + context "an item" do + specify "should accept string or array for species_support_ids" do + items = [ + Factory.build(:item, :species_support_ids => '1,2,3'), + Factory.build(:item, :species_support_ids => [1,2,3]) ] - end - - specify "class should search name for phrase" do - query_should '"one two"', - :return => [ - 'Zero one two three', - 'Zero one two', - 'One two three' - ], - :not_return => [ - 'Zero one three', - 'Zero two three', - 'Zero one and two', - 'Three two one' - ] - end - - specify "class should search name for multiple words" do - query_should 'one two', - :return => [ - 'Zero one two three', - 'Zero one two', - 'One two three', - 'Zero one and two', - 'Three two one' - ], - :not_return => [ - 'Zero one three', - 'Zero two three' - ] - end - - specify "class should search name for words and phrases" do - query_should 'zero "one two" three', - :return => [ - 'zero one two three', - 'zero four one two three', - 'one two zero three', - 'three zero one two' - ], - :not_return => [ - 'one two three', - 'zero one two', - 'three one zero two', - 'two one three zero' - ] - end - - specify "class should search name and description" do - query_should 'zero "one two"', - :return => [ - 'zero one two', - ['zero one', 'one two three'], - ['one two four', 'one three zero'], - ['three', 'one two four zero'] - ], - :not_return => [ - ['zero one', 'two'], - ['one two four', 'three'], - ['five two', 'zero one three two'] - ] - end - - specify "class should search by species" do - [[2],[1,2,3],[2,3],[3],[1,3]].each do |ids| - Factory.create :item, :species_support_ids => ids + items.each { |i| i.species_support_ids.should == [1,2,3] } end - Item.search('species:acara').count.should == 2 - Item.search('species:aisha').count.should == 3 - Item.search('species:blumaroo').count.should == 4 end - specify "class should search by species and words" do - Factory.create :item, :name => 'Blue Hat', :species_support_ids => [1] - Factory.create :item, :name => 'Very Blue Hat', :species_support_ids => [1,2] - Factory.create :item, :name => 'Red Hat', :species_support_ids => [2] - Item.search('blue species:acara').count.should == 2 - Item.search('blue species:aisha').count.should == 1 - Item.search('red species:acara').count.should == 0 - Item.search('red species:aisha').count.should == 1 + context "class" do + specify "should search name for word" do + query_should 'blue', + :return => [ + 'A Hat That is Blue', + 'Blue Hat', + 'Blueish Hat', + 'Very Blue Hat' + ], + :not_return => [ + 'Green Hat', + 'Red Hat' + ] + end + + specify "should search name for phrase" do + query_should '"one two"', + :return => [ + 'Zero one two three', + 'Zero one two', + 'One two three' + ], + :not_return => [ + 'Zero one three', + 'Zero two three', + 'Zero one and two', + 'Three two one' + ] + end + + specify "should search name for multiple words" do + query_should 'one two', + :return => [ + 'Zero one two three', + 'Zero one two', + 'One two three', + 'Zero one and two', + 'Three two one' + ], + :not_return => [ + 'Zero one three', + 'Zero two three' + ] + end + + specify "should search name for words and phrases" do + query_should 'zero "one two" three', + :return => [ + 'zero one two three', + 'zero four one two three', + 'one two zero three', + 'three zero one two' + ], + :not_return => [ + 'one two three', + 'zero one two', + 'three one zero two', + 'two one three zero' + ] + end + + specify "should search name and description" do + query_should 'zero "one two"', + :return => [ + 'zero one two', + ['zero one', 'one two three'], + ['one two four', 'one three zero'], + ['three', 'one two four zero'] + ], + :not_return => [ + ['zero one', 'two'], + ['one two four', 'three'], + ['five two', 'zero one three two'] + ] + end + + specify "should search by species" do + [[2],[1,2,3],[2,3],[3],[1,3]].each do |ids| + Factory.create :item, :species_support_ids => ids + end + Item.search('species:acara').count.should == 2 + Item.search('species:aisha').count.should == 3 + Item.search('species:blumaroo').count.should == 4 + end + + specify "should search by species and words" do + Factory.create :item, :name => 'Blue Hat', :species_support_ids => [1] + Factory.create :item, :name => 'Very Blue Hat', :species_support_ids => [1,2] + Factory.create :item, :name => 'Red Hat', :species_support_ids => [2] + Item.search('blue species:acara').count.should == 2 + Item.search('blue species:aisha').count.should == 1 + Item.search('red species:acara').count.should == 0 + Item.search('red species:aisha').count.should == 1 + end + + specify "should be able to negate word in search" do + query_should 'hat -blue', + :return => [ + 'Green Hat', + 'Red Hat', + 'Blu E Hat', + ['Yellow Hat', 'This hat is not green!'] + ], + :not_return => [ + 'Blue Hat', + 'Green Shirt', + 'Blue Shirt', + ['Orange Hat', 'This hat is not blue!'], + ['Blue Pants', 'This is not a hat!'] + ] + end + + specify "should be able to negate species in search" do + Factory.create :item, :name => 'Blue Hat', :species_support_ids => [1] + Factory.create :item, :name => 'Very Blue Hat', :species_support_ids => [1,2] + Factory.create :item, :name => 'Red Hat', :species_support_ids => [1,2] + Factory.create :item, :name => 'Green Hat', :species_support_ids => [3] + Factory.create :item, :name => 'Red Shirt', :species_support_ids => [3] + Item.search('hat -species:acara').count.should == 1 + Item.search('hat -species:aisha').count.should == 2 + Item.search('hat -species:acara -species:aisha').count.should == 1 + end + + specify "should be able to negate phrase in search" do + query_should 'zero -"one two"', + :return => [ + 'Zero two one', + ['Two one', 'Zero'], + 'One three two zero' + ], + :not_return => [ + 'Zero one two', + ['Zero', 'one two three'], + ['One two four', 'Zero one'] + ] + end end end