From 851aa4909147929f52ef81c62c9bcf44d0f6901c Mon Sep 17 00:00:00 2001 From: Matchu Date: Sat, 15 May 2010 11:38:45 -0400 Subject: [PATCH] basic specs for items, including search --- app/models/item.rb | 63 ++++++++++- app/models/species.rb | 20 ++++ config/species.txt | 54 +++++++++ ...ms.rb => 20100514224031_create_objects.rb} | 6 +- ...100515020945_add_description_to_objects.rb | 9 ++ db/schema.rb | 22 ++++ spec/models/item_spec.rb | 105 ++++++++++++++++++ spec/models/species_spec.rb | 18 +++ spec/spec_helper.rb | 8 ++ 9 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 app/models/species.rb create mode 100644 config/species.txt rename db/migrate/{20100514224031_create_items.rb => 20100514224031_create_objects.rb} (56%) create mode 100644 db/migrate/20100515020945_add_description_to_objects.rb create mode 100644 db/schema.rb create mode 100644 spec/models/item_spec.rb create mode 100644 spec/models/species_spec.rb diff --git a/app/models/item.rb b/app/models/item.rb index b8a69cd5..f5523e1d 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -1,3 +1,64 @@ -class Item +class Item < ActiveRecord::Base + set_table_name 'objects' # Neo & PHP Impress call them objects, but the class name is a conflict (duh!) + set_inheritance_column 'inheritance_type' # PHP Impress used "type" to describe category + # Not defining validations, since this app is currently read-only + + def species_support_ids + @species_support_ids_array ||= read_attribute('species_support_ids').split(',').map(&:to_i) + end + + def species_support_ids=(replacement) + replacement = replacement.join(',') if replacement.is_a?(Array) + write_attribute('species_support_ids', replacement) + end + + def self.search(query) + query_conditions = [Condition.new] + in_phrase = false + query.each_char do |c| + if c == ' ' && !in_phrase + query_conditions << Condition.new + elsif c == '"' + in_phrase = !in_phrase + elsif c == ':' && !in_phrase + query_conditions.last.to_property! + else + query_conditions.last << c + end + end + query_conditions.inject(self) do |scope, condition| + condition.narrow(scope) + end + end + + private + + class Condition < String + attr_reader :property + + def to_property! + @property = self.clone + self.replace '' + end + + def narrow(scope) + 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 ?', + species.id, + "#{species.id},%", + "%,#{species.id},%", + "%,#{species.id}" + ) + else + scope.where('name LIKE :matcher OR description LIKE :matcher', :matcher => "%#{self}%") + end + end + + def inspect + @property ? "#{@property}:#{super}" : super + end + end end diff --git a/app/models/species.rb b/app/models/species.rb new file mode 100644 index 00000000..c6257788 --- /dev/null +++ b/app/models/species.rb @@ -0,0 +1,20 @@ +class Species + attr_accessor :id, :name + + @objects = [] + @objects_by_name = {} + File.open(Rails.root.join('config', 'species.txt')).each do |line| + name = line.chomp.downcase + @objects << @objects_by_name[name] = species = Species.new + species.id = @objects.size + species.name = name + end + + def self.find(id) + @objects[id-1] + end + + def self.find_by_name(name) + @objects_by_name[name.downcase] + end +end diff --git a/config/species.txt b/config/species.txt new file mode 100644 index 00000000..6c87089a --- /dev/null +++ b/config/species.txt @@ -0,0 +1,54 @@ +Acara +Aisha +Blumaroo +Bori +Bruce +Buzz +Chia +Chomby +Cybunny +Draik +Elephante +Eyrie +Flotsam +Gelert +Gnorbu +Grarrl +Grundo +Hissi +Ixi +Jetsam +Jubjub +Kacheek +Kau +Kiko +Koi +Korbat +Kougra +Krawk +Kyrii +Lenny +Lupe +Lutari +Meerca +Moehog +Mynci +Nimmo +Ogrin +Peophin +Poogle +Pteri +Quiggle +Ruki +Scorchio +Shoyru +Skeith +Techo +Tonu +Tuskaninny +Uni +Usul +Wocky +Xweetok +Yurble +Zafara diff --git a/db/migrate/20100514224031_create_items.rb b/db/migrate/20100514224031_create_objects.rb similarity index 56% rename from db/migrate/20100514224031_create_items.rb rename to db/migrate/20100514224031_create_objects.rb index 26c5bb39..7f74fa9c 100644 --- a/db/migrate/20100514224031_create_items.rb +++ b/db/migrate/20100514224031_create_objects.rb @@ -1,6 +1,6 @@ -class CreateItems < ActiveRecord::Migration +class CreateObjects < ActiveRecord::Migration def self.up - create_table :items do |t| + create_table :objects do |t| t.string :name t.string :species_support_ids @@ -9,6 +9,6 @@ class CreateItems < ActiveRecord::Migration end def self.down - drop_table :items + drop_table :objects end end diff --git a/db/migrate/20100515020945_add_description_to_objects.rb b/db/migrate/20100515020945_add_description_to_objects.rb new file mode 100644 index 00000000..747cbfca --- /dev/null +++ b/db/migrate/20100515020945_add_description_to_objects.rb @@ -0,0 +1,9 @@ +class AddDescriptionToObjects < ActiveRecord::Migration + def self.up + add_column :objects, :description, :text + end + + def self.down + remove_column :objects, :description + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 00000000..32eff455 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,22 @@ +# This file is auto-generated from the current state of the database. Instead of editing this file, +# please use the migrations feature of Active Record to incrementally modify your database, and +# then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your database schema. If you need +# to create the application database on another system, you should be using db:schema:load, not running +# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 20100515020945) do + + create_table "objects", :force => true do |t| + t.string "name" + t.string "species_support_ids" + t.datetime "created_at" + t.datetime "updated_at" + t.text "description" + end + +end diff --git a/spec/models/item_spec.rb b/spec/models/item_spec.rb new file mode 100644 index 00000000..449419cc --- /dev/null +++ b/spec/models/item_spec.rb @@ -0,0 +1,105 @@ +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' + ] + 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 + 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 + end +end diff --git a/spec/models/species_spec.rb b/spec/models/species_spec.rb new file mode 100644 index 00000000..4889b939 --- /dev/null +++ b/spec/models/species_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Species do + specify "should find by id, report name" do + Species.find(1).name.should == 'acara' + Species.find(2).name.should == 'aisha' + end + + specify "should find by name, report id" do + Species.find_by_name('acara').id.should == 1 + Species.find_by_name('aisha').id.should == 2 + end + + specify "name should be case-insensitive" do + Species.find_by_name('Acara').id.should == 1 + Species.find_by_name('acara').id.should == 1 + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 311ba35c..65b115b6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -21,4 +21,12 @@ Rspec.configure do |config| # If you'd prefer not to run each of your examples within a transaction, # uncomment the following line. # config.use_transactional_examples = false + + def query_should(query, sets) + sets = sets.each { |k,v| sets[k] = v.map { |x| x.is_a?(Array) ? x : [x, ''] } } + all_sets = sets[:return] + sets[:not_return] + all_sets.each { |s| Factory.create :item, :name => s[0], :description => s[1]} + returned_sets = Item.search(query).all.map { |i| [i.name, i.description] }.sort + returned_sets.should == sets[:return].sort + end end