Preload member_role associations and add inverse has_many to MemberRole
## Context
| | |
|---|---|
| **Phase** | 2 of 6 |
| **Parallel with** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594877+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594878+ |
| **Blocked by** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594874+ |
| **Unblocks** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594880+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594881+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594882+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594883+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594884+ |
## Summary
Add preloading of `member_role` associations to prevent N+1 queries when listing protected branches, and add inverse `has_many` associations on `MemberRole` for completeness.
## Background
When rendering protected branch lists (e.g. in the API index response or branch rules UI), each access level row's `humanize` method (and any display of the custom role name) will trigger a query for `member_role` if not preloaded. This issue ensures association preloading is in place before the feature is enabled.
## Relevant files
- `ee/app/models/concerns/ee/protected_branch.rb` — contains `preload_access_levels` scope override
- `ee/app/models/members/member_role.rb` — the `MemberRole` model
## Changes required
### Update `ee/app/models/concerns/ee/protected_branch.rb`
The existing `preload_access_levels` EE override currently adds `:unprotect_access_levels`. Extend it to also preload `member_role` on all three access level types:
```ruby
override :preload_access_levels
scope :preload_access_levels, -> do
preload(
merge_access_levels: [:user, :group, :member_role],
push_access_levels: [:user, :group, :deploy_key, :member_role],
unprotect_access_levels: [:user, :group, :member_role]
)
end
```
### Update `ee/app/models/members/member_role.rb`
Add inverse associations:
```ruby
has_many :protected_branch_merge_access_levels,
class_name: 'ProtectedBranch::MergeAccessLevel',
foreign_key: :member_role_id,
inverse_of: :member_role
has_many :protected_branch_push_access_levels,
class_name: 'ProtectedBranch::PushAccessLevel',
foreign_key: :member_role_id,
inverse_of: :member_role
has_many :protected_branch_unprotect_access_levels,
class_name: 'ProtectedBranch::UnprotectAccessLevel',
foreign_key: :member_role_id,
inverse_of: :member_role
```
## Testing
- Use `QueryRecorder` / `exceed_query_limit` to verify no N+1 when fetching a list of protected branches that each have a `member_role`-type access level
- Verify `MemberRole` inverse associations load correctly
## Dependencies
- Issue 1 (DB migration) — `member_role_id` column must exist
- Issue 3 (`EE::ProtectedBranchAccess` concern) — `belongs_to :member_role` must be defined first
## Labels
issue