0

I have a function which takes * parameter as for passing an array.

def has_role?(*role_names)
  self.roles.where(:name => role_names).present?
end

where I can call it like:

user.has_role?(['super admin', 'member'])

or like

user.has_role?('super admin', 'member')

or like

user.has_role?('member')

When I have two parameters to pass, how can I do it with a list and a parameter?

For example, I have a another function the scopes down a similar query:

 def has_account_role?(role_names, account)
    self.account_user_roles.joins(:role).where('roles.name = ? AND account_id = ?', role_names, account.id).present?
 end

If I add a * to has_account_role like this has_account_role?(*role_names, account) and call user.has_account_role?(['member', 'supervisor'], account) I get the error: can't quote Array

I tried changing the query from = ? to IN (?) but get the same issue. If I try to change the query to something like ...where(:name => role_names)... I lose the scoping that role.name provides the filtering on the association.

How do I pass an array into this query without making it overly complete or inefficient?

1 Answer 1

2

The easiest thing to do would be to reverse the argument order and let the splat (*) swallow up everything that isn't the account:

def has_account_role?(account, *role_names)

Then you'd say things like this:

user.has_account_role?(account, ['member', 'supervisor'])
user.has_account_role?(account, 'member', 'supervisor')

You'd also want to use the hash form of where inside the method:

self.account_user_roles
    .joins(:role)
    .where(roles: { name: role_names }, account_id: account.id)
    .present?

so that where can use the appropriate SQL (roles.name = ... or roles.name in (...)) depending on what role_names ends up looking like.

BTW, a splat in a method signature:

def has_role?(*role_names)
  #... 
end

simply collects all the splatable arguments to the method into a single array (called role_names in this case) but it doesn't flatten the array. That means that these two calls:

user.has_role?(['super admin', 'member'])
user.has_role?('super admin', 'member')

are different things. In the first one, role_names looks like [['super admin', 'member']] (i.e. an array inside an array) and in the second one, role_names is just ['super admin', 'member'] (i.e. just a simple array). The where(:name => role_names) inside the method will flatten the array behind your back but they're still quite different as far as has_role? and the caller are concerned and not every method that has_role? calls will flatten arrays the way where does.

With that in mind, you might want to manually flatten your splats:

def has_role?(*role_names)
  role_names = role_names.flatten
  # Do whatever needs to be done now that you know that
  # `role_names` is a flat array.
  # ...
end
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.