Commit b1ac13770d65f52d2cd22b0e973c1e8be1299975
1 parent
b67c05a5
Exists in
staging
and in
42 other branches
ActionItem243: reimplemented search to allow be filtered
git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@1590 3f533792-8f58-4932-b0fe-aaf55b0a4547
Showing
11 changed files
with
214 additions
and
22 deletions
Show diff stats
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 < ActiveRecord::Base | @@ -211,4 +211,7 @@ class Environment < 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 < Test::Unit::TestCase | @@ -11,15 +11,6 @@ class SearchControllerTest < 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 < Test::Unit::TestCase | @@ -28,4 +19,84 @@ class SearchControllerTest < 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 < ActionController::IntegrationTest | @@ -69,7 +69,7 @@ class RoutingTest < 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 < ActionController::IntegrationTest | @@ -91,4 +91,12 @@ class RoutingTest < 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 < Test::Unit::TestCase | @@ -93,4 +93,12 @@ class CommentTest < 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 < Test::Unit::TestCase | @@ -263,4 +263,18 @@ class EnvironmentTest < 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 |