Summary of changes in behavior of create_association method in Rails 7.0.5 and later
Tags: rails, ruby
Published: 2023-07-04 07:11:53

---
title: Summary of changes in behavior of create_association method in Rails 7.0.5 and later
published: true
description: 
tags: rails, ruby
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-07-04 06:43 +0000
---

This article is an English translation from https://blog.willnet.in/entry/2023/07/04/113321

There are several related PRs and Issues on this matter and comments are scattered, and it is complicated to explain to people, so this is a summary as a blog. Please comment if you find any mistakes or opinions!

## Update 2023/08/02

A commit has been made to revert this change to the 7-0-stable branch. It seems that the policy is to revert back and start over because some people had trouble with the new behavior.

[[7-0-stable] Revert singular association breaking changes by zzak · Pull Request #48809 · rails/rails](https://github.com/rails/rails/pull/48809)

I don't know when 7.0.7 will be released, but if it is released in its current state, it will revert to 7.0.4 behavior; those who are having trouble with changes after 7.0.5 may want to point to 7-0-stable once it is released.

## Overview

- Behavior of create_association method generated when defining has_one association is different in Rails 7.0.4 and 7.0.5 or later.
- If you think that the behavior of create_association is only "create a new association record", you may need to modify your code to use 7.0.5 or later.

## Assumptions

- If you try to create a new model via has_one association when the association is already persisted, the existing association will be deleted(behavior of `association=` is written in http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one but `build_association` and `create_association` do not seem to be described, including in the Rails guide).
- The way the deletion is done depends on the dependent option of has_one (This is where the code is applicable. https://github.com/rails/rails/blob/89508b95d8d289b430cca7db05109d8171ff7a5f/activerecord/lib/active_record/associations/has_one_association.rb#L94-L116 )
	- When `:nullify` or `:dependent` option is not set, set NULL to the foreign key and UPDATE
	- When `:delete`, delete with the `destroy` method
	- When `:delete`, delete with `delete` method.


For example, in all of (1)~(3) in the following code, if there is a record associated with an existing associate, it will be deleted.

```ruby
class User < ApplicationRecord
  has_one :address, dependent: :destroy
end

class Address
  belongs_to :user
end

user = User.find(42)
user.address = Address.new # (1)
user.create_address # (2)
user.build_address # (3)
```


## Behavior up to Rails 7.0.4

Previously, the behavior of create_association was to insert and then delete in separate transactions as described in [this PR](https://github.com/rails/rails/pull/46386).

```
# begin transaction
INSERT new_record; # commit transaction
# commit transaction

# begin transaction
DELETE old_record; # commit transaction
# commit transaction
```

Furthermore, even if the preceding insert (save method) fails due to a validation error, the subsequent delete will still be executed. This behavior caused a bug in which a validation error would result in the deletion of an existing record when execute `create_association!`

Related Issue: [has_one association getting deleted on using create_association & validation fails - Issue #46737 - rails/rails](https://github.com/rails/rails/issues/46737)

There was another issue where deletion of existing records did not work in the following way

1. create a new record when the validation passes
2. then delete the existing record
3. retrieving an existing record for deletion is done via has_one related method.
4. when the order by related to has_one is not set (in most cases, order by is not set for has_one), the newly created record is retrieved in rare cases. 
5. if this newly created record is retrieved, it is treated as if there are no records to delete
6. as a result, there are two or more records without deletion.

Related Issue: [Sometimes `create_association` does not delete existing records - Issue #47554 - rails/rails](https://github.com/rails/rails/issues/47554)

## Behavior since Rails 7.0.5

In 7.0.5, the behavior has changed to delete and then insert in the same transaction, as shown below, and the above bug has been mostly resolved.

```
# begin transaction
DELETE old_record;
INSERT new_record; # commit transaction
# commit transaction
```

However, the code would need to be modified for an application that originally had a unique constraint on the has_one foreign key and wanted to make a validation error if create_association was performed when there was an existing record.

Related Issue: [has_one relation start deleting existing record even when new record fails passing validation - Issue #48330 - rails/rails](https://github.com/rails/rails/issues/48330)


## Thoughts

I personally feel that the changes in 7.0.5 were necessary for the consistency of the behavior described in the "Assumptions" section, but on the other hand, I understand that it may be surprising to people who do not know that `association=`, `build_association`, or `create_association` will delete existing records. On the other hand, I can understand why some people might find it hard to modify their code to accommodate this change, as it might be a surprise to those who are not familiar with this behavior.

I wonder what kind of behavior would make everyone happy...?