【问题标题】:Can I write an activerecord scope which wraps the result in a block at query time?我可以编写一个 activerecord 范围,在查询时将结果包装在一个块中吗?
【发布时间】:2021-05-18 11:13:48
【问题描述】:

我们的 rails 应用有一个主数据库和一个副本数据库。我们已经定义了一个接收块的方法,并且在块内部时,将对副本进行查询,如下所示:

on_replica do
  @my_things = MyModel.where(my_attr: "my value").to_a
end

这在大多数情况下都很好用,但如果你没有真正在块中执行查询,它就不起作用:

on_replica do
  @my_things = MyModel.where(my_attr: "my value")
end

# this will run its query against the primary DB
@my_things.to_a 

我真的很想能够定义一个范围(或类似范围的东西),以便在方法链中超过该点发生的任何查询都发生在副本上:

@my_things = MyModel.where(my_attr: "my value").eventually_on_replica

# this runs its query against the replica DB:
@my_things.to_a

是否可以使用现有的on_replica 方法定义一个可以以这种方式工作的eventually_on_replica 方法?如果MyModel.eventually_on_replica.where(my_attr: "my value") 也有效,则加分。

【问题讨论】:

  • 这应该如何表现? on_replica { query = MyModel.where(....) }; on_primary { query = query.where(....) }; query.to_a -- 现在这是一组矛盾的要求:)
  • 如果您展示了当前如何定义 on_replica 可能会有所帮助。 AFAIK,这种方法基本上是ActiveRecord::Base.connected_to 的包装,但我认为它以您描述的方式工作是不合逻辑/不可能的。
  • 但是,我也不认为这是必要/可取的!...为什么要/需要指定连接,在上下文之外连接所在的位置居然成立了??如果需要在副本数据库上运行查询,那么为什么不能在查询时指定呢?
  • @TomLord 我们没有on_primary 方法,也没有一个用例。也许我在这里两次使用名称on_replica 时犯了一个错误。我提到的第一个已经存在;我不需要帮助来写它。我需要帮助的地方是写第二个,它就像一个范围。我会更新问题。
  • @TomLord 你问“为什么不能在查询时指定” - 因为规范发生在控制器中,而查询发生在视图中。如果可能的话,我们不能只调用to_a,因为我们想使用will_paginate

标签: ruby-on-rails rails-activerecord database-replication


【解决方案1】:

我的想法是将您的 on_replica 包装到 relation 中,然后我们可以将其附加到查询链的末尾。

我不知道你的on_replica 是否会处理这个提案,我只是用ActiveRecord::Base.connected_to 演示

# lib/active_record/base.rb
module ActiveRecord
  module Querying
    delegate :eventually_on_replica, to: :all
  end

  class Relation
    def eventually_on_replica(database: nil, role: nil, shard: nil, prevent_writes: false)
      ActiveRecord::Base.connected_to(
        database: database,
        role: role,
        shard: shard,
        prevent_writes: prevent_writes
      ) do
        self.load
      end
    end
  end
end

演示

# config/application.rb
config.active_record.reading_role = :dev
config.active_record.reading_role = :test

# models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { dev: :development, test: :test }
end

# database development
result = Requirement.where(id: [1,2,3]).eventually_on_replica(role: :dev)
result.to_a # have data

# database test
result = Requirement.where(id: [1,2,3]).eventually_on_replica(role: :test)
result.to_a # [] no data

所以您可以尝试用您的on_relica 替换ActiveRecord::Base.connected_to

如果是Model.eventually_on_replica.where,我需要设置一个标志来标记正在运行的on replica,然后尝试缓存所有查询链以便稍后执行(在connected_to 块上),结果非常复杂case 并且在正常情况下可能会影响其他查询,所以我不再继续。

【讨论】:

    【解决方案2】:

    对于链尾的eventually_on_replica方法,我有一个简单的方法。

    在您的application_record.rb 文件中(如果有,否则您可能需要修补ActiveRecord::Base

    # app/models/application_record.rb
    class ApplicationRecord < ActiveRecord::Base
    
      def self.eventually_on_replica
        on_replica do
          self.all
        end
      end
    
    end
    

    但是,只要您调用eventually_on_replica,它就会运行数据库查询并加载结果。它不支持延迟加载,但毕竟,您只需要在真正需要结果时调用eventually_on_replica,所以这可能适用于您的用例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-30
      • 2011-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多