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