Commit 8b945809d1d522c39798b10d96ecca9eccd13328

Authored by Marcos Pereira
Committed by Larissa Reis
1 parent fceeac68

Profile followers feature

Signed-off-by: Alessandro Caetano <alessandro.caetanob@gmail.com>
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 58 changed files with 1448 additions and 92 deletions   Show diff stats
app/controllers/application_controller.rb
... ... @@ -115,6 +115,10 @@ class ApplicationController &lt; ActionController::Base
115 115  
116 116 protected
117 117  
  118 + def accept_only_post
  119 + return render_not_found if !request.post?
  120 + end
  121 +
118 122 def verified_request?
119 123 super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
120 124 end
... ...
app/controllers/my_profile/circles_controller.rb 0 → 100644
... ... @@ -0,0 +1,58 @@
  1 +class CirclesController < MyProfileController
  2 +
  3 + before_action :accept_only_post, :only => [:create, :update, :destroy]
  4 +
  5 + def index
  6 + @circles = current_person.circles
  7 + end
  8 +
  9 + def new
  10 + @circle = Circle.new
  11 + end
  12 +
  13 + def create
  14 + @circle = Circle.new(params[:circle].merge({ :person => current_person }))
  15 + if @circle.save
  16 + redirect_to :action => 'index'
  17 + else
  18 + render :action => 'new'
  19 + end
  20 + end
  21 +
  22 + def xhr_create
  23 + if request.xhr?
  24 + circle = Circle.new(params[:circle].merge({:person => current_person }))
  25 + if circle.save
  26 + render :partial => "circle_checkbox", :locals => { :circle => circle },
  27 + :status => 201
  28 + else
  29 + render :text => _('The circle could not be saved'), :status => 400
  30 + end
  31 + else
  32 + render_not_found
  33 + end
  34 + end
  35 +
  36 + def edit
  37 + @circle = Circle.find_by_id(params[:id])
  38 + render_not_found if @circle.nil?
  39 + end
  40 +
  41 + def update
  42 + @circle = Circle.find_by_id(params[:id])
  43 + return render_not_found if @circle.nil?
  44 +
  45 + if @circle.update(params[:circle])
  46 + redirect_to :action => 'index'
  47 + else
  48 + render :action => 'edit'
  49 + end
  50 + end
  51 +
  52 + def destroy
  53 + @circle = Circle.find_by_id(params[:id])
  54 + return render_not_found if @circle.nil?
  55 + @circle.destroy
  56 + redirect_to :action => 'index'
  57 + end
  58 +end
... ...
app/controllers/my_profile/followers_controller.rb 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +class FollowersController < MyProfileController
  2 +
  3 + before_action :only_for_person, :only => :index
  4 + before_action :accept_only_post, :only => [:update_category]
  5 +
  6 + def index
  7 + @followed_people = current_person.followed_profiles.order(:type)
  8 + @profile_types = {_('All profiles') => nil}.merge(Circle.profile_types).to_a
  9 +
  10 + if params['filter'].present?
  11 + @followed_people = @followed_people.where(:type => params['filter'])
  12 + @active_filter = params['filter']
  13 + end
  14 +
  15 + @followed_people = @followed_people.paginate(:per_page => 15, :page => params[:npage])
  16 + end
  17 +
  18 + def set_category_modal
  19 + profile = Profile.find(params[:followed_profile_id])
  20 + circles = Circle.where(:person => current_person, :profile_type => profile.class.name)
  21 + render :partial => 'followers/edit_circles_modal', :locals => { :circles => circles, :profile => profile }
  22 + end
  23 +
  24 + def update_category
  25 + followed_profile = Profile.find_by(:id => params["followed_profile_id"])
  26 +
  27 + selected_circles = params[:circles].map{|circle_name, circle_id| Circle.find_by(:id => circle_id)}.select{|c|not c.nil?}
  28 +
  29 + if followed_profile
  30 + current_person.update_profile_circles(followed_profile, selected_circles)
  31 + render :text => _("Circles of %s updated successfully") % followed_profile.name, :status => 200
  32 + else
  33 + render :text => _("Error: No profile to follow."), :status => 400
  34 + end
  35 + end
  36 +
  37 + protected
  38 +
  39 + def only_for_person
  40 + render_not_found unless profile.person?
  41 + end
  42 +
  43 +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_followers?, :only => [:follow, :unfollow]
7 8  
8 9 helper TagsHelper
9 10 helper ActionTrackerHelper
... ... @@ -65,6 +66,14 @@ class ProfileController &lt; PublicController
65 66 end
66 67 end
67 68  
  69 + def following
  70 + @followed_people = profile.followed_profiles.paginate(:per_page => per_page, :page => params[:npage], :total_entries => profile.followed_profiles.count)
  71 + end
  72 +
  73 + def followed
  74 + @followed_by = profile.followers.paginate(:per_page => per_page, :page => params[:npage], :total_entries => profile.followers.count)
  75 + end
  76 +
68 77 def members
69 78 if is_cache_expired?(profile.members_cache_key(params))
70 79 sort = (params[:sort] == 'desc') ? params[:sort] : 'asc'
... ... @@ -151,6 +160,37 @@ class ProfileController &lt; PublicController
151 160 end
152 161 end
153 162  
  163 + def follow
  164 + if request.post?
  165 + if profile.followed_by?(current_person)
  166 + render :text => _("You are already following %s.") % profile.name, :status => 400
  167 + else
  168 + selected_circles = params[:circles].map{|circle_name, circle_id| Circle.find_by(:id => circle_id)}.select{|c|not c.nil?}
  169 + if selected_circles.present?
  170 + current_person.follow(profile, selected_circles)
  171 + render :text => _("You are now following %s") % profile.name, :status => 200
  172 + else
  173 + render :text => _("Select at least one circle to follow %s.") % profile.name, :status => 400
  174 + end
  175 + end
  176 + else
  177 + render_not_found
  178 + end
  179 + end
  180 +
  181 + def find_profile_circles
  182 + circles = Circle.where(:person => current_person, :profile_type => profile.class.name)
  183 + render :partial => 'blocks/profile_info_actions/circles', :locals => { :circles => circles, :profile_types => Circle.profile_types.to_a }
  184 + end
  185 +
  186 + def unfollow
  187 + if current_person.follows?(profile)
  188 + current_person.unfollow(profile)
  189 + end
  190 + redirect_url = params["redirect_to"] ? params["redirect_to"] : profile.url
  191 + redirect_to redirect_url
  192 + end
  193 +
154 194 def check_friendship
155 195 unless logged_in?
156 196 render :text => ''
... ... @@ -437,4 +477,8 @@ class ProfileController &lt; PublicController
437 477 [:image, :domains, :preferred_domain, :environment]
438 478 end
439 479  
  480 + def allow_followers?
  481 + render_not_found unless profile.allow_followers?
  482 + end
  483 +
440 484 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 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/helpers/profile_helper.rb
... ... @@ -11,7 +11,7 @@ module ProfileHelper
11 11 PERSON_CATEGORIES[:location] = [:address, :address_reference, :zip_code, :city, :state, :district, :country, :nationality]
12 12 PERSON_CATEGORIES[:work] = [:organization, :organization_website, :professional_activity]
13 13 PERSON_CATEGORIES[:study] = [:schooling, :formation, :area_of_study]
14   - PERSON_CATEGORIES[:network] = [:friends, :communities, :enterprises]
  14 + PERSON_CATEGORIES[:network] = [:friends, :followers, :followed_profiles, :communities, :enterprises]
15 15 PERSON_CATEGORIES.merge!(COMMON_CATEGORIES)
16 16  
17 17 ORGANIZATION_CATEGORIES = {}
... ... @@ -42,7 +42,8 @@ module ProfileHelper
42 42 :created_at => _('Profile created at'),
43 43 :members_count => _('Members'),
44 44 :privacy_setting => _('Privacy setting'),
45   - :article_tags => _('Tags')
  45 + :article_tags => _('Tags'),
  46 + :followed_profiles => _('Following')
46 47 }
47 48  
48 49 EXCEPTION = {
... ... @@ -144,6 +145,14 @@ module ProfileHelper
144 145 link_to(n_('One picture', '%{num} pictures', gallery.images.published.count) % { :num => gallery.images.published.count }, gallery.url)
145 146 end
146 147  
  148 + def treat_followers(followers)
  149 + link_to(profile.followers.count, {:action=>"followed", :controller=>"profile", :profile=>"#{profile.identifier}"})
  150 + end
  151 +
  152 + def treat_followed_profiles(followed_profiles)
  153 + link_to(profile.followed_profiles.count, {:action=>"following", :controller=>"profile", :profile=>"#{profile.identifier}"})
  154 + end
  155 +
147 156 def treat_events(events)
148 157 link_to events.published.count, :controller => 'events', :action => 'events'
149 158 end
... ...
app/jobs/notify_activity_to_profiles_job.rb
... ... @@ -19,8 +19,8 @@ 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 + # Notify all followers
  23 + ActionTrackerNotification.connection.execute("INSERT INTO action_tracker_notifications(profile_id, action_tracker_id) SELECT DISTINCT c.person_id, #{tracked_action.id} FROM profiles_circles AS p JOIN circles as c ON c.id = p.circle_id WHERE p.profile_id = #{tracked_action.user.id} AND (c.person_id NOT IN (SELECT atn.profile_id FROM action_tracker_notifications AS atn WHERE atn.action_tracker_id = #{tracked_action.id}))")
24 24  
25 25 if tracked_action.user.is_a? Organization
26 26 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, Circle.find_or_create_by(:person => person, :name =>_('memberships'), :profile_type => 'Community'))
25 26 end
26 27  
27 28 def title
... ...
app/models/article.rb
... ... @@ -538,13 +538,13 @@ class Article &lt; ApplicationRecord
538 538  
539 539 scope :display_filter, lambda {|user, profile|
540 540 return published if (user.nil? && profile && profile.public?)
541   - return [] if user.nil? || (profile && !profile.public? && !user.follows?(profile))
  541 + return [] if user.nil? || (profile && !profile.public? && !profile.in_social_circle?(user))
542 542 where(
543 543 [
544 544 "published = ? OR last_changed_by_id = ? OR profile_id = ? OR ?
545 545 OR (show_to_followers = ? AND ? AND profile_id IN (?))", true, user.id, user.id,
546 546 profile.nil? ? false : user.has_permission?(:view_private_content, profile),
547   - true, (profile.nil? ? true : user.follows?(profile)), ( profile.nil? ? (user.friends.select('profiles.id')) : [profile.id])
  547 + true, (profile.nil? ? true : profile.in_social_circle?(user)), ( profile.nil? ? (user.friends.select('profiles.id')) : [profile.id])
548 548 ]
549 549 )
550 550 }
... ...
app/models/block.rb
... ... @@ -89,7 +89,7 @@ class Block &lt; ApplicationRecord
89 89 end
90 90  
91 91 def display_to_user?(user)
92   - display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && user.follows?(owner))
  92 + display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && owner.in_social_circle?(user))
93 93 end
94 94  
95 95 def display_always(context)
... ...
app/models/circle.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +class Circle < ApplicationRecord
  2 + has_many :profile_followers
  3 + belongs_to :person
  4 +
  5 + attr_accessible :name, :person, :profile_type
  6 +
  7 + validates :name, presence: true
  8 + validates :person_id, presence: true
  9 + validates :profile_type, presence: true
  10 + validates :person_id, :uniqueness => {:scope => :name, :message => "can't add two circles with the same name"}
  11 +
  12 + validate :profile_type_must_be_in_list
  13 +
  14 + scope :by_owner, -> person{
  15 + where(:person => person)
  16 + }
  17 +
  18 + scope :with_name, -> name{
  19 + where(:name => name)
  20 + }
  21 +
  22 + def self.profile_types
  23 + {
  24 + _("Person") => Person.name,
  25 + _("Community") => Community.name,
  26 + _("Enterprise") => Enterprise.name
  27 + }
  28 + end
  29 +
  30 + def profile_type_must_be_in_list
  31 + valid_profile_types = Circle.profile_types.values
  32 + unless self.profile_type.in? valid_profile_types
  33 + self.errors.add(:profile_type, "invalid profile type")
  34 + end
  35 + end
  36 +
  37 +end
... ...
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, Circle.find_or_create_by(:person => favorite.person, :name =>_('favorites'), :profile_type => 'Enterprise'))
  12 + end
  13 +
10 14 protected
11 15  
12 16 def is_trackable?
... ...
app/models/friendship.rb
... ... @@ -9,11 +9,15 @@ 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, Circle.find_or_create_by(:person => friendship.person, :name => (friendship.group.blank? ? 'friendships': friendship.group), :profile_type => 'Person'))
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 +
  19 + circle = Circle.find_by(:person => friendship.person, :name => (friendship.group.blank? ? 'friendships': friendship.group) )
  20 + friendship.person.remove_profile_from_circle(friendship.friend, circle) if circle
17 21 end
18 22  
19 23 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 :circles
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,33 @@ class Person &lt; Profile
200 200 end
201 201 end
202 202  
  203 + def follow (profile, circles)
  204 + circles = [circles] unless circles.is_a?(Array)
  205 + circles.each do |new_circle|
  206 + ProfileFollower.create(profile: profile, circle: new_circle)
  207 + end
  208 + end
  209 +
  210 + def update_profile_circles (profile, new_circles)
  211 + profile_circles = ProfileFollower.with_profile(profile).with_follower(self).map(&:circle)
  212 + circles_to_add = new_circles - profile_circles
  213 + circles_to_remove = profile_circles - new_circles
  214 + circles_to_add.each do |new_circle|
  215 + ProfileFollower.create(profile: profile, circle: new_circle)
  216 + end
  217 +
  218 + ProfileFollower.where('circle_id IN (?) AND profile_id = ?',
  219 + circles_to_remove.map(&:id), profile.id).destroy_all
  220 + end
  221 +
  222 + def unfollow(profile)
  223 + ProfileFollower.with_follower(self).with_profile(profile).destroy_all
  224 + end
  225 +
  226 + def remove_profile_from_circle(profile, circle)
  227 + ProfileFollower.with_profile(profile).with_circle(circle).destroy_all
  228 + end
  229 +
203 230 def already_request_friendship?(person)
204 231 person.tasks.where(requestor_id: self.id, type: 'AddFriend', status: Task::Status::ACTIVE).first
205 232 end
... ... @@ -580,9 +607,12 @@ class Person &lt; Profile
580 607 person.has_permission?(:manage_friends, self)
581 608 end
582 609  
583   - protected
  610 + def followed_profiles
  611 + Profile.followed_by self
  612 + end
584 613  
585   - def followed_by?(profile)
586   - self == profile || self.is_a_friend?(profile)
  614 + def in_social_circle?(person)
  615 + self.is_a_friend?(person) || super
587 616 end
  617 +
588 618 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_followers
9 9  
10 10 # use for internationalizable human type names in search facets
11 11 # reimplement on subclasses
... ... @@ -206,6 +206,23 @@ class Profile &lt; ApplicationRecord
206 206 scope :more_active, -> { order 'activities_count DESC' }
207 207 scope :more_recent, -> { order "created_at DESC" }
208 208  
  209 + scope :followed_by, -> person{
  210 + distinct.select('profiles.*').
  211 + joins('left join profiles_circles ON profiles_circles.profile_id = profiles.id').
  212 + joins('left join circles ON circles.id = profiles_circles.circle_id').
  213 + where('circles.person_id = ?', person.id)
  214 + }
  215 +
  216 + scope :in_circle, -> circle{
  217 + distinct.select('profiles.*').
  218 + joins('left join profiles_circles ON profiles_circles.profile_id = profiles.id').
  219 + joins('left join circles ON circles.id = profiles_circles.circle_id').
  220 + where('circles.id = ?', circle.id)
  221 + }
  222 +
  223 + settings_items :allow_followers, :type => :boolean, :default => true
  224 + alias_method :allow_followers?, :allow_followers
  225 +
209 226 acts_as_trackable :dependent => :destroy
210 227  
211 228 has_many :profile_activities
... ... @@ -218,6 +235,9 @@ class Profile &lt; ApplicationRecord
218 235  
219 236 has_many :email_templates, :foreign_key => :owner_id
220 237  
  238 + has_many :profile_followers
  239 + has_many :followers, :class_name => 'Person', :through => :profile_followers, :source => :person
  240 +
221 241 # Although this should be a has_one relation, there are no non-silly names for
222 242 # a foreign key on article to reference the template to which it is
223 243 # welcome_page... =P
... ... @@ -769,6 +789,7 @@ private :generate_url, :url_options
769 789 else
770 790 self.affiliate(person, Profile::Roles.admin(environment.id), attributes) if members.count == 0
771 791 self.affiliate(person, Profile::Roles.member(environment.id), attributes)
  792 + person.follow(self, Circle.find_or_create_by(:person => person, :name =>_('memberships'), :profile_type => 'Community'))
772 793 end
773 794 person.tasks.pending.of("InviteMember").select { |t| t.data[:community_id] == self.id }.each { |invite| invite.cancel }
774 795 remove_from_suggestion_list person
... ... @@ -1112,7 +1133,11 @@ private :generate_url, :url_options
1112 1133 end
1113 1134  
1114 1135 def followed_by?(person)
1115   - person.is_member_of?(self)
  1136 + (person == self) || (person.in? self.followers)
  1137 + end
  1138 +
  1139 + def in_social_circle?(person)
  1140 + (person == self) || (person.is_member_of?(self))
1116 1141 end
1117 1142  
1118 1143 def display_private_info_to?(user)
... ... @@ -1153,4 +1178,8 @@ private :generate_url, :url_options
1153 1178 def allow_destroy?(person = nil)
1154 1179 person.kind_of?(Profile) && person.has_permission?('destroy_profile', self)
1155 1180 end
  1181 +
  1182 + def in_circle?(circle, follower)
  1183 + ProfileFollower.with_follower(follower).with_circle(circle).with_profile(self).present?
  1184 + end
1156 1185 end
... ...
app/models/profile_follower.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +class ProfileFollower < ApplicationRecord
  2 + self.table_name = :profiles_circles
  3 + track_actions :new_follower, :after_create, :keep_params => ["follower.name", "follower.url", "follower.profile_custom_icon"], :custom_user => :profile
  4 +
  5 + attr_accessible :profile, :circle
  6 +
  7 + belongs_to :profile
  8 + belongs_to :circle
  9 +
  10 + has_one :person, through: :circle
  11 + alias follower person
  12 +
  13 + validates_presence_of :profile_id, :circle_id
  14 + validates :profile_id, :uniqueness => {:scope => :circle_id, :message => "can't put a profile in the same circle twice"}
  15 +
  16 + scope :with_follower, -> person{
  17 + joins(:circle).where('circles.person_id = ?', person.id)
  18 + }
  19 +
  20 + scope :with_profile, -> profile{
  21 + where(:profile => profile)
  22 + }
  23 +
  24 + scope :with_circle, -> circle{
  25 + where(:circle => circle)
  26 + }
  27 +
  28 +end
... ...
app/views/blocks/profile_info_actions/_circles.html.erb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +<div class="circles" id='circles-list'>
  2 +
  3 + <%= form_for :circles, :url => {:controller => 'profile', :action => 'follow'}, :html => {:id => "follow-circles-form"} do |f|%>
  4 + <%= render partial: "blocks/profile_info_actions/select_circles", :locals => {:circles => circles} %>
  5 +
  6 + <div id="circle-actions">
  7 + <%= submit_button :ok, _("Follow") %>
  8 + <input type="button" value="<%= _("Cancel") %>" id="cancel-set-circle" class="button with-text icon-cancel"/>
  9 + </div>
  10 + <% end %>
  11 +</div>
... ...
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_followers?%>
  3 +<li>
  4 + <% follow = user.follows?(profile) %>
  5 + <%= button(:unfollow, content_tag('span', _('Unfollow')), {:profile => profile.identifier, :controller => 'profile', :action => 'unfollow'}, :id => 'action-unfollow', :title => _("Unfollow"), :style => follow ? "" : "display: none;") %>
  6 + <%= button(:ok, content_tag('span', _('Follow')), {:profile => profile.identifier, :controller => 'profile', :action => 'find_profile_circles'}, :id => 'action-follow', :title => _("Follow"), :style => follow ? "display: none;" : "") %>
  7 + <div id="circles-container" style="display: none;">
  8 + </div>
  9 +</li>
  10 +<%end%>
2 11 <%= render_environment_features(:profile_actions) %>
... ...
app/views/blocks/profile_info_actions/_select_circles.html.erb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +<div class="circles" id='circles-list'>
  2 + <p><%= _("Select the circles for %s") % profile.name %></p>
  3 + <div id="circles-checkboxes">
  4 + <% circles.each do |circle| %>
  5 + <div class="circle">
  6 + <%= labelled_check_box circle.name, "circles[#{circle.name}]", circle.id, profile.in_circle?(circle, current_person) %>
  7 + </div>
  8 + <% end %>
  9 + </div>
  10 +
  11 + <a href="#" id="new-circle">
  12 + <span><%= _("New Circle") %></span>
  13 + </a>
  14 +
  15 + <div id="new-circle-form" style="display: none;">
  16 + <%= labelled_text_field _('Circle name') , 'circle[name]', "",:id => 'text-field-name-new-circle'%>
  17 + <%= hidden_field_tag('circle[profile_type]', profile.class.name) %>
  18 +
  19 + <%= button_bar do %>
  20 + <%= button(:save, _('Create'), {:profile => profile.identifier, :controller => 'circles', :action => 'xhr_create'}, :id => "new-circle-submit") %>
  21 + <%= button(:cancel, _('Cancel'), '#', :id => "new-circle-cancel") %>
  22 + <% end %>
  23 + </div>
  24 +</div>
... ...
app/views/circles/_circle_checkbox.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<div class="circle">
  2 + <%= labelled_check_box circle.name, "circles[#{circle.name}]", circle.id %>
  3 +</div>
... ...
app/views/circles/_form.html.erb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<%= error_messages_for :circle %>
  2 +
  3 +<%= labelled_form_for :circle, :url => (mode == :edit) ? {:action => 'update', :id => circle} : {:action => 'create'} do |f| %>
  4 +
  5 + <%= required_fields_message %>
  6 +
  7 + <%= required f.text_field(:name) %>
  8 +
  9 + <%= required labelled_form_field _("Profile type"), f.select(:profile_type, Circle.profile_types.to_a) %>
  10 +
  11 + <%= button_bar do %>
  12 + <%= submit_button('save', (mode == :edit) ? _('Save changes') : _('Create circle'), :cancel => {:action => 'index'} ) %>
  13 + <% end %>
  14 +<% end %>
... ...
app/views/circles/edit.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1><%= _("Edit circle") %></h1>
  2 +
  3 +<%= render :partial => 'form', :locals => { :mode => :edit, :circle => @circle, :profile_types => @profile_types } %>
... ...
app/views/circles/index.html.erb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +<h1><%= _('Manage circles') %></h1>
  2 +
  3 +<table>
  4 + <tr>
  5 + <th><%= _('Circle name') %></th>
  6 + <th><%= _('Profile type') %></th>
  7 + <th><%= _('Actions') %></th>
  8 + </tr>
  9 + <% @circles.each do |circle| %>
  10 + <tr>
  11 + <td>
  12 + <%= circle.name %>
  13 + </td>
  14 + <td>
  15 + <%= _(circle.profile_type) %>
  16 + </td>
  17 + <td>
  18 + <div style="text-align: center;">
  19 + <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => circle %>
  20 + <%= button_without_text :delete, _('Delete'), { :action => 'destroy', :id => circle }, { "data-method" => "POST" } %>
  21 + </div>
  22 + </td>
  23 + </tr>
  24 + <% end %>
  25 +</table>
  26 +
  27 +<%= button_bar do %>
  28 + <%= button :add, _('Create a new circle'), :action => 'new' %>
  29 + <%= button :back, _('Back to control panel'), :controller => 'profile_editor' %>
  30 +<% end %>
... ...
app/views/circles/new.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1><%= _("New circle") %></h1>
  2 +
  3 +<%= render :partial => 'form', :locals => { :mode => :new, :circle => @circle, :profile_types => @profile_types } %>
... ...
app/views/followers/_edit_circles_modal.html.erb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<div class="circles" id='circles-list'>
  2 + <%= form_for :circles, :url => {:controller => 'followers', :action => 'update_category'}, :html => {:id => "follow-circles-form"} do |f|%>
  3 + <%= render partial: "blocks/profile_info_actions/select_circles", :locals => {:circles => circles, :profile => profile} %>
  4 +
  5 + <%= hidden_field_tag('followed_profile_id', profile.id) %>
  6 +
  7 + <div id="circle-actions">
  8 + <div id="actions-container">
  9 + <%= submit_button('save', _('Save')) %>
  10 + <%= modal_close_button _("Cancel") %>
  11 + </div>
  12 + </div>
  13 + <% end %>
  14 +</div>
... ...
app/views/followers/_profile_list.html.erb 0 → 100644
... ... @@ -0,0 +1,16 @@
  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 + <div class="controll">
  7 + <%= button_without_text :remove, content_tag('span',_('unfollow')),
  8 + { :controller => "profile", :profile => profile.identifier , :action => 'unfollow', :redirect_to => url_for({:controller => "followers", :profile => user.identifier}) },
  9 + :title => _('remove') %>
  10 + <%= modal_icon_button :edit, _('change category'),
  11 + url_for(:controller => 'followers', :action => 'set_category_modal',
  12 + :followed_profile_id => profile.id) %>
  13 + </div><!-- end class="controll" -->
  14 + </li>
  15 + <% end %>
  16 +</ul>
... ...
app/views/followers/index.html.erb 0 → 100644
... ... @@ -0,0 +1,27 @@
  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 + <%= labelled_select(_('Profile type')+': ', :filter_profile_type, :last, :first, @active_filter, @profile_types, :id => "profile-type-filter") %>
  20 +
  21 + <%= render :partial => 'profile_list', :locals => { :profiles => @followed_people } %>
  22 +
  23 + <br style="clear:both" />
  24 + <%= pagination_links @followed_people, :param_name => 'npage' %>
  25 +<% end %>
  26 +
  27 +</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/_follow.html.erb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +<% cache_timeout(profile.friends_cache_key(params), 4.hours) do %>
  2 + <ul class='profile-list'>
  3 + <% follow.each do |follower| %>
  4 + <%= profile_image_link(follower) %>
  5 + <% end%>
  6 + </ul>
  7 +
  8 + <div id='pagination-profiles'>
  9 + <%= pagination_links follow, :param_name => 'npage' %>
  10 + </div>
  11 +<% end %>
  12 +
  13 +<%= button_bar do %>
  14 + <%= button :back, _('Go back'), { :controller => 'profile' } %>
  15 + <% if user == profile %>
  16 + <%= button :edit, _('Manage followed people'), :controller => 'friends', :action => 'index', :profile => profile.identifier %>
  17 + <% end %>
  18 +<% end %>
... ...
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/followed.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<div class="common-profile-list-block">
  2 +
  3 +<h1><%= _("%s is followed by") % profile.name %></h1>
  4 +
  5 +<%= render :partial => 'follow', :locals => {:follow => @followed_by} %>
  6 +
  7 +</div>
... ...
app/views/profile/following.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<div class="common-profile-list-block">
  2 +
  3 +<h1><%= _("%s is following") % profile.name %></h1>
  4 +
  5 +<%= render :partial => 'follow', :locals => {:follow => @followed_people} %>
  6 +
  7 +</div>
... ...
app/views/profile_editor/edit.html.erb
... ... @@ -23,9 +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_followers]', true, @profile.allow_followers?, :class => "person-can-be-followed" %>
  28 + </div>
27 29 <% if profile.person? %>
28   - <div>
  30 + <div id="profile_allow_follows">
29 31 <%= labelled_radio_button _('Public &mdash; show my contents to all internet users').html_safe, 'profile_data[public_profile]', true, @profile.public_profile? %>
30 32 </div>
31 33 <div>
... ...
app/views/profile_editor/index.html.erb
... ... @@ -72,6 +72,11 @@
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'), 'edit-profile', :controller => :followers) %>
  77 + <%= control_panel_button(_('Manage circles'), 'edit-profile-group', :controller => :circles) %>
  78 + <% end %>
  79 +
75 80 <% @plugins.dispatch(:control_panel_buttons).each do |button| %>
76 81 <%= control_panel_button(button[:title], button[:icon], button[:url], button[:html_options]) %>
77 82 <% 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,42 @@
  1 +class CreateProfileFollowersTable < ActiveRecord::Migration
  2 + def up
  3 + create_table :profiles_circles do |t|
  4 + t.column :profile_id, :integer
  5 + t.column :circle_id, :integer
  6 + t.timestamps
  7 + end
  8 +
  9 + create_table :circles do |t|
  10 + t.column :name, :string
  11 + t.belongs_to :person
  12 + t.column :profile_type, :string, :null => false
  13 + end
  14 +
  15 + add_foreign_key :profiles_circles, :circles, :on_delete => :nullify
  16 +
  17 + add_index :profiles_circles, [:profile_id, :circle_id], :name => "profiles_circles_composite_key_index", :unique => true
  18 + add_index :circles, [:person_id, :name], :name => "circles_composite_key_index", :unique => true
  19 +
  20 + #insert one category for each friend group a person has
  21 + execute("INSERT INTO circles(name, person_id, profile_type) SELECT DISTINCT (CASE WHEN (f.group IS NULL OR f.group = '') THEN 'friendships' ELSE f.group END), f.person_id, 'Person' FROM friendships as f")
  22 + #insert 'memberships' category if a person is in a community as a member, moderator or profile admin
  23 + execute("INSERT INTO circles(name, person_id, profile_type) SELECT DISTINCT 'memberships', ra.accessor_id, 'Community' FROM role_assignments as ra JOIN roles ON ra.role_id = roles.id WHERE roles.name IN ('Member','Moderator','Profile Administrator')")
  24 + #insert 'favorites' category if a person has any favorited enterprise
  25 + execute("INSERT INTO circles(name, person_id, profile_type) SELECT DISTINCT 'favorites', person_id, 'Enterprise' FROM favorite_enterprise_people")
  26 +
  27 + #insert a follower entry for each friend, with the category the same as the friendship group or equals 'friendships'
  28 + execute("INSERT INTO profiles_circles(profile_id, circle_id) SELECT DISTINCT f.friend_id, c.id FROM friendships as f JOIN circles as c ON f.person_id = c.person_id WHERE c.name = f.group OR c.name = 'friendships'")
  29 + #insert a follower entry for each favorited enterprise, with the category 'favorites'
  30 + execute("INSERT INTO profiles_circles(profile_id, circle_id) SELECT DISTINCT f.enterprise_id, c.id FROM favorite_enterprise_people AS f JOIN circles as c ON f.person_id = c.person_id WHERE c.name = 'favorites' ")
  31 + #insert a follower entry for each community a person participates as a member, moderator or admininstrator
  32 + execute("INSERT INTO profiles_circles(profile_id, circle_id) SELECT DISTINCT ra.resource_id, c.id FROM role_assignments as ra JOIN roles ON ra.role_id = roles.id JOIN circles as c ON ra.accessor_id = c.person_id WHERE roles.name IN ('Member','Moderator','Profile Administrator') AND c.name = 'memberships'")
  33 + end
  34 +
  35 + def down
  36 + remove_foreign_key :profiles_circles, :circles
  37 + remove_index :profiles_circles, :name => "profiles_circles_composite_key_index"
  38 + remove_index :circles, :name => "circles_composite_key_index"
  39 + drop_table :circles
  40 + drop_table :profiles_circles
  41 + end
  42 +end
... ...
features/follow_profile.feature 0 → 100644
... ... @@ -0,0 +1,114 @@
  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 circles
  14 + | name | profile_type |
  15 + | Family | Person |
  16 + | Work | Community |
  17 + | Favorites | Community |
  18 +
  19 + @selenium
  20 + Scenario: Common noofero user follow a community
  21 + Given I am logged in as "johnsnow"
  22 + When I go to nightswatch's homepage
  23 + When I follow "Follow"
  24 + When I check "Work"
  25 + When I press "Follow"
  26 + And I wait 1 second
  27 + Then "johnsnow" should be a follower of "nightswatch" in circle "Work"
  28 +
  29 + @selenium
  30 + Scenario: Common noofero user follow a community in more than one circle
  31 + Given I am logged in as "johnsnow"
  32 + When I go to nightswatch's homepage
  33 + When I follow "Follow"
  34 + When I check "Work"
  35 + When I check "Favorites"
  36 + When I press "Follow"
  37 + And I wait 1 second
  38 + Then "johnsnow" should be a follower of "nightswatch" in circle "Work"
  39 + And "johnsnow" should be a follower of "nightswatch" in circle "Favorites"
  40 +
  41 + @selenium
  42 + Scenario: No see another profile type circle when following a community
  43 + Given I am logged in as "johnsnow"
  44 + When I go to nightswatch's homepage
  45 + When I follow "Follow"
  46 + Then I should not see "Family"
  47 + And I should see "Favorites"
  48 + And I should see "Work"
  49 +
  50 + @selenium
  51 + Scenario: Common noofero user follow a community then cancel the action
  52 + Given I am logged in as "johnsnow"
  53 + When I go to nightswatch's homepage
  54 + When I follow "Follow"
  55 + When I press "Cancel"
  56 + And I wait 1 second
  57 + Then I should not see "Family"
  58 + And I should not see "Favorites"
  59 + And I should not see "Work"
  60 + And I should not see "New Circle"
  61 + Then "johnsnow" should not be a follower of "nightswatch"
  62 +
  63 + @selenium
  64 + Scenario: Common noofero user cancel the circle creation action
  65 + Given I am logged in as "johnsnow"
  66 + When I go to nightswatch's homepage
  67 + When I follow "Follow"
  68 + When I follow "New Circle"
  69 + When I press "Cancel"
  70 + And I wait 1 second
  71 + Then I should not see "Circle name"
  72 + And I should not see "Create"
  73 +
  74 + @selenium
  75 + Scenario: Noosfero user see new circle option when following a community
  76 + Given I am logged in as "johnsnow"
  77 + When I go to nightswatch's homepage
  78 + When I follow "Follow"
  79 + Then I should see "New Circle"
  80 +
  81 + @selenium
  82 + Scenario: Common noofero user follow a community with a new circle
  83 + Given I am logged in as "johnsnow"
  84 + When I go to nightswatch's homepage
  85 + When I follow "Follow"
  86 + When I follow "New Circle"
  87 + And I fill in "text-field-name-new-circle" with "Winterfell"
  88 + When I follow "Create"
  89 + When I check "Winterfell"
  90 + When I press "Follow"
  91 + And I wait 1 second
  92 + Then "johnsnow" should be a follower of "nightswatch" in circle "Winterfell"
  93 +
  94 + @selenium
  95 + Scenario: Common noofero user create a new circle when following a community
  96 + Given I am logged in as "johnsnow"
  97 + When I go to nightswatch's homepage
  98 + When I follow "Follow"
  99 + When I follow "New Circle"
  100 + And I fill in "text-field-name-new-circle" with "Winterfell"
  101 + When I follow "Create"
  102 + And I wait 1 second
  103 + Then "johnsnow" should have the circle "Winterfell" with profile type "Community"
  104 + Then I should not see "Circle name"
  105 + Then I should not see "Create"
  106 +
  107 + @selenium
  108 + Scenario: Common noofero user unfollow a community
  109 + Given "johnsnow" is a follower of "nightswatch" in circle "Work"
  110 + And I am logged in as "johnsnow"
  111 + When I go to nightswatch's homepage
  112 + When I follow "Unfollow"
  113 + Then "johnsnow" should not be a follower of "nightswatch"
  114 +
... ...
features/step_definitions/followers_steps.rb 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +Given /^the user "(.+)" has the following circles$/ do |user_name,table|
  2 + person = User.find_by(:login => user_name).person
  3 + table.hashes.each do |circle|
  4 + Circle.create!(:person => person, :name => circle[:name], :profile_type => circle[:profile_type])
  5 + end
  6 +end
  7 +
  8 +Then /^"(.+)" should be a follower of "(.+)" in circle "(.+)"$/ do |person, profile, circle|
  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 + circle = Circle.find_by(:name => circle, :person => person)
  15 + ProfileFollower.find_by(:circle => circle, :profile => profile).should_not == nil
  16 +end
  17 +
  18 +Then /^"(.+)" should not be a follower of "(.+)"$/ do |person, profile|
  19 + profile = Profile.find_by(identifier: profile)
  20 + followers = profile.followers
  21 + person = Person.find_by(identifier: person)
  22 + followers.should_not include(person)
  23 +end
  24 +
  25 +Given /^"(.+)" is a follower of "(.+)" in circle "(.+)"$/ do |person, profile, circle|
  26 + profile = Profile.find_by(identifier: profile)
  27 + person = Person.find_by(identifier: person)
  28 + circle = Circle.find_by(:name => circle, :person => person)
  29 + ProfileFollower.create!(:circle => circle, :profile => profile)
  30 +end
  31 +
  32 +Then /^"(.+)" should have the circle "(.+)" with profile type "(.+)"$/ do |user_name, circle, profile_type|
  33 + person = User.find_by(:login => user_name).person
  34 + Circle.find_by(:name => circle, :person => person, :profile_type => profile_type).should_not == nil
  35 +end
... ...
public/javascripts/application.js
... ... @@ -26,6 +26,8 @@
26 26 *= require pagination.js
27 27 * views speficics
28 28 *= require add-and-join.js
  29 +*= require followers.js
  30 +*= require manage-followers.js
29 31 *= require report-abuse.js
30 32 *= require autogrow.js
31 33 *= require require_login.js
... ... @@ -550,6 +552,11 @@ function loading_for_button(selector) {
550 552 jQuery(selector).css('cursor', 'progress');
551 553 }
552 554  
  555 +function hide_loading_for_button(selector) {
  556 + selector.css("cursor","");
  557 + $(".small-loading").remove();
  558 +}
  559 +
553 560 function new_qualifier_row(selector, select_qualifiers, delete_button) {
554 561 index = jQuery(selector + ' tr').size() - 1;
555 562 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,81 @@
  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 + $("#circles-container").html(data);
  9 + $("#circles-container").fadeIn();
  10 + });
  11 + }).always(function() {
  12 + hide_loading_for_button(button);
  13 + });
  14 + return false;
  15 +});
  16 +
  17 +$("#cancel-set-circle").live("click", function() {
  18 + $("#circles-container").fadeOut("fast", function() {
  19 + $("#action-follow").fadeIn();
  20 + });
  21 + return false;
  22 +});
  23 +
  24 +$("#new-circle").live("click", function() {
  25 + $(this).fadeOut();
  26 + $("#circle-actions").fadeOut("fast", function() {
  27 + $("#new-circle-form").fadeIn();
  28 + });
  29 + return false;
  30 +});
  31 +
  32 +$("#new-circle-cancel").live("click", function() {
  33 + $("#new-circle-form").fadeOut("fast", function() {
  34 + $("#circle-actions").fadeIn();
  35 + $("#new-circle").fadeIn();
  36 + $("#text-field-name-new-circle").val('')
  37 + });
  38 + return false;
  39 +});
  40 +
  41 +$('#follow-circles-form').live("submit", function() {
  42 + var valuesToSubmit = $(this).serialize();
  43 + $.ajax({
  44 + type: "POST",
  45 + url: $(this).attr('action'),
  46 + data: valuesToSubmit,
  47 + dataType: "JSON",
  48 + statusCode: {
  49 + 200: function(response){
  50 + $("#circles-container").fadeOut();
  51 + $("#action-unfollow").fadeIn();
  52 + $.colorbox.close();
  53 + display_notice(response.responseText);
  54 + },
  55 + 400: function(response) {
  56 + display_notice(response.responseText);
  57 + }
  58 + }
  59 + })
  60 + return false;
  61 +});
  62 +
  63 +$("#new-circle-submit").live("click", function() {
  64 + $.ajax({
  65 + method: 'POST',
  66 + url: $(this).attr("href"),
  67 + data: {'circle[name]': $("#text-field-name-new-circle").val(),
  68 + 'circle[profile_type]': $("#circle_profile_type").val()},
  69 + success: function(response) {
  70 + $('#circles-checkboxes').append(response);
  71 + },
  72 + error: function(response) {
  73 + display_notice(response.responseText);
  74 + },
  75 + complete: function(response) {
  76 + $("#text-field-name-new-circle").val('')
  77 + $("#new-circle-cancel").trigger("click");
  78 + }
  79 + })
  80 + return false;
  81 +});
... ...
public/javascripts/manage-followers.js 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +$('#profile-type-filter').live('change', function() {
  2 + var filter_type = $(this).val();
  3 + $(".profile-list").addClass("fetching");
  4 + $.get(window.location.pathname, {filter: filter_type}, function(data) {
  5 + $(".main-content").html(data);
  6 + }).fail(function(data) {
  7 + $(".profile-list").removeClass("fetching");
  8 + });
  9 +});
... ...
public/stylesheets/blocks/profile-info.scss
... ... @@ -99,3 +99,44 @@
99 99 margin: 0px 0px 5px 0px;
100 100 padding: 2px;
101 101 }
  102 +#circles-container {
  103 + background-color: #eee;
  104 + padding: 5px;
  105 + display: flex;
  106 +}
  107 +#circles-container p {
  108 + font-size: 12px;
  109 + margin-bottom: 5px;
  110 +}
  111 +#circle-actions {
  112 + margin-top: 15px;
  113 +}
  114 +#new-category-field-actions-block {
  115 + float: left;
  116 + width: 80%;
  117 + margin-bottom: 10px;
  118 +}
  119 +#new-circle-form {
  120 + margin-top: 10px;
  121 +}
  122 +#new-circle-form input {
  123 + width: 90px;
  124 +}
  125 +#new-circle-form select {
  126 + margin-top: 2px;
  127 + width: 95px;
  128 +}
  129 +#new-circle-form label {
  130 + font-size: 10px;
  131 + margin-right: 5px;
  132 +}
  133 +#new-circle-form .button-bar {
  134 + padding-top: 0px;
  135 +}
  136 +#new-circle-form .button {
  137 + width: 60px;
  138 +}
  139 +#new-circle-form .button-bar .button {
  140 + width: 40px;
  141 + font-size: 10px;
  142 +}
... ...
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-editor.scss
... ... @@ -263,3 +263,6 @@
263 263 -webkit-border-radius: 5px;
264 264 }
265 265  
  266 +#profile_allow_follows {
  267 + margin-top: 10px;
  268 +}
... ...
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 }
... ...
public/stylesheets/profile.scss
... ... @@ -39,3 +39,8 @@
39 39 width: 470px;
40 40 overflow-x: hidden;
41 41 }
  42 +
  43 +#circles-checkboxes {
  44 + text-align: left;
  45 + margin-left: 15%;
  46 +}
... ...
test/functional/circles_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,129 @@
  1 +require_relative "../test_helper"
  2 +require 'circles_controller'
  3 +
  4 +class CirclesControllerTest < ActionController::TestCase
  5 +
  6 + def setup
  7 + @controller = CirclesController.new
  8 + @person = create_user('person').person
  9 + login_as(@person.identifier)
  10 + end
  11 +
  12 + should 'return all circles of a profile' do
  13 + circle1 = Circle.create!(:name => "circle1", :person => @person, :profile_type => 'Person')
  14 + circle2 = Circle.create!(:name => "circle2", :person => @person, :profile_type => 'Person')
  15 + get :index, :profile => @person.identifier
  16 +
  17 + assert_equivalent [circle1, circle2], assigns[:circles]
  18 + end
  19 +
  20 + should 'initialize an empty circle for creation' do
  21 + get :new, :profile => @person.identifier
  22 + assert_nil assigns[:circle].id
  23 + assert_nil assigns[:circle].name
  24 + end
  25 +
  26 + should 'create a new circle' do
  27 + assert_difference '@person.circles.count' do
  28 + post :create, :profile => @person.identifier,
  29 + :circle => { :name => 'circle' , :profile_type => Person.name}
  30 + end
  31 + assert_redirected_to :action => :index
  32 + end
  33 +
  34 + should 'not create a circle without a name' do
  35 + assert_no_difference '@person.circles.count' do
  36 + post :create, :profile => @person.identifier, :circle => { :name => nil }
  37 + end
  38 + assert_template :new
  39 + end
  40 +
  41 + should 'retrieve an existing circle when editing' do
  42 + circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  43 + get :edit, :profile => @person.identifier, :id => circle.id
  44 + assert_equal circle.name, assigns[:circle].name
  45 + end
  46 +
  47 + should 'return 404 when editing a circle 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 circle' do
  53 + circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  54 + post :update, :profile => @person.identifier, :id => circle.id,
  55 + :circle => { :name => "new name" }
  56 +
  57 + circle.reload
  58 + assert_equal "new name", circle.name
  59 + assert_redirected_to :action => :index
  60 + end
  61 +
  62 + should 'not update an existing circle without a name' do
  63 + circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  64 + post :update, :profile => @person.identifier, :id => circle.id,
  65 + :circle => { :name => nil }
  66 +
  67 + circle.reload
  68 + assert_equal "circle", circle.name
  69 + assert_template :edit
  70 + end
  71 +
  72 + should 'return 404 when updating a circle that does not exist' do
  73 + post :update, :profile => @person.identifier, :id => "nope", :name => "new name"
  74 + assert_response 404
  75 + end
  76 +
  77 + should 'destroy an existing circle and update related profiles' do
  78 + circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  79 + follower = fast_create(ProfileFollower, :profile_id => fast_create(Person).id,
  80 + :circle_id => circle.id)
  81 +
  82 + assert_difference "@person.circles.count", -1 do
  83 + post :destroy, :profile => @person.identifier, :id => circle.id
  84 + end
  85 +
  86 + follower.reload
  87 + assert_nil follower.circle
  88 + end
  89 +
  90 + should 'not destroy an existing circle if action is not post' do
  91 + circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  92 +
  93 + assert_no_difference "@person.circles.count" do
  94 + get :destroy, :profile => @person.identifier, :id => circle.id
  95 + end
  96 + assert_response 404
  97 + end
  98 +
  99 + should 'return 404 when deleting and circle that does not exist' do
  100 + get :destroy, :profile => @person.identifier, :id => "nope"
  101 + assert_response 404
  102 + end
  103 +
  104 + should 'return 404 for xhr_create if request is not xhr' do
  105 + post :xhr_create, :profile => @person.identifier
  106 + assert_response 404
  107 + end
  108 +
  109 + should 'return 400 if not possible to create circle via xhr' do
  110 + xhr :post, :xhr_create, :profile => @person.identifier,
  111 + :circle => { :name => 'Invalid Circle' }
  112 + assert_response 400
  113 + end
  114 +
  115 + should 'create a new circle via xhr' do
  116 + xhr :post, :xhr_create, :profile => @person.identifier,
  117 + :circle => { :name => 'A Brand New Circle',
  118 + :profile_type => Person.name }
  119 + assert_response 201
  120 + assert_match /A Brand New Circle/, response.body
  121 + end
  122 +
  123 + should 'not create a new circle via xhr with an invalid profile_type' do
  124 + xhr :post, :xhr_create, :profile => @person.identifier,
  125 + :circle => { :name => 'A Brand New Circle',
  126 + :profile_type => '__invalid__' }
  127 + assert_response 400
  128 + end
  129 +end
... ...
test/functional/followers_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,58 @@
  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 + circle = Circle.create!(:person=> @profile, :name => "Zombies", :profile_type => 'Person')
  13 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle.id)
  14 +
  15 + get :index, :profile => @profile.identifier
  16 + assert_includes assigns(:followed_people), person
  17 + end
  18 +
  19 + should 'return filtered followed people list' do
  20 + login_as(@profile.identifier)
  21 + person = fast_create(Person)
  22 + community = fast_create(Community)
  23 + circle = Circle.create!(:person=> @profile, :name => "Zombies", :profile_type => 'Person')
  24 + circle2 = Circle.create!(:person=> @profile, :name => "Teams", :profile_type => 'Community')
  25 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle.id)
  26 + fast_create(ProfileFollower, :profile_id => community.id, :circle_id => circle2.id)
  27 +
  28 + get :index, :profile => @profile.identifier, :filter => "Community"
  29 + assert_equal assigns(:followed_people), [community]
  30 +
  31 + get :index, :profile => @profile.identifier, :filter => "Person"
  32 + assert_equal assigns(:followed_people), [person]
  33 + end
  34 +
  35 + should 'redirect to login page if not logged in' do
  36 + get :index, :profile => @profile.identifier
  37 + assert_redirected_to :controller => 'account', :action => 'login'
  38 + end
  39 +
  40 + should 'render set category modal' do
  41 + login_as(@profile.identifier)
  42 + person = fast_create(Person)
  43 + get :set_category_modal, :profile => @profile.identifier, :followed_profile_id => person.id
  44 + assert_tag :tag => "input", :attributes => { :id => "followed_profile_id", :value => person.id }
  45 + end
  46 +
  47 + should 'update followed person category' do
  48 + login_as(@profile.identifier)
  49 + person = fast_create(Person)
  50 + circle = Circle.create!(:person=> @profile, :name => "Zombies", :profile_type => 'Person')
  51 + circle2 = Circle.create!(:person=> @profile, :name => "DotA", :profile_type => 'Person')
  52 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle.id)
  53 +
  54 + post :update_category, :profile => @profile.identifier, :circles => {"DotA"=> circle2.id}, :followed_profile_id => person.id
  55 + assert_equivalent ProfileFollower.with_profile(person).with_follower(@profile).map(&:circle), [circle2]
  56 + end
  57 +
  58 +end
... ...
test/functional/profile_controller_test.rb
... ... @@ -771,12 +771,15 @@ 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 + circle = Circle.create!(:person=> profile, :name => "Zombies", :profile_type => 'Person')
  776 +
775 777 p2 = create_user.person
776   - refute profile.is_a_friend?(p2)
  778 + refute profile.follows?(p2)
777 779 p3 = create_user.person
778   - p3.add_friend(profile)
779   - assert p3.is_a_friend?(profile)
  780 + profile.follow(p3, circle)
  781 + assert profile.follows?(p3)
  782 +
780 783 ActionTracker::Record.destroy_all
781 784  
782 785 scrap1 = create(Scrap, defaults_for_scrap(:sender => p2, :receiver => p3))
... ... @@ -964,7 +967,11 @@ class ProfileControllerTest &lt; ActionController::TestCase
964 967 should 'have activities defined if logged in and is following profile' do
965 968 login_as(profile.identifier)
966 969 p1= fast_create(Person)
967   - p1.add_friend(profile)
  970 +
  971 + circle = Circle.create!(:person=> profile, :name => "Zombies", :profile_type => 'Person')
  972 +
  973 + profile.follow(p1, circle)
  974 +
968 975 ActionTracker::Record.destroy_all
969 976 get :index, :profile => p1.identifier
970 977 assert_equal [], assigns(:activities)
... ... @@ -1932,4 +1939,110 @@ class ProfileControllerTest &lt; ActionController::TestCase
1932 1939 assert_redirected_to :controller => 'account', :action => 'login'
1933 1940 end
1934 1941  
  1942 + should 'not follow a user without defining a circle' do
  1943 + login_as(@profile.identifier)
  1944 + person = fast_create(Person)
  1945 + assert_no_difference 'ProfileFollower.count' do
  1946 + post :follow, :profile => person.identifier, :circles => {}
  1947 + end
  1948 + end
  1949 +
  1950 + should "not follow user if not logged" do
  1951 + person = fast_create(Person)
  1952 + get :follow, :profile => person.identifier
  1953 +
  1954 + assert_redirected_to :controller => 'account', :action => 'login'
  1955 + end
  1956 +
  1957 + should 'follow a user with a circle' do
  1958 + login_as(@profile.identifier)
  1959 + person = fast_create(Person)
  1960 +
  1961 + circle = Circle.create!(:person=> @profile, :name => "Zombies", :profile_type => 'Person')
  1962 +
  1963 + assert_difference 'ProfileFollower.count' do
  1964 + post :follow, :profile => person.identifier, :circles => {"Zombies" => circle.id}
  1965 + end
  1966 + end
  1967 +
  1968 + should 'follow a user with more than one circle' do
  1969 + login_as(@profile.identifier)
  1970 + person = fast_create(Person)
  1971 +
  1972 + circle = Circle.create!(:person=> @profile, :name => "Zombies", :profile_type => 'Person')
  1973 + circle2 = Circle.create!(:person=> @profile, :name => "Brainsss", :profile_type => 'Person')
  1974 +
  1975 + assert_difference 'ProfileFollower.count', 2 do
  1976 + post :follow, :profile => person.identifier, :circles => {"Zombies" => circle.id, "Brainsss"=> circle2.id}
  1977 + end
  1978 + end
  1979 +
  1980 + should 'not follow a user with no circle selected' do
  1981 + login_as(@profile.identifier)
  1982 + person = fast_create(Person)
  1983 +
  1984 + circle = Circle.create!(:person=> @profile, :name => "Zombies", :profile_type => 'Person')
  1985 + circle2 = Circle.create!(:person=> @profile, :name => "Brainsss", :profile_type => 'Person')
  1986 +
  1987 + assert_no_difference 'ProfileFollower.count' do
  1988 + post :follow, :profile => person.identifier, :circles => {"Zombies" => "0", "Brainsss" => "0"}
  1989 + end
  1990 +
  1991 + assert_match /Select at least one circle to follow/, response.body
  1992 + end
  1993 +
  1994 + should 'not follow if current_person already follows the person' do
  1995 + login_as(@profile.identifier)
  1996 + person = fast_create(Person)
  1997 +
  1998 + circle = Circle.create!(:person=> @profile, :name => "Zombies", :profile_type => 'Person')
  1999 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle.id)
  2000 +
  2001 + assert_no_difference 'ProfileFollower.count' do
  2002 + post :follow, :profile => person.identifier, :follow => { :circles => {"Zombies" => circle.id} }
  2003 + end
  2004 + assert_response 400
  2005 + end
  2006 +
  2007 + should "not unfollow user if not logged" do
  2008 + person = fast_create(Person)
  2009 + get :unfollow, :profile => person.identifier
  2010 +
  2011 + assert_redirected_to :controller => 'account', :action => 'login'
  2012 + end
  2013 +
  2014 + should "unfollow a followed person" do
  2015 + login_as(@profile.identifier)
  2016 + person = fast_create(Person)
  2017 +
  2018 + circle = Circle.create!(:person=> @profile, :name => "Zombies", :profile_type => 'Person')
  2019 + follower = fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle.id)
  2020 +
  2021 + assert_not_nil follower
  2022 +
  2023 + get :unfollow, :profile => person.identifier
  2024 + follower = ProfileFollower.find_by(:profile_id => person.id, :circle_id => circle.id)
  2025 + assert_nil follower
  2026 + end
  2027 +
  2028 + should "not unfollow a not followed person" do
  2029 + login_as(@profile.identifier)
  2030 + person = fast_create(Person)
  2031 +
  2032 + assert_no_difference 'ProfileFollower.count' do
  2033 + get :unfollow, :profile => person.identifier
  2034 + end
  2035 + end
  2036 +
  2037 + should "redirect to page after unfollow" do
  2038 + login_as(@profile.identifier)
  2039 + person = fast_create(Person)
  2040 +
  2041 + circle = Circle.create!(:person=> @profile, :name => "Zombies", :profile_type => 'Person')
  2042 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle.id)
  2043 +
  2044 + get :unfollow, :profile => person.identifier, :redirect_to => "/some/url"
  2045 + assert_redirected_to "/some/url"
  2046 + end
  2047 +
1935 2048 end
... ...
test/unit/article_test.rb
... ... @@ -1099,9 +1099,10 @@ 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 + circle = Circle.create!(:person=> friend, :name => "Zombies", :profile_type => 'Person')
  1105 + friend.follow(profile, circle)
1105 1106 Article.destroy_all
1106 1107 ActionTracker::Record.destroy_all
1107 1108 ActionTrackerNotification.destroy_all
... ... @@ -1112,9 +1113,10 @@ class ArticleTest &lt; ActiveSupport::TestCase
1112 1113 assert_equal friend, ActionTrackerNotification.last.profile
1113 1114 end
1114 1115  
1115   - should 'create the notification to the friend when one friend has the notification and the other no' do
  1116 + should 'create the notification to the follower when one follower has the notification and the other no' do
1116 1117 f1 = fast_create(Person)
1117   - profile.add_friend(f1)
  1118 + circle = Circle.create!(:person=> f1, :name => "Zombies", :profile_type => 'Person')
  1119 + f1.follow(profile, circle)
1118 1120  
1119 1121 User.current = profile.user
1120 1122 article = create TinyMceArticle, :name => 'Tracked Article 1', :profile_id => profile.id
... ... @@ -1123,16 +1125,22 @@ class ArticleTest &lt; ActiveSupport::TestCase
1123 1125 assert_equal 2, ActionTrackerNotification.where(action_tracker_id: article.activity.id).count
1124 1126  
1125 1127 f2 = fast_create(Person)
1126   - profile.add_friend(f2)
  1128 + circle2 = Circle.create!(:person=> f2, :name => "Zombies", :profile_type => 'Person')
  1129 + f2.follow(profile, circle2)
  1130 +
1127 1131 article2 = create TinyMceArticle, :name => 'Tracked Article 2', :profile_id => profile.id
1128 1132 assert_equal 2, ActionTracker::Record.where(verb: 'create_article').count
1129 1133 process_delayed_job_queue
1130 1134 assert_equal 3, ActionTrackerNotification.where(action_tracker_id: article2.activity.id).count
1131 1135 end
1132 1136  
1133   - should 'destroy activity and notifications of friends when destroying an article' do
  1137 + should 'destroy activity and notifications of followers when destroying an article' do
1134 1138 friend = fast_create(Person)
1135   - profile.add_friend(friend)
  1139 +
  1140 + circle = Circle.create!(:person=> friend, :name => "Zombies", :profile_type => 'Person')
  1141 +
  1142 + friend.follow(profile, circle)
  1143 +
1136 1144 Article.destroy_all
1137 1145 ActionTracker::Record.destroy_all
1138 1146 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,55 @@ 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 + circle1 = Circle.create!(:person=> p1, :name => "Zombies", :profile_type => 'Person')
  119 + circle2 = Circle.create!(:person=> p2, :name => "Zombies", :profile_type => 'Person')
  120 + p1.follow(p2, circle1)
  121 + p2.follow(p1, circle2)
  122 +
  123 + assert_not_includes p1.friends(true), p2
  124 + assert_not_includes p2.friends(true), p1
  125 + end
75 126 end
... ...
test/unit/notify_activity_to_profiles_job_test.rb
... ... @@ -24,15 +24,21 @@ 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 +
  34 + circle1 = Circle.create!(:person=> p1, :name => "Zombies", :profile_type => 'Person')
  35 + circle2 = Circle.create!(:person=> p2, :name => "Zombies", :profile_type => 'Person')
  36 + circle = Circle.create!(:person=> person, :name => "Zombies", :profile_type => 'Person')
  37 +
  38 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle1.id)
  39 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle2.id)
  40 + fast_create(ProfileFollower, :profile_id => m1.id, :circle_id => circle.id)
  41 +
36 42 fast_create(RoleAssignment, :accessor_id => m2.id, :role_id => 3, :resource_id => community.id)
37 43 ActionTrackerNotification.delete_all
38 44 job = NotifyActivityToProfilesJob.new(action_tracker.id)
... ... @@ -66,23 +72,24 @@ class NotifyActivityToProfilesJobTest &lt; ActiveSupport::TestCase
66 72 end
67 73 end
68 74  
69   - should 'notify users its friends, the community and its members' do
  75 + should 'notify users its followers, the community and its members' do
70 76 person = fast_create(Person)
71 77 community = fast_create(Community)
72 78 action_tracker = fast_create(ActionTracker::Record, :user_type => 'Profile', :user_id => person.id, :target_type => 'Profile', :target_id => community.id, :verb => 'create_article')
73 79 refute NotifyActivityToProfilesJob::NOTIFY_ONLY_COMMUNITY.include?(action_tracker.verb)
74 80 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)
  81 +
  82 + circle1 = Circle.create!(:person=> p1, :name => "Zombies", :profile_type => 'Person')
  83 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle1.id)
  84 +
77 85 fast_create(RoleAssignment, :accessor_id => m1.id, :role_id => 3, :resource_id => community.id)
78 86 fast_create(RoleAssignment, :accessor_id => m2.id, :role_id => 3, :resource_id => community.id)
79 87 ActionTrackerNotification.delete_all
80 88 job = NotifyActivityToProfilesJob.new(action_tracker.id)
81 89 job.perform
82 90 process_delayed_job_queue
83   -
84   - assert_equal 6, ActionTrackerNotification.count
85   - [person, community, p1, p2, m1, m2].each do |profile|
  91 + assert_equal 5, ActionTrackerNotification.count
  92 + [person, community, p1, m1, m2].each do |profile|
86 93 notification = ActionTrackerNotification.find_by profile_id: profile.id
87 94 assert_equal action_tracker, notification.action_tracker
88 95 end
... ... @@ -119,8 +126,13 @@ class NotifyActivityToProfilesJobTest &lt; ActiveSupport::TestCase
119 126 action_tracker = fast_create(ActionTracker::Record, :user_type => 'Profile', :user_id => person.id, :target_type => 'Profile', :target_id => community.id, :verb => 'join_community')
120 127 refute NotifyActivityToProfilesJob::NOTIFY_ONLY_COMMUNITY.include?(action_tracker.verb)
121 128 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)
  129 +
  130 + circle1 = Circle.create!(:person=> p1, :name => "Zombies", :profile_type => 'Person')
  131 + circle2 = Circle.create!(:person=> p2, :name => "Zombies", :profile_type => 'Person')
  132 +
  133 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle1.id)
  134 + fast_create(ProfileFollower, :profile_id => person.id, :circle_id => circle2.id)
  135 +
124 136 fast_create(RoleAssignment, :accessor_id => m1.id, :role_id => 3, :resource_id => community.id)
125 137 fast_create(RoleAssignment, :accessor_id => m2.id, :role_id => 3, :resource_id => community.id)
126 138 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,21 @@ 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 + circle2 = Circle.create!(:person=> p2, :name => "Zombies", :profile_type => 'Person')
  847 + circle4 = Circle.create!(:person=> p4, :name => "Zombies", :profile_type => 'Person')
  848 +
  849 + p2.follow(p1, circle2)
  850 + assert p2.follows?(p1)
  851 + refute p3.follows?(p1)
  852 + p4.follow(p1, circle4)
  853 + assert p4.follows?(p1)
851 854  
852 855 action_tracker = fast_create(ActionTracker::Record, :user_id => p1.id)
853 856 ActionTrackerNotification.delete_all
... ... @@ -880,17 +883,19 @@ class PersonTest &lt; ActiveSupport::TestCase
880 883 end
881 884 end
882 885  
883   - should "the tracked action notify friends with one delayed job process" do
  886 + should "the tracked action notify followers with one delayed job process" do
884 887 p1 = fast_create(Person)
885 888 p2 = fast_create(Person)
886 889 p3 = fast_create(Person)
887 890 p4 = fast_create(Person)
888 891  
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)
  892 + circle2 = Circle.create!(:person=> p2, :name => "Zombies", :profile_type => 'Person')
  893 + circle4 = Circle.create!(:person=> p4, :name => "Zombies", :profile_type => 'Person')
  894 + p2.follow(p1, circle2)
  895 + assert p2.follows?(p1)
  896 + refute p3.follows?(p1)
  897 + p4.follow(p1, circle4)
  898 + assert p4.follows?(p1)
894 899  
895 900 action_tracker = fast_create(ActionTracker::Record, :user_id => p1.id)
896 901  
... ... @@ -1035,11 +1040,13 @@ class PersonTest &lt; ActiveSupport::TestCase
1035 1040 p2 = create_user('p2').person
1036 1041 p3 = create_user('p3').person
1037 1042 c = fast_create(Community, :name => "Foo")
  1043 +
1038 1044 c.add_member(p1)
1039 1045 process_delayed_job_queue
1040 1046 c.add_member(p3)
1041 1047 process_delayed_job_queue
1042   - assert_equal 4, ActionTracker::Record.count
  1048 +
  1049 + assert_equal 5, ActionTracker::Record.count
1043 1050 assert_equal 5, ActionTrackerNotification.count
1044 1051 has_add_member_notification = false
1045 1052 ActionTrackerNotification.all.map do |notification|
... ... @@ -1951,4 +1958,51 @@ class PersonTest &lt; ActiveSupport::TestCase
1951 1958 person.save!
1952 1959 end
1953 1960  
  1961 + should 'update profile circles for a person' do
  1962 + person = create_user('testuser').person
  1963 + community = fast_create(Community)
  1964 + circle = Circle.create!(:person=> person, :name => "Zombies", :profile_type => 'Community')
  1965 + circle2 = Circle.create!(:person=> person, :name => "Dota", :profile_type => 'Community')
  1966 + circle3 = Circle.create!(:person=> person, :name => "Quadrado", :profile_type => 'Community')
  1967 + person.follow(community, [circle, circle2])
  1968 + person.update_profile_circles(community, [circle2, circle3])
  1969 + assert_equivalent [circle2, circle3], ProfileFollower.with_profile(community).with_follower(person).map(&:circle)
  1970 + end
  1971 +
  1972 + should 'a person follow a profile' do
  1973 + person = create_user('testuser').person
  1974 + community = fast_create(Community)
  1975 + circle = Circle.create!(:person=> person, :name => "Zombies", :profile_type => 'Community')
  1976 + person.follow(community, circle)
  1977 + assert_includes person.followed_profiles, community
  1978 + end
  1979 +
  1980 + should 'a person follow a profile with more than one circle' do
  1981 + person = create_user('testuser').person
  1982 + community = fast_create(Community)
  1983 + circle = Circle.create!(:person=> person, :name => "Zombies", :profile_type => 'Community')
  1984 + circle2 = Circle.create!(:person=> person, :name => "Dota", :profile_type => 'Community')
  1985 + person.follow(community, [circle, circle2])
  1986 + assert_includes person.followed_profiles, community
  1987 + assert_equivalent [circle, circle2], ProfileFollower.with_profile(community).with_follower(person).map(&:circle)
  1988 + end
  1989 +
  1990 + should 'a person unfollow a profile' do
  1991 + person = create_user('testuser').person
  1992 + community = fast_create(Community)
  1993 + circle = Circle.create!(:person=> person, :name => "Zombies", :profile_type => 'Community')
  1994 + person.follow(community, circle)
  1995 + person.unfollow(community)
  1996 + assert_not_includes person.followed_profiles, community
  1997 + end
  1998 +
  1999 + should 'a person remove a profile from a circle' do
  2000 + person = create_user('testuser').person
  2001 + community = fast_create(Community)
  2002 + circle = Circle.create!(:person=> person, :name => "Zombies", :profile_type => 'Community')
  2003 + circle2 = Circle.create!(:person=> person, :name => "Dota", :profile_type => 'Community')
  2004 + person.follow(community, [circle, circle2])
  2005 + person.remove_profile_from_circle(community, circle)
  2006 + assert_equivalent [circle2], ProfileFollower.with_profile(community).with_follower(person).map(&:circle)
  2007 + end
1954 2008 end
... ...
test/unit/profile_followers_test.rb 0 → 100644
... ... @@ -0,0 +1,73 @@
  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 + circle = Circle.create!(:person=> p1, :name => "Zombies", :profile_type => 'Person')
  9 +
  10 + assert_difference 'ProfileFollower.count' do
  11 + p1.follow(p2, circle)
  12 + end
  13 +
  14 + assert_includes p2.followers(true), p1
  15 + assert_not_includes p1.followers(true), p2
  16 + end
  17 +
  18 + should 'a person unfollow another person' do
  19 + p1 = create_user('person_test').person
  20 + p2 = create_user('person_test_2').person
  21 + circle = Circle.create!(:person=> p1, :name => "Zombies", :profile_type => 'Person')
  22 +
  23 + p1.follow(p2,circle)
  24 +
  25 + assert_difference 'ProfileFollower.count', -1 do
  26 + p1.unfollow(p2)
  27 + end
  28 +
  29 + assert_not_includes p2.followers(true), p1
  30 + end
  31 +
  32 + should 'get the followed persons for a profile' do
  33 + p1 = create_user('person_test').person
  34 + p2 = create_user('person_test_2').person
  35 + p3 = create_user('person_test_3').person
  36 + circle = Circle.create!(:person=> p1, :name => "Zombies", :profile_type => 'Person')
  37 +
  38 + p1.follow(p2, circle)
  39 + p1.follow(p3, circle)
  40 +
  41 + assert_equivalent p1.followed_profiles, [p2,p3]
  42 + assert_equivalent Profile.followed_by(p1), [p2,p3]
  43 + end
  44 +
  45 + should 'not follow same person twice' do
  46 + p1 = create_user('person_test').person
  47 + p2 = create_user('person_test_2').person
  48 + circle = Circle.create!(:person=> p1, :name => "Zombies", :profile_type => 'Person')
  49 +
  50 + assert_difference 'ProfileFollower.count' do
  51 + p1.follow(p2, circle)
  52 + p1.follow(p2, circle)
  53 + end
  54 +
  55 + assert_equivalent p1.followed_profiles, [p2]
  56 + assert_equivalent p2.followers, [p1]
  57 + end
  58 +
  59 + should 'show the correct message when a profile is followed by the same person' do
  60 + p1 = create_user('person_test').person
  61 + p2 = create_user('person_test_2').person
  62 + circle = Circle.create!(:person=> p1, :name => "Zombies", :profile_type => 'Person')
  63 +
  64 + p1.follow(p2, circle)
  65 + profile_follower = ProfileFollower.new
  66 + profile_follower.circle = circle
  67 + profile_follower.profile = p2
  68 + profile_follower.valid?
  69 +
  70 + assert_includes profile_follower.errors.messages[:profile_id],
  71 + "can't put a profile in the same circle twice"
  72 + end
  73 +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
... ...