Describe the bug
It seems that using $elemMatch and ability.can() from the useAbility hook causes the first true returned by ability.can() to be applied to all subsequent can() calls when iterating over an array of elements.
To Reproduce
Steps to reproduce the behavior:
import { AbilityBuilder, Ability, AbilityClass } from '@casl/ability';
import { TeamFragment } from '../../generated/graphql';
import subjectTypeFromGraphql from './subjectTypeFromGraphql';
type Abilities = ['manage', 'Team' | TeamFragment];
type AppAbility = Ability<Abilities>;
export const AppAbility = Ability as AbilityClass<AppAbility>;
export default function defineAbilityFor() {
const { can, build } = new AbilityBuilder(AppAbility);
can('manage', 'Team', {
groupMembers: {
$elemMatch: {
'account.id': '8461e09d-7a54-4649-9427-28e71dff9e73'
'roles.roleId': 'admin',
},
},
});
return build({ detectSubjectType: subjectTypeFromGraphql });
}
import { detectSubjectType } from '@casl/ability';
export default (subject: any) => {
if (subject?.__typename) {
return subject.__typename;
}
return detectSubjectType(subject);
};
I have an array of two Teams. The first I should not be able to manage, and the second I should. On first render, this is shown correctly. When the component re-renders, it's incorrectly showing that both Teams are now manageable.
If I reverse the order of the array, both are shown as manageable on first and additional renders. I believe the true being returned by the ability.can('manage', team) for the first item is being cached or memoized somewhere for the second, even though they are separate objects.
I've hardcoded the account id for simplicity at the moment.
Expected behavior
I expect each call of ability.can('manage', team) in a loop to be specific the team object that was passed in.
CASL Version
@casl/ability - 4.1.6
@casl/react - 2.1.1
Environment:
Chrome 85, Typescript 3.9.
Could you please also provide data structure for teams?
Simplified graphql query:
fragment Team on Team {
id
groupMembers {
roles {
roleId
roleDisplayName
}
account {
id
}
}
}
So in this case:
const teams = [
{
id: "1",
groupMembers: [
{
roles: [
{
roleId: "owner",
roleDisplayName: "Captain",
__typename: "Role",
},
],
account: {
id: "8461e09d-7a54-4649-9427-28e71dff9e73",
__typename: "Person",
},
__typename: "GroupMember",
},
],
__typename: "Team",
},
{
id: "2",
groupMembers: [
{
roles: [
{
roleId: "admin",
roleDisplayName: "Admin",
__typename: "Role",
},
],
account: {
id: "8461e09d-7a54-4649-9427-28e71dff9e73",
__typename: "Person",
},
__typename: "GroupMember",
},
],
__typename: "Team",
},
];
Edit: changed order to reflect above scenario.
The good news is that this bug is not reproducible in v5 which will be released in the early October.
The bad news is that this is a bug in sift.js (underlying MongoDB query checking engine). So, I will move the issue to sift.js
And unfortunately until it's fixed and released a new version I cannot do anything. I cannot release v5 faster because there are some docs stuff to finish...
Sounds good @stalniy. I was likely holding off for v5 anyway as I'd really like to be using #333
Can you confirm you were able to reproduce in v4? Just want to be sure it's not something I'm doing wrong.
Edit: I see the open ticket on sift.js. Thanks so much!
@martdavidson could you please check whether all good? The sift issue was closed
It's good! Thank you very much :)
Thank you for checking!
this does not seem to be resolved? where is this issue fixed?
This is not casl鈥檚 issue. It鈥檚 sift.js issue. Ensure that your siftjs version includes that fix by doing npm ls sift and checking its version
The author of sift says it鈥檚 fixed: https://github.com/crcn/sift.js/issues/212#issuecomment-696749415