Reputation: 16469
If a User has a set of roles attached to them and each role has a set of scopes, how can I check if the User has all the scopes
For example, I have a mapping of roles to scopes
scopes_for_roles = {
artist: ['draw', 'sing'],
teacher: ['read', 'write'],
athlete: ['draw', 'read', 'dance']
}
For User_A
who has roles athlete
and teacher
, how can I check (return T/F
) if he/she has the following scopes?
scopes = ['read', 'write'] --> TRUE
scopes = ['sing', 'read', 'write', 'dance'] --> FALSE # Missing sing
What I've tried is the below
roles.product(scopes).select { |role, scope| scopes_for_roles[role].include?(scope) if scopes_for_roles.key?(role) }.size >= scopes.size
However, I believe the snippet above has a bug where it only checks the .size
of the selected permutations and not the actual scopes themselves. So if a User has the roles teacher
and athlete
, the snippet will return true
for scopes ['read', 'sing']
because 'read'
is found TWICE in roles teacher
and athlete
.
Note: I could have several for loops w/ key-value checks but I was wondering if there was a cleaner/shorter approach like the snippet I provided
Upvotes: 1
Views: 49
Reputation: 110685
Yes, the duplicate scopes are a problem when you are relying on counting. To continue with that approach you will need to apply Array#uniq
to remove duplicates at some point. (Also, includes_code?
is not a Ruby method.) I suggest that you abandon the counting approach. First extract the relevant scopes from scopes_for_roles
:
roles = [:athlete, :teacher]
scopes = ['read', 'write']
a = scopes_for_roles.values_at(*roles)
#=> [["draw", "read", "dance"], ["read", "write"]]
See Hash#values_at.
At this point you have several choices. Perhaps the easiest is to first flatten a
:
b = a.flatten
#=> ["draw", "read", "dance", "read", "write"]
Then execute one of the following:
scopes.all? { |s| b.include?(s) }
#=> true
(scopes - b).empty?
#=> true (scopes - b #=> [])
scopes & b == scopes
#=> true (scopes & b #=> ['write', 'dance'])
Instead of flatten
you could use
b = a.reduce(:+)
#=> ["draw", "read", "dance", "read", "write"]
or
b = a.reduce(:|)
#=> ["draw", "read", "dance", "write"]
See Array#|.
If
scopes = ['sing', 'read', 'write', 'dance']
then
scopes.all? { |s| b.include?(s) }
#=> false
(scopes - b).empty?
#=> false (scopes - b #=> ['sing'])
scopes & b == scopes
#=> false (scopes & b #=> ['read', 'write', 'dance'])
I suggest the following.
def have_em_all?(scopes_for_roles, roles, scopes)
(scopes - scopes_for_roles.values_at(*roles).reduce(:|)).empty?
end
roles = [:athlete, :teacher]
scopes1 = ['read', 'write']
scopes2 = ['sing', 'read', 'write', 'dance']
have_em_all?(scopes_for_roles, roles, scopes1) #=> true
have_em_all?(scopes_for_roles, roles, scopes2) #=> false
For the first example the steps are as follows.
a = scopes_for_roles.values_at(*roles)
#=> [["draw", "read", "dance"], ["read", "write"]]
b = a.reduce(:|)
#=> ["draw", "read", "dance", "write"]
c = scopes1 - b
#=> []
c.empty?
#=> true
In the second example we would obtain
c = scopes2 - b
#=> ["sing"]
c.empty?
#=> false
Upvotes: 2