Commit 66a944b5fd58e50fcc767ce1d91640fef80ff6d7

Authored by Marcos Pereira
1 parent 9db0c7d6
Exists in private-scraps

Profile followers feature

Signed-off-by: Artur Bersan de Faria <arturbersan@gmail.com>
Signed-off-by: Gabriel Silva <gabriel93.silva@gmail.com>
Signed-off-by: Marcos Ronaldo <marcos.rpj2@gmail.com>
Signed-off-by: Matheus Miranda <matheusmirandalacerda@gmail.com>
Signed-off-by: Sabryna Sousa <sabryna.sousa1323@gmail.com>
Signed-off-by: Victor Matias Navarro <victor.matias.navarro@gmail.com>
Signed-off-by: Vitor Barbosa <vitormga15@gmail.com>
Showing 50 changed files with 1005 additions and 90 deletions   Show diff stats
app/controllers/my_profile/follow_categories_controller.rb 0 → 100644
... ... @@ -0,0 +1,46 @@
  1 +class FollowCategoriesController < MyProfileController
  2 +
  3 + def index
  4 + @categories = current_person.follow_categories
  5 + end
  6 +
  7 + def new
  8 + @follow_category = FollowCategory.new
  9 + end
  10 +
  11 + def create
  12 + @follow_category = FollowCategory.new(:name => params[:follow_category][:name],
  13 + :person => current_person)
  14 + if @follow_category.save
  15 + redirect_to :action => 'index'
  16 + else
  17 + render :action => 'new'
  18 + end
  19 + end
  20 +
  21 + def edit
  22 + @follow_category = FollowCategory.find_by_id(params[:id])
  23 + render_not_found if @follow_category.nil?
  24 + end
  25 +
  26 + def update
  27 + @follow_category = FollowCategory.find_by_id(params[:id])
  28 + return render_not_found if @follow_category.nil?
  29 +
  30 + if @follow_category.update(params[:follow_category])
  31 + redirect_to :action => 'index'
  32 + else
  33 + render :action => 'edit'
  34 + end
  35 + end
  36 +
  37 + def destroy
  38 + @follow_category = FollowCategory.find_by_id(params[:id])
  39 + return render_not_found if @follow_category.nil?
  40 +
  41 + if !@follow_category.destroy
  42 + session[:notice] = _('Failed to remove category')
  43 + end
  44 + redirect_to :action => 'index'
  45 + end
  46 +end
... ...
app/controllers/my_profile/followers_controller.rb 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +class FollowersController < MyProfileController
  2 +
  3 + before_filter :only_for_person, :only => :index
  4 +
  5 + def index
  6 + @followed_people = current_person.following_profiles.order(:type).paginate(:per_page => 15, :page => params[:npage])
  7 + end
  8 +
  9 + def set_category_modal
  10 + categories = FollowCategory.where(:person => current_person).map(&:name)
  11 + profile = Profile.find(params[:followed_profile_id])
  12 + render :partial => 'blocks/profile_info_actions/follow_categories', :locals => { :categories => categories, :profile => profile }
  13 + end
  14 +
  15 + def update_category
  16 + params["followed_profile_id"] ||= profile.id
  17 + follower = ProfileFollower.find_by(follower_id: current_person.id, profile_id: params[:followed_profile_id])
  18 + if params[:category_name]
  19 + category = FollowCategory.find_or_create_by(:name => params[:category_name], :person => current_person)
  20 + else
  21 + category = nil
  22 + end
  23 +
  24 + if follower
  25 + follower.follow_category = category
  26 + follower.save
  27 + end
  28 +
  29 + redirect_url = params["redirect_to"] ? params["redirect_to"] : url_for(:controller => "followers", :action => "index", :profile => current_person.identifier)
  30 + redirect_to redirect_url
  31 + end
  32 +
  33 + protected
  34 +
  35 + def only_for_person
  36 + render_not_found unless profile.person?
  37 + end
  38 +
  39 +end
... ...
app/controllers/public/content_viewer_controller.rb
... ... @@ -128,9 +128,9 @@ class ContentViewerController &lt; ApplicationController
128 128 end
129 129  
130 130 unless @page.display_to?(user)
131   - if !profile.visible? || profile.secret? || (user && user.follows?(profile)) || user.blank?
  131 + if !profile.visible? || profile.secret? || (user && profile.in_social_circle?(user)) || user.blank?
132 132 render_access_denied
133   - else #!profile.public?
  133 + else
134 134 private_profile_partial_parameters
135 135 render :template => 'profile/_private_profile', :status => 403, :formats => [:html]
136 136 end
... ...
app/controllers/public/profile_controller.rb
... ... @@ -3,7 +3,8 @@ class ProfileController &lt; PublicController
3 3 needs_profile
4 4 before_filter :check_access_to_profile, :except => [:join, :join_not_logged, :index, :add]
5 5 before_filter :store_location, :only => [:join, :join_not_logged, :report_abuse, :send_mail]
6   - before_filter :login_required, :only => [:add, :join, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report, :leave_comment_on_activity, :send_mail]
  6 + before_filter :login_required, :only => [:add, :join, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report, :leave_comment_on_activity, :send_mail, :follow, :unfollow]
  7 + before_filter :allow_following?, :only => [:follow, :unfollow]
7 8  
8 9 helper TagsHelper
9 10 helper ActionTrackerHelper
... ... @@ -65,6 +66,10 @@ class ProfileController &lt; PublicController
65 66 end
66 67 end
67 68  
  69 + def following
  70 + @followed_people = [].paginate(:per_page => per_page, :page => params[:npage], :total_entries => profile.friends.count)
  71 + end
  72 +
68 73 def members
69 74 if is_cache_expired?(profile.members_cache_key(params))
70 75 sort = (params[:sort] == 'desc') ? params[:sort] : 'asc'
... ... @@ -151,6 +156,26 @@ class ProfileController &lt; PublicController
151 156 end
152 157 end
153 158  
  159 + def follow
  160 + if !current_person.follows?(profile)
  161 + group = params['follow'] ? params['follow']['category'] : ''
  162 + current_person.follow(profile, group)
  163 +
  164 + categories = FollowCategory.where(:person => current_person).map(&:name)
  165 + render :partial => 'blocks/profile_info_actions/follow_categories', :locals => { :categories => categories }
  166 + else
  167 + render :text => _("It was not possible to follow %s") % profile.name, :status => 400
  168 + end
  169 + end
  170 +
  171 + def unfollow
  172 + if current_person.follows?(profile)
  173 + current_person.unfollow(profile)
  174 + end
  175 + redirect_url = params["redirect_to"] ? params["redirect_to"] : profile.url
  176 + redirect_to redirect_url
  177 + end
  178 +
154 179 def check_friendship
155 180 unless logged_in?
156 181 render :text => ''
... ... @@ -437,4 +462,8 @@ class ProfileController &lt; PublicController
437 462 [:image, :domains, :preferred_domain, :environment]
438 463 end
439 464  
  465 + def allow_following?
  466 + render_not_found unless profile.allow_following?
  467 + end
  468 +
440 469 end
... ...
app/helpers/action_tracker_helper.rb
... ... @@ -14,13 +14,23 @@ module ActionTrackerHelper
14 14 }
15 15 end
16 16  
  17 + def new_follower_description ta
  18 + n_('has 1 new follower:<br />%{name}', 'has %{num} new followers:<br />%{name}', ta.get_follower_name.size).html_safe % {
  19 + num: ta.get_follower_name.size,
  20 + name: safe_join(ta.collect_group_with_index(:follower_name) do |n,i|
  21 + link_to image_tag(ta.get_follower_profile_custom_icon[i] || default_or_themed_icon("/images/icons-app/person-icon.png")),
  22 + ta.get_follower_url[i], title: n
  23 + end)
  24 + }
  25 + end
  26 +
17 27 def join_community_description ta
18   - n_('has joined 1 community:<br />%{name}'.html_safe, 'has joined %{num} communities:<br />%{name}'.html_safe, ta.get_resource_name.size) % {
  28 + n_('has joined 1 community:<br />%{name}', 'has joined %{num} communities:<br />%{name}', ta.get_resource_name.size).html_safe % {
19 29 num: ta.get_resource_name.size,
20   - name: ta.collect_group_with_index(:resource_name) do |n,i|
  30 + name: safe_join(ta.collect_group_with_index(:resource_name) do |n,i|
21 31 link = link_to image_tag(ta.get_resource_profile_custom_icon[i] || default_or_themed_icon("/images/icons-app/community-icon.png")),
22 32 ta.get_resource_url[i], title: n
23   - end.join.html_safe
  33 + end)
24 34 }
25 35 end
26 36  
... ... @@ -68,9 +78,9 @@ module ActionTrackerHelper
68 78 end
69 79  
70 80 def favorite_enterprise_description ta
71   - _('favorited enterprise %{title}') % {
  81 + (_('favorited enterprise %{title}') % {
72 82 title: link_to(truncate(ta.get_enterprise_name), ta.get_enterprise_url),
73   - }
  83 + }).html_safe
74 84 end
75 85  
76 86 end
... ...
app/jobs/notify_activity_to_profiles_job.rb
... ... @@ -19,8 +19,9 @@ class NotifyActivityToProfilesJob &lt; Struct.new(:tracked_action_id)
19 19 # Notify the user
20 20 ActionTrackerNotification.create(:profile_id => tracked_action.user.id, :action_tracker_id => tracked_action.id)
21 21  
22   - # Notify all friends
23   - ActionTrackerNotification.connection.execute("insert into action_tracker_notifications(profile_id, action_tracker_id) select f.friend_id, #{tracked_action.id} from friendships as f where person_id=#{tracked_action.user.id} and f.friend_id not in (select atn.profile_id from action_tracker_notifications as atn where atn.action_tracker_id = #{tracked_action.id})")
  22 + #TODO fix spaming notifications when following and unfolling many times
  23 + # Notify all followers
  24 + ActionTrackerNotification.connection.execute("INSERT INTO action_tracker_notifications(profile_id, action_tracker_id) SELECT f.follower_id, #{tracked_action.id} FROM profile_followers AS f WHERE profile_id=#{tracked_action.user.id} AND (f.follower_id NOT IN (SELECT atn.profile_id FROM action_tracker_notifications AS atn WHERE atn.action_tracker_id = #{tracked_action.id}))")
24 25  
25 26 if tracked_action.user.is_a? Organization
26 27 ActionTrackerNotification.connection.execute "insert into action_tracker_notifications(profile_id, action_tracker_id) " +
... ...
app/models/add_member.rb
... ... @@ -22,6 +22,7 @@ class AddMember &lt; Task
22 22 self.roles = [Profile::Roles.member(organization.environment.id).id]
23 23 end
24 24 target.affiliate(requestor, self.roles.select{|r| !r.to_i.zero? }.map{|i| Role.find(i)})
  25 + person.follow(organization, _('memberships'))
25 26 end
26 27  
27 28 def title
... ...
app/models/article.rb
... ... @@ -534,13 +534,13 @@ class Article &lt; ApplicationRecord
534 534  
535 535 scope :display_filter, lambda {|user, profile|
536 536 return published if (user.nil? && profile && profile.public?)
537   - return [] if user.nil? || (profile && !profile.public? && !user.follows?(profile))
  537 + return [] if user.nil? || (profile && !profile.public? && !profile.in_social_circle?(user))
538 538 where(
539 539 [
540 540 "published = ? OR last_changed_by_id = ? OR profile_id = ? OR ?
541 541 OR (show_to_followers = ? AND ? AND profile_id IN (?))", true, user.id, user.id,
542 542 profile.nil? ? false : user.has_permission?(:view_private_content, profile),
543   - true, (profile.nil? ? true : user.follows?(profile)), ( profile.nil? ? (user.friends.select('profiles.id')) : [profile.id])
  543 + true, (profile.nil? ? true : profile.in_social_circle?(user)), ( profile.nil? ? (user.friends.select('profiles.id')) : [profile.id])
544 544 ]
545 545 )
546 546 }
... ...
app/models/block.rb
... ... @@ -88,7 +88,7 @@ class Block &lt; ApplicationRecord
88 88 end
89 89  
90 90 def display_to_user?(user)
91   - display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && user.follows?(owner))
  91 + display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && owner.in_social_circle?(user))
92 92 end
93 93  
94 94 def display_always(context)
... ...
app/models/enterprise.rb
... ... @@ -174,8 +174,4 @@ class Enterprise &lt; Organization
174 174 ''
175 175 end
176 176  
177   - def followed_by? person
178   - super or self.fans.where(id: person.id).count > 0
179   - end
180   -
181 177 end
... ...
app/models/favorite_enterprise_person.rb
... ... @@ -7,6 +7,10 @@ class FavoriteEnterprisePerson &lt; ApplicationRecord
7 7 belongs_to :enterprise
8 8 belongs_to :person
9 9  
  10 + after_create do |favorite|
  11 + favorite.person.follow(favorite.enterprise, _('favorites'))
  12 + end
  13 +
10 14 protected
11 15  
12 16 def is_trackable?
... ...
app/models/follow_category.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class FollowCategory < ApplicationRecord
  2 + belongs_to :environment
  3 + has_many :profile_followers
  4 + belongs_to :person
  5 +
  6 + attr_accessible :name, :person
  7 + validates :name, presence: true
  8 +end
... ...
app/models/friendship.rb
... ... @@ -9,11 +9,13 @@ class Friendship &lt; ApplicationRecord
9 9 after_create do |friendship|
10 10 Friendship.update_cache_counter(:friends_count, friendship.person, 1)
11 11 Friendship.update_cache_counter(:friends_count, friendship.friend, 1)
  12 + friendship.person.follow(friendship.friend, friendship.group)
12 13 end
13 14  
14 15 after_destroy do |friendship|
15 16 Friendship.update_cache_counter(:friends_count, friendship.person, -1)
16 17 Friendship.update_cache_counter(:friends_count, friendship.friend, -1)
  18 + friendship.person.unfollow(friendship.friend)
17 19 end
18 20  
19 21 def self.remove_friendship(person1, person2)
... ...
app/models/person.rb
... ... @@ -8,7 +8,6 @@ class Person &lt; Profile
8 8 :display => %w[compact]
9 9 }
10 10  
11   -
12 11 def self.type_name
13 12 _('Person')
14 13 end
... ... @@ -93,6 +92,7 @@ class Person &lt; Profile
93 92 has_many :following_articles, :class_name => 'Article', :through => :article_followers, :source => :article
94 93 has_many :friendships, :dependent => :destroy
95 94 has_many :friends, :class_name => 'Person', :through => :friendships
  95 + has_many :follow_categories
96 96  
97 97 scope :online, -> {
98 98 joins(:user).where("users.chat_status != '' AND users.chat_status_at >= ?", DateTime.now - User.expires_chat_status_every.minutes)
... ... @@ -200,6 +200,17 @@ class Person &lt; Profile
200 200 end
201 201 end
202 202  
  203 + def follow(profile, category_name = "")
  204 + unless self.following_profiles.include?(profile)
  205 + profile_follower = ProfileFollower.new
  206 + profile_follower.profile = profile
  207 + profile_follower.follower = self
  208 + category = FollowCategory.find_or_create_by(:name => category_name, :person => self)
  209 + profile_follower.follow_category = category
  210 + profile_follower.save
  211 + end
  212 + end
  213 +
203 214 def already_request_friendship?(person)
204 215 person.tasks.where(requestor_id: self.id, type: 'AddFriend', status: Task::Status::ACTIVE).first
205 216 end
... ... @@ -208,6 +219,11 @@ class Person &lt; Profile
208 219 Friendship.where(friend_id: friend, person_id: id).first.destroy
209 220 end
210 221  
  222 + def unfollow(profile)
  223 + follower = ProfileFollower.where(follower_id: id, profile_id: profile.id) if profile
  224 + follower.first.destroy if follower.present?
  225 + end
  226 +
211 227 FIELDS = %w[
212 228 description
213 229 image
... ... @@ -580,9 +596,12 @@ class Person &lt; Profile
580 596 person.has_permission?(:manage_friends, self)
581 597 end
582 598  
583   - protected
  599 + def following_profiles
  600 + Profile.following_profiles self
  601 + end
584 602  
585   - def followed_by?(profile)
586   - self == profile || self.is_a_friend?(profile)
  603 + def in_social_circle?(person)
  604 + self.is_a_friend?(person) || super
587 605 end
  606 +
588 607 end
... ...
app/models/profile.rb
... ... @@ -5,7 +5,7 @@ class Profile &lt; ApplicationRecord
5 5  
6 6 attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time,
7 7 :redirection_after_login, :custom_url_redirection,
8   - :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :profile_admin_mail_notification
  8 + :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :profile_admin_mail_notification, :allow_following
9 9  
10 10 # use for internationalizable human type names in search facets
11 11 # reimplement on subclasses
... ... @@ -203,6 +203,16 @@ class Profile &lt; ApplicationRecord
203 203 scope :more_active, -> { order 'activities_count DESC' }
204 204 scope :more_recent, -> { order "created_at DESC" }
205 205  
  206 + scope :following_profiles, -> person {
  207 + distinct.select('profiles.*, follow_categories.name AS category').
  208 + joins('left join profile_followers ON profile_followers.profile_id = profiles.id').
  209 + joins('left join follow_categories ON follow_categories.id = profile_followers.follow_category_id').
  210 + where('profile_followers.follower_id = ?', person.id)
  211 + }
  212 +
  213 + settings_items :allow_following, :type => :boolean, :default => true
  214 + alias_method :allow_following?, :allow_following
  215 +
206 216 acts_as_trackable :dependent => :destroy
207 217  
208 218 has_many :profile_activities
... ... @@ -215,6 +225,9 @@ class Profile &lt; ApplicationRecord
215 225  
216 226 has_many :email_templates, :foreign_key => :owner_id
217 227  
  228 + has_many :profile_followers
  229 + has_many :followers, :class_name => 'Person', :through => :profile_followers
  230 +
218 231 # Although this should be a has_one relation, there are no non-silly names for
219 232 # a foreign key on article to reference the template to which it is
220 233 # welcome_page... =P
... ... @@ -764,6 +777,7 @@ private :generate_url, :url_options
764 777 else
765 778 self.affiliate(person, Profile::Roles.admin(environment.id), attributes) if members.count == 0
766 779 self.affiliate(person, Profile::Roles.member(environment.id), attributes)
  780 + person.follow(self, _('memberships'))
767 781 end
768 782 person.tasks.pending.of("InviteMember").select { |t| t.data[:community_id] == self.id }.each { |invite| invite.cancel }
769 783 remove_from_suggestion_list person
... ... @@ -1107,7 +1121,11 @@ private :generate_url, :url_options
1107 1121 end
1108 1122  
1109 1123 def followed_by?(person)
1110   - person.is_member_of?(self)
  1124 + (person == self) || (person.in? self.followers)
  1125 + end
  1126 +
  1127 + def in_social_circle?(person)
  1128 + (person == self) || (person.is_member_of?(self))
1111 1129 end
1112 1130  
1113 1131 def display_private_info_to?(user)
... ...
app/models/profile_follower.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class ProfileFollower < ApplicationRecord
  2 + track_actions :new_follower, :after_create, :keep_params => ["follower.name", "follower.url", "follower.profile_custom_icon"], :custom_user => :profile
  3 +
  4 + attr_accessible :profile, :follower, :follow_category
  5 +
  6 + belongs_to :profile, :foreign_key => :profile_id
  7 + belongs_to :follower, :class_name => 'Person', :foreign_key => :follower_id
  8 + belongs_to :follow_category, :foreign_key => :follow_category_id
  9 +
  10 + validates_presence_of :profile_id, :follower_id
  11 + validates :profile_id, :uniqueness => {:scope => :follower_id, :message => "can't follow the same profile twice"}
  12 +
  13 +end
... ...
app/views/blocks/profile_info_actions/_common.html.erb
1 1 <li><%= report_abuse(profile, :button) %></li>
  2 +<%if logged_in? && (user != profile) && profile.allow_following?%>
  3 +<li>
  4 + <% if user.follows?(profile) %>
  5 + <%= button(:unfollow, content_tag('span', _('Unfollow')), {:profile => profile.identifier, :controller => 'profile', :action => 'unfollow'}) %>
  6 + <% else %>
  7 + <%= button(:follow, content_tag('span', _('Follow')), {:profile => profile.identifier, :controller => 'profile', :action => 'follow'}, :class => 'action-follow') %>
  8 + <div id="follow-categories-container" style="display: none;">
  9 + </div>
  10 + <% end %>
  11 +</li>
  12 +<%end%>
2 13 <%= render_environment_features(:profile_actions) %>
... ...
app/views/blocks/profile_info_actions/_follow_categories.html.erb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +<div class="follow-categories">
  2 +<p><%= _("You can set a category for %s") % profile.name %></p>
  3 + <% categories.each do |category| %>
  4 + <div class="category">
  5 + <a href="<%= url_for(:controller => 'followers', :action => 'update_category', :category_name => category,
  6 + :followed_profile_id => profile.id, :redirect_to => url_for(profile.url)) %>" class="action-change-category">
  7 + <span><%= category %></span>
  8 + </a>
  9 + </div>
  10 + <% end %>
  11 + <div id="no-category-link">
  12 + <a href="<%= url_for(:controller => 'followers', :action => 'update_category',
  13 + :followed_profile_id => profile.id, :redirect_to => url_for(profile.url)) %>" class="action-change-category">
  14 + <span><%= _("No Category") %></span>
  15 + </a>
  16 + </div>
  17 + <%= form_for :follow_categories, :url => {:controller => 'followers', :action => 'update_category'} do |f|%>
  18 + <%= text_field_tag(:category_name,"", :id=>"new-category-field-actions-block") %>
  19 + <%= hidden_field_tag(:redirect_to, url_for(profile.url)) %>
  20 + <div id="new-category-submit-actions-block">
  21 + <%= submit_button :add, '', :id =>"new-category-submit-inline"%>
  22 + </div>
  23 + <% end %>
  24 +</div>
... ...
app/views/follow_categories/_form.html.erb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +<%= error_messages_for :follow_category %>
  2 +
  3 +<%= labelled_form_for :follow_category, :url => (mode == :edit) ? {:action => 'update', :id => category} : {:action => 'create'} do |f| %>
  4 +
  5 + <%= required_fields_message %>
  6 +
  7 + <%= required f.text_field(:name) %>
  8 +
  9 + <%= button_bar do %>
  10 + <%= submit_button('save', (mode == :edit) ? _('Save changes') : _('Create category'), :cancel => {:action => 'index'} ) %>
  11 + <% end %>
  12 +<% end %>
... ...
app/views/follow_categories/edit.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1><%= _("Edit follow category") %></h1>
  2 +
  3 +<%= render :partial => 'form', :locals => { :mode => :edit, :category => @follow_category } %>
... ...
app/views/follow_categories/index.html.erb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +<h1><%= _('Manage follow categories') %></h1>
  2 +
  3 +<table>
  4 + <tr>
  5 + <th><%= _('Category') %></th>
  6 + <th><%= _('Actions') %></th>
  7 + </tr>
  8 + <% @categories.each do |category| %>
  9 + <tr>
  10 + <td>
  11 + <%= category.name %>
  12 + </td>
  13 + <td>
  14 + <div style="text-align: center;">
  15 + <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => category %>
  16 + <%= button_without_text :delete, _('Delete'), :action => 'destroy', :id => category %>
  17 + </div>
  18 + </td>
  19 + </tr>
  20 + <% end %>
  21 +</table>
  22 +
  23 +<%= button_bar do %>
  24 + <%= button :add, _('Create a new category'), :action => 'new' %>
  25 + <%= button :back, _('Back to control panel'), :controller => 'profile_editor' %>
  26 +<% end %>
... ...
app/views/follow_categories/new.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1><%= _("Nww follow category") %></h1>
  2 +
  3 +<%= render :partial => 'form', :locals => { :mode => :new, :category => @follow_category } %>
... ...
app/views/followers/_profile_list.html.erb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +<ul class="profile-list">
  2 + <% profiles.each do |profile| %>
  3 + <li>
  4 + <%= link_to_profile profile_image(profile) + tag('br') + profile.short_name,
  5 + profile.identifier, :class => 'profile-link' %>
  6 + <p class="category-name">
  7 + <%= profile.category %>
  8 + </p>
  9 + <div class="controll">
  10 + <%= button_without_text :remove, content_tag('span',_('unfollow')),
  11 + { :controller => "profile", :profile => profile.identifier , :action => 'unfollow', :redirect_to => url_for({:controller => "followers", :profile => user.identifier}) },
  12 + :title => _('remove') %>
  13 + <%= modal_icon_button :change_categoy, content_tag('span',_('change category')),
  14 + url_for(:controller => 'followers', :action => 'set_category_modal',
  15 + :followed_profile_id => profile.id) %>
  16 + </div><!-- end class="controll" -->
  17 + </li>
  18 + <% end %>
  19 +</ul>
... ...
app/views/followers/_set_category_modal.html.erb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +<div class='set-category-modal'>
  2 + <h2><%= _("Choose a new category") %></h2>
  3 + <%= form_for :follower_category, :url => url_for(:controller => 'followers', :action => 'set_category') do |f| %>
  4 + <div id="category-name">
  5 + <%= labelled_text_field _("Category: "), "category_name" %>
  6 + </div>
  7 + <%= hidden_field_tag 'followed_profile_id', followed_profile_id %>
  8 + <div id="actions-container">
  9 + <%= submit_button('save', _('Save')) %>
  10 + <%= modal_close_button _("Cancel") %>
  11 + </div>
  12 + <% end %>
  13 +</div>
... ...
app/views/followers/index.html.erb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +<div id="manage_followed people">
  2 +
  3 +<h1><%= _("%s following") % profile.name %></h1>
  4 +
  5 +<% cache_timeout(profile.manage_friends_cache_key(params), 4.hours) do %>
  6 + <% if @followed_people.empty? %>
  7 + <p>
  8 + <em>
  9 + <%= _("You don't follow anybody yet.") %>
  10 + </em>
  11 + </p>
  12 + <% end %>
  13 +
  14 + <%= button_bar do %>
  15 + <%= button(:back, _('Back to control panel'), :controller => 'profile_editor') %>
  16 + <%= button(:search, _('Find people'), :controller => 'search', :action => 'assets', :asset => 'people') %>
  17 + <% end %>
  18 +
  19 + <%= render :partial => 'profile_list', :locals => { :profiles => @followed_people } %>
  20 +
  21 + <br style="clear:both" />
  22 + <%= pagination_links @followed_people, :param_name => 'npage' %>
  23 +<% end %>
  24 +
  25 +</div>
... ...
app/views/person_notifier/mailer/_new_follower.html.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +<%= render :partial => 'default_activity', :locals => { :activity => activity } %>
... ...
app/views/profile/_new_follower.html.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +<%= render :partial => 'default_activity', :locals => { :activity => activity, :tab_action => tab_action } %>
... ...
app/views/profile/following.html.erb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +<div class="common-profile-list-block">
  2 +
  3 +<h1><%= _("%s is following") % profile.name %></h1>
  4 +
  5 +<% cache_timeout(profile.friends_cache_key(params), 4.hours) do %>
  6 +<ul class='profile-list'>
  7 + <% @followed_people.each do |followed_person| %>
  8 + <%= profile_image_link(followed_person) %>
  9 + <% end%>
  10 +</ul>
  11 +
  12 + <div id='pagination-profiles'>
  13 + <%= pagination_links @followed_people, :param_name => 'npage' %>
  14 + </div>
  15 +<% end %>
  16 +
  17 +<%= button_bar do %>
  18 + <%= button :back, _('Go back'), { :controller => 'profile' } %>
  19 + <% if user == profile %>
  20 + <%= button :edit, _('Manage followed people'), :controller => 'friends', :action => 'index', :profile => profile.identifier %>
  21 + <% end %>
  22 +<% end %>
  23 +
  24 +</div>
... ...
app/views/profile_editor/edit.html.erb
... ... @@ -23,8 +23,11 @@
23 23 </div>
24 24  
25 25 <h2><%= _('Privacy options') %></h2>
26   -
  26 + <div>
  27 + <%= labelled_check_box _("Allow other users to follow me"), 'profile_data[allow_following]', true, @profile.allow_following?, :class => "person-can-be-followed" %>
  28 + </div>
27 29 <% if profile.person? %>
  30 + <br>
28 31 <div>
29 32 <%= labelled_radio_button _('Public &mdash; show my contents to all internet users').html_safe, 'profile_data[public_profile]', true, @profile.public_profile? %>
30 33 </div>
... ...
app/views/profile_editor/index.html.erb
... ... @@ -72,6 +72,12 @@
72 72  
73 73 <%= control_panel_button(_('Email Templates'), 'email-templates', :controller => :profile_email_templates) if profile.organization? %>
74 74  
  75 + <% if profile.person? %>
  76 + <%= control_panel_button(_('Manage followed profiles'), 'manage-followed-people', :controller => :followers) %>
  77 + <% end %>
  78 +
  79 + <%= control_panel_button(_('Manage follow categories'), 'manage-follow-categories', :controller => :follow_categories) if profile.person? %>
  80 +
75 81 <% @plugins.dispatch(:control_panel_buttons).each do |button| %>
76 82 <%= control_panel_button(button[:title], button[:icon], button[:url], button[:html_options]) %>
77 83 <% end %>
... ...
config/initializers/action_tracker.rb
... ... @@ -12,6 +12,10 @@ ActionTrackerConfig.verbs = {
12 12 type: :groupable
13 13 },
14 14  
  15 + new_follower: {
  16 + type: :groupable
  17 + },
  18 +
15 19 join_community: {
16 20 type: :groupable
17 21 },
... ...
db/migrate/20160608123748_create_profile_followers_table.rb 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +class CreateProfileFollowersTable < ActiveRecord::Migration
  2 + def up
  3 + create_table :profile_followers do |t|
  4 + t.column :profile_id, :integer
  5 + t.column :follower_id, :integer
  6 + t.column :follow_category_id, :integer
  7 + t.timestamps
  8 + end
  9 +
  10 + create_table :follow_categories do |t|
  11 + t.column :name, :string
  12 + t.belongs_to :person
  13 + end
  14 +
  15 + add_foreign_key :profile_followers, :follow_categories, :on_delete => :nullify
  16 +
  17 + add_index :profile_followers, [:profile_id, :follower_id], :name => "profile_followers_composite_key_index", :unique => true
  18 +
  19 + #insert one category for each friend group a person has
  20 + execute("INSERT INTO follow_categories(name, person_id) SELECT DISTINCT (CASE WHEN (f.group IS NULL OR f.group = '') THEN 'friendships' ELSE f.group END), f.person_id FROM friendships as f")
  21 + #insert 'memberships' category if a person is in a community as a member, moderator or profile admin
  22 + execute("INSERT INTO follow_categories(name, person_id) SELECT DISTINCT 'memberships', ra.accessor_id FROM role_assignments as ra JOIN roles ON ra.role_id = roles.id WHERE roles.name IN ('Member','Moderator','Profile Administrator')")
  23 + #insert 'favorites' category if a person has any favorited enterprise
  24 + execute("INSERT INTO follow_categories(name, person_id) SELECT DISTINCT 'favorites', person_id FROM favorite_enterprise_people")
  25 +
  26 + #insert a follower entry for each friend, with the category the same as the friendship group or equals 'friendships'
  27 + execute("INSERT INTO profile_followers(follower_id, profile_id, follow_category_id) SELECT DISTINCT f.person_id, f.friend_id, c.id FROM friendships as f JOIN follow_categories as c ON f.person_id = c.person_id WHERE c.name = f.group OR c.name = 'friendships'")
  28 + #insert a follower entry for each favorited enterprise, with the category 'favorites'
  29 + execute("INSERT INTO profile_followers(follower_id, profile_id, follow_category_id) SELECT DISTINCT f.person_id, f.enterprise_id, c.id FROM favorite_enterprise_people AS f JOIN follow_categories as c ON f.person_id = c.person_id WHERE c.name = 'favorites' ")
  30 + #insert a follower entry for each community a person participates as a member, moderator or admininstrator
  31 + execute("INSERT INTO profile_followers(follower_id, profile_id, follow_category_id) SELECT DISTINCT ra.accessor_id, ra.resource_id, c.id FROM role_assignments as ra JOIN roles ON ra.role_id = roles.id JOIN follow_categories as c ON ra.accessor_id = c.person_id WHERE roles.name IN ('Member','Moderator','Profile Administrator') AND c.name = 'memberships'")
  32 +
  33 + end
  34 +
  35 + def down
  36 + remove_foreign_key :profile_followers, :follow_categories
  37 + drop_table :follow_categories
  38 + remove_index :profile_followers, :name => "profile_followers_composite_key_index"
  39 + drop_table :profile_followers
  40 + end
  41 +end
... ...
features/follow_profile.feature 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +Feature: follow profile
  2 + As a noosfero user
  3 + I want to follow a profile
  4 + So I can receive notifications from it
  5 +
  6 + Background:
  7 + Given the following community
  8 + | identifier | name |
  9 + | nightswatch | Nights Watch |
  10 + And the following users
  11 + | login |
  12 + | johnsnow |
  13 + And the user "johnsnow" has the following categories to follow
  14 + | name |
  15 + | Family |
  16 + | Work |
  17 +
  18 + @selenium
  19 + Scenario: Common noofero user follow a community with a category
  20 + Given I am logged in as "johnsnow"
  21 + When I go to nightswatch's homepage
  22 + When I follow "Follow"
  23 + And I should see "Family"
  24 + And I should see "Work"
  25 + And I should see "No Category"
  26 + Then "johnsnow" should be a follower of "nightswatch" with no category
  27 + When I follow "Work"
  28 + Then "johnsnow" should be a follower of "nightswatch" with category "Work"
  29 +
  30 + @selenium
  31 + Scenario: Common noofero user follow a community with no category
  32 + Given I am logged in as "johnsnow"
  33 + When I go to nightswatch's homepage
  34 + When I follow "Follow"
  35 + When I follow "No Category"
  36 + Then "johnsnow" should be a follower of "nightswatch" with no category
  37 +
  38 + @selenium
  39 + Scenario: Common noofero user follow a community with a new category
  40 + Given I am logged in as "johnsnow"
  41 + When I go to nightswatch's homepage
  42 + When I follow "Follow"
  43 + And I fill in "category_name" with "Winterfell"
  44 + When I click on anything with selector "#new-category-submit-inline"
  45 + And I wait 3 second
  46 + Then "johnsnow" should be a follower of "nightswatch" with category "Winterfell"
  47 +
  48 + @selenium
  49 + Scenario: Common noofero user unfollow a community
  50 + Given "johnsnow" is a follower of "nightswatch" with no category
  51 + And I am logged in as "johnsnow"
  52 + When I go to nightswatch's homepage
  53 + When I follow "Unfollow"
  54 + Then "johnsnow" should not be a follower of "nightswatch"
  55 +
... ...
features/step_definitions/followers_steps.rb 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +Given /^the user "(.+)" has the following categories to follow$/ do |user_name,table|
  2 + person = User.find_by(:login => user_name).person
  3 + table.hashes.each do |category|
  4 + FollowCategory.create!(:person => person, :name => category[:name])
  5 + end
  6 +end
  7 +
  8 +Then /^"(.+)" should be a follower of "(.+)" (?:with no category|with category "(.+)")$/ do |person, profile, category|
  9 + profile = Profile.find_by(identifier: profile)
  10 + followers = profile.followers
  11 + person = Person.find_by(identifier: person)
  12 + followers.should include(person)
  13 +
  14 + if category
  15 + ProfileFollower.find_by(:follower => person, :profile => profile).follow_category.name.should == category
  16 + else
  17 + ProfileFollower.find_by(:follower => person, :profile => profile).follow_category.should == nil
  18 + end
  19 +end
  20 +
  21 +Then /^"(.+)" should not be a follower of "(.+)"$/ do |person, profile|
  22 + profile = Profile.find_by(identifier: profile)
  23 + followers = profile.followers
  24 + person = Person.find_by(identifier: person)
  25 + followers.should_not include(person)
  26 +end
  27 +
  28 +Given /^"(.+)" is a follower of "(.+)" (?:with no category|with category "(.+)")$/ do |person, profile, category|
  29 + profile = Profile.find_by(identifier: profile)
  30 + person = Person.find_by(identifier: person)
  31 + params = {:follower => person, :profile => profile}
  32 +
  33 + if category
  34 + category = FollowCategory.find_by(:name => category, :person => person)
  35 + params.merge!({:follow_category => category})
  36 + end
  37 + ProfileFollower.create!(params)
  38 +end
  39 +
... ...
features/step_definitions/web_steps.rb
... ... @@ -298,3 +298,6 @@ When /^(?:|I )wait ([^ ]+) seconds?(?:| .+)$/ do |seconds|
298 298 sleep seconds.to_f
299 299 end
300 300  
  301 +Given /^I click on anything with selector "([^"]*)"$/ do |selector|
  302 + page.evaluate_script("jQuery('#{selector}').click();")
  303 +end
... ...
public/javascripts/application.js
... ... @@ -26,6 +26,7 @@
26 26 *= require pagination.js
27 27 * views speficics
28 28 *= require add-and-join.js
  29 +*= require followers.js
29 30 *= require report-abuse.js
30 31 *= require autogrow.js
31 32 *= require require_login.js
... ... @@ -550,6 +551,11 @@ function loading_for_button(selector) {
550 551 jQuery(selector).css('cursor', 'progress');
551 552 }
552 553  
  554 +function hide_loading_for_button(selector) {
  555 + selector.css("cursor","");
  556 + $(".small-loading").remove();
  557 +}
  558 +
553 559 function new_qualifier_row(selector, select_qualifiers, delete_button) {
554 560 index = jQuery(selector + ' tr').size() - 1;
555 561 jQuery(selector).append("<tr><td>" + select_qualifiers + "</td><td id='certifier-area-" + index + "'><select></select>" + delete_button + "</td></tr>");
... ...
public/javascripts/followers.js 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +$(".action-follow").live("click", function() {
  2 + var button = $(this);
  3 + var url = button.attr("href");
  4 + loading_for_button(button);
  5 +
  6 + $.post(url, function(data) {
  7 + button.fadeOut('fast', function() {
  8 + $("#follow-categories-container").html(data);
  9 + $("#follow-categories-container").fadeIn();
  10 + });
  11 + }).fail(function(response) {
  12 + display_notice(response.responseText);
  13 + }).always(function() {
  14 + hide_loading_for_button(button);
  15 + });
  16 +
  17 + return false;
  18 +});
... ...
public/stylesheets/blocks/profile-info.scss
... ... @@ -99,3 +99,25 @@
99 99 margin: 0px 0px 5px 0px;
100 100 padding: 2px;
101 101 }
  102 +#follow-categories-container {
  103 + background-color: #eee;
  104 + padding: 5px;
  105 + display: flex;
  106 +}
  107 +#follow-categories-container p {
  108 + font-size: 12px;
  109 + margin-bottom: 5px;
  110 +}
  111 +#no-category-link {
  112 + margin-top: 10px;
  113 + margin-bottom: 10px;
  114 +}
  115 +#new-category-field-actions-block {
  116 + float: left;
  117 + width: 80%;
  118 +}
  119 +#new-category-submit-actions-block {
  120 + float: right;
  121 + width: 10%;
  122 + padding-right: 10px;
  123 +}
... ...
public/stylesheets/profile-activity.scss
... ... @@ -167,7 +167,9 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img {
167 167  
168 168 #profile-wall li.profile-activity-item.join_community .profile-activity-text a img,
169 169 #profile-wall li.profile-activity-item.new_friendship .profile-activity-text a img,
  170 +#profile-wall li.profile-activity-item.new_follower .profile-activity-text a img,
170 171 #profile-network li.profile-activity-item.join_community .profile-activity-text a img,
  172 +#profile-network li.profile-activity-item.new_follower .profile-activity-text a img,
171 173 #profile-network li.profile-activity-item.new_friendship .profile-activity-text a img {
172 174 margin: 5px 5px 0 0;
173 175 padding: 1px;
... ...
public/stylesheets/profile-list.scss
... ... @@ -23,6 +23,7 @@
23 23 }
24 24 .controller-favorite_enterprises .profile-list a.profile-link,
25 25 .controller-friends .profile-list a.profile-link,
  26 +.controller-followers .profile-list a.profile-link,
26 27 .list-profile-connections .profile-list a.profile-link,
27 28 .profiles-suggestions .profile-list a.profile-link {
28 29 text-decoration: none;
... ... @@ -32,11 +33,13 @@
32 33 }
33 34 .controller-favorite_enterprises .profile-list a.profile-link:hover,
34 35 .controller-friends .profile-list a.profile-link:hover,
  36 +.controller-followers .profile-list a.profile-link:hover,
35 37 .profiles-suggestions .profile-list a.profile-link:hover {
36 38 color: #FFF;
37 39 }
38 40 .controller-favorite_enterprises .profile-list .profile_link span,
39 41 .controller-friends .profile-list .profile_link span,
  42 +.controller-followers .profile-list .profile_link span,
40 43 .box-1 .profiles-suggestions .profile-list .profile_link span {
41 44 width: 80px;
42 45 display: block;
... ... @@ -44,12 +47,14 @@
44 47 }
45 48 .controller-favorite_enterprises .profile-list,
46 49 .controller-friends .profile-list,
  50 +.controller-followers .profile-list,
47 51 .profiles-suggestions .profile-list {
48 52 position: relative;
49 53 }
50 54  
51 55 .controller-favorite_enterprises .profile-list .controll,
52 56 .controller-friends .profile-list .controll,
  57 +.controller-followers .profile-list .controll,
53 58 .profiles-suggestions .profile-list .controll {
54 59 position: absolute;
55 60 top: 7px;
... ... @@ -57,17 +62,20 @@
57 62 }
58 63 .controller-favorite_enterprises .profile-list .controll a,
59 64 .controller-friends .profile-list .controll a,
  65 +.controller-followers .profile-list .controll a,
60 66 .profiles-suggestions .profile-list .controll a {
61 67 display: block;
62 68 margin-bottom: 2px;
63 69 }
64 70 .controller-favorite_enterprises .msie6 .profile-list .controll a,
65 71 .controller-friends .msie6 .profile-list .controll a,
  72 +.controller-folloed_people .msie6 .profile-list .controll a,
66 73 .profiles-suggestions .msie6 .profile-list .controll a {
67 74 width: 0px;
68 75 }
69 76 .controller-favorite_enterprises .button-bar,
70 77 .controller-friends .button-bar,
  78 +.controller-followers .button-bar,
71 79 .profiles-suggestions .button-bar {
72 80 clear: both;
73 81 padding-top: 20px;
... ... @@ -208,22 +216,35 @@
208 216 font-size: 12px;
209 217 }
210 218 .action-profile-members .profile_link{
211   - position: relative;
  219 + position: relative;
212 220 }
213 221 .action-profile-members .profile_link span.new-profile:last-child{
214   - position: absolute;
215   - top: 3px;
216   - right: 2px;
217   - text-transform: uppercase;
218   - color: #FFF;
219   - font-size: 9px;
220   - background: #66CC33;
221   - padding: 2px;
222   - display: block;
223   - width: 35px;
224   - font-weight: 700;
  222 + position: absolute;
  223 + top: 3px;
  224 + right: 2px;
  225 + text-transform: uppercase;
  226 + color: #FFF;
  227 + font-size: 9px;
  228 + background: #66CC33;
  229 + padding: 2px;
  230 + display: block;
  231 + width: 35px;
  232 + font-weight: 700;
225 233 }
226 234 .action-profile-members .profile_link .fn{
227   - font-style: normal;
228   - color: #000;
  235 + font-style: normal;
  236 + color: #000;
  237 +}
  238 +.category-name {
  239 + margin-top: 0px;
  240 + margin-bottom: 0px;
  241 + font-style: italic;
  242 + color: #888a85;
  243 + text-align: center;
  244 +}
  245 +.set-category-modal {
  246 + width: 250px;
  247 +}
  248 +.set-category-modal #actions-container {
  249 + margin-top: 20px
229 250 }
... ...
test/functional/follow_categories_controller_tests.rb 0 → 100644
... ... @@ -0,0 +1,103 @@
  1 +require_relative "../test_helper"
  2 +require 'follow_categories_controller'
  3 +
  4 +class FollowCategoriesControllerTest < ActionController::TestCase
  5 +
  6 + def setup
  7 + @controller = FollowCategoriesController.new
  8 + @person = create_user('person').person
  9 + login_as(@person.identifier)
  10 + end
  11 +
  12 + should 'return all categories of a profile' do
  13 + category1 = FollowCategory.create(:name => "category1", :person => @person)
  14 + category2 = FollowCategory.create(:name => "category2", :person => @person)
  15 + get :index, :profile => @person.identifier
  16 +
  17 + assert_equivalent [category1, category2], assigns[:categories]
  18 + end
  19 +
  20 + should 'initialize an empty category for creation' do
  21 + get :new, :profile => @person.identifier
  22 + assert_nil assigns[:follow_category].id
  23 + assert_nil assigns[:follow_category].name
  24 + end
  25 +
  26 + should 'create a new category' do
  27 + assert_difference '@person.follow_categories.count' do
  28 + post :create, :profile => @person.identifier,
  29 + :follow_category => { :name => 'category' }
  30 + end
  31 + assert_redirected_to :action => :index
  32 + end
  33 +
  34 + should 'not create a category without a name' do
  35 + assert_no_difference '@person.follow_categories.count' do
  36 + post :create, :profile => @person.identifier, :follow_category => { :name => nil }
  37 + end
  38 + assert_template :new
  39 + end
  40 +
  41 + should 'retrieve an existing category when editing' do
  42 + category = FollowCategory.create(:name => "category", :person => @person)
  43 + get :edit, :profile => @person.identifier, :id => category.id
  44 + assert_equal category.name, assigns[:follow_category].name
  45 + end
  46 +
  47 + should 'return 404 when editing a category that does not exist' do
  48 + get :edit, :profile => @person.identifier, :id => "nope"
  49 + assert_response 404
  50 + end
  51 +
  52 + should 'update an existing category' do
  53 + category = FollowCategory.create(:name => "category", :person => @person)
  54 + get :update, :profile => @person.identifier, :id => category.id,
  55 + :follow_category => { :name => "new name" }
  56 +
  57 + category.reload
  58 + assert_equal "new name", category.name
  59 + assert_redirected_to :action => :index
  60 + end
  61 +
  62 + should 'not update an existing category without a name' do
  63 + category = FollowCategory.create(:name => "category", :person => @person)
  64 + get :update, :profile => @person.identifier, :id => category.id,
  65 + :follow_category => { :name => nil }
  66 +
  67 + category.reload
  68 + assert_equal "category", category.name
  69 + assert_template :edit
  70 + end
  71 +
  72 + should 'return 404 when updating a category that does not exist' do
  73 + get :update, :profile => @person.identifier, :id => "nope", :name => "new name"
  74 + assert_response 404
  75 + end
  76 +
  77 + should 'destroy an existing category and update related profiles' do
  78 + category = FollowCategory.create(:name => "category", :person => @person)
  79 + follower = fast_create(ProfileFollower, :profile_id => fast_create(Person).id,
  80 + :follower_id => @person.id, :follow_category_id => category.id)
  81 +
  82 + assert_difference "@person.follow_categories.count", -1 do
  83 + get :destroy, :profile => @person.identifier, :id => category.id
  84 + end
  85 +
  86 + follower.reload
  87 + assert_nil follower.follow_category
  88 + end
  89 +
  90 + should 'return 404 when deleting and category that does not exist' do
  91 + get :destroy, :profile => @person.identifier, :id => "nope"
  92 + assert_response 404
  93 + end
  94 +
  95 + should 'display notice when ' do
  96 + category = FollowCategory.create(:name => "category", :person => @person)
  97 + FollowCategory.any_instance.stubs(:destroy).returns(false)
  98 +
  99 + get :destroy, :profile => @person.identifier, :id => category.id
  100 + assert_not_nil session[:notice]
  101 + end
  102 +
  103 +end
... ...
test/functional/followers_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,48 @@
  1 +require_relative "../test_helper"
  2 +require 'followers_controller'
  3 +
  4 +class FollowersControllerTest < ActionController::TestCase
  5 + def setup
  6 + @profile = create_user('testuser').person
  7 + end
  8 +
  9 + should 'return followed people list' do
  10 + login_as(@profile.identifier)
  11 + person = fast_create(Person)
  12 + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id)
  13 +
  14 + get :index, :profile => @profile.identifier
  15 + assert_includes assigns(:followed_people), person
  16 + end
  17 +
  18 + should 'redirect to login page if not logged in' do
  19 + person = fast_create(Person)
  20 + get :index, :profile => @profile.identifier
  21 + assert_redirected_to :controller => 'account', :action => 'login'
  22 + end
  23 +
  24 + should 'render set category modal' do
  25 + login_as(@profile.identifier)
  26 + get :set_category, :profile => @profile.identifier, :followed_profile_id => 3
  27 + assert_tag :tag => "input", :attributes => { :id => "followed_profile_id", :value => 3 }
  28 + end
  29 +
  30 + should 'update followed person category' do
  31 + login_as(@profile.identifier)
  32 + person = fast_create(Person)
  33 + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id)
  34 +
  35 + post :set_category, :profile => @profile.identifier, :category_name => "category test", :followed_profile_id => person.id
  36 + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id)
  37 + assert_equal "WRONG","FIX THIS TEST TO USE CATEGORY INsTEAD OF GROUP" #follower.group, "category test"
  38 + end
  39 +
  40 + should 'not update category of not followed person' do
  41 + login_as(@profile.identifier)
  42 + person = fast_create(Person)
  43 +
  44 + post :set_category, :profile => @profile.identifier, :category_name => "category test", :followed_profile_id => person.id
  45 + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id)
  46 + ProfileFollower.any_instance.expects(:update_attributes).times(0)
  47 + end
  48 +end
... ...
test/functional/profile_controller_test.rb
... ... @@ -771,12 +771,13 @@ class ProfileControllerTest &lt; ActionController::TestCase
771 771 assert_equal 15, assigns(:activities).size
772 772 end
773 773  
774   - should 'not see the friends activities in the current profile' do
  774 + should 'not see the followers activities in the current profile' do
775 775 p2 = create_user.person
776   - refute profile.is_a_friend?(p2)
  776 + refute profile.follows?(p2)
777 777 p3 = create_user.person
778   - p3.add_friend(profile)
779   - assert p3.is_a_friend?(profile)
  778 + profile.follow(p3)
  779 + assert profile.follows?(p3)
  780 +
780 781 ActionTracker::Record.destroy_all
781 782  
782 783 scrap1 = create(Scrap, defaults_for_scrap(:sender => p2, :receiver => p3))
... ... @@ -964,7 +965,9 @@ class ProfileControllerTest &lt; ActionController::TestCase
964 965 should 'have activities defined if logged in and is following profile' do
965 966 login_as(profile.identifier)
966 967 p1= fast_create(Person)
967   - p1.add_friend(profile)
  968 +
  969 + profile.follow(p1)
  970 +
968 971 ActionTracker::Record.destroy_all
969 972 get :index, :profile => p1.identifier
970 973 assert_equal [], assigns(:activities)
... ... @@ -1932,4 +1935,77 @@ class ProfileControllerTest &lt; ActionController::TestCase
1932 1935 assert_redirected_to :controller => 'account', :action => 'login'
1933 1936 end
1934 1937  
  1938 + should 'follow a user without defining a group' do
  1939 + login_as(@profile.identifier)
  1940 + person = fast_create(Person)
  1941 + get :follow, :profile => person.identifier
  1942 +
  1943 + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id)
  1944 + assert_not_nil follower
  1945 + end
  1946 +
  1947 + should "not follow user if not logged" do
  1948 + person = fast_create(Person)
  1949 + get :follow, :profile => person.identifier
  1950 +
  1951 + assert_redirected_to :controller => 'account', :action => 'login'
  1952 + end
  1953 +
  1954 + should 'follow a user with a group' do
  1955 + login_as(@profile.identifier)
  1956 + person = fast_create(Person)
  1957 + get :follow, :profile => person.identifier, :follow => { :category => "A Group" }
  1958 +
  1959 + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id)
  1960 + assert_not_nil follower
  1961 + assert_equal "A Group", "FIX THIS TEST TO USE CATEGORY INsTEAD OF GROUP"#follower.category.name
  1962 + end
  1963 +
  1964 + should 'not follow if current_person already follows the person' do
  1965 + login_as(@profile.identifier)
  1966 + person = fast_create(Person)
  1967 + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id)
  1968 +
  1969 + assert_no_difference 'ProfileFollower.count' do
  1970 + get :follow, :profile => person.identifier, :follow => { :category => "A Group" }
  1971 + end
  1972 + assert_response 400
  1973 + end
  1974 +
  1975 + should "not unfollow user if not logged" do
  1976 + person = fast_create(Person)
  1977 + get :unfollow, :profile => person.identifier
  1978 +
  1979 + assert_redirected_to :controller => 'account', :action => 'login'
  1980 + end
  1981 +
  1982 + should "unfollow a followed person" do
  1983 + login_as(@profile.identifier)
  1984 + person = fast_create(Person)
  1985 + follower = fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id)
  1986 + assert_not_nil follower
  1987 +
  1988 + get :unfollow, :profile => person.identifier
  1989 + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id)
  1990 + assert_nil follower
  1991 + end
  1992 +
  1993 + should "not unfollow a not followed person" do
  1994 + login_as(@profile.identifier)
  1995 + person = fast_create(Person)
  1996 +
  1997 + assert_no_difference 'ProfileFollower.count' do
  1998 + get :unfollow, :profile => person.identifier
  1999 + end
  2000 + end
  2001 +
  2002 + should "redirect to page after unfollow" do
  2003 + login_as(@profile.identifier)
  2004 + person = fast_create(Person)
  2005 + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id)
  2006 +
  2007 + get :unfollow, :profile => person.identifier, :redirect_to => "/some/url"
  2008 + assert_redirected_to "/some/url"
  2009 + end
  2010 +
1935 2011 end
... ...
test/unit/article_test.rb
... ... @@ -1099,9 +1099,9 @@ class ArticleTest &lt; ActiveSupport::TestCase
1099 1099 assert_equal 3, ActionTrackerNotification.where(action_tracker_id: second_activity.id).count
1100 1100 end
1101 1101  
1102   - should 'create notifications to friends when creating an article' do
  1102 + should 'create notifications to followers when creating an article' do
1103 1103 friend = fast_create(Person)
1104   - profile.add_friend(friend)
  1104 + friend.follow(profile)
1105 1105 Article.destroy_all
1106 1106 ActionTracker::Record.destroy_all
1107 1107 ActionTrackerNotification.destroy_all
... ... @@ -1112,9 +1112,9 @@ class ArticleTest &lt; ActiveSupport::TestCase
1112 1112 assert_equal friend, ActionTrackerNotification.last.profile
1113 1113 end
1114 1114  
1115   - should 'create the notification to the friend when one friend has the notification and the other no' do
  1115 + should 'create the notification to the follower when one follower has the notification and the other no' do
1116 1116 f1 = fast_create(Person)
1117   - profile.add_friend(f1)
  1117 + f1.follow(profile)
1118 1118  
1119 1119 User.current = profile.user
1120 1120 article = create TinyMceArticle, :name => 'Tracked Article 1', :profile_id => profile.id
... ... @@ -1123,16 +1123,17 @@ class ArticleTest &lt; ActiveSupport::TestCase
1123 1123 assert_equal 2, ActionTrackerNotification.where(action_tracker_id: article.activity.id).count
1124 1124  
1125 1125 f2 = fast_create(Person)
1126   - profile.add_friend(f2)
  1126 + f2.follow(profile)
  1127 +
1127 1128 article2 = create TinyMceArticle, :name => 'Tracked Article 2', :profile_id => profile.id
1128 1129 assert_equal 2, ActionTracker::Record.where(verb: 'create_article').count
1129 1130 process_delayed_job_queue
1130 1131 assert_equal 3, ActionTrackerNotification.where(action_tracker_id: article2.activity.id).count
1131 1132 end
1132 1133  
1133   - should 'destroy activity and notifications of friends when destroying an article' do
  1134 + should 'destroy activity and notifications of followers when destroying an article' do
1134 1135 friend = fast_create(Person)
1135   - profile.add_friend(friend)
  1136 + friend.follow(profile)
1136 1137 Article.destroy_all
1137 1138 ActionTracker::Record.destroy_all
1138 1139 ActionTrackerNotification.destroy_all
... ...
test/unit/friendship_test.rb
... ... @@ -28,14 +28,14 @@ class FriendshipTest &lt; ActiveSupport::TestCase
28 28 f.person = a
29 29 f.friend = b
30 30 f.save!
31   - ta = ActionTracker::Record.last
  31 + ta = ActionTracker::Record.where(:target_type => "Friendship").last
32 32 assert_equal a, ta.user
33 33 assert_equal 'b', ta.get_friend_name[0]
34 34 f = Friendship.new
35 35 f.person = a
36 36 f.friend = c
37 37 f.save!
38   - ta = ActionTracker::Record.last
  38 + ta = ActionTracker::Record.where(:target_type => "Friendship").last
39 39 assert_equal a, ta.user
40 40 assert_equal 'c', ta.get_friend_name[1]
41 41 end
... ... @@ -46,14 +46,14 @@ class FriendshipTest &lt; ActiveSupport::TestCase
46 46 f.person = a
47 47 f.friend = b
48 48 f.save!
49   - ta = ActionTracker::Record.last
  49 + ta = ActionTracker::Record.where(:target_type => "Friendship").last
50 50 assert_equal a, ta.user
51 51 assert_equal ['b'], ta.get_friend_name
52 52 f = Friendship.new
53 53 f.person = b
54 54 f.friend = a
55 55 f.save!
56   - ta = ActionTracker::Record.last
  56 + ta = ActionTracker::Record.where(:target_type => "Friendship").last
57 57 assert_equal b, ta.user
58 58 assert_equal ['a'], ta.get_friend_name
59 59 end
... ... @@ -72,4 +72,53 @@ class FriendshipTest &lt; ActiveSupport::TestCase
72 72 assert_not_includes p2.friends(true), p1
73 73 end
74 74  
  75 + should 'add follower when adding friend' do
  76 + p1 = create_user('testuser1').person
  77 + p2 = create_user('testuser2').person
  78 +
  79 + assert_difference 'ProfileFollower.count', 2 do
  80 + p1.add_friend(p2, 'friends')
  81 + p2.add_friend(p1, 'friends')
  82 + end
  83 +
  84 + assert_includes p1.followers(true), p2
  85 + assert_includes p2.followers(true), p1
  86 + end
  87 +
  88 + should 'remove follower when a friend removal occurs' do
  89 + p1 = create_user('testuser1').person
  90 + p2 = create_user('testuser2').person
  91 +
  92 + p1.add_friend(p2, 'friends')
  93 + p2.add_friend(p1, 'friends')
  94 +
  95 + Friendship.remove_friendship(p1, p2)
  96 +
  97 + assert_not_includes p1.followers(true), p2
  98 + assert_not_includes p2.followers(true), p1
  99 + end
  100 +
  101 + should 'keep friendship intact when stop following' do
  102 + p1 = create_user('testuser1').person
  103 + p2 = create_user('testuser2').person
  104 +
  105 + p1.add_friend(p2, 'friends')
  106 + p2.add_friend(p1, 'friends')
  107 +
  108 + p1.unfollow(p2)
  109 +
  110 + assert_includes p1.friends(true), p2
  111 + assert_includes p2.friends(true), p1
  112 + end
  113 +
  114 + should 'do not add friendship when start following' do
  115 + p1 = create_user('testuser1').person
  116 + p2 = create_user('testuser2').person
  117 +
  118 + p1.follow(p2, 'favorites')
  119 + p2.follow(p1, 'favorites')
  120 +
  121 + assert_not_includes p1.friends(true), p2
  122 + assert_not_includes p2.friends(true), p1
  123 + end
75 124 end
... ...
test/unit/notify_activity_to_profiles_job_test.rb
... ... @@ -24,15 +24,15 @@ class NotifyActivityToProfilesJobTest &lt; ActiveSupport::TestCase
24 24 end
25 25 end
26 26  
27   - should 'notify just the users and his friends tracking user actions' do
  27 + should 'notify just the users and his followers tracking user actions' do
28 28 person = fast_create(Person)
29 29 community = fast_create(Community)
30 30 action_tracker = fast_create(ActionTracker::Record, :user_type => 'Profile', :user_id => person.id, :target_type => 'Profile', :verb => 'create_article')
31 31 refute NotifyActivityToProfilesJob::NOTIFY_ONLY_COMMUNITY.include?(action_tracker.verb)
32 32 p1, p2, m1, m2 = fast_create(Person), fast_create(Person), fast_create(Person), fast_create(Person)
33   - fast_create(Friendship, :person_id => person.id, :friend_id => p1.id)
34   - fast_create(Friendship, :person_id => person.id, :friend_id => p2.id)
35   - fast_create(Friendship, :person_id => p1.id, :friend_id => m1.id)
  33 + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p1.id)
  34 + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p2.id)
  35 + fast_create(ProfileFollower, :profile_id => m1.id, :follower_id => person.id)
36 36 fast_create(RoleAssignment, :accessor_id => m2.id, :role_id => 3, :resource_id => community.id)
37 37 ActionTrackerNotification.delete_all
38 38 job = NotifyActivityToProfilesJob.new(action_tracker.id)
... ... @@ -66,23 +66,21 @@ class NotifyActivityToProfilesJobTest &lt; ActiveSupport::TestCase
66 66 end
67 67 end
68 68  
69   - should 'notify users its friends, the community and its members' do
  69 + should 'notify users its followers, the community and its members' do
70 70 person = fast_create(Person)
71 71 community = fast_create(Community)
72 72 action_tracker = fast_create(ActionTracker::Record, :user_type => 'Profile', :user_id => person.id, :target_type => 'Profile', :target_id => community.id, :verb => 'create_article')
73 73 refute NotifyActivityToProfilesJob::NOTIFY_ONLY_COMMUNITY.include?(action_tracker.verb)
74 74 p1, p2, m1, m2 = fast_create(Person), fast_create(Person), fast_create(Person), fast_create(Person)
75   - fast_create(Friendship, :person_id => person.id, :friend_id => p1.id)
76   - fast_create(Friendship, :person_id => person.id, :friend_id => p2.id)
  75 + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p1.id)
77 76 fast_create(RoleAssignment, :accessor_id => m1.id, :role_id => 3, :resource_id => community.id)
78 77 fast_create(RoleAssignment, :accessor_id => m2.id, :role_id => 3, :resource_id => community.id)
79 78 ActionTrackerNotification.delete_all
80 79 job = NotifyActivityToProfilesJob.new(action_tracker.id)
81 80 job.perform
82 81 process_delayed_job_queue
83   -
84   - assert_equal 6, ActionTrackerNotification.count
85   - [person, community, p1, p2, m1, m2].each do |profile|
  82 + assert_equal 5, ActionTrackerNotification.count
  83 + [person, community, p1, m1, m2].each do |profile|
86 84 notification = ActionTrackerNotification.find_by profile_id: profile.id
87 85 assert_equal action_tracker, notification.action_tracker
88 86 end
... ... @@ -119,8 +117,8 @@ class NotifyActivityToProfilesJobTest &lt; ActiveSupport::TestCase
119 117 action_tracker = fast_create(ActionTracker::Record, :user_type => 'Profile', :user_id => person.id, :target_type => 'Profile', :target_id => community.id, :verb => 'join_community')
120 118 refute NotifyActivityToProfilesJob::NOTIFY_ONLY_COMMUNITY.include?(action_tracker.verb)
121 119 p1, p2, m1, m2 = fast_create(Person), fast_create(Person), fast_create(Person), fast_create(Person)
122   - fast_create(Friendship, :person_id => person.id, :friend_id => p1.id)
123   - fast_create(Friendship, :person_id => person.id, :friend_id => p2.id)
  120 + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p1.id)
  121 + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p2.id)
124 122 fast_create(RoleAssignment, :accessor_id => m1.id, :role_id => 3, :resource_id => community.id)
125 123 fast_create(RoleAssignment, :accessor_id => m2.id, :role_id => 3, :resource_id => community.id)
126 124 ActionTrackerNotification.delete_all
... ...
test/unit/person_notifier_test.rb
... ... @@ -178,6 +178,7 @@ class PersonNotifierTest &lt; ActiveSupport::TestCase
178 178 update_product: -> { create Product, profile: @profile, product_category: create(ProductCategory, environment: Environment.default) },
179 179 remove_product: -> { create Product, profile: @profile, product_category: create(ProductCategory, environment: Environment.default) },
180 180 favorite_enterprise: -> { create FavoriteEnterprisePerson, enterprise: create(Enterprise), person: @member },
  181 + new_follower: -> { @member }
181 182 }
182 183  
183 184 ActionTrackerConfig.verb_names.each do |verb|
... ... @@ -197,6 +198,7 @@ class PersonNotifierTest &lt; ActiveSupport::TestCase
197 198 'friend_url' => '/', 'friend_profile_custom_icon' => [], 'friend_name' => ['joe'],
198 199 'resource_name' => ['resource'], 'resource_profile_custom_icon' => [], 'resource_url' => ['/'],
199 200 'enterprise_name' => 'coop', 'enterprise_url' => '/coop',
  201 + 'follower_url' => '/', 'follower_profile_custom_icon' => [], 'follower_name' => ['joe'],
200 202 'view_url'=> ['/'], 'thumbnail_path' => ['1'],
201 203 }
202 204 a.get_url = ''
... ...
test/unit/person_test.rb
... ... @@ -728,7 +728,7 @@ class PersonTest &lt; ActiveSupport::TestCase
728 728 assert_equal [s4], p2.scraps_received.not_replies
729 729 end
730 730  
731   - should "the followed_by method be protected and true to the person friends and herself by default" do
  731 + should "the followed_by method return true to the person friends and herself by default" do
732 732 p1 = fast_create(Person)
733 733 p2 = fast_create(Person)
734 734 p3 = fast_create(Person)
... ... @@ -740,9 +740,9 @@ class PersonTest &lt; ActiveSupport::TestCase
740 740 assert p1.is_a_friend?(p4)
741 741  
742 742 assert_equal true, p1.send(:followed_by?,p1)
743   - assert_equal true, p1.send(:followed_by?,p2)
744   - assert_equal true, p1.send(:followed_by?,p4)
745   - assert_equal false, p1.send(:followed_by?,p3)
  743 + assert_equal true, p2.send(:followed_by?,p1)
  744 + assert_equal true, p4.send(:followed_by?,p1)
  745 + assert_equal false, p3.send(:followed_by?,p1)
746 746 end
747 747  
748 748 should "the person follows her friends and herself by default" do
... ... @@ -757,9 +757,9 @@ class PersonTest &lt; ActiveSupport::TestCase
757 757 assert p4.is_a_friend?(p1)
758 758  
759 759 assert_equal true, p1.follows?(p1)
760   - assert_equal true, p1.follows?(p2)
761   - assert_equal true, p1.follows?(p4)
762   - assert_equal false, p1.follows?(p3)
  760 + assert_equal true, p2.follows?(p1)
  761 + assert_equal true, p4.follows?(p1)
  762 + assert_equal false, p3.follows?(p1)
763 763 end
764 764  
765 765 should "a person member of a community follows the community" do
... ... @@ -836,18 +836,18 @@ class PersonTest &lt; ActiveSupport::TestCase
836 836 assert_nil Scrap.find_by(id: scrap.id)
837 837 end
838 838  
839   - should "the tracked action be notified to person friends and herself" do
  839 + should "the tracked action be notified to person followers and herself" do
840 840 Person.destroy_all
841 841 p1 = fast_create(Person)
842 842 p2 = fast_create(Person)
843 843 p3 = fast_create(Person)
844 844 p4 = fast_create(Person)
845 845  
846   - p1.add_friend(p2)
847   - assert p1.is_a_friend?(p2)
848   - refute p1.is_a_friend?(p3)
849   - p1.add_friend(p4)
850   - assert p1.is_a_friend?(p4)
  846 + p2.follow(p1)
  847 + assert p2.follows?(p1)
  848 + refute p3.follows?(p1)
  849 + p4.follow(p1)
  850 + assert p4.follows?(p1)
851 851  
852 852 action_tracker = fast_create(ActionTracker::Record, :user_id => p1.id)
853 853 ActionTrackerNotification.delete_all
... ... @@ -880,17 +880,17 @@ class PersonTest &lt; ActiveSupport::TestCase
880 880 end
881 881 end
882 882  
883   - should "the tracked action notify friends with one delayed job process" do
  883 + should "the tracked action notify followers with one delayed job process" do
884 884 p1 = fast_create(Person)
885 885 p2 = fast_create(Person)
886 886 p3 = fast_create(Person)
887 887 p4 = fast_create(Person)
888 888  
889   - p1.add_friend(p2)
890   - assert p1.is_a_friend?(p2)
891   - refute p1.is_a_friend?(p3)
892   - p1.add_friend(p4)
893   - assert p1.is_a_friend?(p4)
  889 + p2.follow(p1)
  890 + assert p2.follows?(p1)
  891 + refute p3.follows?(p1)
  892 + p4.follow(p1)
  893 + assert p4.follows?(p1)
894 894  
895 895 action_tracker = fast_create(ActionTracker::Record, :user_id => p1.id)
896 896  
... ... @@ -1035,11 +1035,13 @@ class PersonTest &lt; ActiveSupport::TestCase
1035 1035 p2 = create_user('p2').person
1036 1036 p3 = create_user('p3').person
1037 1037 c = fast_create(Community, :name => "Foo")
  1038 +
1038 1039 c.add_member(p1)
1039 1040 process_delayed_job_queue
1040 1041 c.add_member(p3)
1041 1042 process_delayed_job_queue
1042   - assert_equal 4, ActionTracker::Record.count
  1043 +
  1044 + assert_equal 5, ActionTracker::Record.count
1043 1045 assert_equal 5, ActionTrackerNotification.count
1044 1046 has_add_member_notification = false
1045 1047 ActionTrackerNotification.all.map do |notification|
... ...
test/unit/profile_followers_test.rb 0 → 100644
... ... @@ -0,0 +1,68 @@
  1 +require_relative "../test_helper"
  2 +
  3 +class ProfileFollowersTest < ActiveSupport::TestCase
  4 +
  5 + should 'a person follow another' do
  6 + p1 = create_user('person_test').person
  7 + p2 = create_user('person_test_2').person
  8 +
  9 + assert_difference 'ProfileFollower.count' do
  10 + p1.follow(p2)
  11 + end
  12 +
  13 + assert_includes p2.followers(true), p1
  14 + assert_not_includes p1.followers(true), p2
  15 + end
  16 +
  17 + should 'a person unfollow another person' do
  18 + p1 = create_user('person_test').person
  19 + p2 = create_user('person_test_2').person
  20 +
  21 + p1.follow(p2)
  22 +
  23 + assert_difference 'ProfileFollower.count', -1 do
  24 + p1.unfollow(p2)
  25 + end
  26 +
  27 + assert_not_includes p2.followers(true), p1
  28 + end
  29 +
  30 + should 'get the followed persons for a profile' do
  31 + p1 = create_user('person_test').person
  32 + p2 = create_user('person_test_2').person
  33 + p3 = create_user('person_test_3').person
  34 +
  35 + p1.follow(p2)
  36 + p1.follow(p3)
  37 +
  38 + assert_equivalent p1.following_profiles, [p2,p3]
  39 + assert_equivalent Profile.following_profiles(p1), [p2,p3]
  40 + end
  41 +
  42 + should 'not follow same person twice' do
  43 + p1 = create_user('person_test').person
  44 + p2 = create_user('person_test_2').person
  45 +
  46 + assert_difference 'ProfileFollower.count' do
  47 + p1.follow(p2)
  48 + p1.follow(p2)
  49 + end
  50 +
  51 + assert_equivalent p1.following_profiles, [p2]
  52 + assert_equivalent p2.followers, [p1]
  53 + end
  54 +
  55 + should 'show the correct message when a profile is followed by the same person' do
  56 + p1 = create_user('person_test').person
  57 + p2 = create_user('person_test_2').person
  58 +
  59 + p1.follow(p2)
  60 + profile_follower = ProfileFollower.new
  61 + profile_follower.follower = p1
  62 + profile_follower.profile = p2
  63 + profile_follower.valid?
  64 +
  65 + assert_includes profile_follower.errors.messages[:profile_id],
  66 + "can't follow the same profile twice"
  67 + end
  68 +end
... ...
test/unit/scrap_test.rb
... ... @@ -125,11 +125,11 @@ class ScrapTest &lt; ActiveSupport::TestCase
125 125 assert_equal c, ta.target
126 126 end
127 127  
128   - should "notify leave_scrap action tracker verb to friends and itself" do
  128 + should "notify leave_scrap action tracker verb to followers and itself" do
129 129 User.current = create_user
130 130 p1 = User.current.person
131 131 p2 = create_user.person
132   - p1.add_friend(p2)
  132 + p2.add_friend(p1)
133 133 process_delayed_job_queue
134 134 s = Scrap.new
135 135 s.sender= p1
... ... @@ -180,11 +180,11 @@ class ScrapTest &lt; ActiveSupport::TestCase
180 180 assert_equal p, ta.user
181 181 end
182 182  
183   - should "notify leave_scrap_to_self action tracker verb to friends and itself" do
  183 + should "notify leave_scrap_to_self action tracker verb to followers and itself" do
184 184 User.current = create_user
185 185 p1 = User.current.person
186 186 p2 = create_user.person
187   - p1.add_friend(p2)
  187 + p2.add_friend(p1)
188 188 ActionTrackerNotification.delete_all
189 189 Delayed::Job.delete_all
190 190 s = Scrap.new
... ...