ruby 如何在创建关联模型时避免不必要的加载?

wn9m85ua  于 2023-10-17  发布在  Ruby
关注(0)|答案(2)|浏览(107)

在我的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

我试过使用build_association,然后是savecreate_association,以及使用association=Association.create,它们都在某些时候导致不必要的负载。

4uqofj5v

4uqofj5v1#

您所看到的是由于在after_create中创建Ink时,Pen已经被持久化了。
Docs

  • 将一个对象转换为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,而不需要查询数据库。

i1icjdpr

i1icjdpr2#

如果你需要一些空笔(没有墨水)怎么办?
如果你需要一支有绿色墨水的笔呢?
我建议这样做

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

相关问题