ruby-on-rails 如何强制rails不使用has_many through关系的缓存结果?

olmpazwi  于 2023-04-08  发布在  Ruby
关注(0)|答案(6)|浏览(169)

我有以下三个模型(大规模简化):

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

看起来A.cs在第一次使用时(每个对象)就被缓存了,而我真的不希望它被缓存。
下面是一个控制台会话,它突出显示了这个问题(其中的不足之处已被编辑掉)

首先,它的工作方式

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>]

这确实如您所料,a.cs在www.example.com关系中运行良好a.bs。

现在来说说缓存的愤怒

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

因此,第一次调用a2.cs(导致db查询)完全正确地没有返回Cs。然而,第二次调用显示明显缺少Cs,即使它们应该在那里(没有发生db查询)。
只是为了测试我的理智不是责备

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

同样,执行db查询以获得A记录和相关联的C。
所以,回到问题:我如何强制rails不使用缓存的结果呢?我当然可以让自己做这个变通方案(如控制台步骤12所示),但因为这会导致在只需要一个查询时多出两个查询,所以我宁愿不这样做。

eulz3vhy

eulz3vhy1#

我对这个问题做了更多的研究。虽然使用clear_association_cache足够方便,但在每次该高速缓存无效的操作之后添加它并不感到干燥。我认为Rails应该能够跟踪这一点。谢天谢地,有一种方法!
我将使用您的示例模型:A(有许多B,有许多C到B),B(属于A,有许多C),C(属于B)。
我们需要为belongs_to方法使用touch: true选项。该方法更新父模型上的updated_at属性,但更重要的是,它还触发after_touch回调。该回调允许我们在修改、创建或销毁B或C的相关示例时自动清除A的任何示例的关联缓存。
首先修改B和C的belongs_to方法调用,添加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
希望这有帮助!

新的Rails版本

正如@stevenspiel评论的那样,这个方法是私有的,然后在较新的Rails中,它甚至被删除了。所以你可以使用#reset#reload示例(可能你想重置关联)。

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

  after_touch :reset_associations

  private

  def reset_associations
    bs.reset
    cs.reset
  end
end
uyto3xhc

uyto3xhc2#

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

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

z0qdvdin

z0qdvdin3#

(Edit:看丹尼尔Waltrip的回答,他的远远比我的好)
所以,在输入了所有这些内容并检查了一些无关的东西之后,我的眼睛碰巧看到了关联基础指南的“3.1控制缓存”部分。
我会做一个好孩子,分享答案,因为我刚刚花了大约八个小时的沮丧徒劳的谷歌搜索。
但是,如果你想重新加载该高速缓存,因为数据可能已经被应用程序的其他部分更改了呢?只需将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放在所有的地方可能不是一件好事,该高速缓存会被绕过,即使它不需要。丹尼尔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的代码!

zzzyeukh

zzzyeukh4#

我发现了另一种禁用查询缓存的方法。

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
nzk0hqpo

nzk0hqpo5#

要清除该高速缓存,请使用.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

来源:控制缓存

3df52oht

3df52oht6#

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

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

  module UncachedAssociation
    def load_target
      @association.reset
      super
    end
  end

相关问题