简体   繁体   中英

Why is my ruby case statement not working?

What's wrong with that? Yes, I could make an if else statement, but I wanna do that with case statements.

In my Controller

query_limit = case current_user
                when nil
                  return 5
                when is_admin?
                  return 200
                when has_role?('registered')
                  return 20
                else
                  return 5
              end


NoMethodError (undefined method `is_admin?' for #<V1::MyController:123123123>):

puts query_limit # is always ELSE when I do this:

 query_limit = case current_user
                    when nil
                      return 5
                    when current_user.is_admin?
                      return 200
                    when current_user.has_role?('registered')
                      return 20
                    else
                      return 5
                  end

Model User

class User
  def is_admin?
    self.has_role?('administrator')
  end

  def has_role?(the_role)
    self.roles.any? {|role| role.slug == the_role}
  end
end

The way case works is it doesn't call whatever method you wanted to put in the when clause. Instead it uses === on the when clause passing the case -ed thingy as argument.

In other words:

case user
when admin? then 42
end

Isn't

42 if user.admin?

But

42 if admin? === user

And as you don't have an admin? method in the controller, you get a NoMethodError .


Just rewrite it with simple if - elsif instead.

ndn explained already in his answer why the case statement in your example is not working.

You can switch to an if/elsif block or a slightly longer when current_user.admin? syntax. Or you can define some methods returning lambdas:

def is_admin?
  ->(user) { user.is_admin? }
end

def has_role?(role)
  ->(user) { user.has_role?(role) }
end

With these you can write:

query_limit = case current_user
              when nil then 5
              when is_admin? then 200
              when has_role?('registered') then 20
              else 5
              end

You could write it this way, which is basically a disguised if/elsif :

query_limit = case
                when current_user.nil?
                  return 5
                when current_user.is_admin?
                  return 200
                when current_user.has_role?('registered')
                  return 20
                else
                  return 5
                end

According to the Ruby documentation:

Case statements consist of an optional condition, which is in the position of an argument to case, and zero or more when clauses. The first when clause to match the condition (or to evaluate to Boolean truth, if the condition is null) “wins”, and its code stanza is executed.

If you specify current_user as the case condition, then the first when expression that matches current_user will be executed. current_user.is_admin? returns a boolean value which will never be equal to current_user , so your second example will always take the else branch:

case current_user
  when nil  # current_user != nil, skip condition
    return 5
  when current_user.is_admin?  # current_user != current_user.is_admin?, skip condition
    return 200
  when current_user.has_role?('registered')  # and so on
    return 20
  else
    return 5
end

Your first example is broken because there is no local is_admin? method defined. case does not know to call is_admin? on current_user in this case.

To fix your code, you can remove case condition. In this case the first when clause that evaluates to a truthy value will be chosen:

case  # no current_user here!
  when current_user.nil?  # current_user.nil? is false, skip condition
    return 5
  when current_user.is_admin?  # current_user.is_admin? is truthy, run this one!
    return 200
  when current_user.has_role?('registered')
    return 20
  else
    return 5
end

A case statement works by comparing the given value against the values of the when statements (using === ). So case current_user; when current_user.is_admin? case current_user; when current_user.is_admin? checks whether current_user is equal to current_user.is_admin? , which it's obviously not going to be.

Basically

case x
  when y
    case1
  when z
    case2
  ...
end

is equivalent to

if y === x
  case1
elsif z === x
  case2
...
end

If that structure does not fit your use case, you should use case .

PS: return returns from the current method, so it's not what you want here.

PPS: You can also use case without an expression as a different way to write an arbitrary if-elsif chain. So you could do what you want like this:

query_limit = case
  when current_user == nil
    5
  when current_user.is_admin?
    200
  ...
end

But of course you can also just use a plain if-elsif for this. It's really just a matter of preference.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM