##// END OF EJS Templates
Sort members on the DB side....
Jean-Philippe Lang -
r15658:e22159a3cb72
parent child
Show More
@@ -1,198 +1,205
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 Member < ActiveRecord::Base
19 19 belongs_to :user
20 20 belongs_to :principal, :foreign_key => 'user_id'
21 21 has_many :member_roles, :dependent => :destroy
22 22 has_many :roles, lambda { distinct }, :through => :member_roles
23 23 belongs_to :project
24 24
25 25 validates_presence_of :principal, :project
26 26 validates_uniqueness_of :user_id, :scope => :project_id
27 27 validate :validate_role
28 28 attr_protected :id
29 29
30 30 before_destroy :set_issue_category_nil
31 31
32 32 scope :active, lambda { joins(:principal).where(:users => {:status => Principal::STATUS_ACTIVE})}
33 33
34 # Sort by first role and principal
35 scope :sorted, lambda {
36 includes(:member_roles, :roles, :principal).
37 reorder("#{Role.table_name}.position").
38 order(Principal.fields_for_order_statement)
39 }
40
34 41 alias :base_reload :reload
35 42 def reload(*args)
36 43 @managed_roles = nil
37 44 base_reload(*args)
38 45 end
39 46
40 47 def role
41 48 end
42 49
43 50 def role=
44 51 end
45 52
46 53 def name
47 54 self.user.name
48 55 end
49 56
50 57 alias :base_role_ids= :role_ids=
51 58 def role_ids=(arg)
52 59 ids = (arg || []).collect(&:to_i) - [0]
53 60 # Keep inherited roles
54 61 ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id)
55 62
56 63 new_role_ids = ids - role_ids
57 64 # Add new roles
58 65 new_role_ids.each {|id| member_roles << MemberRole.new(:role_id => id, :member => self) }
59 66 # Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
60 67 member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)}
61 68 if member_roles_to_destroy.any?
62 69 member_roles_to_destroy.each(&:destroy)
63 70 end
64 71 end
65 72
66 73 def <=>(member)
67 74 a, b = roles.sort, member.roles.sort
68 75 if a == b
69 76 if principal
70 77 principal <=> member.principal
71 78 else
72 79 1
73 80 end
74 81 elsif a.any?
75 82 b.any? ? a <=> b : -1
76 83 else
77 84 1
78 85 end
79 86 end
80 87
81 88 # Set member role ids ignoring any change to roles that
82 89 # user is not allowed to manage
83 90 def set_editable_role_ids(ids, user=User.current)
84 91 ids = (ids || []).collect(&:to_i) - [0]
85 92 editable_role_ids = user.managed_roles(project).map(&:id)
86 93 untouched_role_ids = self.role_ids - editable_role_ids
87 94 touched_role_ids = ids & editable_role_ids
88 95 self.role_ids = untouched_role_ids + touched_role_ids
89 96 end
90 97
91 98 # Returns true if one of the member roles is inherited
92 99 def any_inherited_role?
93 100 member_roles.any? {|mr| mr.inherited_from}
94 101 end
95 102
96 103 # Returns true if the member has the role and if it's inherited
97 104 def has_inherited_role?(role)
98 105 member_roles.any? {|mr| mr.role_id == role.id && mr.inherited_from.present?}
99 106 end
100 107
101 108 # Returns true if the member's role is editable by user
102 109 def role_editable?(role, user=User.current)
103 110 if has_inherited_role?(role)
104 111 false
105 112 else
106 113 user.managed_roles(project).include?(role)
107 114 end
108 115 end
109 116
110 117 # Returns true if the member is deletable by user
111 118 def deletable?(user=User.current)
112 119 if any_inherited_role?
113 120 false
114 121 else
115 122 roles & user.managed_roles(project) == roles
116 123 end
117 124 end
118 125
119 126 # Destroys the member
120 127 def destroy
121 128 member_roles.reload.each(&:destroy_without_member_removal)
122 129 super
123 130 end
124 131
125 132 # Returns true if the member is user or is a group
126 133 # that includes user
127 134 def include?(user)
128 135 if principal.is_a?(Group)
129 136 !user.nil? && user.groups.include?(principal)
130 137 else
131 138 self.user == user
132 139 end
133 140 end
134 141
135 142 def set_issue_category_nil
136 143 if user_id && project_id
137 144 # remove category based auto assignments for this member
138 145 IssueCategory.where(["project_id = ? AND assigned_to_id = ?", project_id, user_id]).
139 146 update_all("assigned_to_id = NULL")
140 147 end
141 148 end
142 149
143 150 # Returns the roles that the member is allowed to manage
144 151 # in the project the member belongs to
145 152 def managed_roles
146 153 @managed_roles ||= begin
147 154 if principal.try(:admin?)
148 155 Role.givable.to_a
149 156 else
150 157 members_management_roles = roles.select do |role|
151 158 role.has_permission?(:manage_members)
152 159 end
153 160 if members_management_roles.empty?
154 161 []
155 162 elsif members_management_roles.any?(&:all_roles_managed?)
156 163 Role.givable.to_a
157 164 else
158 165 members_management_roles.map(&:managed_roles).reduce(&:|)
159 166 end
160 167 end
161 168 end
162 169 end
163 170
164 171 # Creates memberships for principal with the attributes
165 172 # * project_ids : one or more project ids
166 173 # * role_ids : ids of the roles to give to each membership
167 174 #
168 175 # Example:
169 176 # Member.create_principal_memberships(user, :project_ids => [2, 5], :role_ids => [1, 3]
170 177 def self.create_principal_memberships(principal, attributes)
171 178 members = []
172 179 if attributes
173 180 project_ids = Array.wrap(attributes[:project_ids] || attributes[:project_id])
174 181 role_ids = attributes[:role_ids]
175 182 project_ids.each do |project_id|
176 183 members << Member.new(:principal => principal, :role_ids => role_ids, :project_id => project_id)
177 184 end
178 185 principal.members << members
179 186 end
180 187 members
181 188 end
182 189
183 190 # Finds or initilizes a Member for the given project and principal
184 191 def self.find_or_new(project, principal)
185 192 project_id = project.is_a?(Project) ? project.id : project
186 193 principal_id = principal.is_a?(Principal) ? principal.id : principal
187 194
188 195 member = Member.find_by_project_id_and_user_id(project_id, principal_id)
189 196 member ||= Member.new(:project_id => project_id, :user_id => principal_id)
190 197 member
191 198 end
192 199
193 200 protected
194 201
195 202 def validate_role
196 203 errors.add(:role, :empty) if member_roles.empty? && roles.empty?
197 204 end
198 205 end
@@ -1,62 +1,62
1 1 <% roles = Role.find_all_givable
2 members = @project.memberships.active.includes(:member_roles, :roles, :principal).to_a.sort %>
2 members = @project.memberships.active.sorted.to_a %>
3 3
4 4 <p><%= link_to l(:label_member_new), new_project_membership_path(@project), :remote => true, :class => "icon icon-add" %></p>
5 5
6 6 <% if members.any? %>
7 7 <table class="list members">
8 8 <thead>
9 9 <tr>
10 10 <th><%= l(:label_user) %> / <%= l(:label_group) %></th>
11 11 <th><%= l(:label_role_plural) %></th>
12 12 <th style="width:15%"></th>
13 13 <%= call_hook(:view_projects_settings_members_table_header, :project => @project) %>
14 14 </tr>
15 15 </thead>
16 16 <tbody>
17 17 <% members.each do |member| %>
18 18 <% next if member.new_record? %>
19 19 <tr id="member-<%= member.id %>" class="<%= cycle 'odd', 'even' %> member">
20 20 <td class="name icon icon-<%= member.principal.class.name.downcase %>"><%= link_to_user member.principal %></td>
21 21 <td class="roles">
22 22 <span id="member-<%= member.id %>-roles"><%= member.roles.sort.collect(&:to_s).join(', ') %></span>
23 23 <%= form_for(member,
24 24 {:as => :membership, :remote => true,
25 25 :url => membership_path(member),
26 26 :method => :put,
27 27 :html => { :id => "member-#{member.id}-roles-form", :class => 'hol' }}
28 28 ) do |f| %>
29 29 <p>
30 30 <% roles.each do |role| %>
31 31 <label>
32 32 <%= check_box_tag('membership[role_ids][]',
33 33 role.id, member.roles.include?(role),
34 34 :id => nil,
35 35 :disabled => !member.role_editable?(role)) %> <%= role %>
36 36 </label><br />
37 37 <% end %>
38 38 </p>
39 39 <%= hidden_field_tag 'membership[role_ids][]', '', :id => nil %>
40 40 <p>
41 41 <%= submit_tag l(:button_save), :class => "small" %>
42 42 <%= link_to_function(l(:button_cancel),
43 43 "$('#member-#{member.id}-roles').show(); $('#member-#{member.id}-roles-form').hide(); return false;") %>
44 44 </p>
45 45 <% end %>
46 46 </td>
47 47 <td class="buttons">
48 48 <%= link_to_function l(:button_edit),
49 49 "$('#member-#{member.id}-roles').hide(); $('#member-#{member.id}-roles-form').show(); return false;",
50 50 :class => 'icon icon-edit' %>
51 51 <%= delete_link membership_path(member),
52 52 :remote => true,
53 53 :data => (!User.current.admin? && member.include?(User.current) ? {:confirm => l(:text_own_membership_delete_confirmation)} : {}) if member.deletable? %>
54 54 </td>
55 55 <%= call_hook(:view_projects_settings_members_table_row, { :project => @project, :member => member}) %>
56 56 </tr>
57 57 <% end; reset_cycle %>
58 58 </tbody>
59 59 </table>
60 60 <% else %>
61 61 <p class="nodata"><%= l(:label_no_data) %></p>
62 62 <% end %>
@@ -1,193 +1,199
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 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class MemberTest < ActiveSupport::TestCase
21 21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 22 :enumerations, :users, :issue_categories,
23 23 :projects_trackers,
24 24 :roles,
25 25 :member_roles,
26 26 :members,
27 27 :enabled_modules,
28 28 :groups_users,
29 29 :watchers,
30 30 :journals, :journal_details,
31 31 :messages,
32 32 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
33 33 :boards
34 34
35 35 include Redmine::I18n
36 36
37 37 def setup
38 38 @jsmith = Member.find(1)
39 39 end
40 40
41 def test_sorted_scope_on_project_members
42 members = Project.find(1).members.sorted.to_a
43 roles = members.map {|m| m.roles.sort.first}
44 assert_equal roles, roles.sort
45 end
46
41 47 def test_create
42 48 member = Member.new(:project_id => 1, :user_id => 4, :role_ids => [1, 2])
43 49 assert member.save
44 50 member.reload
45 51
46 52 assert_equal 2, member.roles.size
47 53 assert_equal Role.find(1), member.roles.sort.first
48 54 end
49 55
50 56 def test_update
51 57 assert_equal "eCookbook", @jsmith.project.name
52 58 assert_equal "Manager", @jsmith.roles.first.name
53 59 assert_equal "jsmith", @jsmith.user.login
54 60
55 61 @jsmith.mail_notification = !@jsmith.mail_notification
56 62 assert @jsmith.save
57 63 end
58 64
59 65 def test_update_roles
60 66 assert_equal 1, @jsmith.roles.size
61 67 @jsmith.role_ids = [1, 2]
62 68 assert @jsmith.save
63 69 assert_equal 2, @jsmith.reload.roles.size
64 70 end
65 71
66 72 def test_validate
67 73 member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [2])
68 74 # same use cannot have more than one membership for a project
69 75 assert !member.save
70 76
71 77 # must have one role at least
72 78 user = User.new(:firstname => "new1", :lastname => "user1",
73 79 :mail => "test_validate@somenet.foo")
74 80 user.login = "test_validate"
75 81 user.password, user.password_confirmation = "password", "password"
76 82 assert user.save
77 83
78 84 set_language_if_valid 'fr'
79 85 member = Member.new(:project_id => 1, :user_id => user.id, :role_ids => [])
80 86 assert !member.save
81 87 assert_include I18n.translate('activerecord.errors.messages.empty'), member.errors[:role]
82 88 assert_equal "R\xc3\xb4le doit \xc3\xaatre renseign\xc3\xa9(e)".force_encoding('UTF-8'),
83 89 [member.errors.full_messages].flatten.join
84 90 end
85 91
86 92 def test_validate_member_role
87 93 user = User.new(:firstname => "new1", :lastname => "user1",
88 94 :mail => "test_validate@somenet.foo")
89 95 user.login = "test_validate_member_role"
90 96 user.password, user.password_confirmation = "password", "password"
91 97 assert user.save
92 98 member = Member.new(:project_id => 1, :user_id => user.id, :role_ids => [5])
93 99 assert !member.save
94 100 end
95 101
96 102 def test_set_issue_category_nil_should_handle_nil_values
97 103 m = Member.new
98 104 assert_nil m.user
99 105 assert_nil m.project
100 106
101 107 assert_nothing_raised do
102 108 m.set_issue_category_nil
103 109 end
104 110 end
105 111
106 112 def test_destroy
107 113 category1 = IssueCategory.find(1)
108 114 assert_equal @jsmith.user.id, category1.assigned_to_id
109 115 assert_difference 'Member.count', -1 do
110 116 assert_difference 'MemberRole.count', -1 do
111 117 @jsmith.destroy
112 118 end
113 119 end
114 120 assert_raise(ActiveRecord::RecordNotFound) { Member.find(@jsmith.id) }
115 121 category1.reload
116 122 assert_nil category1.assigned_to_id
117 123 end
118 124
119 125 def test_destroy_should_trigger_callbacks_only_once
120 126 Member.class_eval { def destroy_test_callback; end}
121 127 Member.after_destroy :destroy_test_callback
122 128
123 129 m = Member.create!(:user_id => 1, :project_id => 1, :role_ids => [1,3])
124 130
125 131 Member.any_instance.expects(:destroy_test_callback).once
126 132 assert_difference 'Member.count', -1 do
127 133 assert_difference 'MemberRole.count', -2 do
128 134 m.destroy
129 135 end
130 136 end
131 137 assert m.destroyed?
132 138 ensure
133 139 Member._destroy_callbacks.delete(:destroy_test_callback)
134 140 end
135 141
136 142 def test_roles_should_be_unique
137 143 m = Member.new(:user_id => 1, :project_id => 1)
138 144 m.member_roles.build(:role_id => 1)
139 145 m.member_roles.build(:role_id => 1)
140 146 m.save!
141 147 m.reload
142 148 assert_equal 1, m.roles.count
143 149 assert_equal [1], m.roles.ids
144 150 end
145 151
146 152 def test_sort_without_roles
147 153 a = Member.new(:roles => [Role.first])
148 154 b = Member.new
149 155
150 156 assert_equal -1, a <=> b
151 157 assert_equal 1, b <=> a
152 158 end
153 159
154 160 def test_sort_without_principal
155 161 role = Role.first
156 162 a = Member.new(:roles => [role], :principal => User.first)
157 163 b = Member.new(:roles => [role])
158 164
159 165 assert_equal -1, a <=> b
160 166 assert_equal 1, b <=> a
161 167 end
162 168
163 169 def test_managed_roles_should_return_all_roles_for_role_with_all_roles_managed
164 170 member = Member.new
165 171 member.roles << Role.generate!(:permissions => [:manage_members], :all_roles_managed => true)
166 172 assert_equal Role.givable.all, member.managed_roles
167 173 end
168 174
169 175 def test_managed_roles_should_return_all_roles_for_admins
170 176 member = Member.new(:user => User.find(1))
171 177 member.roles << Role.generate!
172 178 assert_equal Role.givable.all, member.managed_roles
173 179 end
174 180
175 181 def test_managed_roles_should_return_limited_roles_for_role_without_all_roles_managed
176 182 member = Member.new
177 183 member.roles << Role.generate!(:permissions => [:manage_members], :all_roles_managed => false, :managed_role_ids => [2, 3])
178 184 assert_equal [2, 3], member.managed_roles.map(&:id).sort
179 185 end
180 186
181 187 def test_managed_roles_should_cumulated_managed_roles
182 188 member = Member.new
183 189 member.roles << Role.generate!(:permissions => [:manage_members], :all_roles_managed => false, :managed_role_ids => [3])
184 190 member.roles << Role.generate!(:permissions => [:manage_members], :all_roles_managed => false, :managed_role_ids => [2])
185 191 assert_equal [2, 3], member.managed_roles.map(&:id).sort
186 192 end
187 193
188 194 def test_managed_roles_should_return_no_roles_for_role_without_permission
189 195 member = Member.new
190 196 member.roles << Role.generate!(:all_roles_managed => true)
191 197 assert_equal [], member.managed_roles
192 198 end
193 199 end
General Comments 0
You need to be logged in to leave comments. Login now