12

I have the following three models (massively simplified):

class A < ActiveRecord::Base
  has_many :bs
  has_many :cs, :through => :bs
end

class B < ActiveRecord::Base
  belongs_to :a
  has_many :cs
end

class C < ActiveRecord::Base
  belongs_to :b
end

It seems that A.cs gets cached the first time it is used (per object) when I'd really rather it not.

Here's a console session that highlights the problem (the fluff has been edited out)

First, the way it should work

rails console
001 > b = B.create
002 > c = C.new
003 > c.b = b
004 > c.save
005 > a = A.create
006 > a.bs << b
007 > a.cs
=> [#<C id: 1, b_id: 1>]

This is indeed as you would expect. The a.cs is going nicely through the a.bs relation.

And now for the caching infuriations

008 > a2 = A.create
009 > a2.cs
=> []
010 > a2.bs << b
011 > a2.cs
=> []

So the first call to a2.cs (resulting in a db query) quite correctly returned no Cs. The second call, however, shows a distinct lack of Cs even though they jolly well should be there (no db queries occurred).

And just to test my sanity is not to blame

012 > A.find(a2.id).cs
=> [#<C id: 1, b_id: 1>]

Again, a db query was performed to get both the A record and the associated C's.

So, back to the question: How do I force rails to not use the cached result? I could of course resign myself to doing this workaround (as shown in console step 12), but since that would result in an extra two queries when only one is necessary, I'd rather not.

4

6 回答 6

18

我对这个问题做了更多的研究。虽然使用clear_association_cache起来很方便,但在每次使缓存无效的操作后添加它并没有感觉 DRY。我认为 Rails 应该能够跟踪这一点。谢天谢地,有办法!

我将使用您的示例模型:A(有很多 B,通过 B 有很多 C),B(属于 A,有很多 C)和 C(属于 B)。

我们将需要使用该方法的touch: true选项belongs_to。此方法更新updated_at父模型上的属性,但更重要的是它还会触发after_touch回调。这个回调允许我们在修改、创建或销毁 B 或 C 的相关实例时自动清除 A 的任何实例的关联缓存。

首先修改belongs_toB和C的方法调用,添加touch:true

class B < ActiveRecord::Base
  belongs_to :a, touch: true
  has_many   :cs
end

class C < ActiveRecord::Base
  belongs_to :b, touch: true
end

然后after_touch给 A 添加一个回调

class A < ActiveRecord::Base
  has_many :bs
  has_many :cs, through: :bs

  after_touch :clear_association_cache
end

现在我们可以安全地进行破解,创建各种修改/创建/销毁 B 和 C 实例的方法,并且它们所属的 A 实例将自动更新其缓存,而无需我们记得clear_association_cache全部调用这个地方。

根据您使用模型 B 的方式,您可能还想在after_touch那里添加一个回调。

belongs_to选项和 ActiveRecord 回调 的文档:

http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to

希望这可以帮助!

于 2014-01-23T22:43:13.960 回答
3

所有的关联方法都是围绕缓存构建的,它使最新查询的结果可用于进一步的操作。缓存甚至可以跨方法共享。例如:

customer.orders                 # retrieves orders from the database
customer.orders.size            # uses the cached copy of orders
customer.orders.empty?          # uses the cached copy of orders

但是,如果您想重新加载缓存,因为数据可能已被应用程序的其他部分更改,该怎么办?只需将 true 传递给关联调用:

customer.orders                 # retrieves orders from the database
customer.orders.size            # uses the cached copy of orders
customer.orders(true).empty?    # discards the cached copy of orders
                                # and goes back to the database

来源http://guides.rubyonrails.org/association_basics.html

于 2015-08-10T16:01:37.313 回答
2

(编辑:见 Daniel Waltrip 的回答,他的比我的要好得多)

因此,在输入了所有内容并检查了一些不相关的内容之后,我的目光投向了 Association Basics 指南的“3.1 Controlling Caching”部分。

我会成为一个好孩子并分享答案,因为我刚刚花了大约 8 个小时令人沮丧的谷歌搜索,但毫无结果。

但是,如果您想重新加载缓存,因为数据可能已被应用程序的其他部分更改,该怎么办?只需将 true 传递给关联调用:

013 > a2.cs(true)
C Load (0.2ms)  SELECT "cs".* FROM "cs" INNER JOIN "bs" ON "cs"."b_id" = "bs"."id" WHERE "bs"."a_id" = 2
=> [#<C id: 1, b_id: 1>]

所以故事的寓意:RTFM;所有的。

编辑:所以不得不把true所有地方都可能不是一件好事,因为即使不需要缓存也会被绕过。Daniel Waltrip 在评论中提供的解决方案要好得多:使用clear_association_cache

013 > a2.clear_association_cache
014 > a2.cs
C Load (0.2ms)  SELECT "cs".* FROM "cs" INNER JOIN "bs" ON "cs"."b_id" = "bs"."id" WHERE "bs"."a_id" = 2
=> [#<C id: 1, b_id: 1>]

所以现在,我们不仅应该 RTFM,我们还应该搜索:nodoc:s 的代码!

于 2013-08-16T08:21:50.523 回答
2

我找到了另一种禁用查询缓存的方法。在您的模型中,只需添加一个default_scope

class B < ActiveRecord::Base
  belongs_to :a
  has_many   :cs
end

class C < ActiveRecord::Base
  default_scope { } # can be empty too
  belongs_to :b
end

验证它在本地工作。我通过查看active_record/associations/association.rb中的active_record源代码发现了这一点:

# Returns true if statement cache should be skipped on the association reader.
def skip_statement_cache?
  reflection.scope_chain.any?(&:any?) ||
    scope.eager_loading? ||
    klass.current_scope ||
    klass.default_scopes.any? ||
    reflection.source_reflection.active_record.default_scopes.any?
end
于 2016-07-28T06:41:24.890 回答
0

要清除缓存,请使用.reload

author.books                 # retrieves books from the database
author.books.size            # uses the cached copy of books
author.books.empty?          # uses the cached copy of books

author.books                 # retrieves books from the database
author.books.size            # uses the cached copy of books
author.books.reload.empty?   # discards the cached copy of books
                         # and goes back to the database

来源:控制缓存

于 2020-01-27T09:16:13.010 回答
0

您可以使用该extend选项并提供一个模块来在加载之前重置加载,如下所示:

  # Usage:
  # ---
  #
  # has_many :versions,
  #   ...
  #   extend: UncachedAssociation
  #

  module UncachedAssociation
    def load_target
      @association.reset
      super
    end
  end
于 2020-11-24T21:28:16.473 回答