The Trouble with `has_one`
Tags: rails, ruby, webdev, database
Published: 2021-08-10 13:19:27

Recently I was working on my dissertation and came across an interesting problem with using the has_one association in Rails. But before we get into that, let’s take a step back to understand a little about how associations work in Rails.

![A woman on big brother looking confused](https://media.giphy.com/media/l0HlI6NdcrtkV5C7e/giphy.gif)

## Rails Associations
I’m not going to assume everyone reading this knows or understands in depth about databases and object relational mappers so I’ll try to keep this brief and simple. If you want a more in depth understanding, the [Rails docs](https://guides.rubyonrails.org/association_basics.html) are a good starting point to understanding this in that specific context.

Let’s say you want to build a content site where different **creators** can create **posts**, you’d have a relational database with two tables **Creator** and **Post** that may look something like this:

![A spreadsheet with two tables, Creator with the rows ID and Name and Post with the rows ID, Title, Type and Body](https://miro.medium.com/max/1400/1*zTLAQfTO-wxZDMXgvOQ3Fg.png)

Your creator table has an *ID* and *Name* column for every item that gets created and your post table has an *ID*, *Title*, *Type* and *Body* column for every item. In each column, the primary key will be the *ID* column since that’s going to be the unique identifier for each record. The [primary key](https://www.bbc.co.uk/bitesize/guides/ztsvb9q/revision/5) is important when creating associations because it acts as the [foreign key](https://www.bbc.co.uk/bitesize/guides/ztsvb9q/revision/5) in the associated table.

Now we need to decide what kind of relationship the two tables will have with each other since we’d likely want to know which creators created which posts. There are a few different kinds of associations you can create but I’ll only touch on two.

### has_many

A has_many association, says one item in a table can be associated with many items in another table. For example, if our system is a standard content creation system then a **creator** can have many **posts** since one **creator** can create many **posts** in which case, the association in Rails would look like:

```ruby
class Creator < ApplicationRecord
  has_many :posts
end
class Post < ApplicationRecord
  belongs_to :creator
end
```
And our database would look like:
![A spreadsheet with two tables, Creator with the rows ID and Name and Post with the rows ID, Title, Type, Body and Creator_ID](https://miro.medium.com/max/1400/1*UvKD65Tc6EPzhXAK3IsoDA.png)

The `creator_id` would be pulled from the `id` column, which is the primary key in the **creator** table and act as a foreign key in the **post** table. So if you wanted to find all the posts created by Tiffany Pollard, you’d do a search on the **post** table where the `creator_id` matches Tiffany Pollard’s `id` in the **creator** table.

### has_one
A `has_one` relationship says one item in a table can only be associated with one item in another table, no more than one. We may want to create a system where a **creator** can only ever create one **post** in which case, our Rails code would look like:

```ruby
class Creator < ApplicationRecord
  has_one :post
end
class Post < ApplicationRecord
  belongs_to :creator
end
```

A **creator** `has_one` post and a post `belongs_to` a **creator**. The database looks the same as before:
![A spreadsheet with two tables, Creator with the rows ID and Name and Post with the rows ID, Title, Type, Body and Creator_ID](https://miro.medium.com/max/1400/1*UvKD65Tc6EPzhXAK3IsoDA.png)

And just like with `has_many` the `creator_id` is the foreign key for the creator’s `id` in the post table.

Okay, so now onto the problem.

...

## The Problem

In Rails when you create a `has_one` association, understandably the assumption is that a **creator** can only ever have one **post**. So what happens when a **creator** tries to create another **post**? You’d hope that it’d check to see if a post exists and ask the creator if they want to replace the post or just replace the post with the assumption that that’s the intention of the creator. But that’s not what happens, instead, you get this error:

![A rails error message that reads "Failed to remove the existing associated post. The record failed to save after its foreign key was set to nil.](https://miro.medium.com/max/1400/1*NBIfqCesY82KGPb0P4jLyQ.png)

This is a common error with `has_one` and is summed up nicely in this [issue](https://github.com/rails/rails/issues/17325):

> This is because prior to deleting, the foreign key of the target association is set to nil and a save operation is performed on the target.

It’s unclear *why* this happens, if you go through the issue you’ll notice it was created in 2014 and the discussion is still ongoing. It seems this started out as a bug but may have evolved to become the desired functionality.

## The Fix

![A small dog wearing glasses and a shirt using a laptop](https://miro.medium.com/max/1400/0*S57ZWGL44S9x3ayM)

There are a number of ways to fix this and it all depends on your system. Essentially, you want to make sure the old association is deleted before you try to create a new one. So, you can do this in your model:

```ruby
class Creator < ApplicationRecord
  has_one :post, dependent: :destroy
end
class Post < ApplicationRecord
  belongs_to :creator
end
```

Or in the method where you create your resource (either in your controller or specific lib file) you can run something like:

```ruby
creator = Creator.find_by_id(id)
creator.post&.delete
```

before you create any post. Andy Croll from Coverage Book recommends [rolling things up in a transaction](https://andycroll.com/ruby/be-careful-assigning-to-has-one-relations/) and there are more suggestions in the [GitHub issue](https://github.com/rails/rails/issues/17325) too.

## Further Reading
- [Be Careful Assigning has_one Relations](https://andycroll.com/ruby/be-careful-assigning-to-has-one-relations/)
- [Building a has_one association fails on save if target has a validation on foreign_key](https://github.com/rails/rails/issues/17325)
- [BBC Bitesize: Introducing Databases](https://www.bbc.co.uk/bitesize/guides/ztsvb9q/revision/5)
- [Rails ActiveRecord Associations](https://guides.rubyonrails.org/association_basics.html)