When will ActiveRecord save associations?
Asked Answered
O

3

50
  1. I know that it will save associations when autosave: true as per https://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html

  2. I know that it will save associations that are constructed like

book = Book.new(name: 'foo')
book.authors.build(name: 'bar') #has_many
book.save

or like

book = Book.new(name: 'foo')
book.build_author(name: 'bar') #has_one
book.save
  1. I think associations are also saved when they are assigned or added
book = Book.new(name: 'foo')
book.author = Author.new(name: 'bar')
book.save

or

book = Book.new(name: 'foo')
book.authors << Author.new(name: 'bar')
book.save

But, I have a complicated bug that involves something not auto-saving when I would expect it to. So, I want to debug by inspecting book to verify what I think is going to be saved will actually be saved.

TL; DR; What internal state is checked when saving associations? I'm assuming that a model has an internal instance variable like associations_to_save that associations get added to when they are created. Then, when the model is saved, it loops through those associations and saves them too.

Ollie answered 9/9, 2013 at 16:44 Comment(4)
What version of ActiveRecord/Rails are you running? There was a bug(s) in earlier versions that caused this to not work entirely properly.Dentalium
@Dentalium - I'm using the most recent 3.2.13. Can you be more specific about what didn't work properly in earlier versions?Ollie
The bugs I'm referring to were in earlier 2.3 releases - this shouldn't be affecting you now.Dentalium
The link to the ActiveRecord Autosave Association documentation is broken - here for the latest: api.rubyonrails.org/classes/ActiveRecord/…Dodecasyllable
J
40

Unfortunately there are no such thing like associations_to_save. However there are some rules saying what is being saved when. You can find those here: http://guides.rubyonrails.org/association_basics.html. Points: 4.1.5 (belongs_to), 4.2.5 (has_one), 4.3.4 (has_many) and 4.4.4 (habtm).

UPDATE:

In case of has_many association, the child is saved on saving the parent if child.new_record? returns true (child was not yet saved to db), or the foreign_key column needs to be updated. This is why:

  1. Adding object to association on saved parent do save new child.
  2. Adding object to association on unsaved parent doesn't save (no foreign key value)
  3. If unsaved parent is being saved and has some child objects in association cache, those objects are saved to update foreign_key.
Judah answered 9/9, 2013 at 16:54 Comment(6)
Thanks for trying. The sections called "When are Objects Saved?" are help at the coding level, but it does not answer my question. I want to know how to debug a complicated issue by inspecting the current state of book. I must misunderstand something deep, because something in my application is not behaving as expected.Ollie
I am not entirely sure what is the difference. Sections above will tell you when associated objects are being saved and when not (especially they explain the differences between saving objects for a first time and other saves for n-n associations), which is quite different than most people thinks. There is no other method to say what will be saved before the save except those rules (or at least I don't know about any and can't imagine how it would be useful). Could you actually post what is your bug?Judah
I read through all 4 sections of "When are Objects Saved?", but that doesn't help me. (accidentally hit enter) My issue is that I set an attribute of an association that was constructed through .build, and then save the parent. But, the attribute of the association does not get saved to the DB. So, the parent isn't saving the association, which is constructed in one of the ways that should save the association. I want to figure out why by inspecting the association the way ActiveRecord does it. Because somewhere in ActiveRecord, it must be checking something.Ollie
Ah, I had same issue quite recently. I'll update the answer sin a moment.Judah
Sorry, it was a different issue then. Anyway, if you have two objects and both parent and child is already saved, saving the parent won't save the child. Child is being saved only if the foreign_key needs to be updated or child has not been saved yet (new_record? returns true).Judah
Yeah, that was it. The child had already been saved through the parent! Then a property was changed on the child, and then I was trying to save the parent again!Ollie
C
4

Not sure if this will help anyone else, but I recently ran into a similar issue recently in Rails 5.2.

When trying to save an object 2 layers deep my tests failed if the top level and the first level objects had already been saved. Ie.

book_cover.persisted? == true
book_cover.book.persisted? == true

page = book_cover.book.pages.new

page.persisted? == false

# After saving the top level object
book_cover.save
page.persisted? == false

# After saving the immediate parent of page
book_cover.book.save
page.persisted? == true

Since the parent "book cover" wasn't the direct parent of the new object "page" saving "book cover" didn't actually end up saving the "page" object.

Depending on the situation I just explicitly called save on the "book" object to save all the child objects.

Contagious answered 14/3, 2019 at 18:12 Comment(1)
I have not tested it myself, but maybe it would help to add sth like class BookCover < ApplicationRecord; has_one :book; has_many :pages, through: :book; end for Rails to know about the association? 🤔Westfalen
W
0

I guess my answer comes way to late for the questioner 😅 (and I am not directly answering when active record associations are saved) but there are some methods that could help gaining insight into an object's associations and their state – like changed_for_autosave? and even (private) methods like nested_records_changed_for_autosave? (both part of autosave_association.rb (API / documentation)) or (also private) association_instance_get (within associations.rb).

Be careful with nested_records_changed_for_autosave? as it goes through nested autosave associations that are loaded in memory (without loading any new ones!), though.

These might also be useful in combination with some reflection class methods like reflect_on_aggregation, reflect_on_all_aggregations, reflect_on_all_associations, reflect_on_all_autosave_associations, reflect_on_association, and reflections (API for further documentation), for automating the process of association inspection like so:

self.class.reflect_on_all_autosave_associations #=> reflections
association_instance_get(reflection.name) #=> association
association.target #=> actual associated object(s)
object.changes #=> e. g. { "name" => ["bill", "bob"] }

With target (that seems to not be documented very well (briefly mentioned here or here)) returning the associated object for #belongs_to and #has_one, or the collection of associated objects for #has_many and #has_and_belongs_to_many.

(I have not provided usable code, as some more thoughts has to go into checking for empty arrays, nil-values and so forth.)

On these associated object methods like new_record?, marked_for_destruction?, or changes exist.

Westfalen answered 28/4, 2021 at 16:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.