Skip to content

AO3-7468 Preload current_user roles - #6002

Open
y7nieSEl5 wants to merge 4 commits into
otwcode:masterfrom
y7nieSEl5:AO3-7468
Open

AO3-7468 Preload current_user roles#6002
y7nieSEl5 wants to merge 4 commits into
otwcode:masterfrom
y7nieSEl5:AO3-7468

Conversation

@y7nieSEl5

Copy link
Copy Markdown
Contributor

Pull Request Checklist

Issue

https://otwarchive.atlassian.net/browse/AO3-7468

Purpose

This PR preloads a user's roles when Devise restores current_user from the session. As a result, subsequent has_role? permission checks use the loaded association instead of performing repeated role lookups throughout the request.

It also:

  • Uses has_role? for named-role checks in the admin user form.
  • Preloads roles when loading users for admin user pages that render the role collection.
  • Adds regression coverage for users with roles, users without roles, invalid session salts, and admin user role preloading.

Credit

y7nieSEl5, she/her

expect(response).to have_http_status(:success)
end

it "preloads the user's roles" do

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking that roles are preloaded like this, you should create an n+1 test where the number of roles is variable, but get admin_user_path(user) should perform a constant number of queries for role lookups.

We have similar tests for other controller actions under spec/requests.

Comment thread spec/models/user_spec.rb Outdated
@y7nieSEl5

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback! I replaced the controller preload assertion with a request-level N+1 test, preloaded roles for the history entries rendered on that page, and removed the two extra model expectations.
All three relevant spec files pass. Ready for another look!

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) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're limiting the number of roles to 5 or below. In populate you should create objects using the scale factor n, otherwise results won't be accurate.

Since we don't care what the roles actually are, you can do:

populate do |n|
  user.roles = create_list(:role, n)
end


describe "#show", n_plus_one: true do
let!(:user) { create(:user) }
let!(:admin) { create(:policy_and_abuse_admin) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can inline admin in the before block - it won't be referred to elsewhere.


it "performs a constant number of role queries" do
expect do
get admin_user_path(user)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can define the get action as a subject:

subject do
  proc do
    get admin_user_path(user)
  end
end

then use subject.call in both warmup and the actual test.

it "performs a constant number of role queries" do
expect do
get admin_user_path(user)
expect(response).to have_http_status(:success)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These expects don't need to be nested here. You can move them after the first expect for constant query count.

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/)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to skip matching and with_scale_factors, since these are the effects of only working with a max of 5 roles.

include LoginMacros

describe "#show", n_plus_one: true do
let!(:user) { create(:user) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should wrap the tests in a context block e.g. "with a logged in user with multiple roles". There may be other kinds of n+1 queries we want to test for this controller action in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants