在 Ruby 中,您可以决定从主方法到 return 还是在调用子方法时继续?

In Ruby, can you decide from a main method to return or continue when calling a submethod?

我正在使用 Pundit gem 进行我的授权 类,其中根据模型策略检查每个控制器操作,以查看用户是否允许操作。

这些方法有时变得非常臃肿和不可读,因为我正在检查一些对象的相当多的东西。

现在我正在考虑重构这些方法,并将每个“验证”放在它自己的方法中:

上一个:

class PostPolicy < ApplicationPolicy

 def update
   return true if @user.has_role? :admin
   return true if @object.owner == user
   return true if 'some other reason'
   
   false
 end
end

现在理想情况下,我想将其重构为:

class PostPolicy < ApplicationPolicy

 def update
   allow_if_user_is_admin
   allow_if_user_owns_record
   allow_for_some_other_reason
   
   false
 end

 private

 def allow_if_user_is_admin
  # this would go in the parent class, as the logic is the same for other objects
  return true if @user.has_role? :admin
 end
end

现在的问题是,mane update 方法将继续运行,即使用户是管理员,因为没有 return。如果我包含 return,那么其他方法将永远不会被评估。在 ruby 中有没有一种方法可以做一种“超级return”,这样当用户是管理员时,主要的 update 方法将停止评估?

谢谢!

你可以做的是链式 && 运算符。

只要一个是 false,ruby 将不会评估其他的(更新方法将 return false)。

class PostPolicy < ApplicationPolicy

 def update
   allow_if_user_is_admin &&
     allow_if_user_owns_record &&
     allow_for_some_other_reason &&
 end

 private

 def allow_if_user_is_admin
  # this would go in the parent class, as the logic is the same for other objects
  return true if @user.has_role? :admin
 end
end

看来这样可以达到你的目的,而且更加地道:

class PostPolicy < ApplicationPolicy

  def update
    user_has_admin_role? ||
      user_owns_object? ||
      some_other_reason?
  end

  private
  
  def user_has_admin_role?
    @user.has_role? :admin
  end

  def user_owns_object?
    @object.owner == user
  end

  def some_other_reason?
    'some other reason'
  end
end

您可以 short-circuit 布尔语法,在某些情况下,长链接看起来像是糟糕的风格,这里有一个替代方案,但使用 Enumerable#all? See this answer 的想法基本相同 short-circuit s

class PostPolicy < ApplicationPolicy

  def update
    deny?
  end
  
  private
  
  def deny?
    [ 
      user_is_admin?,
      user_owns_record,
      allow_for_some_other_reason?,
      thing1?,
      thing2? 
    ].any?
  end

  def user_is_admin?
    @user.has_role? :admin
  end

  def user_owns_record?
   @user.owns_record?
  end

  def allow_for_some_other_reason?
    @user.has_cheezebuerger?
  end

  def thing1?
    @user.thing1
  end

  def thing2?
    @user.thing2
  end
end

鉴于您的示例和此评论:“...没有本地方法可以在 Ruby 中执行 'super return' 吗?感觉有点像“加薪”但后来有了积极的结果......我可以使用它吗?".

虽然通常有其他方法可以解决被认为“更惯用”的问题,但 ruby 确实有一个 Kernel#throw and Kernel#catch 实现,在浏览大量内容时对控制流程非常有用以及可能不同的方法和操作。

throw 和相应的 catch 将短路看起来是您要查找的语法的块的结果。

非常基本的示例:

class PostPolicy 
  def initialize(n) 
    @n = n 
  end
  def update
    catch(:fail) do
      stop_bad_actor!
      catch(:success) do 
        allow_if_user_is_admin
        allow_if_user_owns_record
        stop_bad_actor!(2)
        allow_for_some_other_reason
        false
      end 
    end
  end

 private

  def allow_if_user_is_admin
   puts "Is User Admin?"
   throw(:success, true) if @n == 1
  end

  def allow_if_user_owns_record
    puts "Is User Owner?"
    throw(:success,true) if @n == 2
  end 

  def allow_for_some_other_reason
    puts "Is User Special?"
    throw(:success,true) if @n == 3
  end 
  
  def stop_bad_actor!(m=1)
    puts "Is a Bad Actor?"
    throw(:fail, false) if @n == 6 || @n ** m == 64
  end
end

示例输出:

PostPolicy.new(1).update 
# Is a Bad Actor?
# Is User Admin?
#=> true
PostPolicy.new(2).update 
# Is a Bad Actor?
# Is User Admin?
# Is User Owner?
#=> true
PostPolicy.new(3).update 
# Is a Bad Actor?
# Is User Admin?
# Is User Owner?
# Is a Bad Actor?
# Is User Special?
#=> true
PostPolicy.new(4).update 
# Is a Bad Actor?
# Is User Admin?
# Is User Owner?
# Is a Bad Actor?
# Is User Special?
#=> false
PostPolicy.new(6).update 
# Is a Bad Actor?
#=> false
PostPolicy.new(8).update 
# Is a Bad Actor?
# Is User Admin?
# Is User Owner?
# Is a Bad Actor?
#=> false