4

我的问题

似乎 Rails 4 忽略了嵌套的无范围块(而在 Rails 3 中它们没问题)。我一直在疯狂地谷歌搜索,在这里找不到任何表明变化的东西。有什么想法可以让我在 Rails 4 中使用它吗?

我在做什么

我将 default_scope 用于多租户,如#388 Multitenancy with Scopes所示。一些管理员将是多个租户的管理员,我想在报告中向他们显示汇总数据。我通过使用unscoped blocks来做到这一点。我还预加载了关联对象,因为 1)它更高效 2)我需要将所有关联对象放在一个地方,这样我就不必在我的视图中继续使用无范围的块。为了预加载关联的对象,我使用了嵌套的无范围块。

我在 Rails 3.2.17 上的应用程序中有这个工作,但现在我已经升级到 Rails 4.0.1.rc1,它不再工作。

一个简单的例子来说明差异

下面,我将展示我在控制台中得到的内容。这个例子比我实际想要做的要简单得多,但我认为这是展示正在发生的事情的最简单方法。

Loading development environment (Rails 3.2.17)
1.9.3-p374 :001 > s = nil
   => nil 
1.9.3-p374 :002 > Submission.unscoped { Checklist.unscoped { s = Submission.preload(:checklist).find(3269) }}
  Submission Load (27.8ms)  SELECT "submissions".* FROM "submissions" WHERE "submissions"."id" = $1 LIMIT 1  [["id", 3269]]
  Checklist Load (0.6ms)  SELECT "checklists".* FROM "checklists" WHERE "checklists"."id" IN (17)
 => #<Submission id: 3269, user_id: nil, workday_id: 17, checklist_id: 17, note: nil, submitted: false, submitted_at: nil, created_at: "2014-03-12 01:06:03", updated_at: "2014-03-12 01:06:03", for_date: "2014-03-11", tenant_id: 2, position: 1, department_id: nil, status: "blank"> 
1.9.3-p374 :003 > s
 => #<Submission id: 3269, user_id: nil, workday_id: 17, checklist_id: 17, note: nil, submitted: false, submitted_at: nil, created_at: "2014-03-12 01:06:03", updated_at: "2014-03-12 01:06:03", for_date: "2014-03-11", tenant_id: 2, position: 1, department_id: nil, status: "blank"> 
1.9.3-p374 :004 > s.checklist
 => #<Checklist id: 17, name: "Open", description: "Chores Required To Open Store Front", creator_id: 2, created_at: "2013-09-23 21:55:23", updated_at: "2013-09-23 21:55:23", archived: false, archived_at: nil, ancestry: nil, tenant_id: 2>

因此,我正在加载提交并预加载其关联的清单。然后我可以确认提交及其相关的清单都可用。

如果我切换到我的 Rails 4 环境(都在同一个数据库上工作),这就是我看到的:

 Loading development environment (Rails 4.1.0.rc1)
 1.9.3-p374 :001 > s = nil
  => nil 
 1.9.3-p374 :002 > Submission.unscoped { Checklist.unscoped { s = Submission.preload(:checklist).find(3269) }}
   Submission Load (0.4ms)  SELECT  "submissions".* FROM "submissions"  WHERE "submissions"."id" = $1 LIMIT 1  [["id", 3269]]
   Checklist Load (0.6ms)  SELECT "checklists".* FROM "checklists"  WHERE "checklists"."tenant_id" IS NULL AND "checklists"."id" IN (17)
  => #<Submission id: 3269, user_id: nil, workday_id: 17, checklist_id: 17, note: nil, submitted: false, submitted_at: nil, created_at: "2014-03-12 01:06:03", updated_at: "2014-03-12 01:06:03", for_date: "2014-03-11", tenant_id: 2, position: 1, department_id: nil, status: "blank"> 
 1.9.3-p374 :003 > s
  => #<Submission id: 3269, user_id: nil, workday_id: 17, checklist_id: 17, note: nil, submitted: false, submitted_at: nil, created_at: "2014-03-12 01:06:03", updated_at: "2014-03-12 01:06:03", for_date: "2014-03-11", tenant_id: 2, position: 1, department_id: nil, status: "blank"> 
 1.9.3-p374 :004 > s.checklist
  => nil

我正在运行完全相同的代码,但只有提交可用 - 其关联的清单为零。

FWIW,如果我在控制台中运行它,我可以在 Rails 3.2.17 中复制它。注意:这里的区别是我只使用了 Submission.unscoped {} 块,而不是嵌套 Checklist.unscoped {} 块。

  Loading development environment (Rails 3.2.17)
  1.9.3-p374 :001 > s = nil
   => nil 
  1.9.3-p374 :002 > Submission.unscoped { s = Submission.preload(:checklist).find(3269) }
    Submission Load (3.6ms)  SELECT "submissions".* FROM "submissions" WHERE "submissions"."id" = $1 LIMIT 1  [["id", 3269]]
    Checklist Load (0.4ms)  SELECT "checklists".* FROM "checklists" WHERE "checklists"."tenant_id" IS NULL AND "checklists"."id" IN (17)
   => #<Submission id: 3269, user_id: nil, workday_id: 17, checklist_id: 17, note: nil, submitted: false, submitted_at: nil, created_at: "2014-03-12 01:06:03", updated_at: "2014-03-12 01:06:03", for_date: "2014-03-11", tenant_id: 2, position: 1, department_id: nil, status: "blank"> 
  1.9.3-p374 :003 > s
   => #<Submission id: 3269, user_id: nil, workday_id: 17, checklist_id: 17, note: nil, submitted: false, submitted_at: nil, created_at: "2014-03-12 01:06:03", updated_at: "2014-03-12 01:06:03", for_date: "2014-03-11", tenant_id: 2, position: 1, department_id: nil, status: "blank"> 
  1.9.3-p374 :004 > s.checklist
   => nil

更新

我深入研究了 Rails 源代码,我可以看到 3.2-stable 中的“unscoped”方法与当前 master 相同,但该方法调用“relation.scoping”,并且该方法在两个分支之间有很大不同。我不确定是否引入了错误,或者预期的行为是否发生了变化。

以供参考:

当前主人

def unscoped
  block_given? ? relation.scoping { yield } : relation
end

关系范围

def scoping
  previous, klass.current_scope = klass.current_scope, self
  yield
ensure
  klass.current_scope = previous
end

当前范围

def current_scope #:nodoc:
  ScopeRegistry.value_for(:current_scope, base_class.to_s)
end

3.2-稳定

def unscoped #:nodoc:
  block_given? ? relation.scoping { yield } : relation
end

关系范围

def scoping
  @klass.with_scope(self, :overwrite) { yield }
end

链接到with_scope方法...

def with_scope
  # Pretty long and involved method
  # See Line 60 in the linked doc
end
4

2 回答 2

1

看起来我偶然发现了 Rails 4 错误。据我所知,目前在 Rails 4 中无法同时使用无作用域和急切加载,因为似乎“无作用域”基本上不会嵌套或链接。

这是 Github 上的问题:

使用包含和无范围 #11036

我最终做的(现在)是在我需要的地方创建无范围的关联(现在在 Rails 4 中可用)。这是一个示例(来自我添加到 Github 问题的要点):

class Submission < ActiveRecord::Base
  default_scope { where(tenant_id: Tenant.current_id) }

  belongs_to :checklist
  ###### This unscoped association is my workaround in Rails 4 
  ###### You'll want comment out if testing in Rails 3
  belongs_to :unscoped_checklist, -> { unscope(where: :tenant_id) }, foreign_key: :checklist_id, class_name: "Checklist"
  belongs_to :tenant
end

所以,我可以调用submission.unscoped_checklist 来绕过default_scope。但是请注意,这不能与预加载一起使用。例如,这将不起作用

Submission.unscoped.preload(:unscoped_checklist).where(id: 1)

提交将被返回,但随后调用 submit.unscoped_checklist 将返回 nil。

所以我目前不能急切地加载无范围的关联,但我已经知道如何在需要时访问无范围的关联。

于 2014-03-15T16:31:51.107 回答
0

我在一个基于 Spree 的 Rails 项目中遇到了同样的问题,并且在实现这个拐杖之前经历了一堆失败的方法:

https://gist.github.com/coderifous/33e24f7e63800e169b03a16eb7eebb5b

与其他方法相比,我更喜欢这种方法,因为它不涉及猴子修补或任何其他巧妙的技巧。它只是清除(然后恢复)default_scopes阵列。

在我的项目中,将该文件放入 中lib/default_scope_crutch.rb,然后用以下内容包装我的代码:

disable_default_scopes(Product, User) {
  @orders = Order.preload(:product, :user).page(1).per(10)
  @orders.load
}

我更喜欢 ActiveRecord 提供的方法,但我不知道有一种方法。希望这对遇到此问题的其他人有用。

于 2017-05-17T15:34:41.993 回答