Rails-应用一系列范围

时间:2019-01-16 16:39:14

标签: ruby-on-rails ruby

我有一个采用一系列作用域并迭代应用它们的类:

class AssignableLearningObjectives::Collector
  def initialize(user:, only_self_assignable: false, scopes: [])
    @user = user
    @only_self_assignable = only_self_assignable
    @scopes = scopes
  end

  .....

  def available_objectives
    objectives = assignable_objectives.or(manager_assigned_objectives).or(global_objectives).distinct
    return objectives unless scopes.any?

    scopes.each{ |scope| objectives = objectives.send(scope) }
    objectives
  end

我的问题是

scopes.each{ |scope| objectives = objectives.send(scope) }
objectives

是否有更好的方法?我希望使用rails方法apply_scopes或类似的方法,但是找不到类似的方法。

我担心的是范围是从控制器发送的,用户可以提交范围为“ destroy_all”或同样有趣的请求。

我有一种简便的方法让铁轨处理此问题吗?还是在将其应用于集合之前需要手动检查每个范围?

预先感谢

编辑:

如果有必要,我很乐意单独验证每个范围,但这甚至引起了问题。在Rails中有一种方法已在3.0.9中删除,我可以使用Model.scopes:

https://apidock.com/rails/v3.0.9/ActiveRecord/NamedScope/ClassMethods/scopes

但是不推荐使用。我可以在类上调用任何方法来列出其范围吗?我不敢相信该功能已在Rails 3中被完全删除...

1 个答案:

答案 0 :(得分:1)

来自fine guide

  

14个作用域
  [...]
  为了定义一个简单的作用域,我们在类内部使用了scope方法,并在该作用域被调用时传递我们要运行的查询:

class Article < ApplicationRecord
  scope :published, -> { where(published: true) }
end
     

这与定义类方法完全相同,您使用的方法取决于个人喜好:

class Article < ApplicationRecord
  def self.published
    where(published: true)
  end
end

因此,scope只是创建类方法的一种奇特方法,该类方法应该具有某些行为(即返回关系),而任何返回关系的类方法都是作用域。 scope以前很特殊,但是现在它们只是类方法,所有类方法都复制到关系中以支持链接。

没有办法知道方法Model.m是不是一个“真实”范围,它将在不运行该关系或某个随机类方法的情况下运行该方法或检查它返回的内容或手动检查其源代码。您寻求的scopes方法已经消失,也永远不会回来。

您可以尝试将所有您认为不好的类方法列入黑名单;这种方式掩盖了错误和疯狂。

唯一明智的选择是将您知道的每个好的类方法都列入白名单,并且您希望用户能够调用该方法。然后,应在控制器中和scopes内部过滤AssignableLearningObjectives::Collector数组。我会在两个地方都检查一下,因为根据可用的信息和通过代码的路径,对于允许的内容可能有不同的标准。我想DRY要少一点,但是效率和鲁棒性不是朋友。

您可以在AssignableLearningObjectives::Collector构造函数或available_objectives中应用范围白名单。


如果您想要的东西比以下更漂亮:

scopes.each{ |scope| objectives = objectives.send(scope) }
objectives

然后您可以使用inject

def available_objectives
  objectives = assignable_objectives....
  scopes.inject(objectives) { |objectives, scope| objectives.send(scope) }
end