Use role caching for Rolify to avoid N+1 queries

Resolves #2018

See https://github.com/RolifyCommunity/rolify#cached-roles-to-avoid-n1-issue
This commit is contained in:
James Mason 2018-03-28 17:31:18 -07:00
parent 30ba7b133b
commit 98defe2d0f
13 changed files with 44 additions and 35 deletions

View file

@ -13,10 +13,10 @@ module Admin
redirect_to sign_in_path redirect_to sign_in_path
return false return false
end end
unless (current_user.has_role? :organizer, :any) || (current_user.has_role? :cfp, :any) || unless (current_user.has_cached_role? :organizer, :any) || (current_user.has_cached_role? :cfp, :any) ||
(current_user.has_role? :info_desk, :any) || (current_user.has_role? :organization_admin, :any) || (current_user.has_cached_role? :info_desk, :any) || (current_user.has_cached_role? :organization_admin, :any) ||
(current_user.has_role? :volunteers_coordinator, :any) || (current_user.has_cached_role? :volunteers_coordinator, :any) ||
(current_user.has_role? :track_organizer, :any) || current_user.is_admin (current_user.has_cached_role? :track_organizer, :any) || current_user.is_admin
raise CanCan::AccessDenied.new('You are not authorized to access this page.') raise CanCan::AccessDenied.new('You are not authorized to access this page.')
end end
end end

View file

@ -45,7 +45,7 @@ module Admin
end end
def assign_org_admins def assign_org_admins
if @user.has_role? 'organization_admin', @organization if @user.has_cached_role? 'organization_admin', @organization
flash[:error] = "User #{@user.email} already has the role organization admin" flash[:error] = "User #{@user.email} already has the role organization admin"
elsif @user.add_role 'organization_admin', @organization elsif @user.add_role 'organization_admin', @organization
flash[:notice] = "Successfully added role organization admin to user #{@user.email}" flash[:notice] = "Successfully added role organization admin to user #{@user.email}"

View file

@ -86,7 +86,7 @@ module Admin
else else
flash[:error] = "Could not remove role #{@role.name} from user #{user.email}" flash[:error] = "Could not remove role #{@role.name} from user #{user.email}"
end end
elsif user.has_role? @role.name, role_resource elsif user.has_cached_role? @role.name, role_resource
flash[:error] = "User #{user.email} already has the role #{@role.name}" flash[:error] = "User #{user.email} already has the role #{@role.name}"
# Add user # Add user
elsif user.add_role @role.name, role_resource elsif user.add_role @role.name, role_resource

View file

@ -6,7 +6,7 @@ module Admin
def index def index
@conferences_with_role = current_user.is_admin? ? Conference.pluck(:short_title) : Conference.with_role([:organizer, :cfp, :info_desk], current_user).pluck(:short_title) @conferences_with_role = current_user.is_admin? ? Conference.pluck(:short_title) : Conference.with_role([:organizer, :cfp, :info_desk], current_user).pluck(:short_title)
if current_user.has_role? :organization_admin, :any if current_user.has_cached_role? :organization_admin, :any
@conferences_with_role = Organization.with_role('organization_admin', current_user).map { |org| org.conferences.pluck :short_title }.flatten @conferences_with_role = Organization.with_role('organization_admin', current_user).map { |org| org.conferences.pluck :short_title }.flatten
end end
@conferences_with_role.uniq! @conferences_with_role.uniq!

View file

@ -1,7 +1,7 @@
module Admin module Admin
module VolunteersHelper module VolunteersHelper
def can_manage_volunteers?(conference) def can_manage_volunteers?(conference)
current_user.has_role?(:organizer, conference) || current_user.has_role?(:volunteers_coordinator, conference) current_user.has_cached_role?(:organizer, conference) || current_user.has_cached_role?(:volunteers_coordinator, conference)
end end
end end
end end

View file

@ -5,8 +5,8 @@ class AdminAbility
# Order Abilities # Order Abilities
# (Check https://github.com/CanCanCommunity/cancancan/wiki/Ability-Precedence) # (Check https://github.com/CanCanCommunity/cancancan/wiki/Ability-Precedence)
# Check roles of user, using rolify. Role name is *case sensitive* # Check roles of user, using rolify. Role name is *case sensitive*
# user.is_organizer? or user.has_role? :organizer # user.is_organizer? or user.has_cached_role? :organizer
# user.is_cfp_of? Conference or user.has_role? :cfp, Conference # user.is_cfp_of? Conference or user.has_cached_role? :cfp, Conference
# user.is_info_desk_of? Conference # user.is_info_desk_of? Conference
# user.is_volunteers_coordinator_of? Conference # user.is_volunteers_coordinator_of? Conference
# user.is_attendee_of? Conference # user.is_attendee_of? Conference
@ -79,12 +79,12 @@ class AdminAbility
# Abilities for signed in users with roles # Abilities for signed in users with roles
def signed_in_with_roles(user) def signed_in_with_roles(user)
signed_in_with_organization_admin_role(user) if user.has_role? :organization_admin, :any signed_in_with_organization_admin_role(user) if user.has_cached_role? :organization_admin, :any
signed_in_with_organizer_role(user) if user.has_role? :organizer, :any signed_in_with_organizer_role(user) if user.has_cached_role? :organizer, :any
signed_in_with_cfp_role(user) if user.has_role? :cfp, :any signed_in_with_cfp_role(user) if user.has_cached_role? :cfp, :any
signed_in_with_info_desk_role(user) if user.has_role? :info_desk, :any signed_in_with_info_desk_role(user) if user.has_cached_role? :info_desk, :any
signed_in_with_volunteers_coordinator_role(user) if user.has_role? :volunteers_coordinator, :any signed_in_with_volunteers_coordinator_role(user) if user.has_cached_role? :volunteers_coordinator, :any
signed_in_with_track_organizer_role(user) if user.has_role? :track_organizer, :any signed_in_with_track_organizer_role(user) if user.has_cached_role? :track_organizer, :any
common_abilities_for_roles(user) common_abilities_for_roles(user)
end end

View file

@ -6,6 +6,9 @@ end
class User < ApplicationRecord class User < ApplicationRecord
rolify rolify
# prevent N+1 queries with has_cached_role? by preloading roles *always*
default_scope { preload(:roles) }
has_many :physical_tickets, through: :ticket_purchases do has_many :physical_tickets, through: :ticket_purchases do
def by_conference(conference) def by_conference(conference)
where('ticket_purchases.conference_id = ?', conference) where('ticket_purchases.conference_id = ?', conference)

View file

@ -73,7 +73,7 @@
method: :patch, class: 'btn btn-mini btn-success', id: "resubmit_track_request_#{track.id}" method: :patch, class: 'btn btn-mini btn-success', id: "resubmit_track_request_#{track.id}"
- if can? :edit, track - if can? :edit, track
= link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), class: 'btn btn-default' = link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), class: 'btn btn-default'
- if current_user.has_role? :track_organizer, track - if current_user.has_cached_role? :track_organizer, track
= link_to 'Manage', admin_conference_program_track_path(@conference.short_title, track), class: 'btn btn-default' = link_to 'Manage', admin_conference_program_track_path(@conference.short_title, track), class: 'btn btn-default'
.row .row

View file

@ -8,7 +8,7 @@
.btn-group.pull-right .btn-group.pull-right
- if can? :edit, @track - if can? :edit, @track
= link_to 'Edit Track request', edit_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-primary' = link_to 'Edit Track request', edit_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-primary'
- if current_user.has_role? :track_organizer, @track - if current_user.has_cached_role? :track_organizer, @track
= link_to 'Manage', admin_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-default' = link_to 'Manage', admin_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-default'
.row .row
.col-md-8 .col-md-8

View file

@ -30,7 +30,8 @@ feature Conference do
.to eq('Conference was successfully created.') .to eq('Conference was successfully created.')
expect(Conference.count).to eq(expected_count) expect(Conference.count).to eq(expected_count)
expect(Conference.last.organization).to eq(organization) expect(Conference.last.organization).to eq(organization)
expect(user.has_role? :organizer, Conference.last).to eq(true) user.reload
expect(user.has_cached_role? :organizer, Conference.last).to eq(true)
end end
scenario 'update conference', feature: true, js: true do scenario 'update conference', feature: true, js: true do

View file

@ -58,7 +58,7 @@ feature Role do
click_button 'Add' click_button 'Add'
user_with_no_role.reload user_with_no_role.reload
expect(user_with_no_role.has_role?(role.name, conference)).to eq true expect(user_with_no_role.has_cached_role?(role.name, conference)).to eq true
end end
scenario "removes role #{role_name}", feature: true, js: true do scenario "removes role #{role_name}", feature: true, js: true do
@ -69,7 +69,8 @@ feature Role do
expect(find('.alert').text).to eq "×Successfully removed role #{role_name} from user #{user_with_role.email}" expect(find('.alert').text).to eq "×Successfully removed role #{role_name} from user #{user_with_role.email}"
expect(by_role_name).to eq(role_name) | eq('organizer') expect(by_role_name).to eq(role_name) | eq('organizer')
expect(user_with_role.has_role?(role_name, conference)).to eq false user_with_role.reload
expect(user_with_role.has_cached_role?(role_name, conference)).to eq false
end end
end end
@ -118,14 +119,15 @@ feature Role do
click_button 'Add' click_button 'Add'
user_with_no_role.reload user_with_no_role.reload
expect(user_with_no_role.has_role?('organization_admin', organization)).to eq true expect(user_with_no_role.has_cached_role?('organization_admin', organization)).to eq true
end end
scenario 'successfully removes role organization_admin' do scenario 'successfully removes role organization_admin' do
click_link('Admins', href: admins_admin_organization_path(organization.id)) click_link('Admins', href: admins_admin_organization_path(organization.id))
first('tr').find('.btn-danger').click first('tr').find('.btn-danger').click
expect(organization_admin.has_role?('organization_admin', organization)).to eq false organization_admin.reload
expect(organization_admin.has_cached_role?('organization_admin', organization)).to eq false
end end
end end

View file

@ -347,17 +347,17 @@ describe Track do
end end
it 'gives the role of the track organizer to the submitter of the track' do it 'gives the role of the track organizer to the submitter of the track' do
expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq false expect(@submitter.has_cached_role?(:track_organizer, self_organized_track)).to eq false
self_organized_track.assign_role_to_submitter self_organized_track.assign_role_to_submitter
expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq true expect(@submitter.has_cached_role?(:track_organizer, self_organized_track)).to eq true
end end
it 'is executed when the track is confirmed' do it 'is executed when the track is confirmed' do
self_organized_track.state = 'accepted' self_organized_track.state = 'accepted'
self_organized_track.save! self_organized_track.save!
expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq false expect(@submitter.has_cached_role?(:track_organizer, self_organized_track)).to eq false
self_organized_track.confirm self_organized_track.confirm
expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq true expect(@submitter.has_cached_role?(:track_organizer, self_organized_track)).to eq true
end end
end end
@ -374,9 +374,10 @@ describe Track do
end end
it 'revokes the role of the track organizer' do it 'revokes the role of the track organizer' do
expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq true expect(@a_track_organizer.has_cached_role?(:track_organizer, self_organized_track)).to eq true
self_organized_track.revoke_role_and_cleanup self_organized_track.revoke_role_and_cleanup
expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false @a_track_organizer.reload
expect(@a_track_organizer.has_cached_role?(:track_organizer, self_organized_track)).to eq false
end end
it 'destroys the track\'s schedules' do it 'destroys the track\'s schedules' do
@ -403,14 +404,16 @@ describe Track do
self_organized_track.state = 'confirmed' self_organized_track.state = 'confirmed'
self_organized_track.save! self_organized_track.save!
self_organized_track.cancel self_organized_track.cancel
expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false @a_track_organizer.reload
expect(@a_track_organizer.has_cached_role?(:track_organizer, self_organized_track)).to eq false
@event_of_self_organized_track.reload @event_of_self_organized_track.reload
expect(@event_of_self_organized_track.track).to eq nil expect(@event_of_self_organized_track.track).to eq nil
end end
it 'is executed when the track is withdrawn' do it 'is executed when the track is withdrawn' do
self_organized_track.withdraw self_organized_track.withdraw
expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false @a_track_organizer.reload
expect(@a_track_organizer.has_cached_role?(:track_organizer, self_organized_track)).to eq false
@event_of_self_organized_track.reload @event_of_self_organized_track.reload
expect(@event_of_self_organized_track.track).to eq nil expect(@event_of_self_organized_track.track).to eq nil
end end

View file

@ -395,22 +395,22 @@ describe User do
expect(another_user.roles[1]).to eq(cfp_role) expect(another_user.roles[1]).to eq(cfp_role)
end end
describe '#has_role?' do describe '#has_cached_role?' do
describe 'when user has a role' do describe 'when user has a role' do
it 'returns true when the user has the role' do it 'returns true when the user has the role' do
user = create(:user, role_ids: organizer_role.id) user = create(:user, role_ids: organizer_role.id)
expect(user.has_role?('organizer', conference)).to be true expect(user.has_cached_role?('organizer', conference)).to be true
end end
it 'returns false when the user does not have the role' do it 'returns false when the user does not have the role' do
user = create(:user, role_ids: cfp_role.id) user = create(:user, role_ids: cfp_role.id)
expect(user.has_role?('organizer', conference)).to be false expect(user.has_cached_role?('organizer', conference)).to be false
end end
end end
it 'returns false when the user does not have a role' do it 'returns false when the user does not have a role' do
user = create(:user, role_ids: []) user = create(:user, role_ids: [])
expect(user.has_role?('organizer', conference)).to be false expect(user.has_cached_role?('organizer', conference)).to be false
end end
end end
end end