問題描述
我可以編寫一個 activerecord 範圍,在查詢時將結果包裝在一個塊中嗎? (Can I write an activerecord scope which wraps the result in a block at query time?)
我們的 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")
加分
參考解法
方法 1:
my idea is to wrap your on_replica
into a relation
then we can append it at the end of query chains.
i don't know whether your on_replica
will work on this proposal or not, i just demo with 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
Demo
# 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
So you can try replace ActiveRecord::Base.connected_to
by your on_relica
.
In case of Model.eventually_on_replica.where
, i need to set a flag to mark that is running on replica
then try to cache all query chains in order to execute later (on connected_to
block) and it turns out a very complex case and maybe effect other queries in normal case so i don't go further.
方法 2:
For the eventually_on_replica
method at the end of the chain, I have a simple method.
In your application_record.rb
file (if you have it, otherwise you may need to patch ActiveRecord::Base
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
def self.eventually_on_replica
on_replica do
self.all
end
end
end
However, as soon as you call eventually_on_replica
, it will run the DB query and load the results. It doesn't support lazy loading but after all, you only need to call eventually_on_replica
when you really need the results so maybe this will work for your use case.
(by Simon、Lam Phan、Kashif Umair Liaqat)