Liondancer
Liondancer

Reputation: 16469

Verifying all values of array are found in a hash of selected keys

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

Answers (1)

Cary Swoveland
Cary Swoveland

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'])

See Array#- and Array#&.

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

Related Questions