Commit 45c0df82cf8ce0fa3d49810da8f225415f7da412

Authored by Marcos Ramos
Committed by Rodrigo Souto
1 parent c95e0983

Add feature: Article Followers

- Every article can be followed so the user can receive notifications without
  having to comment on it

Signed-off-by: Marcos Ronaldo <marcos.rpj2@gmail.com>
Signed-off-by: Marcos Ramos <ms.ramos@outlook.com>
Signed-off-by: Gustavo Coelho <gust.rod.coelho@gmail.com>
Signed-off-by: Joenio Costa <joenio@colivre.coop.br>
Signed-off-by: Carlos Purificacao <carloseugenio@gmail.com>
Signed-off-by: Victor Costa <vfcosta@gmail.com>
Signed-off-by: Caio SBA <caio@colivre.coop.br>
Signed-off-by: Leandro Nunes dos Santos <leandro.santos@serpro.gov.br>
Signed-off-by: Michel Felipe de Oliveira Ferreira <michel.ferreira@serpro.gov.br>
Signed-off-by: Evandro Jr <evandrojr@gmail.com>
app/controllers/public/profile_controller.rb
@@ -155,6 +155,18 @@ class ProfileController &lt; PublicController @@ -155,6 +155,18 @@ class ProfileController &lt; PublicController
155 end 155 end
156 end 156 end
157 157
  158 + def follow_article
  159 + article = profile.environment.articles.find params[:article_id]
  160 + article.person_followers << user
  161 + redirect_to article.url
  162 + end
  163 +
  164 + def unfollow_article
  165 + article = profile.environment.articles.find params[:article_id]
  166 + article.person_followers.delete(user)
  167 + redirect_to article.url
  168 + end
  169 +
158 def unblock 170 def unblock
159 if current_user.person.is_admin?(profile.environment) 171 if current_user.person.is_admin?(profile.environment)
160 profile.unblock 172 profile.unblock
app/helpers/article_helper.rb
@@ -155,4 +155,30 @@ module ArticleHelper @@ -155,4 +155,30 @@ module ArticleHelper
155 _('Edit') 155 _('Edit')
156 end 156 end
157 157
  158 + def follow_button_text(article)
  159 + if article.event?
  160 + _('Attend')
  161 + else
  162 + _('Follow')
  163 + end
  164 + end
  165 +
  166 + def unfollow_button_text(article)
  167 + if article.event?
  168 + _('Unattend')
  169 + else
  170 + _('Unfollow')
  171 + end
  172 + end
  173 +
  174 + def following_button(page, user)
  175 + if !user.blank? and user != page.author
  176 + if page.is_followed_by? user
  177 + button :cancel, unfollow_button_text(page), {:controller => 'profile', :action => 'unfollow_article', :article_id => page.id}
  178 + else
  179 + button :add, follow_button_text(page), {:controller => 'profile', :action => 'follow_article', :article_id => page.id}
  180 + end
  181 + end
  182 + end
  183 +
158 end 184 end
app/models/article.rb
@@ -9,7 +9,7 @@ class Article &lt; ActiveRecord::Base @@ -9,7 +9,7 @@ class Article &lt; ActiveRecord::Base
9 :highlighted, :notify_comments, :display_hits, :slug, 9 :highlighted, :notify_comments, :display_hits, :slug,
10 :external_feed_builder, :display_versions, :external_link, 10 :external_feed_builder, :display_versions, :external_link,
11 :image_builder, :show_to_followers, 11 :image_builder, :show_to_followers,
12 - :author, :display_preview, :published_at 12 + :author, :display_preview, :published_at, :person_followers
13 13
14 acts_as_having_image 14 acts_as_having_image
15 15
@@ -77,8 +77,15 @@ class Article &lt; ActiveRecord::Base @@ -77,8 +77,15 @@ class Article &lt; ActiveRecord::Base
77 belongs_to :last_changed_by, :class_name => 'Person', :foreign_key => 'last_changed_by_id' 77 belongs_to :last_changed_by, :class_name => 'Person', :foreign_key => 'last_changed_by_id'
78 belongs_to :created_by, :class_name => 'Person', :foreign_key => 'created_by_id' 78 belongs_to :created_by, :class_name => 'Person', :foreign_key => 'created_by_id'
79 79
  80 +<<<<<<< 6fa2f904983046ed816efe293ba9dcad19a67a4b
80 has_many :comments, :class_name => 'Comment', :as => 'source', :dependent => :destroy, :order => 'created_at asc' 81 has_many :comments, :class_name => 'Comment', :as => 'source', :dependent => :destroy, :order => 'created_at asc'
81 82
  83 +=======
  84 + has_many :comments, :class_name => 'Comment', :foreign_key => 'source_id', :dependent => :destroy, :order => 'created_at asc'
  85 + has_many :article_followers, :dependent => :destroy
  86 + has_many :person_followers, :class_name => 'Person', :through => :article_followers, :source => :person
  87 + has_many :person_followers_emails, :class_name => 'User', :through => :person_followers, :source => :user, :select => :email
  88 +>>>>>>> Add feature: Article Followers
82 has_many :article_categorizations, -> { where 'articles_categories.virtual = ?', false } 89 has_many :article_categorizations, -> { where 'articles_categories.virtual = ?', false }
83 has_many :categories, :through => :article_categorizations 90 has_many :categories, :through => :article_categorizations
84 91
@@ -91,7 +98,6 @@ class Article &lt; ActiveRecord::Base @@ -91,7 +98,6 @@ class Article &lt; ActiveRecord::Base
91 settings_items :author_name, :type => :string, :default => "" 98 settings_items :author_name, :type => :string, :default => ""
92 settings_items :allow_members_to_edit, :type => :boolean, :default => false 99 settings_items :allow_members_to_edit, :type => :boolean, :default => false
93 settings_items :moderate_comments, :type => :boolean, :default => false 100 settings_items :moderate_comments, :type => :boolean, :default => false
94 - settings_items :followers, :type => Array, :default => []  
95 has_and_belongs_to_many :article_privacy_exceptions, :class_name => 'Person', :join_table => 'article_privacy_exceptions' 101 has_and_belongs_to_many :article_privacy_exceptions, :class_name => 'Person', :join_table => 'article_privacy_exceptions'
96 102
97 belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id' 103 belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id'
@@ -167,7 +173,6 @@ class Article &lt; ActiveRecord::Base @@ -167,7 +173,6 @@ class Article &lt; ActiveRecord::Base
167 end 173 end
168 end 174 end
169 175
170 -  
171 def is_trackable? 176 def is_trackable?
172 self.published? && self.notifiable? && self.advertise? && self.profile.public_profile 177 self.published? && self.notifiable? && self.advertise? && self.profile.public_profile
173 end 178 end
@@ -368,6 +373,10 @@ class Article &lt; ActiveRecord::Base @@ -368,6 +373,10 @@ class Article &lt; ActiveRecord::Base
368 self.parent and self.parent.forum? 373 self.parent and self.parent.forum?
369 end 374 end
370 375
  376 + def person_followers_email_list
  377 + person_followers_emails.map{|p|p.email}
  378 + end
  379 +
371 def info_from_last_update 380 def info_from_last_update
372 last_comment = comments.last 381 last_comment = comments.last
373 if last_comment 382 if last_comment
@@ -407,6 +416,10 @@ class Article &lt; ActiveRecord::Base @@ -407,6 +416,10 @@ class Article &lt; ActiveRecord::Base
407 (not self.uploaded_file? and self.mime_type != 'text/html') 416 (not self.uploaded_file? and self.mime_type != 'text/html')
408 end 417 end
409 418
  419 + def is_followed_by?(user)
  420 + self.person_followers.include? user
  421 + end
  422 +
410 def download_headers 423 def download_headers
411 {} 424 {}
412 end 425 end
app/models/article_follower.rb 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +class ArticleFollower < ActiveRecord::Base
  2 +
  3 + attr_accessible :article_id, :person_id
  4 + belongs_to :article, :counter_cache => :followers_count
  5 + belongs_to :person
  6 +
  7 +end
app/models/comment.rb
@@ -6,13 +6,14 @@ class Comment &lt; ActiveRecord::Base @@ -6,13 +6,14 @@ class Comment &lt; ActiveRecord::Base
6 :body => {:label => _('Content'), :weight => 2}, 6 :body => {:label => _('Content'), :weight => 2},
7 } 7 }
8 8
9 - attr_accessible :body, :author, :name, :email, :title, :reply_of_id, :source 9 + attr_accessible :body, :author, :name, :email, :title, :reply_of_id, :source, :follow_article
10 10
11 validates_presence_of :body 11 validates_presence_of :body
12 12
13 belongs_to :source, :counter_cache => true, :polymorphic => true 13 belongs_to :source, :counter_cache => true, :polymorphic => true
14 alias :article :source 14 alias :article :source
15 alias :article= :source= 15 alias :article= :source=
  16 + attr_accessor :follow_article
16 17
17 belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id' 18 belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id'
18 has_many :children, :class_name => 'Comment', :foreign_key => 'reply_of_id', :dependent => :destroy 19 has_many :children, :class_name => 'Comment', :foreign_key => 'reply_of_id', :dependent => :destroy
@@ -100,10 +101,9 @@ class Comment &lt; ActiveRecord::Base @@ -100,10 +101,9 @@ class Comment &lt; ActiveRecord::Base
100 101
101 after_create :new_follower 102 after_create :new_follower
102 def new_follower 103 def new_follower
103 - if source.kind_of?(Article)  
104 - article.followers += [author_email]  
105 - article.followers -= article.profile.notification_emails  
106 - article.followers.uniq! 104 + if source.kind_of?(Article) and !author.nil? and @follow_article
  105 + article.person_followers += [author]
  106 + article.person_followers.uniq!
107 article.save 107 article.save
108 end 108 end
109 end 109 end
@@ -145,7 +145,7 @@ class Comment &lt; ActiveRecord::Base @@ -145,7 +145,7 @@ class Comment &lt; ActiveRecord::Base
145 if !notification_emails.empty? 145 if !notification_emails.empty?
146 CommentNotifier.notification(self).deliver 146 CommentNotifier.notification(self).deliver
147 end 147 end
148 - emails = article.followers - [author_email] 148 + emails = article.person_followers_email_list - [author_email]
149 if !emails.empty? 149 if !emails.empty?
150 CommentNotifier.mail_to_followers(self, emails).deliver 150 CommentNotifier.mail_to_followers(self, emails).deliver
151 end 151 end
app/models/person.rb
1 # A person is the profile of an user holding all relationships with the rest of the system 1 # A person is the profile of an user holding all relationships with the rest of the system
2 class Person < Profile 2 class Person < Profile
3 3
4 - attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website 4 + attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website, :following_articles
5 5
6 SEARCH_FILTERS = { 6 SEARCH_FILTERS = {
7 :order => %w[more_recent more_popular more_active], 7 :order => %w[more_recent more_popular more_active],
@@ -84,7 +84,8 @@ class Person &lt; Profile @@ -84,7 +84,8 @@ class Person &lt; Profile
84 end 84 end
85 85
86 has_many :comments, :foreign_key => :author_id 86 has_many :comments, :foreign_key => :author_id
87 - 87 + has_many :article_followers, :dependent => :destroy
  88 + has_many :following_articles, :class_name => 'Article', :through => :article_followers, :source => :article
88 has_many :friendships, :dependent => :destroy 89 has_many :friendships, :dependent => :destroy
89 has_many :friends, :class_name => 'Person', :through => :friendships 90 has_many :friends, :class_name => 'Person', :through => :friendships
90 91
app/views/comment/_comment_form.html.erb
@@ -77,6 +77,10 @@ function check_captcha(button, confirm_action) { @@ -77,6 +77,10 @@ function check_captcha(button, confirm_action) {
77 <%= labelled_form_field(_('Title'), f.text_field(:title)) %> 77 <%= labelled_form_field(_('Title'), f.text_field(:title)) %>
78 <%= required labelled_form_field(_('Enter your comment'), f.text_area(:body, :rows => 5)) %> 78 <%= required labelled_form_field(_('Enter your comment'), f.text_area(:body, :rows => 5)) %>
79 79
  80 + <% if logged_in? %>
  81 + <%= labelled_form_field check_box(:comment, :follow_article, {}, true, false) + _('Follow this article'), '' %>
  82 + <% end%>
  83 +
80 <%= hidden_field_tag(:confirm, 'false') %> 84 <%= hidden_field_tag(:confirm, 'false') %>
81 <%= hidden_field_tag(:view, params[:view])%> 85 <%= hidden_field_tag(:view, params[:view])%>
82 <%= f.hidden_field(:reply_of_id) %> 86 <%= f.hidden_field(:reply_of_id) %>
app/views/content_viewer/_article_toolbar.html.erb
@@ -55,6 +55,8 @@ @@ -55,6 +55,8 @@
55 <%= button plugin_button[:icon], plugin_button[:title], plugin_button[:url], plugin_button[:html_options] %> 55 <%= button plugin_button[:icon], plugin_button[:title], plugin_button[:url], plugin_button[:html_options] %>
56 <% end %> 56 <% end %>
57 57
  58 + <%= following_button @page, user %>
  59 +
58 <%= report_abuse(profile, :link, @page) %> 60 <%= report_abuse(profile, :link, @page) %>
59 61
60 </div> 62 </div>
app/views/content_viewer/_publishing_info.html.erb
@@ -10,6 +10,24 @@ @@ -10,6 +10,24 @@
10 <%= (" - %s") % link_to_comments(@page)%> 10 <%= (" - %s") % link_to_comments(@page)%>
11 </span> 11 </span>
12 <% end %> 12 <% end %>
  13 +
  14 +<span class="followers-count">
  15 +|
  16 +<% if @page.event? %>
  17 + <% if @page.person_followers.size > 0 %>
  18 + <%= _("%s will attend this event.") % [ pluralize(@page.person_followers.size, _("person"))]%>
  19 + <% else %>
  20 + <%= _("No one attending this event yet.") %>
  21 + <% end %>
  22 +<% else %>
  23 + <% if @page.person_followers.size > 0 %>
  24 + <%= _("%s following this article.") % [ pluralize(@page.person_followers.size, _("person"))]%>
  25 + <% else %>
  26 + <%= _("No one following this article yet.") %>
  27 + <% end %>
  28 +<% end %>
  29 +</span>
  30 +
13 </span> 31 </span>
14 32
15 <% if @page.display_hits? || @page.license.present? %> 33 <% if @page.display_hits? || @page.license.present? %>
db/migrate/20151210230319_add_followers_count_to_article.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +class AddFollowersCountToArticle < ActiveRecord::Migration
  2 +
  3 + def self.up
  4 + add_column :articles, :followers_count, :integer, :default => 0
  5 +
  6 + execute "update articles set followers_count = (select count(*) from article_followers where article_followers.article_id = articles.id)"
  7 + end
  8 +
  9 + def self.down
  10 + remove_column :articles, :followers_count
  11 + end
  12 +
  13 +end
@@ -51,6 +51,18 @@ ActiveRecord::Schema.define(version: 20160202142247) do @@ -51,6 +51,18 @@ ActiveRecord::Schema.define(version: 20160202142247) do
51 add_index "action_tracker_notifications", ["profile_id", "action_tracker_id"], name: "index_action_tracker_notif_on_prof_id_act_tracker_id", unique: true, using: :btree 51 add_index "action_tracker_notifications", ["profile_id", "action_tracker_id"], name: "index_action_tracker_notif_on_prof_id_act_tracker_id", unique: true, using: :btree
52 add_index "action_tracker_notifications", ["profile_id"], name: "index_action_tracker_notifications_on_profile_id", using: :btree 52 add_index "action_tracker_notifications", ["profile_id"], name: "index_action_tracker_notifications_on_profile_id", using: :btree
53 53
  54 + create_table "article_followers", force: :cascade do |t|
  55 + t.integer "person_id", null: false
  56 + t.integer "article_id", null: false
  57 + t.datetime "since"
  58 + t.datetime "created_at"
  59 + t.datetime "updated_at"
  60 + end
  61 +
  62 + add_index "article_followers", ["article_id"], name: "index_article_followers_on_article_id", using: :btree
  63 + add_index "article_followers", ["person_id", "article_id"], name: "index_article_followers_on_person_id_and_article_id", unique: true, using: :btree
  64 + add_index "article_followers", ["person_id"], name: "index_article_followers_on_person_id", using: :btree
  65 +
54 create_table "article_privacy_exceptions", id: false, force: :cascade do |t| 66 create_table "article_privacy_exceptions", id: false, force: :cascade do |t|
55 t.integer "article_id" 67 t.integer "article_id"
56 t.integer "person_id" 68 t.integer "person_id"
@@ -101,6 +113,7 @@ ActiveRecord::Schema.define(version: 20160202142247) do @@ -101,6 +113,7 @@ ActiveRecord::Schema.define(version: 20160202142247) do
101 t.integer "spam_comments_count", default: 0 113 t.integer "spam_comments_count", default: 0
102 t.integer "author_id" 114 t.integer "author_id"
103 t.integer "created_by_id" 115 t.integer "created_by_id"
  116 + t.integer "followers_count"
104 end 117 end
105 118
106 add_index "article_versions", ["article_id"], name: "index_article_versions_on_article_id", using: :btree 119 add_index "article_versions", ["article_id"], name: "index_article_versions_on_article_id", using: :btree
@@ -154,6 +167,7 @@ ActiveRecord::Schema.define(version: 20160202142247) do @@ -154,6 +167,7 @@ ActiveRecord::Schema.define(version: 20160202142247) do
154 t.integer "author_id" 167 t.integer "author_id"
155 t.integer "created_by_id" 168 t.integer "created_by_id"
156 t.boolean "show_to_followers", default: true 169 t.boolean "show_to_followers", default: true
  170 + t.integer "followers_count", default: 0
157 end 171 end
158 172
159 add_index "articles", ["comments_count"], name: "index_articles_on_comments_count", using: :btree 173 add_index "articles", ["comments_count"], name: "index_articles_on_comments_count", using: :btree
lib/noosfero/api/entities.rb
@@ -148,6 +148,7 @@ module Noosfero @@ -148,6 +148,7 @@ module Noosfero
148 expose :children_count 148 expose :children_count
149 expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"} 149 expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"}
150 expose :path 150 expose :path
  151 + expose :followers_count
151 end 152 end
152 153
153 class Article < ArticleBase 154 class Article < ArticleBase
lib/noosfero/api/v1/articles.rb
@@ -112,6 +112,37 @@ module Noosfero @@ -112,6 +112,37 @@ module Noosfero
112 {:vote => vote.save} 112 {:vote => vote.save}
113 end 113 end
114 114
  115 + desc "Returns the total followers for the article" do
  116 + detail 'Get the followers of a specific article by id'
  117 + failure [[403, 'Forbidden']]
  118 + named 'ArticleFollowers'
  119 + end
  120 + get ':id/followers' do
  121 + article = find_article(environment.articles, params[:id])
  122 + total = article.person_followers.count
  123 + {:total_followers => total}
  124 + end
  125 +
  126 + desc "Add a follower for the article" do
  127 + detail 'Add the current user identified by private token, like a follower of a article'
  128 + params Noosfero::API::Entities::UserLogin.documentation
  129 + failure [[401, 'Unauthorized']]
  130 + named 'ArticleFollow'
  131 + end
  132 + post ':id/follow' do
  133 + authenticate!
  134 + article = find_article(environment.articles, params[:id])
  135 + if article.article_followers.exists?(:person_id => current_person.id)
  136 + {:success => false, :already_follow => true}
  137 + else
  138 + article_follower = ArticleFollower.new
  139 + article_follower.article = article
  140 + article_follower.person = current_person
  141 + article_follower.save!
  142 + {:success => true}
  143 + end
  144 + end
  145 +
115 desc 'Return the children of a article identified by id' do 146 desc 'Return the children of a article identified by id' do
116 detail 'Get all children articles of a specific article' 147 detail 'Get all children articles of a specific article'
117 params Noosfero::API::Entities::Article.documentation 148 params Noosfero::API::Entities::Article.documentation
test/functional/comment_controller_test.rb
@@ -387,10 +387,26 @@ class CommentControllerTest &lt; ActionController::TestCase @@ -387,10 +387,26 @@ class CommentControllerTest &lt; ActionController::TestCase
387 Article.record_timestamps = true 387 Article.record_timestamps = true
388 388
389 login_as @profile.identifier 389 login_as @profile.identifier
390 - xhr :post, :create, :profile => profile.identifier, :id => page.id, :comment => { :title => 'crap!', :body => 'I think that this article is crap' }, :confirm => 'true' 390 + xhr :post, :create, :profile => profile.identifier, :id => page.id, :comment => {:title => 'crap!', :body => 'I think that this article is crap' }, :confirm => 'true'
391 assert_not_equal yesterday, page.reload.updated_at 391 assert_not_equal yesterday, page.reload.updated_at
392 end 392 end
393 393
  394 + should 'follow article when commenting' do
  395 + page = create(Article, :profile => profile, :name => 'myarticle', :body => 'the body of the text')
  396 + login_as @profile.identifier
  397 +
  398 + xhr :post, :create, :profile => profile.identifier, :id => page.id, :comment => {:title => 'crap!', :body => 'I think that this article is crap', :follow_article => true}, :confirm => 'true'
  399 + assert_includes page.person_followers, @profile
  400 + end
  401 +
  402 + should 'not follow article when commenting' do
  403 + page = create(Article, :profile => profile, :name => 'myarticle', :body => 'the body of the text')
  404 + login_as @profile.identifier
  405 +
  406 + xhr :post, :create, :profile => profile.identifier, :id => page.id, :comment => {:title => 'crap!', :body => 'I think that this article is crap', :follow_article => false }, :confirm => 'true'
  407 + assert_not_includes page.person_followers, @profile
  408 + end
  409 +
394 should 'be able to mark comments as spam' do 410 should 'be able to mark comments as spam' do
395 login_as profile.identifier 411 login_as profile.identifier
396 article = fast_create(Article, :profile_id => profile.id) 412 article = fast_create(Article, :profile_id => profile.id)
test/functional/content_viewer_controller_test.rb
@@ -1278,18 +1278,6 @@ class ContentViewerControllerTest &lt; ActionController::TestCase @@ -1278,18 +1278,6 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1278 assert_tag :tag => 'div', :attributes => { :id => 'article-actions' }, :descendant => { :tag => 'a', :attributes => { :title => 'This button is expired.', :class => 'button with-text icon-edit disabled' } } 1278 assert_tag :tag => 'div', :attributes => { :id => 'article-actions' }, :descendant => { :tag => 'a', :attributes => { :title => 'This button is expired.', :class => 'button with-text icon-edit disabled' } }
1279 end 1279 end
1280 1280
1281 - should 'remove email from article followers when unfollow' do  
1282 - profile = create_user('testuser').person  
1283 - follower_email = 'john@doe.br'  
1284 - article = profile.articles.create(:name => 'test')  
1285 - article.followers = [follower_email]  
1286 - article.save  
1287 -  
1288 - assert_includes Article.find(article.id).followers, follower_email  
1289 - post :view_page, :profile => profile.identifier, :page => [article.name], :unfollow => 'commit', :email => follower_email  
1290 - assert_not_includes Article.find(article.id).followers, follower_email  
1291 - end  
1292 -  
1293 should 'not display comments marked as spam' do 1281 should 'not display comments marked as spam' do
1294 article = fast_create(Article, :profile_id => profile.id) 1282 article = fast_create(Article, :profile_id => profile.id)
1295 ham = fast_create(Comment, :source_id => article.id, :source_type => 'Article', :title => 'some content') 1283 ham = fast_create(Comment, :source_id => article.id, :source_type => 'Article', :title => 'some content')
test/functional/profile_controller_test.rb
@@ -18,6 +18,19 @@ class ProfileControllerTest &lt; ActionController::TestCase @@ -18,6 +18,19 @@ class ProfileControllerTest &lt; ActionController::TestCase
18 assert assigns(:friends) 18 assert assigns(:friends)
19 end 19 end
20 20
  21 + should 'remove person from article followers when unfollow' do
  22 + profile = create_user('testuser').person
  23 + follower = create_user('follower').person
  24 + article = profile.articles.create(:name => 'test')
  25 + article.person_followers = [follower]
  26 + article.save
  27 + login_as('follower')
  28 + article.reload
  29 + assert_includes Article.find(article.id).person_followers, follower
  30 + post :unfollow_article, :article_id => article.id
  31 + assert_not_includes Article.find(article.id).person_followers, follower
  32 + end
  33 +
21 should 'point to manage friends in user is seeing his own friends' do 34 should 'point to manage friends in user is seeing his own friends' do
22 login_as('testuser') 35 login_as('testuser')
23 get :friends 36 get :friends
@@ -1338,6 +1351,24 @@ class ProfileControllerTest &lt; ActionController::TestCase @@ -1338,6 +1351,24 @@ class ProfileControllerTest &lt; ActionController::TestCase
1338 assert_equivalent [scrap,activity], assigns(:activities).map(&:activity) 1351 assert_equivalent [scrap,activity], assigns(:activities).map(&:activity)
1339 end 1352 end
1340 1353
  1354 + should "follow an article" do
  1355 + article = TinyMceArticle.create!(:profile => profile, :name => 'An article about free software')
  1356 + login_as(@profile.identifier)
  1357 + post :follow_article, :profile => profile.identifier, :article_id => article.id
  1358 + assert_includes article.person_followers, @profile
  1359 + end
  1360 +
  1361 + should "unfollow an article" do
  1362 + article = TinyMceArticle.create!(:profile => profile, :name => 'An article about free software')
  1363 + article.person_followers << @profile
  1364 + article.save!
  1365 + assert_includes article.person_followers, @profile
  1366 +
  1367 + login_as(@profile.identifier)
  1368 + post :unfollow_article, :profile => profile.identifier, :article_id => article.id
  1369 + assert_not_includes article.person_followers, @profile
  1370 + end
  1371 +
1341 should "be logged in to leave comment on an activity" do 1372 should "be logged in to leave comment on an activity" do
1342 article = TinyMceArticle.create!(:profile => profile, :name => 'An article about free software') 1373 article = TinyMceArticle.create!(:profile => profile, :name => 'An article about free software')
1343 activity = ActionTracker::Record.last 1374 activity = ActionTracker::Record.last
test/unit/api/articles_test.rb
@@ -39,6 +39,41 @@ class ArticlesTest &lt; ActiveSupport::TestCase @@ -39,6 +39,41 @@ class ArticlesTest &lt; ActiveSupport::TestCase
39 assert_equal 403, last_response.status 39 assert_equal 403, last_response.status
40 end 40 end
41 41
  42 + should 'follow a article identified by id' do
  43 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  44 + post "/api/v1/articles/#{article.id}/follow?#{params.to_query}"
  45 + json = JSON.parse(last_response.body)
  46 +
  47 + assert_not_equal 401, last_response.status
  48 + assert_equal true, json['success']
  49 + end
  50 +
  51 + should 'return the followers count of an article' do
  52 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  53 + article.person_followers << @person
  54 +
  55 + get "/api/v1/articles/#{article.id}?#{params.to_query}"
  56 + json = JSON.parse(last_response.body)
  57 +
  58 + assert_equal 200, last_response.status
  59 + assert_equal 1, json['article']['followers_count']
  60 + end
  61 +
  62 + should 'return the followers of a article identified by id' do
  63 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  64 +
  65 + article_follower = ArticleFollower.new
  66 + article_follower.article = article
  67 + article_follower.person = @person
  68 + article_follower.save!
  69 +
  70 + get "/api/v1/articles/#{article.id}/followers?#{params.to_query}"
  71 + json = JSON.parse(last_response.body)
  72 +
  73 + assert_equal 200, last_response.status
  74 + assert_equal 1, json['total_followers']
  75 + end
  76 +
42 should 'list article children' do 77 should 'list article children' do
43 article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing") 78 article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
44 child1 = fast_create(Article, :parent_id => article.id, :profile_id => user.person.id, :name => "Some thing") 79 child1 = fast_create(Article, :parent_id => article.id, :profile_id => user.person.id, :name => "Some thing")
test/unit/article_test.rb
@@ -22,6 +22,21 @@ class ArticleTest &lt; ActiveSupport::TestCase @@ -22,6 +22,21 @@ class ArticleTest &lt; ActiveSupport::TestCase
22 refute a.errors[:profile_id.to_s].present? 22 refute a.errors[:profile_id.to_s].present?
23 end 23 end
24 24
  25 + should 'keep unique users in list of followers' do
  26 + person1 = create_user('article_owner').person
  27 + person2 = create_user('article_follower').person
  28 +
  29 + article = fast_create(Article, :profile_id => person1.id)
  30 +
  31 + article.person_followers=[person2]
  32 + article.save
  33 + article.reload
  34 + article.person_followers=[person2]
  35 + article.save
  36 +
  37 + assert_equal 1, article.reload.person_followers.size
  38 + end
  39 +
25 should 'require value for name' do 40 should 'require value for name' do
26 a = Article.new 41 a = Article.new
27 a.valid? 42 a.valid?
@@ -1700,7 +1715,7 @@ class ArticleTest &lt; ActiveSupport::TestCase @@ -1700,7 +1715,7 @@ class ArticleTest &lt; ActiveSupport::TestCase
1700 1715
1701 should 'has a empty list of followers by default' do 1716 should 'has a empty list of followers by default' do
1702 a = Article.new 1717 a = Article.new
1703 - assert_equal [], a.followers 1718 + assert_equal [], a.person_followers
1704 end 1719 end
1705 1720
1706 should 'get first image from lead' do 1721 should 'get first image from lead' do
test/unit/comment_notifier_test.rb
@@ -60,7 +60,7 @@ class CommentNotifierTest &lt; ActiveSupport::TestCase @@ -60,7 +60,7 @@ class CommentNotifierTest &lt; ActiveSupport::TestCase
60 should "deliver mail to followers" do 60 should "deliver mail to followers" do
61 author = create_user('follower_author').person 61 author = create_user('follower_author').person
62 follower = create_user('follower').person 62 follower = create_user('follower').person
63 - @article.followers += [follower.email] 63 + @article.person_followers += [follower]
64 @article.save! 64 @article.save!
65 create_comment_and_notify(:source => @article, :author => author, :title => 'comment title', :body => 'comment body') 65 create_comment_and_notify(:source => @article, :author => author, :title => 'comment title', :body => 'comment body')
66 assert_includes ActionMailer::Base.deliveries.map(&:bcc).flatten, follower.email 66 assert_includes ActionMailer::Base.deliveries.map(&:bcc).flatten, follower.email
test/unit/comment_test.rb
@@ -328,34 +328,27 @@ class CommentTest &lt; ActiveSupport::TestCase @@ -328,34 +328,27 @@ class CommentTest &lt; ActiveSupport::TestCase
328 assert c.rejected? 328 assert c.rejected?
329 end 329 end
330 330
331 - should 'subscribe user as follower of an article on new comment' do 331 + should 'not subscribe user as follower of an article automatically on new comment' do
332 owner = create_user('owner_of_article').person 332 owner = create_user('owner_of_article').person
333 person = create_user('follower').person 333 person = create_user('follower').person
334 article = fast_create(Article, :profile_id => owner.id) 334 article = fast_create(Article, :profile_id => owner.id)
335 - assert_not_includes article.followers, person.email 335 + assert_not_includes article.person_followers, person
336 create(Comment, :source => article, :author => person, :title => 'new comment', :body => 'new comment') 336 create(Comment, :source => article, :author => person, :title => 'new comment', :body => 'new comment')
337 - assert_includes article.reload.followers, person.email 337 + assert_not_includes article.reload.person_followers, person
338 end 338 end
339 339
340 - should 'subscribe guest user as follower of an article on new comment' do 340 + should 'not subscribe guest user as follower of an article on new comment' do
341 article = fast_create(Article, :profile_id => create_user('article_owner').person.id) 341 article = fast_create(Article, :profile_id => create_user('article_owner').person.id)
342 - assert_not_includes article.followers, 'follower@example.com' 342 + old_num_followers = article.reload.person_followers.size
343 create(Comment, :source => article, :name => 'follower', :email => 'follower@example.com', :title => 'new comment', :body => 'new comment') 343 create(Comment, :source => article, :name => 'follower', :email => 'follower@example.com', :title => 'new comment', :body => 'new comment')
344 - assert_includes article.reload.followers, 'follower@example.com'  
345 - end  
346 -  
347 - should 'keep unique emails in list of followers' do  
348 - article = fast_create(Article, :profile_id => create_user('article_owner').person.id)  
349 - create(Comment, :source => article, :name => 'follower one', :email => 'follower@example.com', :title => 'new comment', :body => 'new comment')  
350 - create(Comment, :source => article, :name => 'follower two', :email => 'follower@example.com', :title => 'another comment', :body => 'new comment')  
351 - assert_equal 1, article.reload.followers.select{|v| v == 'follower@example.com'}.count 344 + assert_equal old_num_followers, article.reload.person_followers.size
352 end 345 end
353 346
354 should 'not subscribe owner as follower of an article on new comment' do 347 should 'not subscribe owner as follower of an article on new comment' do
355 owner = create_user('owner_of_article').person 348 owner = create_user('owner_of_article').person
356 article = fast_create(Article, :profile_id => owner.id) 349 article = fast_create(Article, :profile_id => owner.id)
357 create(Comment, :source => article, :author => owner, :title => 'new comment', :body => 'new comment') 350 create(Comment, :source => article, :author => owner, :title => 'new comment', :body => 'new comment')
358 - assert_not_includes article.reload.followers, owner.email 351 + assert_not_includes article.reload.person_followers, owner
359 end 352 end
360 353
361 should 'not subscribe admins as follower of an article on new comment' do 354 should 'not subscribe admins as follower of an article on new comment' do
@@ -366,8 +359,13 @@ class CommentTest &lt; ActiveSupport::TestCase @@ -366,8 +359,13 @@ class CommentTest &lt; ActiveSupport::TestCase
366 article = fast_create(Article, :profile_id => owner.id) 359 article = fast_create(Article, :profile_id => owner.id)
367 create(Comment, :source => article, :author => follower, :title => 'new comment', :body => 'new comment') 360 create(Comment, :source => article, :author => follower, :title => 'new comment', :body => 'new comment')
368 create(Comment, :source => article, :author => admin, :title => 'new comment', :body => 'new comment') 361 create(Comment, :source => article, :author => admin, :title => 'new comment', :body => 'new comment')
369 - assert_not_includes article.reload.followers, admin.email  
370 - assert_includes article.followers, follower.email 362 +
  363 + article.person_followers += [follower]
  364 + article.save!
  365 + article.reload
  366 +
  367 + assert_not_includes article.reload.person_followers, admin
  368 + assert_includes article.reload.person_followers, follower
371 end 369 end
372 370
373 should 'update article activity when add a comment' do 371 should 'update article activity when add a comment' do
test/unit/person_notifier_test.rb
@@ -49,7 +49,8 @@ class PersonNotifierTest &lt; ActiveSupport::TestCase @@ -49,7 +49,8 @@ class PersonNotifierTest &lt; ActiveSupport::TestCase
49 should 'display author name in delivered mail' do 49 should 'display author name in delivered mail' do
50 @community.add_member(@member) 50 @community.add_member(@member)
51 User.current = @admin.user 51 User.current = @admin.user
52 - Comment.create!(:author => @admin, :title => 'test comment', :body => 'body!', :source => @article) 52 + comment = Comment.create!(:author => @admin, :title => 'test comment', :body => 'body!', :source => @article)
  53 + comment.save!
53 process_delayed_job_queue 54 process_delayed_job_queue
54 notify 55 notify
55 sent = ActionMailer::Base.deliveries.last 56 sent = ActionMailer::Base.deliveries.last