Commit 12b82c088c310e5100cd5c7861709b019da19b97

Authored by Antonio Terceiro
1 parent c892d918

Added RSS feed per tag

* Modularized RSS link in head code; added add_rss_feed_to_head helper
    method
* Modularized RSS generation in FeedWriter class
  + Refactored RssFeed to use FeedWriter
  + removed feed_item_description (this fixes ActionItem1463)

(ActionItem1518)
app/controllers/public/profile_controller.rb
@@ -29,6 +29,19 @@ class ProfileController < PublicController @@ -29,6 +29,19 @@ class ProfileController < PublicController
29 end 29 end
30 end 30 end
31 31
  32 + def tag_feed
  33 + @tag = params[:id]
  34 + tagged = profile.find_tagged_with(@tag).paginate(:per_page => 20, :page => 1)
  35 + feed_writer = FeedWriter.new
  36 + data = feed_writer.write(
  37 + tagged,
  38 + :title => _("%s's contents tagged with \"%s\"") % [profile.name, @tag],
  39 + :description => _("%s's contents tagged with \"%s\"") % [profile.name, @tag],
  40 + :link => url_for(:action => 'tag')
  41 + )
  42 + render :text => data, :content_type => "text/xml"
  43 + end
  44 +
32 def communities 45 def communities
33 if is_cache_expired?(profile.communities_cache_key(params)) 46 if is_cache_expired?(profile.communities_cache_key(params))
34 @communities = profile.communities.paginate(:per_page => per_page, :page => params[:npage]) 47 @communities = profile.communities.paginate(:per_page => per_page, :page => params[:npage])
app/helpers/application_helper.rb
@@ -869,12 +869,9 @@ module ApplicationHelper @@ -869,12 +869,9 @@ module ApplicationHelper
869 article_helper.cms_label_for_edit 869 article_helper.cms_label_for_edit
870 end 870 end
871 871
872 - def meta_tags_for_article(article)  
873 - if @controller.controller_name == 'content_viewer'  
874 - if article and (article.blog? or (article.parent and article.parent.blog?))  
875 - blog = article.blog? ? article : article.parent  
876 - "<link rel='alternate' type='application/rss+xml' title='#{blog.feed.title}' href='#{url_for blog.feed.url}' />"  
877 - end 872 + def add_rss_feed_to_head(title, url)
  873 + content_for :feeds do
  874 + "<link rel='alternate' type='application/rss+xml' title='#{h(title)}' href='#{url_for(url)}' />"
878 end 875 end
879 end 876 end
880 877
app/models/blog.rb
@@ -6,7 +6,7 @@ class Blog &lt; Folder @@ -6,7 +6,7 @@ class Blog &lt; Folder
6 attr_accessor :filter 6 attr_accessor :filter
7 7
8 after_create do |blog| 8 after_create do |blog|
9 - blog.children << RssFeed.new(:name => 'feed', :profile => blog.profile, :feed_item_description => 'body') 9 + blog.children << RssFeed.new(:name => 'feed', :profile => blog.profile)
10 blog.feed = blog.feed_attrs 10 blog.feed = blog.feed_attrs
11 end 11 end
12 12
app/models/rss_feed.rb
@@ -41,16 +41,6 @@ class RssFeed &lt; Article @@ -41,16 +41,6 @@ class RssFeed &lt; Article
41 end 41 end
42 validates_inclusion_of :include, :in => [ 'all', 'parent_and_children' ], :if => :include 42 validates_inclusion_of :include, :in => [ 'all', 'parent_and_children' ], :if => :include
43 43
44 - # determinates what to include in the feed as items' description. Possible  
45 - # values are +:body+ (default) and +:abstract+.  
46 - def feed_item_description  
47 - settings[:feed_item_description]  
48 - end  
49 - def feed_item_description=(value)  
50 - settings[:feed_item_description] = value  
51 - end  
52 - validates_inclusion_of :feed_item_description, :in => [ 'body', 'abstract' ], :if => :feed_item_description  
53 -  
54 # TODO 44 # TODO
55 def to_html(options = {}) 45 def to_html(options = {})
56 end 46 end
@@ -74,38 +64,13 @@ class RssFeed &lt; Article @@ -74,38 +64,13 @@ class RssFeed &lt; Article
74 end 64 end
75 end 65 end
76 def data 66 def data
77 - articles = fetch_articles  
78 - result = ""  
79 - xml = Builder::XmlMarkup.new(:target => result)  
80 -  
81 - xml.instruct! :xml, :version=>"1.0"  
82 - xml.rss(:version=>"2.0") do  
83 - xml.channel do  
84 - xml.title(_("%s's RSS feed") % (self.profile.name))  
85 - xml.link(url_for(self.profile.url))  
86 - xml.description(_("%s's content published at %s") % [self.profile.name, self.profile.environment.name])  
87 - xml.language("pt_BR")  
88 - for article in articles  
89 - unless self == article  
90 - xml.item do  
91 - xml.title(article.name)  
92 - if self.feed_item_description == 'body'  
93 - xml.description(article.to_html)  
94 - else  
95 - xml.description(article.abstract)  
96 - end  
97 - # rfc822  
98 - xml.pubDate(article.created_at.rfc2822)  
99 - # link to article  
100 - xml.link(url_for(article.url))  
101 - xml.guid(url_for(article.url))  
102 - end  
103 - end  
104 - end  
105 - end  
106 - end  
107 -  
108 - result 67 + articles = fetch_articles.select { |a| a != self }
  68 + FeedWriter.new.write(
  69 + articles,
  70 + :title => _("%s's RSS feed") % (self.profile.name),
  71 + :description => _("%s's content published at %s") % [self.profile.name, self.profile.environment.name],
  72 + :link => url_for(self.profile.url)
  73 + )
109 end 74 end
110 75
111 def self.short_description 76 def self.short_description
app/views/cms/_blog.rhtml
@@ -56,7 +56,6 @@ @@ -56,7 +56,6 @@
56 56
57 <% f.fields_for 'feed', @article.feed do |feed| %> 57 <% f.fields_for 'feed', @article.feed do |feed| %>
58 <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, [5, 10, 20, 50])) %> 58 <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, [5, 10, 20, 50])) %>
59 - <%= labelled_form_field(_('Use as description in RSS Feed:'), feed.select(:feed_item_description, [ [ _('Article body'), 'body'], [ _('Article abstract'), 'abstract'] ])) %>  
60 <% end %> 59 <% end %>
61 60
62 <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %> 61 <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %>
app/views/content_viewer/blog_page.rhtml
  1 +<% add_rss_feed_to_head(article.name, article.feed.url) if article.blog? && article.feed %>
  2 +
1 <%= content_tag('em', _('(external feed was not loaded yet)'), :id => 'external-feed-info', :class => 'metadata') if article.blog? && article.external_feed && article.external_feed.enabled && article.external_feed.fetched_at.nil? %> 3 <%= content_tag('em', _('(external feed was not loaded yet)'), :id => 'external-feed-info', :class => 'metadata') if article.blog? && article.external_feed && article.external_feed.enabled && article.external_feed.fetched_at.nil? %>
2 4
3 <div> 5 <div>
app/views/content_viewer/view_page.rhtml
  1 +<%
  2 + if @page.parent && @page.parent.blog? && @page.parent.feed
  3 + add_rss_feed_to_head(@page.parent.name, @page.parent.feed.url)
  4 + end
  5 +%>
  6 +
1 <div id="article"> 7 <div id="article">
2 8
3 <% 9 <%
app/views/layouts/application-ng.rhtml
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%= html_language %>" lang="<%= html_language %>"> 2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%= html_language %>" lang="<%= html_language %>">
3 <head> 3 <head>
4 <title><%= h page_title %></title> 4 <title><%= h page_title %></title>
5 - <%= meta_tags_for_article(@page) %> 5 + <%= yield(:feeds) %>
6 <!--<meta http-equiv="refresh" content="1"/>--> 6 <!--<meta http-equiv="refresh" content="1"/>-->
7 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> 7 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
8 <meta name="description" content="<%= @environment.name %>" /> 8 <meta name="description" content="<%= @environment.name %>" />
app/views/layouts/application.rhtml
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 <meta name="description" content="<%= @environment.name %>" /> 6 <meta name="description" content="<%= @environment.name %>" />
7 <meta name="keywords" content="Noosfero, Community, Open Source" /> 7 <meta name="keywords" content="Noosfero, Community, Open Source" />
8 <link rel="shortcut icon" href="<%= image_path('/designs/themes/' + current_theme + '/favicon.ico') %>" type="image/x-icon" /> 8 <link rel="shortcut icon" href="<%= image_path('/designs/themes/' + current_theme + '/favicon.ico') %>" type="image/x-icon" />
9 - <!-- ok --><%= meta_tags_for_article(@page) %> 9 + <%= yield(:feeds) %>
10 10
11 <%= noosfero_javascript %> 11 <%= noosfero_javascript %>
12 <%= javascript_include_tag 'menu', 'auto-open-menu', 'menu-config', :cache => 'cache-menu' %> 12 <%= javascript_include_tag 'menu', 'auto-open-menu', 'menu-config', :cache => 'cache-menu' %>
app/views/profile/tag.rhtml
  1 +<% add_rss_feed_to_head(_("%s's contents tagged with \"%s\"") % [profile.name, @tag], tag_feed_path) %>
  2 +
1 <h1><%= _('Content tagged with "%s"') % @tag %></h1> 3 <h1><%= _('Content tagged with "%s"') % @tag %></h1>
2 4
  5 +<p>
  6 +<%= link_to image_tag('icons-mime/rss-feed.png', :alt => _('Feed for this tag'), :title => _('Feed for this tag')), tag_feed_path, :class => 'blog-feed-link'%>
  7 +</p>
  8 +
3 <% cache_timeout(@tag_cache_key, 4.hour.from_now) do %> 9 <% cache_timeout(@tag_cache_key, 4.hour.from_now) do %>
4 <div class='search-tagged-items'> 10 <div class='search-tagged-items'>
5 <ul> 11 <ul>
config/routes.rb
@@ -65,6 +65,9 @@ ActionController::Routing::Routes.draw do |map| @@ -65,6 +65,9 @@ ActionController::Routing::Routes.draw do |map|
65 # invite 65 # invite
66 map.invite 'profile/:profile/invite/:action', :controller => 'invite', :profile => /#{Noosfero.identifier_format}/ 66 map.invite 'profile/:profile/invite/:action', :controller => 'invite', :profile => /#{Noosfero.identifier_format}/
67 67
  68 + # feeds per tag
  69 + map.tag_feed 'profile/:profile/tag/:id/feed', :controller => 'profile', :action =>'tag_feed', :id => /.*/, :profile => /#{Noosfero.identifier_format}/
  70 +
68 # public profile information 71 # public profile information
69 map.profile 'profile/:profile/:action/:id', :controller => 'profile', :action => 'index', :id => /.*/, :profile => /#{Noosfero.identifier_format}/ 72 map.profile 'profile/:profile/:action/:id', :controller => 'profile', :action => 'index', :id => /.*/, :profile => /#{Noosfero.identifier_format}/
70 73
features/profile_tags.feature 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +Feature: profile tags
  2 + As a Noosfero user
  3 + I want to to view content tagged
  4 + So that I can follow the subjects I care about
  5 +
  6 + Background:
  7 + Given the following users
  8 + | login |
  9 + | terceiro |
  10 + And the following articles
  11 + | owner | name | body | tag_list |
  12 + | terceiro | text 1 | text 1 content | tag1, tag2 |
  13 + | terceiro | text 2 | text 2 content | tag1, tag3 |
  14 +
  15 + Scenario: tag feed
  16 + When I go to terceiro's profile
  17 + And I follow "tag1"
  18 + And I follow "Feed for this tag"
  19 + Then I should see "text 1"
  20 + And I should see "text 2"
features/support/paths.rb
@@ -30,6 +30,9 @@ module NavigationHelpers @@ -30,6 +30,9 @@ module NavigationHelpers
30 when /^(.*)'s sitemap/ 30 when /^(.*)'s sitemap/
31 '/profile/%s/sitemap' % Profile.find_by_name($1).identifier 31 '/profile/%s/sitemap' % Profile.find_by_name($1).identifier
32 32
  33 + when /^(.*)'s profile/
  34 + '/profile/%s' % Profile.find_by_name($1).identifier
  35 +
33 when /^login page$/ 36 when /^login page$/
34 '/account/login' 37 '/account/login'
35 38
lib/feed_writer.rb 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +class FeedWriter
  2 +
  3 + include ActionController::UrlWriter
  4 +
  5 + def write(articles, options = {})
  6 + result = ""
  7 + xml = Builder::XmlMarkup.new(:target => result)
  8 +
  9 + xml.instruct! :xml, :version=>"1.0"
  10 + xml.rss(:version=>"2.0") do
  11 + xml.channel do
  12 + xml.title(options[:title] || _('Feed'))
  13 + if options[:link]
  14 + xml.link(options[:link])
  15 + end
  16 + if options[:description]
  17 + xml.description(options[:description])
  18 + end
  19 + for article in articles
  20 + xml.item do
  21 + xml.title(article.name)
  22 + xml.description(article.to_html)
  23 + if article.created_at
  24 + # rfc822
  25 + xml.pubDate(article.created_at.rfc2822)
  26 + end
  27 + # link to article
  28 + xml.link(url_for(article.url))
  29 + xml.guid(url_for(article.url))
  30 + end
  31 + end
  32 + end
  33 + end
  34 +
  35 + result
  36 + end
  37 +end
test/unit/blog_test.rb
@@ -32,12 +32,6 @@ class BlogTest &lt; ActiveSupport::TestCase @@ -32,12 +32,6 @@ class BlogTest &lt; ActiveSupport::TestCase
32 assert_kind_of RssFeed, b.feed 32 assert_kind_of RssFeed, b.feed
33 end 33 end
34 34
35 - should 'include articles body in feed by default' do  
36 - p = create_user('testuser').person  
37 - b = Blog.create!(:profile => p, :name => 'blog_feed_test')  
38 - assert_equal 'body', b.feed.feed_item_description  
39 - end  
40 -  
41 should 'save feed options' do 35 should 'save feed options' do
42 p = create_user('testuser').person 36 p = create_user('testuser').person
43 p.articles << Blog.new(:profile => p, :name => 'blog_feed_test') 37 p.articles << Blog.new(:profile => p, :name => 'blog_feed_test')
test/unit/feed_writer_test.rb 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class FeedWriterTest < Test::Unit::TestCase
  4 +
  5 + should 'generate feeds' do
  6 + articles = []
  7 + profile = fast_create(:profile, :identifier => "tagger")
  8 + articles << fast_create(:article, :name => 'text 1', :slug => 'text-1', :path => 'text-1', :profile_id => profile.id)
  9 + articles << fast_create(:article, :name => 'text 2', :slug => 'text-2', :path => 'text-2', :profile_id => profile.id)
  10 + writer = FeedWriter.new
  11 +
  12 + feed = writer.write(articles)
  13 + assert_match('text 1', feed)
  14 + assert_match('/tagger/' + articles.first.slug, feed)
  15 + end
  16 +
  17 + should 'use title, link and description' do
  18 + writer = FeedWriter.new
  19 + rss = writer.write([], :title => "my title", :description => "my description", :link => "http://example.com/")
  20 + assert_match("my title", rss)
  21 + assert_match("my description", rss)
  22 + assert_match("http://example.com/", rss)
  23 + end
  24 +
  25 +end
  26 +
test/unit/rss_feed_test.rb
@@ -62,24 +62,6 @@ class RssFeedTest &lt; Test::Unit::TestCase @@ -62,24 +62,6 @@ class RssFeedTest &lt; Test::Unit::TestCase
62 feed.data 62 feed.data
63 end 63 end
64 64
65 - should 'be able to choose to put abstract or entire body on feed' do  
66 - profile = create_user('testuser').person  
67 - a1 = profile.articles.build(:name => 'article 1', 'abstract' => 'my abstract', 'body' => 'my text'); a1.save!  
68 -  
69 - feed = RssFeed.new(:name => 'testfeed')  
70 - feed.profile = profile  
71 - feed.save!  
72 -  
73 - rss = feed.data  
74 - assert_match /<description>my abstract<\/description>/, rss  
75 - assert_no_match /<description>my text<\/description>/, rss  
76 -  
77 - feed.feed_item_description = 'body'  
78 - rss = feed.data  
79 - assert_match /<description>my text<\/description>/, rss  
80 - assert_no_match /<description>my abstract<\/description>/, rss  
81 - end  
82 -  
83 should "be able to search only children of feed's parent" do 65 should "be able to search only children of feed's parent" do
84 profile = create_user('testuser').person 66 profile = create_user('testuser').person
85 a1 = profile.articles.build(:name => 'article 1'); a1.save! 67 a1 = profile.articles.build(:name => 'article 1'); a1.save!
@@ -201,21 +183,6 @@ class RssFeedTest &lt; Test::Unit::TestCase @@ -201,21 +183,6 @@ class RssFeedTest &lt; Test::Unit::TestCase
201 assert !feed.errors.invalid?(:include) 183 assert !feed.errors.invalid?(:include)
202 end 184 end
203 185
204 - should 'allow only body and abstract as feed_item_description' do  
205 - feed = RssFeed.new  
206 - feed.feed_item_description = :something_else  
207 - feed.valid?  
208 - assert feed.errors.invalid?(:feed_item_description)  
209 -  
210 - feed.feed_item_description = 'body'  
211 - feed.valid?  
212 - assert !feed.errors.invalid?(:feed_item_description)  
213 -  
214 - feed.feed_item_description = 'abstract'  
215 - feed.valid?  
216 - assert !feed.errors.invalid?(:feed_item_description)  
217 - end  
218 -  
219 should 'provide proper short description' do 186 should 'provide proper short description' do
220 assert_kind_of String, RssFeed.short_description 187 assert_kind_of String, RssFeed.short_description
221 end 188 end
@@ -246,7 +213,7 @@ class RssFeedTest &lt; Test::Unit::TestCase @@ -246,7 +213,7 @@ class RssFeedTest &lt; Test::Unit::TestCase
246 blog = fast_create(Blog, :profile_id => profile.id) 213 blog = fast_create(Blog, :profile_id => profile.id)
247 published_article = PublishedArticle.create!(:reference_article => article, :profile => profile) 214 published_article = PublishedArticle.create!(:reference_article => article, :profile => profile)
248 blog.posts << published_article 215 blog.posts << published_article
249 - feed = RssFeed.new(:parent => blog, :profile => profile, :feed_item_description => 'body') 216 + feed = RssFeed.new(:parent => blog, :profile => profile)
250 217
251 assert_match published_article.to_html, feed.data 218 assert_match published_article.to_html, feed.data
252 end 219 end