diff --git a/app/controllers/admin/admin_users_controller.rb b/app/controllers/admin/admin_users_controller.rb
index 0df19517da9..06b7ac338a8 100644
--- a/app/controllers/admin/admin_users_controller.rb
+++ b/app/controllers/admin/admin_users_controller.rb
@@ -11,7 +11,7 @@ def set_roles
end
def load_user
- @user = User.find_by!(login: params[:id])
+ @user = User.includes(:roles).find_by!(login: params[:id])
end
def user_is_banned
@@ -248,6 +248,6 @@ def search_params
end
def log_items
- @log_items ||= @user.log_items.sort_by(&:created_at).reverse
+ @log_items ||= @user.log_items.includes(:role).sort_by(&:created_at).reverse
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 4dcabe22b5f..5af23e989f7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -274,6 +274,12 @@ def self.find_first_by_auth_conditions(tainted_conditions, options = {})
relation.first
end
+ # Override of Devise method to preload roles for permission checks.
+ def self.serialize_from_session(key, salt)
+ record = includes(:roles).where(primary_key => key).first
+ record if record && record.authenticatable_salt == salt
+ end
+
def self.for_claims(claims_ids)
joins(:request_claims)
.where("challenge_claims.id IN (?)", claims_ids)
diff --git a/app/views/admin/admin_users/_user_form.html.erb b/app/views/admin/admin_users/_user_form.html.erb
index addc627d6f4..ad445bb231e 100644
--- a/app/views/admin/admin_users/_user_form.html.erb
+++ b/app/views/admin/admin_users/_user_form.html.erb
@@ -12,7 +12,7 @@
<%= text_field_tag "user[email]", user.email, title: ts("Email"), disabled: !admin_can_update_user_email? %> |
<% for role in @roles %>
- <%= check_box_tag "user[roles][]", role.id, user.roles.include?(role), title: role.name, id: "user_roles_#{role.id}", disabled: !policy(User).can_edit_user_role?(role) %>
+ <%= check_box_tag "user[roles][]", role.id, user.has_role?(role.name), title: role.name, id: "user_roles_#{role.id}", disabled: !policy(User).can_edit_user_role?(role) %>
|
<% end %>
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index e02b1b4efe2..2326c0f91bc 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,6 +1,32 @@
require "spec_helper"
describe User do
+ describe ".serialize_from_session" do
+ let(:role) { create(:role) }
+ let(:user) { create(:user, roles: [role]) }
+
+ it "returns the user with roles loaded when the salt matches" do
+ deserialized_user = described_class.serialize_from_session(user.to_key, user.authenticatable_salt)
+
+ expect(deserialized_user).to eq(user)
+ expect(deserialized_user.association(:roles)).to be_loaded
+ expect(deserialized_user.roles).to contain_exactly(role)
+ end
+
+ it "loads the roles association when the user has no roles" do
+ user = create(:user)
+
+ deserialized_user = described_class.serialize_from_session(user.to_key, user.authenticatable_salt)
+
+ expect(deserialized_user.association(:roles)).to be_loaded
+ expect(deserialized_user.roles).to be_empty
+ end
+
+ it "returns nil when the salt does not match" do
+ expect(described_class.serialize_from_session(user.to_key, "invalid salt")).to be_nil
+ end
+ end
+
describe "audits" do
let(:user) { create(:user) }
diff --git a/spec/requests/admin_users_n_plus_one_spec.rb b/spec/requests/admin_users_n_plus_one_spec.rb
new file mode 100644
index 00000000000..c3453c8089f
--- /dev/null
+++ b/spec/requests/admin_users_n_plus_one_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "n+1 queries in the admin users controller" do
+ include LoginMacros
+
+ describe "#show", n_plus_one: true do
+ let!(:user) { create(:user) }
+ let!(:admin) { create(:policy_and_abuse_admin) }
+
+ before { fake_login_admin(admin) }
+
+ populate do |n|
+ role_names = %w[archivist no_resets official opendoors protected_user]
+ # Assigning roles also creates the role-change history rendered on this page.
+ user.reload.roles = role_names.first(n).map { |name| create(:role, name: name) }
+ end
+
+ warmup { get admin_user_path(user) }
+
+ it "performs a constant number of role queries" do
+ expect do
+ get admin_user_path(user)
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include("user_history")
+ end.to perform_constant_number_of_queries
+ .matching(/\b(?:roles|roles_users)\b/)
+ .with_scale_factors(2, 5)
+ end
+ end
+end
|