Commit 95367d53bdce54355fffdf724209d7d51db04708

Authored by Dylan Guedes
Committed by Gabriel Silva
1 parent 9b3cd0de
Exists in external_followers

Adds Follower behavior to External Person

- Introduces Follower and Followable concerns
- Adds External Profile
- External Person can follow profiles
- External Profiles can be followed
- Refactors Profile/Circle relations

Signed-off-by: DylanGuedes <djmgguedes@gmail.com>
Signed-off-by: Gabriel Silva <gabriel93.silva@gmail.com>
Showing 39 changed files with 665 additions and 401 deletions   Show diff stats
app/controllers/concerns/needs_profile.rb
... ... @@ -32,6 +32,8 @@ module NeedsProfile
32 32 params.delete(:profile)
33 33 redirect_to(Noosfero.url_options.merge(params).merge(:host => profile_hostname))
34 34 end
  35 + elsif session[:external]
  36 + @profile = ExternalPerson.find(session[:external])
35 37 else
36 38 render_not_found
37 39 end
... ...
app/controllers/my_profile/circles_controller.rb
... ... @@ -3,7 +3,7 @@ class CirclesController &lt; MyProfileController
3 3 before_action :accept_only_post, :only => [:create, :update, :destroy]
4 4  
5 5 def index
6   - @circles = profile.circles
  6 + @circles = profile.owned_circles
7 7 end
8 8  
9 9 def new
... ... @@ -11,7 +11,7 @@ class CirclesController &lt; MyProfileController
11 11 end
12 12  
13 13 def create
14   - @circle = Circle.new(params[:circle].merge({ :person => profile }))
  14 + @circle = Circle.new(params[:circle].merge({ :owner => profile }))
15 15 if @circle.save
16 16 redirect_to :action => 'index'
17 17 else
... ... @@ -21,7 +21,7 @@ class CirclesController &lt; MyProfileController
21 21  
22 22 def xhr_create
23 23 if request.xhr?
24   - circle = Circle.new(params[:circle].merge({:person => profile }))
  24 + circle = Circle.new(params[:circle].merge({:owner => profile }))
25 25 if circle.save
26 26 render :partial => "circle_checkbox", :locals => { :circle => circle },
27 27 :status => 201
... ...
app/controllers/my_profile/followers_controller.rb
... ... @@ -4,11 +4,11 @@ class FollowersController &lt; MyProfileController
4 4 before_action :accept_only_post, :only => [:update_category]
5 5  
6 6 def index
7   - @followed_people = profile.followed_profiles.order(:type)
  7 + @followed_people = profile.followed_profiles.sort_by(&:type)
8 8 @profile_types = {_('All profiles') => nil}.merge(Circle.profile_types).to_a
9 9  
10 10 if params['filter'].present?
11   - @followed_people = @followed_people.where(:type => params['filter'])
  11 + @followed_people = @followed_people.select{|c| c.type == params['filter']}
12 12 @active_filter = params['filter']
13 13 end
14 14  
... ... @@ -17,7 +17,7 @@ class FollowersController &lt; MyProfileController
17 17  
18 18 def set_category_modal
19 19 followed_profile = Profile.find(params[:followed_profile_id])
20   - circles = Circle.where(:person => profile, :profile_type => followed_profile.class.name)
  20 + circles = Circle.where(:owner => profile, :profile_type => followed_profile.class.name)
21 21 render :partial => 'followers/edit_circles_modal', :locals => { :circles => circles, :followed_profile => followed_profile }
22 22 end
23 23  
... ...
app/controllers/public/profile_controller.rb
... ... @@ -74,6 +74,8 @@ class ProfileController &lt; PublicController
74 74  
75 75 def following
76 76 @followed_people = profile.followed_profiles.paginate(:per_page => per_page, :page => params[:npage], :total_entries => profile.followed_profiles.count)
  77 + puts "FOLLOWED_PEOPLE: #{@followed_people.map(&:name)}"
  78 + return @followed_people
77 79 end
78 80  
79 81 def followed
... ... @@ -170,6 +172,7 @@ class ProfileController &lt; PublicController
170 172 if profile.followed_by?(current_person)
171 173 render :text => _("You are already following %s.") % profile.name, :status => 400
172 174 else
  175 + params[:circles] ||= []
173 176 selected_circles = params[:circles].map{ |circle_name, circle_id| Circle.find_by(:id => circle_id) }.select{ |c| c.present? }
174 177 if selected_circles.present?
175 178 current_person.follow(profile, selected_circles)
... ... @@ -181,7 +184,7 @@ class ProfileController &lt; PublicController
181 184 end
182 185  
183 186 def find_profile_circles
184   - circles = Circle.where(:person => current_person, :profile_type => profile.class.name)
  187 + circles = Circle.where(:owner => current_person, :profile_type => profile.class.name)
185 188 render :partial => 'blocks/profile_info_actions/circles', :locals => { :circles => circles, :profile_types => Circle.profile_types.to_a }
186 189 end
187 190  
... ... @@ -260,10 +263,11 @@ class ProfileController &lt; PublicController
260 263 end
261 264  
262 265 def search_followed
263   - result = []
264   - circles = find_by_contents(:circles, user, user.circles.where(:profile_type => 'Person'), params[:q])[:results]
265   - followed = find_by_contents(:followed, user, Profile.followed_by(user), params[:q])[:results]
266   - result = circles + followed
  266 + circles = find_by_contents(:circles, user, user.owned_circles.where(:profile_type => 'Person'), params[:q])[:results]
  267 + local_followed = find_by_contents(:followed, user, user.local_followed_profiles, params[:q])[:results]
  268 + external_followed = find_by_contents(:followed, user, user.external_followed_profiles, params[:q])[:results]
  269 +
  270 + result = circles + local_followed + external_followed
267 271 render :text => prepare_to_token_input_by_class(result).to_json
268 272 end
269 273  
... ... @@ -516,7 +520,7 @@ class ProfileController &lt; PublicController
516 520 followed << Person.find(identifier)
517 521 when 'Circle'
518 522 circle = Circle.find(identifier)
519   - followed += Profile.in_circle(circle)
  523 + followed += circle.profiles
520 524 end
521 525 end
522 526 followed.uniq
... ...
app/jobs/notify_activity_to_profiles_job.rb
... ... @@ -24,7 +24,7 @@ class NotifyActivityToProfilesJob &lt; Struct.new(:tracked_action_id)
24 24 ActionTrackerNotification.connection.execute("INSERT INTO action_tracker_notifications(profile_id, action_tracker_id) SELECT DISTINCT profiles.id, #{tracked_action.id} FROM profiles WHERE profiles.id IN (#{target.marked_people.map(&:id).join(',')})")
25 25 else
26 26 # Notify all followers
27   - 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}))")
  27 + ActionTrackerNotification.connection.execute("INSERT INTO action_tracker_notifications(profile_id, action_tracker_id) SELECT DISTINCT c.owner_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.owner_id NOT IN (SELECT atn.profile_id FROM action_tracker_notifications AS atn WHERE atn.action_tracker_id = #{tracked_action.id}))")
28 28 end
29 29  
30 30 if tracked_action.user.is_a? Organization
... ...
app/models/add_member.rb
... ... @@ -22,7 +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 + person.follow(organization, Circle.find_or_create_by(:owner => person, :name =>_('memberships'), :profile_type => 'Community'))
26 26 end
27 27  
28 28 def title
... ...
app/models/circle.rb
... ... @@ -6,30 +6,35 @@ class Circle &lt; ApplicationRecord
6 6 _('Circle')
7 7  
8 8 has_many :profile_followers
9   - belongs_to :person
  9 + belongs_to :owner, polymorphic: true
10 10  
11   - attr_accessible :name, :person, :profile_type
  11 + attr_accessible :name, :owner, :profile_type
12 12  
13 13 validates :name, presence: true
14   - validates :person_id, presence: true
  14 + validates :owner_id, presence: true
15 15 validates :profile_type, presence: true
16   - validates :person_id, :uniqueness => {:scope => :name, :message => "can't add two circles with the same name"}
  16 + validates :owner_id, :uniqueness => {:scope => :name, :message => "can't add two circles with the same name"}
17 17  
18 18 validate :profile_type_must_be_in_list
19 19  
20 20 scope :by_owner, -> person{
21   - where(:person => person)
  21 + where(:owner => person)
22 22 }
23 23  
24 24 scope :with_name, -> name{
25 25 where(:name => name)
26 26 }
27 27  
  28 + def profiles
  29 + self.profile_followers.map(&:profile)
  30 + end
  31 +
28 32 def self.profile_types
29 33 {
30 34 _("Person") => Person.name,
31 35 _("Community") => Community.name,
32   - _("Enterprise") => Enterprise.name
  36 + _("Enterprise") => Enterprise.name,
  37 + _("Organization") => Organization.name
33 38 }
34 39 end
35 40  
... ...
app/models/concerns/external_user.rb
... ... @@ -20,7 +20,7 @@ module ExternalUser
20 20 if login && domain && environment.has_federated_network?(domain)
21 21 external_environment = environment.external_environments.find_by_domain(domain)
22 22 scheme = "http#{external_environment.uses_ssl? ? 's' : ''}"
23   - url = URI.parse(scheme+"://"+ domain +'/.well-known/webfinger?resource=acct:'+
  23 +p url = URI.parse(scheme+"://"+ domain +'/.well-known/webfinger?resource=acct:'+
24 24 login+'@'+domain)
25 25 http = build_request(url)
26 26 req = Net::HTTP::Get.new(url.to_s)
... ...
app/models/concerns/followable.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +module Followable
  2 + extend ActiveSupport::Concern
  3 +
  4 + included do
  5 + has_many :profile_followers, :as => :profile
  6 + has_many :circles, :through => :profile_followers
  7 + end
  8 +
  9 + def followers
  10 + person_followers = Person.joins(:owned_circles).merge(circles).uniq
  11 + external_person_followers = ExternalPerson.joins(:owned_circles).merge(circles).uniq
  12 +
  13 + person_followers + external_person_followers
  14 + end
  15 +
  16 + def followed_by?(person)
  17 + (person == self) || (person.in? self.followers)
  18 + end
  19 +
  20 + def in_circle?(circle)
  21 + circle.in? self.circles
  22 + end
  23 +end
... ...
app/models/concerns/follower.rb 0 → 100644
... ... @@ -0,0 +1,52 @@
  1 +module Follower
  2 + extend ActiveSupport::Concern
  3 +
  4 + included do
  5 + has_many :owned_circles, as: :owner, :class_name => "Circle"
  6 + end
  7 +
  8 + def follow(profile, circles)
  9 + circles = [circles] unless circles.is_a?(Array)
  10 + circles.each do |new_circle|
  11 + next if new_circle.owner != self || !profile.kind_of?(new_circle.profile_type.constantize)
  12 + ProfileFollower.create(profile: profile, circle: new_circle)
  13 + end
  14 + end
  15 +
  16 + def follows?(profile)
  17 + return false if profile.nil?
  18 + profile.followed_by?(self)
  19 + end
  20 +
  21 + def unfollow(profile)
  22 + ProfileFollower.with_follower(self).with_profile(profile).destroy_all
  23 + end
  24 +
  25 + def local_followed_profiles
  26 + Profile.joins(:circles).where("circles.owner_id = ?", self.id).uniq
  27 + end
  28 +
  29 + def external_followed_profiles
  30 + ExternalProfile.joins(:circles).where("circles.owner_id = ?", self.id).uniq
  31 + end
  32 +
  33 + def followed_profiles
  34 + local_followed_profiles + external_followed_profiles
  35 + end
  36 +
  37 + def update_profile_circles(profile, new_circles)
  38 + profile_circles = ProfileFollower.with_profile(profile).with_follower(self).map(&:circle)
  39 + circles_to_add = new_circles - profile_circles
  40 + self.follow(profile, circles_to_add)
  41 +
  42 + circles_to_remove = profile_circles - new_circles
  43 + ProfileFollower.where('circle_id IN (?) AND profile_id = ?',
  44 + circles_to_remove.map(&:id), profile.id).destroy_all
  45 + end
  46 +
  47 + def remove_profile_from_circle(profile, circle)
  48 + return if circle.owner != self
  49 + ProfileFollower.with_profile(profile).with_circle(circle).destroy_all
  50 + end
  51 +
  52 +end
... ...
app/models/external_person.rb
1 1 # A pseudo profile is a person from a remote network
2   -class ExternalPerson < ActiveRecord::Base
  2 +class ExternalPerson < ExternalProfile
3 3  
4 4 include Human
5 5 include ProfileEntity
  6 + include Follower
6 7  
7 8 validates_uniqueness_of :identifier, scope: :source
8   -
9 9 validates_presence_of :source, :email, :created_at
10 10  
11 11 attr_accessible :source, :email, :created_at
... ... @@ -27,13 +27,6 @@ class ExternalPerson &lt; ActiveRecord::Base
27 27 _('Public profile')
28 28 end
29 29  
30   - def avatar
31   - "http://#{self.source}/profile/#{self.identifier}/icon/"
32   - end
33   -
34   - def url
35   - "http://#{self.source}/profile/#{self.identifier}"
36   - end
37 30  
38 31 alias :public_profile_url :url
39 32  
... ... @@ -75,10 +68,6 @@ class ExternalPerson &lt; ActiveRecord::Base
75 68 "#{scheme}://#{self.source}"
76 69 end
77 70  
78   - def profile_custom_icon(gravatar_default=nil)
79   - self.avatar
80   - end
81   -
82 71 def preferred_login_redirection
83 72 environment.redirection_after_login
84 73 end
... ... @@ -122,26 +111,6 @@ class ExternalPerson &lt; ActiveRecord::Base
122 111 "#{jid(options)}/#{self.name}"
123 112 end
124 113  
125   - class ExternalPerson::Image
126   - def initialize(path)
127   - @path = path
128   - end
129   -
130   - def public_filename(size = nil)
131   - URI.join(@path, size.to_s)
132   - end
133   -
134   - def content_type
135   - # This is not really going to be used anywhere that matters
136   - # so we are hardcodding it here.
137   - 'image/png'
138   - end
139   - end
140   -
141   - def image
142   - ExternalPerson::Image.new(avatar)
143   - end
144   -
145 114 def data_hash(gravatar_default = nil)
146 115 friends_list = {}
147 116 {
... ... @@ -195,67 +164,11 @@ class ExternalPerson &lt; ActiveRecord::Base
195 164 build_contact: nil, is_a_friend?: false, ask_to_join?: false, refuse_join:
196 165 nil, blocks_to_expire_cache: [], cache_keys: [], communities_cache_key: '',
197 166 friends_cache_key: '', manage_friends_cache_key: '',
198   - relationships_cache_key: '', is_member_of?: false, follows?: false,
  167 + relationships_cache_key: '', is_member_of?: false,
199 168 each_friend: nil, is_last_admin?: false, is_last_admin_leaving?: false,
200 169 leave: nil, last_notification: nil, notification_time: 0, notifier: nil,
201   - remove_suggestion: nil, allow_invitation_from?: false
202   - }
203   -
204   - derivated_methods = generate_derivated_methods(methods_and_responses)
205   - derivated_methods.merge(methods_and_responses)
206   - end
207   -
208   - def profile_instance_methods
209   - methods_and_responses = {
210   - role_assignments: RoleAssignment.none, favorite_enterprises:
211   - Enterprise.none, memberships: Profile.none, friendships: Profile.none,
212   - tasks: Task.none, suggested_profiles: ProfileSuggestion.none,
213   - suggested_people: ProfileSuggestion.none, suggested_communities:
214   - ProfileSuggestion.none, public_profile: true, nickname: nil, custom_footer:
215   - '', custom_header: '', address: '', zip_code: '', contact_phone: '',
216   - image_builder: nil, description: '', closed: false, template_id: nil, lat:
217   - nil, lng: nil, is_template: false, fields_privacy: {}, preferred_domain_id:
218   - nil, category_ids: [], country: '', city: '', state: '',
219   - national_region_code: '', redirect_l10n: false, notification_time: 0,
220   - custom_url_redirection: nil, email_suggestions: false,
221   - allow_members_to_invite: false, invite_friends_only: false, secret: false,
222   - profile_admin_mail_notification: false, redirection_after_login: nil,
223   - profile_activities: ProfileActivity.none, action_tracker_notifications:
224   - ActionTrackerNotification.none, tracked_notifications:
225   - ActionTracker::Record.none, scraps_received: Scrap.none, template:
226   - Profile.none, comments_received: Comment.none, email_templates:
227   - EmailTemplate.none, members: Profile.none, members_like: Profile.none,
228   - members_by: Profile.none, members_by_role: Profile.none, scraps:
229   - Scrap.none, welcome_page_content: nil, settings: {}, find_in_all_tasks:
230   - nil, top_level_categorization: {}, interests: Category.none, geolocation:
231   - '', country_name: '', pending_categorizations: [], add_category: false,
232   - create_pending_categorizations: false, top_level_articles: Article.none,
233   - valid_identifier: true, valid_template: false, create_default_set_of_boxes:
234   - true, copy_blocks_from: nil, default_template: nil,
235   - template_without_default: nil, template_with_default: nil, apply_template:
236   - false, iframe_whitelist: [], recent_documents: Article.none, last_articles:
237   - Article.none, is_validation_entity?: false, hostname: nil, own_hostname:
238   - nil, article_tags: {}, tagged_with: Article.none,
239   - insert_default_article_set: false, copy_articles_from: true,
240   - copy_article_tree: nil, copy_article?: false, add_member: false,
241   - remove_member: false, add_admin: false, remove_admin: false, add_moderator:
242   - false, display_info_to?: true, update_category_from_region: nil,
243   - accept_category?: false, custom_header_expanded: '',
244   - custom_footer_expanded: '', public?: true, themes: [], find_theme: nil,
245   - blogs: Blog.none, blog: nil, has_blog?: false, forums: Forum.none, forum:
246   - nil, has_forum?: false, admins: [], settings_field: {}, setting_changed:
247   - false, public_content: true, enable_contact?: false, folder_types: [],
248   - folders: Article.none, image_galleries: Article.none, image_valid: true,
249   - update_header_and_footer: nil, update_theme: nil, update_layout_template:
250   - nil, recent_actions: ActionTracker::Record.none, recent_notifications:
251   - ActionTracker::Record.none, more_active_label: _('no activity'),
252   - more_popular_label: _('no members'), profile_custom_image: nil,
253   - is_on_homepage?: false, activities: ProfileActivity.none,
254   - may_display_field_to?: true, may_display_location_to?: true, public_fields:
255   - {}, followed_by?: false, display_private_info_to?: true, can_view_field?:
256   - true, remove_from_suggestion_list: nil, layout_template: 'default',
257   - is_admin?: false, add_friend: false, follows?: false, is_a_friend?: false,
258   - already_request_friendship?: false
  170 + remove_suggestion: nil, allow_invitation_from?: false, in_social_circle?: false,
  171 + allow_followers: false
259 172 }
260 173  
261 174 derivated_methods = generate_derivated_methods(methods_and_responses)
... ... @@ -266,25 +179,15 @@ class ExternalPerson &lt; ActiveRecord::Base
266 179 if person_instance_methods.keys.include?(method)
267 180 return person_instance_methods[method]
268 181 end
269   - if profile_instance_methods.keys.include? method
270   - return profile_instance_methods[method]
271   - end
  182 + super(method, *args, &block)
272 183 end
273 184  
274 185 def respond_to_missing?(method_name, include_private = false)
275 186 person_instance_methods.keys.include?(method_name) ||
276   - profile_instance_methods.keys.include?(method_name) ||
277 187 super
278 188 end
279 189  
280   - private
281   -
282   - def generate_derivated_methods(methods)
283   - derivated_methods = {}
284   - methods.keys.each do |method|
285   - derivated_methods[method.to_s.insert(-1, '?').to_sym] = false
286   - derivated_methods[method.to_s.insert(-1, '=').to_sym] = nil
287   - end
288   - derivated_methods
  190 + def kind_of?(klass)
  191 + (klass == Person) ? true : super
289 192 end
290 193 end
... ...
app/models/external_profile.rb 0 → 100644
... ... @@ -0,0 +1,126 @@
  1 +class ExternalProfile < ApplicationRecord
  2 +
  3 + include Followable
  4 +
  5 + SEARCHABLE_FIELDS = {
  6 + :name => {:label => _('Name'), :weight => 10},
  7 + :identifier => {:label => _('Username'), :weight => 5},
  8 + :nickname => {:label => _('Nickname'), :weight => 2},
  9 + }
  10 +
  11 + def name
  12 + "#{self[:name]}@#{self.source}"
  13 + end
  14 +
  15 + class ExternalProfile::Image
  16 + def initialize(path)
  17 + @path = path
  18 + end
  19 +
  20 + def public_filename(size = nil)
  21 + URI.join(@path, size.to_s)
  22 + end
  23 +
  24 + def content_type
  25 + # This is not really going to be used anywhere that matters
  26 + # so we are hardcodding it here.
  27 + 'image/png'
  28 + end
  29 + end
  30 +
  31 + def url
  32 + "http://#{self.source}/profile/#{self.identifier}"
  33 + end
  34 +
  35 + def image
  36 + ExternalProfile::Image.new(avatar)
  37 + end
  38 +
  39 + def profile_custom_icon(gravatar_default=nil)
  40 + self.avatar
  41 + end
  42 +
  43 + def avatar
  44 + "http://#{self.source}/profile/#{self.identifier}/icon/"
  45 + end
  46 +
  47 + # External Profile should respond to all methods in Profile
  48 +def profile_instance_methods
  49 + methods_and_responses = {
  50 + role_assignments: RoleAssignment.none, favorite_enterprises:
  51 + Enterprise.none, memberships: Profile.none, friendships: Profile.none,
  52 + tasks: Task.none, suggested_profiles: ProfileSuggestion.none,
  53 + suggested_people: ProfileSuggestion.none, suggested_communities:
  54 + ProfileSuggestion.none, public_profile: true, nickname: nil, custom_footer:
  55 + '', custom_header: '', address: '', zip_code: '', contact_phone: '',
  56 + image_builder: nil, description: '', closed: false, template_id: nil, lat:
  57 + nil, lng: nil, is_template: false, fields_privacy: {}, preferred_domain_id:
  58 + nil, category_ids: [], country: '', city: '', state: '',
  59 + national_region_code: '', redirect_l10n: false, notification_time: 0,
  60 + custom_url_redirection: nil, email_suggestions: false,
  61 + allow_members_to_invite: false, invite_friends_only: false, secret: false,
  62 + profile_admin_mail_notification: false, redirection_after_login: nil,
  63 + profile_activities: ProfileActivity.none, action_tracker_notifications:
  64 + ActionTrackerNotification.none, tracked_notifications:
  65 + ActionTracker::Record.none, scraps_received: Scrap.none, template:
  66 + Profile.none, comments_received: Comment.none, email_templates:
  67 + EmailTemplate.none, members: Profile.none, members_like: Profile.none,
  68 + members_by: Profile.none, members_by_role: Profile.none, scraps:
  69 + Scrap.none, welcome_page_content: nil, settings: {}, find_in_all_tasks:
  70 + nil, top_level_categorization: {}, interests: Category.none, geolocation:
  71 + '', country_name: '', pending_categorizations: [], add_category: false,
  72 + create_pending_categorizations: false, top_level_articles: Article.none,
  73 + valid_identifier: true, valid_template: false, create_default_set_of_boxes:
  74 + true, copy_blocks_from: nil, default_template: nil,
  75 + template_without_default: nil, template_with_default: nil, apply_template:
  76 + false, iframe_whitelist: [], recent_documents: Article.none, last_articles:
  77 + Article.none, is_validation_entity?: false, hostname: nil, own_hostname:
  78 + nil, article_tags: {}, tagged_with: Article.none,
  79 + insert_default_article_set: false, copy_articles_from: true,
  80 + copy_article_tree: nil, copy_article?: false, add_member: false,
  81 + remove_member: false, add_admin: false, remove_admin: false, add_moderator:
  82 + false, display_info_to?: true, update_category_from_region: nil,
  83 + accept_category?: false, custom_header_expanded: '',
  84 + custom_footer_expanded: '', public?: true, themes: [], find_theme: nil,
  85 + blogs: Blog.none, blog: nil, has_blog?: false, forums: Forum.none, forum:
  86 + nil, has_forum?: false, admins: [], settings_field: {}, setting_changed:
  87 + false, public_content: true, enable_contact?: false, folder_types: [],
  88 + folders: Article.none, image_galleries: Article.none, image_valid: true,
  89 + update_header_and_footer: nil, update_theme: nil, update_layout_template:
  90 + nil, recent_actions: ActionTracker::Record.none, recent_notifications:
  91 + ActionTracker::Record.none, more_active_label: _('no activity'),
  92 + more_popular_label: _('no members'), profile_custom_image: nil,
  93 + is_on_homepage?: false, activities: ProfileActivity.none,
  94 + may_display_field_to?: true, may_display_location_to?: true, public_fields:
  95 + {}, display_private_info_to?: true, can_view_field?:
  96 + true, remove_from_suggestion_list: nil, layout_template: 'default',
  97 + is_admin?: false, add_friend: false, is_a_friend?: false,
  98 + already_request_friendship?: false, tracked_actions: ActionTracker::Record.none
  99 + }
  100 +
  101 + derivated_methods = generate_derivated_methods(methods_and_responses)
  102 + derivated_methods.merge(methods_and_responses)
  103 + end
  104 +
  105 + def method_missing(method, *args, &block)
  106 + if profile_instance_methods.keys.include? method
  107 + return profile_instance_methods[method]
  108 + end
  109 + raise NoMethodError, "undefined method #{method} for #{self}"
  110 + end
  111 +
  112 + def respond_to_missing?(method_name, include_private = false)
  113 + profile_instance_methods.keys.include?(method_name) || super
  114 + end
  115 +
  116 + private
  117 +
  118 + def generate_derivated_methods(methods)
  119 + derivated_methods = {}
  120 + methods.keys.each do |method|
  121 + derivated_methods[method.to_s.insert(-1, '?').to_sym] = false
  122 + derivated_methods[method.to_s.insert(-1, '=').to_sym] = nil
  123 + end
  124 + derivated_methods
  125 + end
  126 +end
... ...
app/models/favorite_enterprise_person.rb
... ... @@ -8,7 +8,7 @@ class FavoriteEnterprisePerson &lt; ApplicationRecord
8 8 belongs_to :person
9 9  
10 10 after_create do |favorite|
11   - favorite.person.follow(favorite.enterprise, Circle.find_or_create_by(:person => favorite.person, :name =>_('favorites'), :profile_type => 'Enterprise'))
  11 + favorite.person.follow(favorite.enterprise, Circle.find_or_create_by(:owner => favorite.person, :name =>_('favorites'), :profile_type => 'Enterprise'))
12 12 end
13 13  
14 14 protected
... ...
app/models/friendship.rb
... ... @@ -12,7 +12,7 @@ class Friendship &lt; ApplicationRecord
12 12  
13 13 circles = friendship.group.blank? ? ['friendships'] : friendship.group.split(',').map(&:strip)
14 14 circles.each do |circle|
15   - friendship.person.follow(friendship.friend, Circle.find_or_create_by(:person => friendship.person, :name => circle, :profile_type => 'Person'))
  15 + friendship.person.follow(friendship.friend, Circle.find_or_create_by(:owner => friendship.person, :name => circle, :profile_type => 'Person'))
16 16 end
17 17 end
18 18  
... ... @@ -22,7 +22,7 @@ class Friendship &lt; ApplicationRecord
22 22  
23 23 groups = friendship.group.blank? ? ['friendships'] : friendship.group.split(',').map(&:strip)
24 24 groups.each do |group|
25   - circle = Circle.find_by(:person => friendship.person, :name => group )
  25 + circle = Circle.find_by(:owner => friendship.person, :name => group )
26 26 friendship.person.remove_profile_from_circle(friendship.friend, circle) if circle
27 27 end
28 28 end
... ...
app/models/person.rb
... ... @@ -2,6 +2,7 @@
2 2 class Person < Profile
3 3  
4 4 include Human
  5 + include Follower
5 6  
6 7 attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website, :following_articles
7 8  
... ... @@ -93,7 +94,6 @@ class Person &lt; Profile
93 94 has_many :following_articles, :class_name => 'Article', :through => :article_followers, :source => :article
94 95 has_many :friendships, :dependent => :destroy
95 96 has_many :friends, :class_name => 'Person', :through => :friendships
96   - has_many :circles
97 97  
98 98 scope :online, -> {
99 99 joins(:user).where("users.chat_status != '' AND users.chat_status_at >= ?", DateTime.now - User.expires_chat_status_every.minutes)
... ... @@ -183,33 +183,6 @@ class Person &lt; Profile
183 183 end
184 184 end
185 185  
186   - def follow(profile, circles)
187   - circles = [circles] unless circles.is_a?(Array)
188   - circles.each do |new_circle|
189   - ProfileFollower.create(profile: profile, circle: new_circle)
190   - end
191   - end
192   -
193   - def update_profile_circles(profile, new_circles)
194   - profile_circles = ProfileFollower.with_profile(profile).with_follower(self).map(&:circle)
195   - circles_to_add = new_circles - profile_circles
196   - circles_to_remove = profile_circles - new_circles
197   - circles_to_add.each do |new_circle|
198   - ProfileFollower.create(profile: profile, circle: new_circle)
199   - end
200   -
201   - ProfileFollower.where('circle_id IN (?) AND profile_id = ?',
202   - circles_to_remove.map(&:id), profile.id).destroy_all
203   - end
204   -
205   - def unfollow(profile)
206   - ProfileFollower.with_follower(self).with_profile(profile).destroy_all
207   - end
208   -
209   - def remove_profile_from_circle(profile, circle)
210   - ProfileFollower.with_profile(profile).with_circle(circle).destroy_all
211   - end
212   -
213 186 def already_request_friendship?(person)
214 187 person.tasks.where(requestor_id: self.id, type: 'AddFriend', status: Task::Status::ACTIVE).first
215 188 end
... ... @@ -494,11 +467,6 @@ class Person &lt; Profile
494 467 profile.members.include?(self)
495 468 end
496 469  
497   - def follows?(profile)
498   - return false if profile.nil?
499   - profile.followed_by?(self)
500   - end
501   -
502 470 def each_friend(offset=0)
503 471 while friend = self.friends.order(:id).offset(offset).first
504 472 yield friend
... ... @@ -603,10 +571,6 @@ class Person &lt; Profile
603 571 }
604 572 end
605 573  
606   - def followed_profiles
607   - Profile.followed_by self
608   - end
609   -
610 574 def in_social_circle?(person)
611 575 self.is_a_friend?(person) || super
612 576 end
... ...
app/models/profile.rb
... ... @@ -4,6 +4,7 @@
4 4 class Profile < ApplicationRecord
5 5  
6 6 include ProfileEntity
  7 + include Followable
7 8  
8 9 attr_accessible :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time,
9 10 :custom_url_redirection, :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :profile_admin_mail_notification, :redirection_after_login, :allow_followers
... ... @@ -222,20 +223,6 @@ class Profile &lt; ApplicationRecord
222 223 scope :more_active, -> { order 'activities_count DESC' }
223 224 scope :more_recent, -> { order "created_at DESC" }
224 225  
225   - scope :followed_by, -> person{
226   - distinct.select('profiles.*').
227   - joins('left join profiles_circles ON profiles_circles.profile_id = profiles.id').
228   - joins('left join circles ON circles.id = profiles_circles.circle_id').
229   - where('circles.person_id = ?', person.id)
230   - }
231   -
232   - scope :in_circle, -> circle{
233   - distinct.select('profiles.*').
234   - joins('left join profiles_circles ON profiles_circles.profile_id = profiles.id').
235   - joins('left join circles ON circles.id = profiles_circles.circle_id').
236   - where('circles.id = ?', circle.id)
237   - }
238   -
239 226 settings_items :allow_followers, :type => :boolean, :default => true
240 227 alias_method :allow_followers?, :allow_followers
241 228  
... ... @@ -251,8 +238,7 @@ class Profile &lt; ApplicationRecord
251 238  
252 239 has_many :email_templates, :foreign_key => :owner_id
253 240  
254   - has_many :profile_followers
255   - has_many :followers, -> { uniq }, :class_name => 'Person', :through => :profile_followers, :source => :person
  241 + # has_many :followers, -> { uniq }, :through => :profile_followers, :source => :person
256 242  
257 243 # Although this should be a has_one relation, there are no non-silly names for
258 244 # a foreign key on article to reference the template to which it is
... ... @@ -729,7 +715,7 @@ private :generate_url, :url_options
729 715 else
730 716 self.affiliate(person, Profile::Roles.admin(environment.id), attributes) if members.count == 0
731 717 self.affiliate(person, Profile::Roles.member(environment.id), attributes)
732   - person.follow(self, Circle.find_or_create_by(:person => person, :name =>_('memberships'), :profile_type => 'Community'))
  718 + person.follow(self, Circle.find_or_create_by(:owner => person, :name =>_('memberships'), :profile_type => self.class.name))
733 719 end
734 720 person.tasks.pending.of("InviteMember").select { |t| t.data[:community_id] == self.id }.each { |invite| invite.cancel }
735 721 remove_from_suggestion_list person
... ... @@ -1011,10 +997,6 @@ private :generate_url, :url_options
1011 997 self.active_fields
1012 998 end
1013 999  
1014   - def followed_by?(person)
1015   - (person == self) || (person.in? self.followers)
1016   - end
1017   -
1018 1000 def in_social_circle?(person)
1019 1001 (person == self) || (person.is_member_of?(self))
1020 1002 end
... ... @@ -1058,7 +1040,4 @@ private :generate_url, :url_options
1058 1040 person.kind_of?(Profile) && person.has_permission?('destroy_profile', self)
1059 1041 end
1060 1042  
1061   - def in_circle?(circle, follower)
1062   - ProfileFollower.with_follower(follower).with_circle(circle).with_profile(self).present?
1063   - end
1064 1043 end
... ...
app/models/profile_follower.rb
... ... @@ -4,17 +4,24 @@ class ProfileFollower &lt; ApplicationRecord
4 4  
5 5 attr_accessible :profile, :circle
6 6  
7   - belongs_to :profile
  7 + #TODO -> user being followed
  8 + belongs_to :profile, :polymorphic => true
8 9 belongs_to :circle
9 10  
10   - has_one :person, through: :circle
11   - alias follower person
  11 + #TODO -> circle owner
  12 + # has_one :person, through: :circle, source_type: "ProfileFollower"
  13 +
  14 + def circle_owner
  15 + self.circle.owner
  16 + end
  17 +
  18 + alias follower circle_owner
12 19  
13 20 validates_presence_of :profile_id, :circle_id
14 21 validates :profile_id, :uniqueness => {:scope => :circle_id, :message => "can't put a profile in the same circle twice"}
15 22  
16 23 scope :with_follower, -> person{
17   - joins(:circle).where('circles.person_id = ?', person.id)
  24 + joins(:circle).where('circles.owner_id = ?', person.id)
18 25 }
19 26  
20 27 scope :with_profile, -> profile{
... ...
app/views/blocks/profile_info_actions/_select_circles.html.erb
... ... @@ -3,7 +3,7 @@
3 3 <div id="circles-checkboxes">
4 4 <% circles.each do |circle| %>
5 5 <div class="circle">
6   - <%= labelled_check_box circle.name, "circles[#{circle.name}]", circle.id, followed_profile.in_circle?(circle, follower) %>
  6 + <%= labelled_check_box circle.name, "circles[#{circle.name}]", circle.id, followed_profile.in_circle?(circle) %>
7 7 </div>
8 8 <% end %>
9 9 </div>
... ...
app/views/followers/_profile_list.html.erb
1 1 <ul class="profile-list">
  2 + <h1>Aqui</h1>
2 3 <% profiles.each do |followed_profile| %>
3 4 <li>
4 5 <%= link_to_profile profile_image(followed_profile) + tag('br') + followed_profile.short_name,
... ...
app/views/profile/_profile_comment_form.html.erb
... ... @@ -10,7 +10,7 @@
10 10 :rows => 1,
11 11 :class => 'submit-with-keypress',
12 12 :title => _('Leave your comment'),
13   - :onfocus => ("if(this.value==this.title){this.value='';this.style.color='#000'};this.style.backgroundImage='url(" + profile_icon(current_person, :icon, false) + ")'" if logged_in?),
  13 + :onfocus => ("if(this.value==this.title){this.value='';this.style.color='#000'};this.style.backgroundImage='url(" + profile_icon(current_person, :icon, false).to_s + ")'" if logged_in?),
14 14 :onblur => ("if(this.value==''){this.value=this.title;this.style.color='#ccc'};this.style.backgroundImage='none'" if logged_in?),
15 15 :value => _('Leave your comment'),
16 16 :style => 'color: #ccc' %>
... ...
db/migrate/20160810131138_adds_person_type_to_circles.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class AddsPersonTypeToCircles < ActiveRecord::Migration
  2 + def up
  3 + add_column :circles, :person_type, :string, default: "Person"
  4 + add_index :circles, [:person_id, :person_type]
  5 + end
  6 +
  7 + def down
  8 + remove_column :circles, :person_type
  9 + remove_index :circles, [:person_id, :person_type]
  10 + end
  11 +end
... ...
db/migrate/20160822174619_adds_profile_type_to_profile_followers.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class AddsProfileTypeToProfileFollowers < ActiveRecord::Migration
  2 + def up
  3 + add_column :profiles_circles, :profile_type, :string, default: "Person"
  4 + add_index :profiles_circles, [:profile_id, :profile_type]
  5 + end
  6 +
  7 + def down
  8 + remove_column :profiles_circles, :profile_type
  9 + remove_index :profiles_circles, [:profile_id, :profile_type]
  10 + end
  11 +end
... ...
db/migrate/20160822184703_rename_person_id_from_circles.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class RenamePersonIdFromCircles < ActiveRecord::Migration
  2 + def up
  3 + rename_column :circles, :person_id, :owner_id
  4 + rename_column :circles, :person_type, :owner_type
  5 + end
  6 +
  7 + def down
  8 + rename_column :circles, :owner_id, :person_id
  9 + rename_column :circles, :owner_type, :person_type
  10 + end
  11 +end
... ...
db/migrate/20160829170524_rename_external_people_to_external_profiles.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class RenameExternalPeopleToExternalProfiles < ActiveRecord::Migration
  2 + def up
  3 + rename_table :external_people, :external_profiles
  4 + end
  5 +
  6 + def down
  7 + rename_table :external_profiles, :external_people
  8 + end
  9 +end
... ...
db/migrate/20160829185118_add_type_to_external_profile.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class AddTypeToExternalProfile < ActiveRecord::Migration
  2 + def up
  3 + add_column :external_profiles, :type, :string, default: "ExternalProfile"
  4 + end
  5 +
  6 + def down
  7 + remove_column :external_profiles, :type
  8 + end
  9 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended that you check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(version: 20160705162914) do
  14 +ActiveRecord::Schema.define(version: 20160829185118) do
15 15  
16 16 # These are extensions that must be enabled in order to support this database
17 17 enable_extension "plpgsql"
... ... @@ -277,11 +277,13 @@ ActiveRecord::Schema.define(version: 20160705162914) do
277 277  
278 278 create_table "circles", force: :cascade do |t|
279 279 t.string "name"
280   - t.integer "person_id"
281   - t.string "profile_type", null: false
  280 + t.integer "owner_id"
  281 + t.string "profile_type", null: false
  282 + t.string "owner_type", default: "Person"
282 283 end
283 284  
284   - add_index "circles", ["person_id", "name"], name: "circles_composite_key_index", unique: true, using: :btree
  285 + add_index "circles", ["owner_id", "name"], name: "circles_composite_key_index", unique: true, using: :btree
  286 + add_index "circles", ["owner_id", "owner_type"], name: "index_circles_on_owner_id_and_owner_type", using: :btree
285 287  
286 288 create_table "comments", force: :cascade do |t|
287 289 t.string "title"
... ... @@ -439,7 +441,7 @@ ActiveRecord::Schema.define(version: 20160705162914) do
439 441 add_index "external_feeds", ["enabled"], name: "index_external_feeds_on_enabled", using: :btree
440 442 add_index "external_feeds", ["fetched_at"], name: "index_external_feeds_on_fetched_at", using: :btree
441 443  
442   - create_table "external_people", force: :cascade do |t|
  444 + create_table "external_profiles", force: :cascade do |t|
443 445 t.string "name"
444 446 t.string "identifier"
445 447 t.string "source"
... ... @@ -448,6 +450,7 @@ ActiveRecord::Schema.define(version: 20160705162914) do
448 450 t.boolean "visible", default: true
449 451 t.datetime "created_at"
450 452 t.datetime "updated_at"
  453 + t.string "type", default: "ExternalProfile"
451 454 end
452 455  
453 456 create_table "favorite_enterprise_people", force: :cascade do |t|
... ... @@ -686,9 +689,11 @@ ActiveRecord::Schema.define(version: 20160705162914) do
686 689 t.integer "circle_id"
687 690 t.datetime "created_at"
688 691 t.datetime "updated_at"
  692 + t.string "profile_type", default: "Person"
689 693 end
690 694  
691 695 add_index "profiles_circles", ["profile_id", "circle_id"], name: "profiles_circles_composite_key_index", unique: true, using: :btree
  696 + add_index "profiles_circles", ["profile_id", "profile_type"], name: "index_profiles_circles_on_profile_id_and_profile_type", using: :btree
692 697  
693 698 create_table "qualifier_certifiers", force: :cascade do |t|
694 699 t.integer "qualifier_id"
... ...
features/step_definitions/followers_steps.rb
1 1 Given /^the user "(.+)" has the following circles$/ do |user_name,table|
2 2 person = User.find_by(:login => user_name).person
3 3 table.hashes.each do |circle|
4   - Circle.create!(:person => person, :name => circle[:name], :profile_type => circle[:profile_type])
  4 + Circle.create!(:owner => person, :name => circle[:name], :profile_type => circle[:profile_type])
5 5 end
6 6 end
7 7  
... ... @@ -11,7 +11,7 @@ Then /^&quot;(.+)&quot; should be a follower of &quot;(.+)&quot; in circle &quot;(.+)&quot;$/ do |person, prof
11 11 person = Person.find_by(identifier: person)
12 12 followers.should include(person)
13 13  
14   - circle = Circle.find_by(:name => circle, :person => person)
  14 + circle = Circle.find_by(:name => circle, :owner => person)
15 15 ProfileFollower.find_by(:circle => circle, :profile => profile).should_not == nil
16 16 end
17 17  
... ... @@ -25,11 +25,11 @@ end
25 25 Given /^"(.+)" is a follower of "(.+)" in circle "(.+)"$/ do |person, profile, circle|
26 26 profile = Profile.find_by(identifier: profile)
27 27 person = Person.find_by(identifier: person)
28   - circle = Circle.find_by(:name => circle, :person => person)
  28 + circle = Circle.find_by(:name => circle, :owner => person)
29 29 ProfileFollower.create!(:circle => circle, :profile => profile)
30 30 end
31 31  
32 32 Then /^"(.+)" should have the circle "(.+)" with profile type "(.+)"$/ do |user_name, circle, profile_type|
33 33 person = User.find_by(:login => user_name).person
34   - Circle.find_by(:name => circle, :person => person, :profile_type => profile_type).should_not == nil
  34 + Circle.find_by(:name => circle, :owner => person, :profile_type => profile_type).should_not == nil
35 35 end
... ...
test/functional/circles_controller_test.rb
... ... @@ -10,8 +10,8 @@ class CirclesControllerTest &lt; ActionController::TestCase
10 10 end
11 11  
12 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')
  13 + circle1 = Circle.create!(:name => "circle1", :owner => @person, :profile_type => 'Person')
  14 + circle2 = Circle.create!(:name => "circle2", :owner => @person, :profile_type => 'Person')
15 15 get :index, :profile => @person.identifier
16 16  
17 17 assert_equivalent [circle1, circle2], assigns[:circles]
... ... @@ -24,7 +24,7 @@ class CirclesControllerTest &lt; ActionController::TestCase
24 24 end
25 25  
26 26 should 'create a new circle' do
27   - assert_difference '@person.circles.count' do
  27 + assert_difference '@person.owned_circles.count' do
28 28 post :create, :profile => @person.identifier,
29 29 :circle => { :name => 'circle' , :profile_type => Person.name}
30 30 end
... ... @@ -32,14 +32,14 @@ class CirclesControllerTest &lt; ActionController::TestCase
32 32 end
33 33  
34 34 should 'not create a circle without a name' do
35   - assert_no_difference '@person.circles.count' do
  35 + assert_no_difference '@person.owned_circles.count' do
36 36 post :create, :profile => @person.identifier, :circle => { :name => nil }
37 37 end
38 38 assert_template :new
39 39 end
40 40  
41 41 should 'retrieve an existing circle when editing' do
42   - circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  42 + circle = Circle.create!(:name => "circle", :owner => @person, :profile_type => 'Person')
43 43 get :edit, :profile => @person.identifier, :id => circle.id
44 44 assert_equal circle.name, assigns[:circle].name
45 45 end
... ... @@ -50,7 +50,7 @@ class CirclesControllerTest &lt; ActionController::TestCase
50 50 end
51 51  
52 52 should 'update an existing circle' do
53   - circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  53 + circle = Circle.create!(:name => "circle", :owner => @person, :profile_type => 'Person')
54 54 post :update, :profile => @person.identifier, :id => circle.id,
55 55 :circle => { :name => "new name" }
56 56  
... ... @@ -60,7 +60,7 @@ class CirclesControllerTest &lt; ActionController::TestCase
60 60 end
61 61  
62 62 should 'not update an existing circle without a name' do
63   - circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  63 + circle = Circle.create!(:name => "circle", :owner => @person, :profile_type => 'Person')
64 64 post :update, :profile => @person.identifier, :id => circle.id,
65 65 :circle => { :name => nil }
66 66  
... ... @@ -75,18 +75,18 @@ class CirclesControllerTest &lt; ActionController::TestCase
75 75 end
76 76  
77 77 should 'destroy an existing circle and remove related profiles' do
78   - circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  78 + circle = Circle.create!(:name => "circle", :owner => @person, :profile_type => 'Person')
79 79 fast_create(ProfileFollower, :profile_id => fast_create(Person).id, :circle_id => circle.id)
80 80  
81   - assert_difference ["@person.circles.count", 'ProfileFollower.count'], -1 do
  81 + assert_difference ["@person.owned_circles.count", 'ProfileFollower.count'], -1 do
82 82 post :destroy, :profile => @person.identifier, :id => circle.id
83 83 end
84 84 end
85 85  
86 86 should 'not destroy an existing circle if action is not post' do
87   - circle = Circle.create!(:name => "circle", :person => @person, :profile_type => 'Person')
  87 + circle = Circle.create!(:name => "circle", :owner => @person, :profile_type => 'Person')
88 88  
89   - assert_no_difference "@person.circles.count" do
  89 + assert_no_difference "@person.owned_circles.count" do
90 90 get :destroy, :profile => @person.identifier, :id => circle.id
91 91 end
92 92 assert_response 404
... ...
test/functional/followers_controller_test.rb
... ... @@ -9,8 +9,8 @@ class FollowersControllerTest &lt; ActionController::TestCase
9 9 should 'return followed people list' do
10 10 login_as(@profile.identifier)
11 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)
  12 + circle = Circle.create!(:owner=> @profile, :name => "Zombies", :profile_type => 'Person')
  13 + ProfileFollower.create(:profile => person, :circle => circle)
14 14  
15 15 get :index, :profile => @profile.identifier
16 16 assert_includes assigns(:followed_people), person
... ... @@ -20,10 +20,10 @@ class FollowersControllerTest &lt; ActionController::TestCase
20 20 login_as(@profile.identifier)
21 21 person = fast_create(Person)
22 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)
  23 + circle = Circle.create!(:owner=> @profile, :name => "Zombies", :profile_type => 'Person')
  24 + circle2 = Circle.create!(:owner=> @profile, :name => "Teams", :profile_type => 'Community')
  25 + ProfileFollower.create(:profile => person, :circle => circle)
  26 + ProfileFollower.create(:profile => community, :circle => circle2)