Commit 3926c76a757800dd1433a8f9cb7dab9d5250954d

Authored by Michel Felipe
1 parent 8c31af4c

Remove admins.map that generate a SQL query for each user causing performance problems.

app/models/organization.rb
... ... @@ -8,6 +8,30 @@ class Organization < Profile
8 8 :display => %w[compact]
9 9 }
10 10  
  11 + # An Organization is considered visible to a given person if one of the
  12 +# following conditions are met:
  13 +# 1) The user is an environment administrator.
  14 +# 2) The user is an administrator of the organization.
  15 +# 3) The user is a member of the organization and the organization is
  16 +# visible.
  17 +# 4) The user is not a member of the organization but the organization is
  18 +# visible, public and enabled.
  19 + def self.visible_for_person(person)
  20 + joins('LEFT JOIN "role_assignments" ON ("role_assignments"."resource_id" = "profiles"."id"
  21 + AND "role_assignments"."resource_type" = \'Profile\') OR (
  22 + "role_assignments"."resource_id" = "profiles"."environment_id" AND
  23 + "role_assignments"."resource_type" = \'Environment\' )')
  24 + .joins('LEFT JOIN "roles" ON "role_assignments"."role_id" = "roles"."id"')
  25 + .where(
  26 + ['( (roles.key = ? OR roles.key = ?) AND role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? )
  27 + OR
  28 + ( ( ( role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) OR
  29 + ( profiles.public_profile = ? AND profiles.enabled = ? ) ) AND
  30 + ( profiles.visible = ? ) )',
  31 + 'profile_admin', 'environment_administrator', Profile.name, person.id,
  32 + Profile.name, person.id, true, true, true]
  33 + ).uniq
  34 + end
11 35  
12 36 settings_items :closed, :type => :boolean, :default => false
13 37 def closed?
... ... @@ -154,7 +178,8 @@ class Organization < Profile
154 178 end
155 179  
156 180 def notification_emails
157   - emails = [contact_email].select(&:present?) + admins.map(&:email)
  181 + # TODO: Add performance improvement here!
  182 + emails = [contact_email].select(&:present?) + admins([:user]).pluck(:email)
158 183 if emails.empty?
159 184 emails << environment.contact_email
160 185 end
... ...
app/models/person.rb
1 1 # A person is the profile of an user holding all relationships with the rest of the system
2 2 class Person < Profile
3 3  
4   - attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website
  4 + attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone,
  5 + :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference,
  6 + :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study,
  7 + :custom_area_of_study, :professional_activity, :organization_website, :following_articiles
5 8  
6 9 SEARCH_FILTERS = {
7 10 :order => %w[more_recent more_popular more_active],
... ... @@ -16,10 +19,12 @@ class Person &lt; Profile
16 19 acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)}
17 20 acts_as_accessor
18 21  
19   - scope :members_of, lambda { |resources|
  22 + scope :members_of, lambda { |resources, extra_joins = nil|
20 23 resources = [resources] if !resources.kind_of?(Array)
21 24 conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ')
22   - { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] }
  25 + joins = [:role_assignments]
  26 + joins += extra_joins if extra_joins.is_a? Array
  27 + { :select => 'DISTINCT profiles.*', :joins => joins, :conditions => [conditions] }
23 28 }
24 29  
25 30 scope :not_members_of, lambda { |resources|
... ... @@ -28,10 +33,11 @@ class Person &lt; Profile
28 33 { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "role_assignments" ON "role_assignments"."accessor_id" = "profiles"."id" AND "role_assignments"."accessor_type" = (\'Profile\') WHERE "profiles"."type" IN (\'Person\') AND (%s))' % conditions] }
29 34 }
30 35  
31   - scope :by_role, lambda { |roles|
  36 + scope :by_role, lambda { |roles, extra_joins = nil|
32 37 roles = [roles] unless roles.kind_of?(Array)
33   - { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)',
34   -roles] }
  38 + joins = [:role_assignments]
  39 + joins += extra_joins if extra_joins.is_a? Array
  40 + { :select => 'DISTINCT profiles.*', :joins => joins, :conditions => ['role_assignments.role_id IN (?)',roles] }
35 41 }
36 42  
37 43 scope :not_friends_of, lambda { |resources|
... ... @@ -39,6 +45,18 @@ roles] }
39 45 { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "friendships" ON "friendships"."person_id" = "profiles"."id" WHERE "friendships"."friend_id" IN (%s))' % resources.map(&:id)] }
40 46 }
41 47  
  48 + scope :visible_for_person, lambda { |person|
  49 + joins('LEFT JOIN "role_assignments" ON
  50 + "role_assignments"."resource_id" = "profiles"."environment_id" AND
  51 + "role_assignments"."resource_type" = \'Environment\'')
  52 + .joins('LEFT JOIN "roles" ON "role_assignments"."role_id" = "roles"."id"')
  53 + .joins('LEFT JOIN "friendships" ON "friendships"."friend_id" = "profiles"."id"')
  54 + .where(
  55 + ['( roles.key = ? AND role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) OR (
  56 + ( ( friendships.person_id = ? ) OR (profiles.public_profile = ?)) AND (profiles.visible = ?) )', 'environment_administrator', Profile.name, person.id, person.id, true, true]
  57 + ).uniq
  58 + }
  59 +
42 60 def has_permission_with_admin?(permission, resource)
43 61 return true if resource.blank? || resource.admins.include?(self)
44 62 return true if resource.kind_of?(Profile) && resource.environment.admins.include?(self)
... ... @@ -68,6 +86,10 @@ roles] }
68 86 memberships.where('role_assignments.role_id = ?', role.id)
69 87 end
70 88  
  89 + #Article followers relation
  90 + has_many :article_followers, :dependent => :destroy
  91 + has_many :following_articiles, :class_name => 'Article', :through => :article_followers, :source => :article
  92 +
71 93 has_many :friendships, :dependent => :destroy
72 94 has_many :friends, :class_name => 'Person', :through => :friendships
73 95  
... ... @@ -125,6 +147,11 @@ roles] }
125 147 self.tracked_notifications.exists?(activity)
126 148 end
127 149  
  150 + def can_post_content?(profile, parent=nil)
  151 + (!parent.nil? && (parent.allow_create?(self))) ||
  152 + (self.has_permission?('post_content', profile) || self.has_permission?('publish_content', profile))
  153 + end
  154 +
128 155 # Sets the identifier for this person. Raises an exception when called on a
129 156 # existing person (since peoples' identifiers cannot be changed)
130 157 def identifier=(value)
... ... @@ -187,9 +214,11 @@ roles] }
187 214 organization
188 215 organization_website
189 216 contact_phone
190   - contact_information
  217 + contact_informatioin
191 218 ]
192 219  
  220 + xss_terminate :only => [ :custom_footer, :custom_header, :description, :preferred_domain, :nickname, :sex, :nationality, :country, :state, :city, :district, :zip_code, :address, :address_reference, :cell_phone, :comercial_phone, :personal_website, :jabber_id, :schooling, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization, :organization_website, :contact_phone, :contact_information ], :with => 'white_list'
  221 +
193 222 validates_multiparameter_assignments
194 223  
195 224 def self.fields
... ...
app/models/profile.rb
... ... @@ -3,7 +3,7 @@
3 3 # which by default is the one returned by Environment:default.
4 4 class Profile < ActiveRecord::Base
5 5  
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, :redirection_after_login, :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret
  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, :redirection_after_login, :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :custom_fields, :administrator_mail_notification, :region
7 7  
8 8 # use for internationalizable human type names in search facets
9 9 # reimplement on subclasses
... ... @@ -22,6 +22,34 @@ class Profile &lt; ActiveRecord::Base
22 22 :display => %w[compact]
23 23 }
24 24  
  25 + settings_items :custom_fields, :default => {}
  26 +
  27 + def custom_field_value(field)
  28 + if !self.custom_fields.blank?
  29 + self.custom_fields[field][:value]
  30 + else
  31 + ''
  32 + end
  33 + end
  34 +
  35 + def custom_field_title(field)
  36 + if !self.custom_fields.blank?
  37 + self.custom_fields[field][:title]
  38 + else
  39 + ''
  40 + end
  41 + end
  42 +
  43 + def custom_fields_template
  44 + fields = {}
  45 + fields = self.template.custom_fields unless self.template.blank?
  46 + fields
  47 + end
  48 +
  49 + def custom_fields_template_title(field)
  50 + self.template.custom_fields[field][:title]
  51 + end
  52 +
25 53 NUMBER_OF_BOXES = 4
26 54  
27 55 def self.default_search_display
... ... @@ -79,6 +107,7 @@ class Profile &lt; ActiveRecord::Base
79 107 'invite_members' => N_('Invite members'),
80 108 'send_mail_to_members' => N_('Send e-Mail to members'),
81 109 'manage_custom_roles' => N_('Manage custom roles'),
  110 + 'manage_email_templates' => N_('Manage Email Templates'),
82 111 }
83 112  
84 113 acts_as_accessible
... ... @@ -100,6 +129,50 @@ class Profile &lt; ActiveRecord::Base
100 129 }
101 130 scope :no_templates, {:conditions => {:is_template => false}}
102 131  
  132 + # Returns a scoped object to select profiles in a given location or in a radius
  133 + # distance from the given location center.
  134 + # The parameter can be the `request.params` with the keys:
  135 + # * `country`: Country code string.
  136 + # * `state`: Second-level administrative country subdivisions.
  137 + # * `city`: City full name for center definition, or as set by users.
  138 + # * `lat`: The latitude to define the center of georef search.
  139 + # * `lng`: The longitude to define the center of georef search.
  140 + # * `distance`: Define the search radius in kilometers.
  141 + # NOTE: This method may return an exception object, to inform filter error.
  142 + # When chaining scopes, is hardly recommended you to add this as the last one,
  143 + # if you can't be sure about the provided parameters.
  144 + def self.by_location(params)
  145 + params = params.with_indifferent_access
  146 + if params[:distance].blank?
  147 + where_code = []
  148 + [ :city, :state, :country ].each do |place|
  149 + unless params[place].blank?
  150 + # ... So we must to find on this named location
  151 + # TODO: convert location attrs to a table collumn
  152 + where_code << "(profiles.data like '%#{place}: #{params[place]}%')"
  153 + end
  154 + end
  155 + self.where where_code.join(' AND ')
  156 + else # Filter in a georef circle
  157 + unless params[:lat].blank? && params[:lng].blank?
  158 + lat, lng = [ params[:lat].to_f, params[:lng].to_f ]
  159 + end
  160 + if !lat
  161 + location = [ params[:city], params[:state], params[:country] ].compact.join(', ')
  162 + if location.blank?
  163 + return Exception.new (
  164 + _('You must to provide `lat` and `lng`, or `city` and `country` to define the center of the search circle, defined by `distance`.')
  165 + )
  166 + end
  167 + lat, lng = Noosfero::GeoRef.location_to_georef location
  168 + end
  169 + dist = params[:distance].to_f
  170 + self.where "#{Noosfero::GeoRef.sql_dist lat, lng} <= #{dist}"
  171 + end
  172 + end
  173 +
  174 + include TimeScopes
  175 +
103 176 def members
104 177 scopes = plugins.dispatch_scopes(:organization_members, self)
105 178 scopes << Person.members_of(self)
... ... @@ -119,8 +192,8 @@ class Profile &lt; ActiveRecord::Base
119 192 alias_method_chain :count, :distinct
120 193 end
121 194  
122   - def members_by_role(roles)
123   - Person.members_of(self).by_role(roles)
  195 + def members_by_role(roles, with_entities = nil)
  196 + Person.members_of(self, with_entities).by_role(roles, with_entities)
124 197 end
125 198  
126 199 acts_as_having_boxes
... ... @@ -151,6 +224,8 @@ class Profile &lt; ActiveRecord::Base
151 224  
152 225 has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments
153 226  
  227 + has_many :email_templates, :foreign_key => :owner_id
  228 +
154 229 # Although this should be a has_one relation, there are no non-silly names for
155 230 # a foreign key on article to reference the template to which it is
156 231 # welcome_page... =P
... ... @@ -178,6 +253,7 @@ class Profile &lt; ActiveRecord::Base
178 253 settings_items :description
179 254 settings_items :fields_privacy, :type => :hash, :default => {}
180 255 settings_items :email_suggestions, :type => :boolean, :default => false
  256 + settings_items :administrator_mail_notification, :type => :boolean, :default => true
181 257  
182 258 validates_length_of :description, :maximum => 550, :allow_nil => true
183 259  
... ... @@ -471,6 +547,10 @@ class Profile &lt; ActiveRecord::Base
471 547 self.articles.find(:all, options)
472 548 end
473 549  
  550 + def to_liquid
  551 + HashWithIndifferentAccess.new :name => name, :identifier => identifier
  552 + end
  553 +
474 554 class << self
475 555  
476 556 # finds a profile by its identifier. This method is a shortcut to
... ... @@ -613,15 +693,15 @@ private :generate_url, :url_options
613 693 after_create :insert_default_article_set
614 694 def insert_default_article_set
615 695 if template
616   - copy_articles_from template
  696 + self.save! if copy_articles_from template
617 697 else
618 698 default_set_of_articles.each do |article|
619 699 article.profile = self
620 700 article.advertise = false
621 701 article.save!
622 702 end
  703 + self.save!
623 704 end
624   - self.save!
625 705 end
626 706  
627 707 # Override this method in subclasses of Profile to create a default article
... ... @@ -642,10 +722,12 @@ private :generate_url, :url_options
642 722 end
643 723  
644 724 def copy_articles_from other
  725 + return false if other.top_level_articles.empty?
645 726 other.top_level_articles.each do |a|
646 727 copy_article_tree a
647 728 end
648 729 self.articles.reload
  730 + true
649 731 end
650 732  
651 733 def copy_article_tree(article, parent=nil)
... ... @@ -690,6 +772,29 @@ private :generate_url, :url_options
690 772 end
691 773 end
692 774  
  775 + # Adds many people to profile by id's
  776 + def add_members_by_id(people_ids)
  777 +
  778 + unless people_ids.nil? && people_ids.empty?
  779 +
  780 + people = Person.where(id: people_ids)
  781 + people.each do |person|
  782 +
  783 + add_member(person) unless person.is_member_of?(self)
  784 + end
  785 + end
  786 + end
  787 +
  788 + # Adds many people to profile by email's
  789 + def add_members_by_email(people_emails)
  790 +
  791 + people = User.where(email: people_emails)
  792 + people.each do |user|
  793 +
  794 + add_member(user.person) unless user.person.is_member_of?(self)
  795 + end
  796 + end
  797 +
693 798 def remove_member(person)
694 799 self.disaffiliate(person, Profile::Roles.all_roles(environment.id))
695 800 end
... ... @@ -821,11 +926,11 @@ private :generate_url, :url_options
821 926 self.forums.count.nonzero?
822 927 end
823 928  
824   - def admins
  929 + def admins(with_entities = nil)
825 930 return [] if environment.blank?
826 931 admin_role = Profile::Roles.admin(environment.id)
827 932 return [] if admin_role.blank?
828   - self.members_by_role(admin_role)
  933 + self.members_by_role(admin_role, with_entities)
829 934 end
830 935  
831 936 def enable_contact?
... ... @@ -936,6 +1041,13 @@ private :generate_url, :url_options
936 1041 image.public_filename(:icon) if image.present?
937 1042 end
938 1043  
  1044 + #FIXME make this test
  1045 + def profile_custom_image(size = :icon)
  1046 + image_path = profile_custom_icon if size == :icon
  1047 + image_path ||= image.public_filename(size) if image.present?
  1048 + image_path
  1049 + end
  1050 +
939 1051 def jid(options = {})
940 1052 domain = options[:domain] || environment.default_hostname
941 1053 "#{identifier}@#{domain}"
... ...