##// END OF EJS Templates
Speeds up rendering of the project list for users who belong to hundreds of projects....
Jean-Philippe Lang -
r15741:f8df935dcada
parent child
Show More
@@ -1,196 +1,206
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Principal < ActiveRecord::Base
18 class Principal < ActiveRecord::Base
19 self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
19 self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
20
20
21 # Account statuses
21 # Account statuses
22 STATUS_ANONYMOUS = 0
22 STATUS_ANONYMOUS = 0
23 STATUS_ACTIVE = 1
23 STATUS_ACTIVE = 1
24 STATUS_REGISTERED = 2
24 STATUS_REGISTERED = 2
25 STATUS_LOCKED = 3
25 STATUS_LOCKED = 3
26
26
27 class_attribute :valid_statuses
27 class_attribute :valid_statuses
28
28
29 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
29 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
30 has_many :memberships,
30 has_many :memberships,
31 lambda {preload(:project, :roles).
31 lambda {preload(:project, :roles).
32 joins(:project).
32 joins(:project).
33 where("#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}")},
33 where("#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}")},
34 :class_name => 'Member',
34 :class_name => 'Member',
35 :foreign_key => 'user_id'
35 :foreign_key => 'user_id'
36 has_many :projects, :through => :memberships
36 has_many :projects, :through => :memberships
37 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
37 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
38
38
39 validate :validate_status
39 validate :validate_status
40
40
41 # Groups and active users
41 # Groups and active users
42 scope :active, lambda { where(:status => STATUS_ACTIVE) }
42 scope :active, lambda { where(:status => STATUS_ACTIVE) }
43
43
44 scope :visible, lambda {|*args|
44 scope :visible, lambda {|*args|
45 user = args.first || User.current
45 user = args.first || User.current
46
46
47 if user.admin?
47 if user.admin?
48 all
48 all
49 else
49 else
50 view_all_active = false
50 view_all_active = false
51 if user.memberships.to_a.any?
51 if user.memberships.to_a.any?
52 view_all_active = user.memberships.any? {|m| m.roles.any? {|r| r.users_visibility == 'all'}}
52 view_all_active = user.memberships.any? {|m| m.roles.any? {|r| r.users_visibility == 'all'}}
53 else
53 else
54 view_all_active = user.builtin_role.users_visibility == 'all'
54 view_all_active = user.builtin_role.users_visibility == 'all'
55 end
55 end
56
56
57 if view_all_active
57 if view_all_active
58 active
58 active
59 else
59 else
60 # self and members of visible projects
60 # self and members of visible projects
61 active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id FROM #{Member.table_name} WHERE project_id IN (?))",
61 active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id FROM #{Member.table_name} WHERE project_id IN (?))",
62 user.id, user.visible_project_ids
62 user.id, user.visible_project_ids
63 )
63 )
64 end
64 end
65 end
65 end
66 }
66 }
67
67
68 scope :like, lambda {|q|
68 scope :like, lambda {|q|
69 q = q.to_s
69 q = q.to_s
70 if q.blank?
70 if q.blank?
71 where({})
71 where({})
72 else
72 else
73 pattern = "%#{q}%"
73 pattern = "%#{q}%"
74 sql = %w(login firstname lastname).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
74 sql = %w(login firstname lastname).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
75 sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p))"
75 sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p))"
76 params = {:p => pattern}
76 params = {:p => pattern}
77 if q =~ /^(.+)\s+(.+)$/
77 if q =~ /^(.+)\s+(.+)$/
78 a, b = "#{$1}%", "#{$2}%"
78 a, b = "#{$1}%", "#{$2}%"
79 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))"
79 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))"
80 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))"
80 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))"
81 params.merge!(:a => a, :b => b)
81 params.merge!(:a => a, :b => b)
82 end
82 end
83 where(sql, params)
83 where(sql, params)
84 end
84 end
85 }
85 }
86
86
87 # Principals that are members of a collection of projects
87 # Principals that are members of a collection of projects
88 scope :member_of, lambda {|projects|
88 scope :member_of, lambda {|projects|
89 projects = [projects] if projects.is_a?(Project)
89 projects = [projects] if projects.is_a?(Project)
90 if projects.blank?
90 if projects.blank?
91 where("1=0")
91 where("1=0")
92 else
92 else
93 ids = projects.map(&:id)
93 ids = projects.map(&:id)
94 active.where("#{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
94 active.where("#{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
95 end
95 end
96 }
96 }
97 # Principals that are not members of projects
97 # Principals that are not members of projects
98 scope :not_member_of, lambda {|projects|
98 scope :not_member_of, lambda {|projects|
99 projects = [projects] unless projects.is_a?(Array)
99 projects = [projects] unless projects.is_a?(Array)
100 if projects.empty?
100 if projects.empty?
101 where("1=0")
101 where("1=0")
102 else
102 else
103 ids = projects.map(&:id)
103 ids = projects.map(&:id)
104 where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
104 where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
105 end
105 end
106 }
106 }
107 scope :sorted, lambda { order(*Principal.fields_for_order_statement)}
107 scope :sorted, lambda { order(*Principal.fields_for_order_statement)}
108
108
109 before_create :set_default_empty_values
109 before_create :set_default_empty_values
110
110
111 def reload(*args)
112 @project_ids = nil
113 super
114 end
115
111 def name(formatter = nil)
116 def name(formatter = nil)
112 to_s
117 to_s
113 end
118 end
114
119
115 def mail=(*args)
120 def mail=(*args)
116 nil
121 nil
117 end
122 end
118
123
119 def mail
124 def mail
120 nil
125 nil
121 end
126 end
122
127
123 def visible?(user=User.current)
128 def visible?(user=User.current)
124 Principal.visible(user).where(:id => id).first == self
129 Principal.visible(user).where(:id => id).first == self
125 end
130 end
126
131
127 # Return true if the principal is a member of project
132 # Returns true if the principal is a member of project
128 def member_of?(project)
133 def member_of?(project)
129 projects.to_a.include?(project)
134 project.is_a?(Project) && project_ids.include?(project.id)
135 end
136
137 # Returns an array of the project ids that the principal is a member of
138 def project_ids
139 @project_ids ||= super.freeze
130 end
140 end
131
141
132 def <=>(principal)
142 def <=>(principal)
133 if principal.nil?
143 if principal.nil?
134 -1
144 -1
135 elsif self.class.name == principal.class.name
145 elsif self.class.name == principal.class.name
136 self.to_s.casecmp(principal.to_s)
146 self.to_s.casecmp(principal.to_s)
137 else
147 else
138 # groups after users
148 # groups after users
139 principal.class.name <=> self.class.name
149 principal.class.name <=> self.class.name
140 end
150 end
141 end
151 end
142
152
143 # Returns an array of fields names than can be used to make an order statement for principals.
153 # Returns an array of fields names than can be used to make an order statement for principals.
144 # Users are sorted before Groups.
154 # Users are sorted before Groups.
145 # Examples:
155 # Examples:
146 def self.fields_for_order_statement(table=nil)
156 def self.fields_for_order_statement(table=nil)
147 table ||= table_name
157 table ||= table_name
148 columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id']
158 columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id']
149 columns.uniq.map {|field| "#{table}.#{field}"}
159 columns.uniq.map {|field| "#{table}.#{field}"}
150 end
160 end
151
161
152 # Returns the principal that matches the keyword among principals
162 # Returns the principal that matches the keyword among principals
153 def self.detect_by_keyword(principals, keyword)
163 def self.detect_by_keyword(principals, keyword)
154 keyword = keyword.to_s
164 keyword = keyword.to_s
155 return nil if keyword.blank?
165 return nil if keyword.blank?
156
166
157 principal = nil
167 principal = nil
158 principal ||= principals.detect {|a| keyword.casecmp(a.login.to_s) == 0}
168 principal ||= principals.detect {|a| keyword.casecmp(a.login.to_s) == 0}
159 principal ||= principals.detect {|a| keyword.casecmp(a.mail.to_s) == 0}
169 principal ||= principals.detect {|a| keyword.casecmp(a.mail.to_s) == 0}
160
170
161 if principal.nil? && keyword.match(/ /)
171 if principal.nil? && keyword.match(/ /)
162 firstname, lastname = *(keyword.split) # "First Last Throwaway"
172 firstname, lastname = *(keyword.split) # "First Last Throwaway"
163 principal ||= principals.detect {|a|
173 principal ||= principals.detect {|a|
164 a.is_a?(User) &&
174 a.is_a?(User) &&
165 firstname.casecmp(a.firstname.to_s) == 0 &&
175 firstname.casecmp(a.firstname.to_s) == 0 &&
166 lastname.casecmp(a.lastname.to_s) == 0
176 lastname.casecmp(a.lastname.to_s) == 0
167 }
177 }
168 end
178 end
169 if principal.nil?
179 if principal.nil?
170 principal ||= principals.detect {|a| keyword.casecmp(a.name) == 0}
180 principal ||= principals.detect {|a| keyword.casecmp(a.name) == 0}
171 end
181 end
172 principal
182 principal
173 end
183 end
174
184
175 protected
185 protected
176
186
177 # Make sure we don't try to insert NULL values (see #4632)
187 # Make sure we don't try to insert NULL values (see #4632)
178 def set_default_empty_values
188 def set_default_empty_values
179 self.login ||= ''
189 self.login ||= ''
180 self.hashed_password ||= ''
190 self.hashed_password ||= ''
181 self.firstname ||= ''
191 self.firstname ||= ''
182 self.lastname ||= ''
192 self.lastname ||= ''
183 true
193 true
184 end
194 end
185
195
186 def validate_status
196 def validate_status
187 if status_changed? && self.class.valid_statuses.present?
197 if status_changed? && self.class.valid_statuses.present?
188 unless self.class.valid_statuses.include?(status)
198 unless self.class.valid_statuses.include?(status)
189 errors.add :status, :invalid
199 errors.add :status, :invalid
190 end
200 end
191 end
201 end
192 end
202 end
193 end
203 end
194
204
195 require_dependency "user"
205 require_dependency "user"
196 require_dependency "group"
206 require_dependency "group"
General Comments 0
You need to be logged in to leave comments. Login now