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 102 end
103 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 114 end
... ...
app/controllers/public/category_controller.rb
... ... @@ -11,12 +11,5 @@ class CategoryController < PublicController
11 11 attr_reader :category
12 12  
13 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 15 end
... ...
app/controllers/public/search_controller.rb
... ... @@ -4,8 +4,8 @@ class SearchController < ApplicationController
4 4  
5 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 9 -(relevance_for(hit))
10 10 end
11 11 end
... ... @@ -14,13 +14,42 @@ class SearchController < ApplicationController
14 14  
15 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 34 def index
18 35 @query = params[:query] || ''
19 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 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 53 def tags
25 54 @tags = Tag.find(:all).inject({}) do |memo,tag|
26 55 memo[tag.name] = tag.taggings.count
... ... @@ -33,6 +62,8 @@ class SearchController < ApplicationController
33 62 @tagged = @tag.taggings.map(&:taggable)
34 63 end
35 64  
  65 + #######################################################
  66 +
36 67 def popup
37 68 render :action => 'popup', :layout => false
38 69 end
... ...
app/models/comment.rb
1 1 class Comment < ActiveRecord::Base
  2 +
  3 + acts_as_searchable :fields => [:title, :body]
  4 +
2 5 validates_presence_of :title, :body
3 6 belongs_to :article, :counter_cache => true
4 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 211 self.articles.recent(limit)
212 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 217 end
... ...
config/environment.rb
... ... @@ -104,3 +104,54 @@ lambda do |localconfig|
104 104 require localconfig
105 105 end
106 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 31 map.tag 'tag/:tag', :controller => 'search', :action => 'tag'
32 32  
33 33 # search
  34 + map.connect 'search/filter/*category_path', :controller => 'search', :action => 'filter'
34 35 map.connect 'search/:action', :controller => 'search'
35 36  
36 37 # categories controller
37 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 41 # controllers for blocks
41 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 11 @response = ActionController::TestResponse.new
12 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 14 should 'filter stop words' do
24 15 @controller.expects(:locale).returns('pt_BR').at_least_once
25 16 get 'index', :query => 'a carne da vaca'
... ... @@ -28,4 +19,84 @@ class SearchControllerTest &lt; Test::Unit::TestCase
28 19 assert_equal 'carne vaca', assigns('filtered_query')
29 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 102 end
... ...
test/integration/routing_test.rb
... ... @@ -69,7 +69,7 @@ class RoutingTest &lt; ActionController::IntegrationTest
69 69 end
70 70  
71 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 73 assert_routing('/cat', :controller => 'category', :action => 'index')
74 74 end
75 75  
... ... @@ -91,4 +91,12 @@ class RoutingTest &lt; ActionController::IntegrationTest
91 91 assert_routing('/profile/ze/friends', :controller => 'profile', :profile => 'ze', :action => 'friends')
92 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 102 end
... ...
test/unit/comment_test.rb
... ... @@ -93,4 +93,12 @@ class CommentTest &lt; Test::Unit::TestCase
93 93 assert_equal 'comment-4321', comment.anchor
94 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 104 end
... ...
test/unit/environment_test.rb
... ... @@ -263,4 +263,18 @@ class EnvironmentTest &lt; Test::Unit::TestCase
263 263 assert_kind_of Role, Environment::Roles.admin
264 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 280 end
... ...