Skip to content

Conversation

@januszm
Copy link

@januszm januszm commented Dec 26, 2025

How it works

For example:

user.has_all_roles?(
  :admin,
  { name: :developer, resource: record.resource },
  { name: :manager, resource: record.resource }
)

Before: 3 separate queries (one for each role)
After: 1 query like:

SELECT DISTINCT COUNT(*) 
FROM roles
WHERE user_id = ? AND (
  (roles.name = 'admin' AND roles.resource_type IS NULL AND roles.resource_id IS NULL)
  OR (roles.name = 'developer' AND roles.resource_type = '...' AND roles.resource_id = ...)
  OR (roles.name = 'manager' AND roles.resource_type = '...' AND roles.resource_id = ...)
)

Then it checks if count == 3 (the number of roles requested). Both methods now use single SQL queries when possible, eliminating the N+1 query problem! 🎉

The has_any_role? method was already using a single query via self.class.adapter.where(self.roles, *args) which leverages the build_conditions method that creates OR conditions.

For the with_any_role and with_all_roles methods, replace the looping parse_args approach with a single query that leverages the existing build_conditions method in the adapter. Here's the optimized implementation:

  1. Leverages existing code: Uses the adapter's build_conditions method which already knows how to build OR conditions
  2. for User.with_any_role: Uses a single query with OR conditions and DISTINCT to get unique users
  3. for User.with_all_roles: Uses GROUP BY and HAVING COUNT to ensure users have ALL the specified roles

After (optimized):

SELECT DISTINCT users.* 
FROM users 
INNER JOIN roles ON users_roles.user_id = users.id
WHERE (
  (roles.name = 'admin' AND roles.resource_type IS NULL AND roles.resource_id IS NULL)
  OR (roles.name = 'developer' AND roles.resource_type = '...' AND roles.resource_id = ...)
  OR (roles.name = 'funder' AND roles.resource_type = '...' AND roles.resource_id = ...)
)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant