Commit b1ac13770d65f52d2cd22b0e973c1e8be1299975

Authored by MoisesMachado
1 parent b67c05a5

ActionItem243: reimplemented search to allow be filtered


git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@1590 3f533792-8f58-4932-b0fe-aaf55b0a4547
app/controllers/application.rb
@@ -102,4 +102,13 @@ class ApplicationController < ActionController::Base @@ -102,4 +102,13 @@ class ApplicationController < ActionController::Base
102 end 102 end
103 end 103 end
104 104
  105 + def load_category
  106 + path = params[:category_path].join('/')
  107 + @category = environment.categories.find_by_path(path)
  108 + if @category.nil?
  109 + render_not_found(path)
  110 + end
  111 + end
  112 +
  113 +
105 end 114 end
app/controllers/public/category_controller.rb
@@ -11,12 +11,5 @@ class CategoryController < PublicController @@ -11,12 +11,5 @@ class CategoryController < PublicController
11 attr_reader :category 11 attr_reader :category
12 12
13 before_filter :load_category, :only => [ :view ] 13 before_filter :load_category, :only => [ :view ]
14 - def load_category  
15 - path = params[:path].join('/')  
16 - @category = environment.categories.find_by_path(path)  
17 - if @category.nil?  
18 - render_not_found(path)  
19 - end  
20 - end  
21 14
22 end 15 end
app/controllers/public/search_controller.rb
@@ -4,8 +4,8 @@ class SearchController < ApplicationController @@ -4,8 +4,8 @@ class SearchController < ApplicationController
4 4
5 protected 5 protected
6 6
7 - def search(klass, query)  
8 - klass.find_by_contents(query).sort_by do |hit| 7 + def search(finder, query)
  8 + finder.find_by_contents(query).sort_by do |hit|
9 -(relevance_for(hit)) 9 -(relevance_for(hit))
10 end 10 end
11 end 11 end
@@ -14,13 +14,42 @@ class SearchController < ApplicationController @@ -14,13 +14,42 @@ class SearchController < ApplicationController
14 14
15 include SearchHelper 15 include SearchHelper
16 16
  17 + ######################################################
  18 +
  19 + class Finder
  20 + attr_reader :environment
  21 + def initialize(env)
  22 + @environment = env
  23 + end
  24 +
  25 + def articles
  26 + environment.articles
  27 + end
  28 +
  29 + def comments
  30 + environment.comments
  31 + end
  32 + end
  33 +
17 def index 34 def index
18 @query = params[:query] || '' 35 @query = params[:query] || ''
19 @filtered_query = remove_stop_words(@query) 36 @filtered_query = remove_stop_words(@query)
20 - @articles, @people, @enterprises, @communities, @products =  
21 - [Article, Person, Enterprise, Community, Product].map{ |klass| search(klass, @query) } 37 +
  38 + @finder ||= SearchController::Finder.new(@environment)
  39 +
  40 + @results = { :articles => search(@finder.articles, @query),
  41 + :comments => search(@finder.comments, @query) }
22 end 42 end
23 43
  44 + before_filter :load_category, :only => :filter
  45 + def filter
  46 + @finder = @category
  47 + index
  48 + render :action => 'index'
  49 + end
  50 +
  51 + #######################################################
  52 +
24 def tags 53 def tags
25 @tags = Tag.find(:all).inject({}) do |memo,tag| 54 @tags = Tag.find(:all).inject({}) do |memo,tag|
26 memo[tag.name] = tag.taggings.count 55 memo[tag.name] = tag.taggings.count
@@ -33,6 +62,8 @@ class SearchController < ApplicationController @@ -33,6 +62,8 @@ class SearchController < ApplicationController
33 @tagged = @tag.taggings.map(&:taggable) 62 @tagged = @tag.taggings.map(&:taggable)
34 end 63 end
35 64
  65 + #######################################################
  66 +
36 def popup 67 def popup
37 render :action => 'popup', :layout => false 68 render :action => 'popup', :layout => false
38 end 69 end
app/models/comment.rb
1 class Comment < ActiveRecord::Base 1 class Comment < ActiveRecord::Base
  2 +
  3 + acts_as_searchable :fields => [:title, :body]
  4 +
2 validates_presence_of :title, :body 5 validates_presence_of :title, :body
3 belongs_to :article, :counter_cache => true 6 belongs_to :article, :counter_cache => true
4 belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id' 7 belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id'
app/models/environment.rb
@@ -211,4 +211,7 @@ class Environment &lt; ActiveRecord::Base @@ -211,4 +211,7 @@ class Environment &lt; ActiveRecord::Base
211 self.articles.recent(limit) 211 self.articles.recent(limit)
212 end 212 end
213 213
  214 + # FIXME is this the better/faster way to do this?
  215 + has_many :comments, :finder_sql => 'select comments.* from comments left join articles on articles.id = comments.article_id left join profiles on profiles.id = articles.profile_id where profiles.environment_id = #{id}'
  216 +
214 end 217 end
config/environment.rb
@@ -104,3 +104,54 @@ lambda do |localconfig| @@ -104,3 +104,54 @@ lambda do |localconfig|
104 require localconfig 104 require localconfig
105 end 105 end
106 end.call(File.join(RAILS_ROOT, 'config', 'local.rb')) 106 end.call(File.join(RAILS_ROOT, 'config', 'local.rb'))
  107 +# These defaults are used in GeoKit::Mappable.distance_to and in acts_as_mappable
  108 +GeoKit::default_units = :miles
  109 +GeoKit::default_formula = :sphere
  110 +
  111 +# This is the timeout value in seconds to be used for calls to the geocoder web
  112 +# services. For no timeout at all, comment out the setting. The timeout unit
  113 +# is in seconds.
  114 +GeoKit::Geocoders::timeout = 3
  115 +
  116 +# These settings are used if web service calls must be routed through a proxy.
  117 +# These setting can be nil if not needed, otherwise, addr and port must be
  118 +# filled in at a minimum. If the proxy requires authentication, the username
  119 +# and password can be provided as well.
  120 +GeoKit::Geocoders::proxy_addr = nil
  121 +GeoKit::Geocoders::proxy_port = nil
  122 +GeoKit::Geocoders::proxy_user = nil
  123 +GeoKit::Geocoders::proxy_pass = nil
  124 +
  125 +# This is your yahoo application key for the Yahoo Geocoder.
  126 +# See http://developer.yahoo.com/faq/index.html#appid
  127 +# and http://developer.yahoo.com/maps/rest/V1/geocode.html
  128 +GeoKit::Geocoders::yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
  129 +
  130 +# This is your Google Maps geocoder key.
  131 +# See http://www.google.com/apis/maps/signup.html
  132 +# and http://www.google.com/apis/maps/documentation/#Geocoding_Examples
  133 +GeoKit::Geocoders::google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
  134 +
  135 +# This is your username and password for geocoder.us.
  136 +# To use the free service, the value can be set to nil or false. For
  137 +# usage tied to an account, the value should be set to username:password.
  138 +# See http://geocoder.us
  139 +# and http://geocoder.us/user/signup
  140 +GeoKit::Geocoders::geocoder_us = false
  141 +
  142 +# This is your authorization key for geocoder.ca.
  143 +# To use the free service, the value can be set to nil or false. For
  144 +# usage tied to an account, set the value to the key obtained from
  145 +# Geocoder.ca.
  146 +# See http://geocoder.ca
  147 +# and http://geocoder.ca/?register=1
  148 +GeoKit::Geocoders::geocoder_ca = false
  149 +
  150 +# This is the order in which the geocoders are called in a failover scenario
  151 +# If you only want to use a single geocoder, put a single symbol in the array.
  152 +# Valid symbols are :google, :yahoo, :us, and :ca.
  153 +# Be aware that there are Terms of Use restrictions on how you can use the
  154 +# various geocoders. Make sure you read up on relevant Terms of Use for each
  155 +# geocoder you are going to use.
  156 +GeoKit::Geocoders::provider_order = [:google,:us]
  157 +
config/routes.rb
@@ -31,11 +31,12 @@ ActionController::Routing::Routes.draw do |map| @@ -31,11 +31,12 @@ ActionController::Routing::Routes.draw do |map|
31 map.tag 'tag/:tag', :controller => 'search', :action => 'tag' 31 map.tag 'tag/:tag', :controller => 'search', :action => 'tag'
32 32
33 # search 33 # search
  34 + map.connect 'search/filter/*category_path', :controller => 'search', :action => 'filter'
34 map.connect 'search/:action', :controller => 'search' 35 map.connect 'search/:action', :controller => 'search'
35 36
36 # categories controller 37 # categories controller
37 map.connect 'cat', :controller => 'category', :action => 'index' 38 map.connect 'cat', :controller => 'category', :action => 'index'
38 - map.category 'cat/*path', :controller => 'category', :action => 'view' 39 + map.category 'cat/*category_path', :controller => 'category', :action => 'view'
39 40
40 # controllers for blocks 41 # controllers for blocks
41 map.controllers 'block/:profile/:controller/:action/:id', :controller => Noosfero.pattern_for_controllers_from_design_blocks 42 map.controllers 'block/:profile/:controller/:action/:id', :controller => Noosfero.pattern_for_controllers_from_design_blocks
test/functional/search_controller_test.rb
@@ -11,15 +11,6 @@ class SearchControllerTest &lt; Test::Unit::TestCase @@ -11,15 +11,6 @@ class SearchControllerTest &lt; Test::Unit::TestCase
11 @response = ActionController::TestResponse.new 11 @response = ActionController::TestResponse.new
12 end 12 end
13 13
14 - should 'find enterprise' do  
15 - ent = Enterprise.create!(:name => 'teste', :identifier => 'teste')  
16 - get 'index', :query => 'teste'  
17 - assert_response :success  
18 - assert_template 'index'  
19 - assert assigns('enterprises')  
20 - assert assigns('enterprises').include?(ent)  
21 - end  
22 -  
23 should 'filter stop words' do 14 should 'filter stop words' do
24 @controller.expects(:locale).returns('pt_BR').at_least_once 15 @controller.expects(:locale).returns('pt_BR').at_least_once
25 get 'index', :query => 'a carne da vaca' 16 get 'index', :query => 'a carne da vaca'
@@ -28,4 +19,84 @@ class SearchControllerTest &lt; Test::Unit::TestCase @@ -28,4 +19,84 @@ class SearchControllerTest &lt; Test::Unit::TestCase
28 assert_equal 'carne vaca', assigns('filtered_query') 19 assert_equal 'carne vaca', assigns('filtered_query')
29 end 20 end
30 21
  22 + should 'search only in specified types of content' do
  23 + get :index, :query => 'something not important', :find_in => [ 'articles' ]
  24 + assert_equal [:articles], assigns(:results).keys
  25 + end
  26 +
  27 + should 'search in more than one specified types of content' do
  28 + get :index, :query => 'something not important', :find_in => [ 'articles', 'comments' ]
  29 + assert_equivalent [:articles, :comments ], assigns(:results).keys
  30 + end
  31 +
  32 + should 'render success in search' do
  33 + get :index, :query => 'something not important'
  34 + assert_response :success
  35 + end
  36 +
  37 + should 'search for articles' do
  38 + person = create_user('teste').person
  39 + art = person.articles.build(:name => 'an article to be found'); art.save!
  40 +
  41 + get 'index', :query => 'article found', :find_in => [ 'articles' ]
  42 +
  43 + assert_includes assigns(:results)[:articles], art
  44 + end
  45 +
  46 + should 'search for articles in a specific category' do
  47 + person = create_user('teste').person
  48 + category = Category.create!(:name => 'my category', :environment => Environment.default)
  49 +
  50 + # in category
  51 + art1 = person.articles.build(:name => 'an article to be found')
  52 + art1.categories << category
  53 + art1.save!
  54 +
  55 + # not in category
  56 + art2 = person.articles.build(:name => 'another article to be found')
  57 + art2.save!
  58 +
  59 + get :filter, :category_path => [ 'my-category' ], :query => 'article found', :find_in => [ 'articles' ]
  60 +
  61 + assert_includes assigns(:results)[:articles], art1
  62 + assert_not_includes assigns(:results)[:articles], art2
  63 + end
  64 +
  65 + should 'search in comments' do
  66 + person = create_user('teste').person
  67 + art = person.articles.build(:name => 'an article to be found'); art.save!
  68 + comment = art.comments.build(:title => 'comment to be found', :body => 'hfyfyh', :author => person); comment.save!
  69 + get 'index', :query => 'found', :find_in => [ 'comments' ]
  70 +
  71 + assert_includes assigns(:results)[:comments], comment
  72 + end
  73 +
  74 + should 'search in comments in a specific category'
  75 +
  76 +
  77 + should 'find in environment' do
  78 + env = mock
  79 + finder = SearchController::Finder.new(env)
  80 + assert_same env, finder.environment
  81 + end
  82 +
  83 + should 'delegate to environment in default finder' do
  84 + env = mock
  85 + articles = mock
  86 + finder = SearchController::Finder.new(env)
  87 + env.expects(:articles).returns(articles)
  88 + assert_same articles, finder.articles
  89 + end
  90 +
  91 + should 'find people'
  92 + should 'find communities'
  93 +
  94 + should 'find enterprises' do
  95 + ent = Enterprise.create!(:name => 'teste', :identifier => 'teste')
  96 + get 'index', :query => 'teste'
  97 + assert_includes assigns(:results)[:enterprises], ent
  98 + end
  99 +
  100 + should 'find products'
  101 +
31 end 102 end
test/integration/routing_test.rb
@@ -69,7 +69,7 @@ class RoutingTest &lt; ActionController::IntegrationTest @@ -69,7 +69,7 @@ class RoutingTest &lt; ActionController::IntegrationTest
69 end 69 end
70 70
71 def test_category_browser 71 def test_category_browser
72 - assert_routing('/cat/products/eletronics', :controller => 'category', :action => 'view', :path => [ 'products', 'eletronics']) 72 + assert_routing('/cat/products/eletronics', :controller => 'category', :action => 'view', :category_path => [ 'products', 'eletronics'])
73 assert_routing('/cat', :controller => 'category', :action => 'index') 73 assert_routing('/cat', :controller => 'category', :action => 'index')
74 end 74 end
75 75
@@ -91,4 +91,12 @@ class RoutingTest &lt; ActionController::IntegrationTest @@ -91,4 +91,12 @@ class RoutingTest &lt; ActionController::IntegrationTest
91 assert_routing('/profile/ze/friends', :controller => 'profile', :profile => 'ze', :action => 'friends') 91 assert_routing('/profile/ze/friends', :controller => 'profile', :profile => 'ze', :action => 'friends')
92 end 92 end
93 93
  94 + def test_search_routing
  95 + assert_routing('/search', :controller => 'search', :action => 'index')
  96 + end
  97 +
  98 + def test_search_filter_routing
  99 + assert_routing('/search/filter/a/b', :controller => 'search', :action => 'filter', :category_path => ['a','b'])
  100 + end
  101 +
94 end 102 end
test/unit/comment_test.rb
@@ -93,4 +93,12 @@ class CommentTest &lt; Test::Unit::TestCase @@ -93,4 +93,12 @@ class CommentTest &lt; Test::Unit::TestCase
93 assert_equal 'comment-4321', comment.anchor 93 assert_equal 'comment-4321', comment.anchor
94 end 94 end
95 95
  96 + should 'be searched by contents' do
  97 + owner = create_user('testuser').person
  98 + art = owner.articles.build(:name => 'ytest'); art.save!
  99 + c1 = art.comments.build(:title => 'test comment', :body => 'anything', :author => owner); c1.save!
  100 +
  101 + assert_includes Comment.find_by_contents('test'), c1
  102 + end
  103 +
96 end 104 end
test/unit/environment_test.rb
@@ -263,4 +263,18 @@ class EnvironmentTest &lt; Test::Unit::TestCase @@ -263,4 +263,18 @@ class EnvironmentTest &lt; Test::Unit::TestCase
263 assert_kind_of Role, Environment::Roles.admin 263 assert_kind_of Role, Environment::Roles.admin
264 end 264 end
265 265
  266 + should 'have comments on the articles' do
  267 + environment = Environment.default
  268 +
  269 + p1 = create_user('testuser').person
  270 +
  271 + doc1 = p1.articles.build(:name => 'text 1'); doc1.save!
  272 + doc2 = p1.articles.build(:name => 'text 2'); doc2.save!
  273 +
  274 + c1 = doc1.comments.build(:title => 'a comment', :body => 'bli', :author => p1); c1.save!
  275 + c2 = doc2.comments.build(:title => 'a comment', :body => 'bli', :author => p1); c2.save!
  276 +
  277 + assert_equivalent [c1,c2], environment.comments
  278 + end
  279 +
266 end 280 end