danevron
danevron

Reputation: 55

Create nested records on parent creation

I have a UserReport model that connects a User model and a Report model. (has many through association).

I have another model called Comment which belongs to UserReport. (has many association) When a report is created I need to create a UserReport for all users with one default comment.

My question is how to do that in a way that will rollback the report creation if any one of the child records fail to save.

My goal is to ensure that the DB will not stay in in-consisted state.

Any suggestions?

Upvotes: 0

Views: 98

Answers (2)

DigitalCora
DigitalCora

Reputation: 2232

When you save a model, the entire process is wrapped in a transaction that will be rolled back if the save fails (due to validations, callbacks, etc). So if you build your whole object tree in memory first, then attempt to save the report, none of your objects will be saved if there are any failures.

Here's an example of how you might do this:

# in report.rb
class Report < ActiveRecord::Base
  validates_associated :user_reports
end

# in user_report.rb
class UserReport < ActiveRecord::Base
  validates_associated :comments
end

# in your controller or wherever you're doing this
report = Report.new
User.pluck(:id).each{ |user_id| report.user_reports.build(user_id: user_id) }
report.user_reports.each{ |user_report| user_report.comments.build }
report.save # will always save either everything or nothing, no inconsistencies

Note the use of #new and #build to avoid committing anything until the final line. The validates_associated lines in the models cause any validation errors on the child objects to propagate to the parent object, preventing it from saving even if the parent object itself passes validation.

Upvotes: 0

kddeisz
kddeisz

Reputation: 5182

You want something called a transaction. The code would look something like

begin
  Report.transaction do
    # create report like Report.create! or something
    # create comments like Comment.create! or something
  end
rescue
  # there was an error
end

Inside the transaction, if an error is thrown the database is reverted to what it was before the entire transaction was begun. In the rescue, you can handle any errors that were thrown.

Upvotes: 2

Related Questions