提问人:jwilm 提问时间:9/12/2023 更新时间:9/12/2023 访问量:42
如何避免在创建关联模型时不必要地加载它?
How can I avoid unnecessary loading of Associated model while creating it?
问:
在我的 Rails 应用程序中,我们有几个类似的模型
class Pen < ActiveRecord::Base
has_one :ink
after_create :add_ink
def add_ink
create_ink(color: "blue")
end
end
class Ink < ActiveRecord::Base
belongs_to :pen
end
在控制器中,我们做这样的事情
pen = Pen.new(..)
pen.save
从概念上讲,笔总是有一个墨迹,我们在创建笔时创建墨迹。(也对比after_create钩更好的方法来实现这一目标持开放态度)。
问题在于,它总是为关联发出 SELECT,即使保证那里什么都没有。
[DEBUG] TRANSACTION (0.2ms) BEGIN
[DEBUG] Pen Create (1.5ms) INSERT INTO pens ("owner_id") VALUES ('00f74077-c079-4482-aa03-15b23951a7bd') RETURNING "id"
[DEBUG] Ink Load (0.4ms) SELECT "inks".* FROM "inks" WHERE "inks"."pen_id" = '93a45bae-2cf3-48c1-ac00-6b3059dca5ae' LIMIT 1
[DEBUG] Ink Create (0.3ms) INSERT INTO "inks" ("pen_id", "color") VALUES ('93a45bae-2cf3-48c1-ac00-6b3059dca5ae', 'blue') RETURNING "pen_id"
[DEBUG] TRANSACTION (0.2ms) COMMIT
具体来说,这不应该发生
[DEBUG] Ink Load (0.4ms) SELECT "inks".* FROM "inks" WHERE "inks"."pen_id" = '93a45bae-2cf3-48c1-ac00-6b3059dca5ae' LIMIT 1
我尝试使用后跟 、 和 以及将 an 与 an 一起使用,它们都在某些时候会导致不必要的 Load。build_association
save
create_association
association=
Association.create
答:
1赞
engineersmnky
9/12/2023
#1
您所看到的是由以下事实引起的:当 的创建发生在 .Pen
Ink
after_create
从文档
- 将对象分配给关联会自动保存该对象和要替换的对象(如果有),以便更新其外键 - 除非父对象未保存 ()。
has_one
new_record? == true
- 如果其中任一保存失败(由于其中一个对象无效),则会引发异常并取消分配。
ActiveRecord::RecordNotSaved
因此,您看到的是 rails 试图确定关联的记录是否已经存在,以便可以相应地更新它。
根据您想要的结果,您可以尝试;但是,我认为将其移动到操作中可能是一个更好的实现,例如Ink.create(pen_id: self.id, color: 'blue')
before_create
class Pen < ActiveRecord::Base
has_one :ink
before_create :add_ink
def add_ink
build_ink(color: "blue")
end
end
根据文档
:自动保存
如果为 true,则在保存父对象时,请始终保存关联的对象或销毁它(如果标记为销毁)。如果为 false,则切勿保存或销毁关联的对象。默认情况下,仅当关联的对象是新记录时,才保存该对象。
因此,由于这是一条新记录,因此它也应该保存,而无需查询数据库。before_create
Pen
Ink
评论
0赞
jwilm
9/12/2023
啊哈,很有见地!我最终不得不在钩子和钩子中做“build_ink”。没有它,我在尝试读取数据库提供的 ID 时会收到一个循环调用。谢谢!before_create
ink.pen_id = id; ink.save
after_create
Pen.save
1赞
jwilm
9/12/2023
没关系;保存是隐式进行的,并且 ID 会使用您的示例自动更新。
0赞
engineersmnky
9/12/2023
@jwilm正确。这就是为什么我们需要确保在执行这些操作时没有保留(新记录),以便当实际保存时,它会保存关联的 和 但不执行 SELECT 查询。Pen
Pen
Pen
Ink
0赞
mechnicov
9/12/2023
#2
如果你需要一些空笔(没有墨水)怎么办?
如果您需要带有绿色墨水的笔怎么办?
我建议这样
class CreatePen
def self.call(params, color: :blue)
new(params:, color:).call
end
def initialize(params:, color:)
@pen = Pen.new(params)
@color = color
end
def call
persist_pen
rescue ActiveRecord::RecordInvalid
pen
end
private
attr_reader :pen, :color
def persist_pen
pen.transaction do
pen.save!
Ink.create!(color:, pen_id: pen.id) if color.present?
pen
end
end
end
在这种情况下,您可以这样调用它:
CreatePen.(params) # return pen with blue ink if everything ok
CreatePen.(params, color: :green) # return pen with green ink if everything ok
CreatePen.(params, color: nil) # return empty pen without ink if everything ok
评论