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 155 end
156 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 170 def unblock
159 171 if current_user.person.is_admin?(profile.environment)
160 172 profile.unblock
... ...
app/helpers/article_helper.rb
... ... @@ -155,4 +155,30 @@ module ArticleHelper
155 155 _('Edit')
156 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 184 end
... ...
app/models/article.rb
... ... @@ -9,7 +9,7 @@ class Article &lt; ActiveRecord::Base
9 9 :highlighted, :notify_comments, :display_hits, :slug,
10 10 :external_feed_builder, :display_versions, :external_link,
11 11 :image_builder, :show_to_followers,
12   - :author, :display_preview, :published_at
  12 + :author, :display_preview, :published_at, :person_followers
13 13  
14 14 acts_as_having_image
15 15  
... ... @@ -77,8 +77,15 @@ class Article &lt; ActiveRecord::Base
77 77 belongs_to :last_changed_by, :class_name => 'Person', :foreign_key => 'last_changed_by_id'
78 78 belongs_to :created_by, :class_name => 'Person', :foreign_key => 'created_by_id'
79 79  
  80 +<<<<<<< 6fa2f904983046ed816efe293ba9dcad19a67a4b
80 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 89 has_many :article_categorizations, -> { where 'articles_categories.virtual = ?', false }
83 90 has_many :categories, :through => :article_categorizations
84 91  
... ... @@ -91,7 +98,6 @@ class Article &lt; ActiveRecord::Base
91 98 settings_items :author_name, :type => :string, :default => ""
92 99 settings_items :allow_members_to_edit, :type => :boolean, :default => false
93 100 settings_items :moderate_comments, :type => :boolean, :default => false
94   - settings_items :followers, :type => Array, :default => []
95 101 has_and_belongs_to_many :article_privacy_exceptions, :class_name => 'Person', :join_table => 'article_privacy_exceptions'
96 102  
97 103 belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id'
... ... @@ -167,7 +173,6 @@ class Article &lt; ActiveRecord::Base
167 173 end
168 174 end
169 175  
170   -
171 176 def is_trackable?
172 177 self.published? && self.notifiable? && self.advertise? && self.profile.public_profile
173 178 end
... ... @@ -368,6 +373,10 @@ class Article &lt; ActiveRecord::Base
368 373 self.parent and self.parent.forum?
369 374 end
370 375  
  376 + def person_followers_email_list
  377 + person_followers_emails.map{|p|p.email}
  378 + end
  379 +
371 380 def info_from_last_update
372 381 last_comment = comments.last
373 382 if last_comment
... ... @@ -407,6 +416,10 @@ class Article &lt; ActiveRecord::Base
407 416 (not self.uploaded_file? and self.mime_type != 'text/html')
408 417 end
409 418  
  419 + def is_followed_by?(user)
  420 + self.person_followers.include? user
  421 + end
  422 +
410 423 def download_headers
411 424 {}
412 425 end
... ...
app/models/article_follower.rb 0 → 100644
... ... @@ -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 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 11 validates_presence_of :body
12 12  
13 13 belongs_to :source, :counter_cache => true, :polymorphic => true
14 14 alias :article :source
15 15 alias :article= :source=
  16 + attr_accessor :follow_article
16 17  
17 18 belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id'
18 19 has_many :children, :class_name => 'Comment', :foreign_key => 'reply_of_id', :dependent => :destroy
... ... @@ -100,10 +101,9 @@ class Comment &lt; ActiveRecord::Base
100 101  
101 102 after_create :new_follower
102 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 107 article.save
108 108 end
109 109 end
... ... @@ -145,7 +145,7 @@ class Comment &lt; ActiveRecord::Base
145 145 if !notification_emails.empty?
146 146 CommentNotifier.notification(self).deliver
147 147 end
148   - emails = article.followers - [author_email]
  148 + emails = article.person_followers_email_list - [author_email]
149 149 if !emails.empty?
150 150 CommentNotifier.mail_to_followers(self, emails).deliver
151 151 end
... ...
app/models/person.rb
1 1 # A person is the profile of an user holding all relationships with the rest of the system
2 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 6 SEARCH_FILTERS = {
7 7 :order => %w[more_recent more_popular more_active],
... ... @@ -84,7 +84,8 @@ class Person &lt; Profile
84 84 end
85 85  
86 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 89 has_many :friendships, :dependent => :destroy
89 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 77 <%= labelled_form_field(_('Title'), f.text_field(:title)) %>
78 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 84 <%= hidden_field_tag(:confirm, 'false') %>
81 85 <%= hidden_field_tag(:view, params[:view])%>
82 86 <%= f.hidden_field(:reply_of_id) %>
... ...
app/views/content_viewer/_article_toolbar.html.erb
... ... @@ -55,6 +55,8 @@
55 55 <%= button plugin_button[:icon], plugin_button[:title], plugin_button[:url], plugin_button[:html_options] %>
56 56 <% end %>
57 57  
  58 + <%= following_button @page, user %>
  59 +
58 60 <%= report_abuse(profile, :link, @page) %>
59 61  
60 62 </div>
... ...
app/views/content_viewer/_publishing_info.html.erb
... ... @@ -10,6 +10,24 @@
10 10 <%= (" - %s") % link_to_comments(@page)%>
11 11 </span>
12 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 31 </span>
14 32  
15 33 <% if @page.display_hits? || @page.license.present? %>
... ...
db/migrate/20151210230319_add_followers_count_to_article.rb 0 → 100644
... ... @@ -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
... ...
db/schema.rb
... ... @@ -51,6 +51,18 @@ ActiveRecord::Schema.define(version: 20160202142247) do
51 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 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 66 create_table "article_privacy_exceptions", id: false, force: :cascade do |t|
55 67 t.integer "article_id"
56 68 t.integer "person_id"
... ... @@ -101,6 +113,7 @@ ActiveRecord::Schema.define(version: 20160202142247) do
101 113 t.integer "spam_comments_count", default: 0
102 114 t.integer "author_id"
103 115 t.integer "created_by_id"
  116 + t.integer "followers_count"
104 117 end
105 118  
106 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 167 t.integer "author_id"
155 168 t.integer "created_by_id"
156 169 t.boolean "show_to_followers", default: true
  170 + t.integer "followers_count", default: 0
157 171 end
158 172  
159 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 148 expose :children_count
149 149 expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"}
150 150 expose :path
  151 + expose :followers_count
151 152 end
152 153  
153 154 class Article < ArticleBase
... ...
lib/noosfero/api/v1/articles.rb
... ... @@ -112,6 +112,37 @@ module Noosfero
112 112 {:vote => vote.save}
113 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 146 desc 'Return the children of a article identified by id' do
116 147 detail 'Get all children articles of a specific article'
117 148 params Noosfero::API::Entities::Article.documentation
... ...
test/functional/comment_controller_test.rb
... ... @@ -387,10 +387,26 @@ class CommentControllerTest &lt; ActionController::TestCase
387 387 Article.record_timestamps = true
388 388  
389 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 391 assert_not_equal yesterday, page.reload.updated_at
392 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 410 should 'be able to mark comments as spam' do
395 411 login_as profile.identifier
396 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 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 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 1281 should 'not display comments marked as spam' do
1294 1282 article = fast_create(Article, :profile_id => profile.id)
1295 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 18 assert assigns(:friends)
19 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 34 should 'point to manage friends in user is seeing his own friends' do
22 35 login_as('testuser')
23 36 get :friends
... ... @@ -1338,6 +1351,24 @@ class ProfileControllerTest &lt; ActionController::TestCase
1338 1351 assert_equivalent [scrap,activity], assigns(:activities).map(&:activity)
1339 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 1372 should "be logged in to leave comment on an activity" do
1342 1373 article = TinyMceArticle.create!(:profile => profile, :name => 'An article about free software')
1343 1374 activity = ActionTracker::Record.last
... ...
test/unit/api/articles_test.rb
... ... @@ -39,6 +39,41 @@ class ArticlesTest &lt; ActiveSupport::TestCase
39 39 assert_equal 403, last_response.status
40 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 77 should 'list article children' do
43 78 article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
44 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 22 refute a.errors[:profile_id.to_s].present?
23 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 40 should 'require value for name' do
26 41 a = Article.new
27 42 a.valid?
... ... @@ -1700,7 +1715,7 @@ class ArticleTest &lt; ActiveSupport::TestCase
1700 1715  
1701 1716 should 'has a empty list of followers by default' do
1702 1717 a = Article.new
1703   - assert_equal [], a.followers
  1718 + assert_equal [], a.person_followers
1704 1719 end
1705 1720  
1706 1721 should 'get first image from lead' do
... ...
test/unit/comment_notifier_test.rb
... ... @@ -60,7 +60,7 @@ class CommentNotifierTest &lt; ActiveSupport::TestCase
60 60 should "deliver mail to followers" do
61 61 author = create_user('follower_author').person
62 62 follower = create_user('follower').person
63   - @article.followers += [follower.email]
  63 + @article.person_followers += [follower]
64 64 @article.save!
65 65 create_comment_and_notify(:source => @article, :author => author, :title => 'comment title', :body => 'comment body')
66 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 328 assert c.rejected?
329 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 332 owner = create_user('owner_of_article').person
333 333 person = create_user('follower').person
334 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 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 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 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 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 345 end
353 346  
354 347 should 'not subscribe owner as follower of an article on new comment' do
355 348 owner = create_user('owner_of_article').person
356 349 article = fast_create(Article, :profile_id => owner.id)
357 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 352 end
360 353  
361 354 should 'not subscribe admins as follower of an article on new comment' do
... ... @@ -366,8 +359,13 @@ class CommentTest &lt; ActiveSupport::TestCase
366 359 article = fast_create(Article, :profile_id => owner.id)
367 360 create(Comment, :source => article, :author => follower, :title => 'new comment', :body => 'new comment')
368 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 369 end
372 370  
373 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 49 should 'display author name in delivered mail' do
50 50 @community.add_member(@member)
51 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 54 process_delayed_job_queue
54 55 notify
55 56 sent = ActionMailer::Base.deliveries.last
... ...