In my Rails app, we have a couple of models similar to this
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
and in a Controller we do something like this
pen = Pen.new(..)
pen.save
Conceptually, a Pen always has an Ink, and we create the Ink when the Pen is created. (Also open to better ways to accomplish this than an after_create hook).
The problem is that it's always issuing a SELECT for the association even though there's guaranteed to be nothing there.
[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
Specifically, this shouldn't be happening
[DEBUG] Ink Load (0.4ms) SELECT "inks".* FROM "inks" WHERE "inks"."pen_id" = '93a45bae-2cf3-48c1-ac00-6b3059dca5ae' LIMIT 1
I have tried using build_association
followed by a save
, create_association
, and using an association=
with an Association.create
and they all result in an unnecessary Load at some point.
What you are seeing is caused by the fact that the Pen
is already persisted when the creation of the Ink
occurs in an after_create
.
From the Docs
- Assigning an object to a
has_one
association automatically saves that object and the object being replaced (if there is one), in order to update their foreign keys - except if the parent object is unsaved (new_record? == true
).- If either of these saves fail (due to one of the objects being invalid), an
ActiveRecord::RecordNotSaved
exception is raised and the assignment is cancelled.
So what you are seeing is rails trying to determine if the associated record already exists, so that it can be updated accordingly.
Depending on how your desired outcome you could try Ink.create(pen_id: self.id, color: 'blue')
; however, I think just moving this to a before_create
action is probably a better implementation e.g.
class Pen < ActiveRecord::Base
has_one :ink
before_create :add_ink
def add_ink
build_ink(color: "blue")
end
end
According to the Docs
:autosave
If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. If false, never save or destroy the associated object. By default, only save the associated object if it’s a new record.
So since this is a before_create
the Pen
is a new record and therefore it should save the Ink
as well, without querying the database.