Recently while working on a programming challenge, I came across a weird behavior when using a has_one association in Rails. It was one of those situations where I couldn’t help trying to figure out exactly why the code was behaving the way it was. In this blog post, I will briefly explain exactly what happened and how simple it was to solve it.
For this example, I will use a different domain than the one I was actually working with to respect the privacy of the actual challenge. However, the association is exactly the same.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Team < ApplicationRecord | |
| has_many :players | |
| has_one :coach | |
| end | |
| class Player < ApplicationRecord | |
| belongs_to :team | |
| end | |
| class Coach < ApplicationRecord | |
| belongs_to :team | |
| end |
Here we are dealing with a sports domain in which a team has many players, but only one coach. For the sake of simplicity, we will assume I’m talking about the head coach, without assistant coaches and the rest of the coaching staff that normally makes up the coaching of an actual sports team. But I digress…
Here is what the corresponding schema looks like:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ActiveRecord::Schema.define(version: 20170308221615) do | |
| create_table "coaches", force: :cascade do |t| | |
| t.integer "team_id" | |
| t.string "name" | |
| t.integer "experience" | |
| t.datetime "created_at", null: false | |
| t.datetime "updated_at", null: false | |
| end | |
| create_table "players", force: :cascade do |t| | |
| t.integer "team_id" | |
| t.string "name" | |
| t.integer "number" | |
| t.datetime "created_at", null: false | |
| t.datetime "updated_at", null: false | |
| end | |
| create_table "teams", force: :cascade do |t| | |
| t.string "name" | |
| t.datetime "created_at", null: false | |
| t.datetime "updated_at", null: false | |
| end | |
| end |
With the associations set up in this way, after running migrations let’s fire up the Rails console to make sure the associations are working as expected.

The above ensured that the has_many association linking Team and Player was working as it should. Straightforward enough. Now let’s give the Warriors a head coach.

Now it’s been a few years and the Warriors aren’t doing so well. They’re looking to hire a new coach. Let’s say they bring back Mark Jackson (seriously, there are many teams that could use him!). They first have to fire Kerr in order for Jackson to take over the team. This is where things get interesting. Back to the rails console.

Here we create a new coach and immediately associate it with the Warriors (using .create_single_association). Of particular interest, however, is what happens right after that. Pay attention the yellow line. This SQL query gets fired right after the new record gets persisted. If you look closely, all it’s doing is updating the record’s team_id attribute to a nil value.
Notice that when I look at Coach.all the previous record’s team_id is in fact nil. Interesting…
The person I was pair programming with and I were both a little perplexed by this. After exercising some Google-fu, looking for an explanation, I came across this Stack Overflow answer which clarified things for me.
From the answer, I was able to learn that by default, the has_one association executes a nullify. This is now evident to me looking carefully at the yellow SQL query in the screenshot above. In addition, according to the answer, the way to prevent this behavior is to add a dependent: :destroy option. Let’s try that!
https://gist.github.com/fidelscodes/cba05a337bc1490acafd6168e9b61748
First, we update the Team model by adding the dependent: :destroy option to the `has_one` association.
Now, to the Batmobile! Err… I mean, to the rails console!

As you can see by the red line of SQL, this time after the new record is created, the previous one is being deleted from the database. This is the behavior I was actually aiming for!
When would we want this default behavior over dependent: :destroy?
Well, this sports domain is a great example where it actually makes sense to go with the default.
We fired Mark Jackson as the Warriors head coach when we hired Michael Jordan. However, we also lost all information regarding Mark Jackson. If another team were to hire him, we would have to create a brand new record.
By relying on the default way that has_one works, his record simply would have been updated to nullify its team_id attribute (just as it happened with Steve Kerr in our first example). This means that if another team wanted to hire Mark Jackson, we would already have access to his information, we’d simply need to associate his record’s team_id with the new team’s id.
Conclusion
Going through the challenge that led to this blog post was a lot of fun. In particular, because I was able to learn something new from the experience.
It turns out that when used the right way, a has_one association can be a very powerful tool to be aware of when modeling a domain. Just make sure you’re aware of the way you want it to work ahead of time. It will save you from asking yourself “but why isn’t it working?” many times over.
Helpful Resources
http://stackoverflow.com/questions/36893028/has-one-relation-automatically-set-nil-if-more-than-one
http://guides.rubyonrails.org/association_basics.html#choosing-between-belongs-to-and-has-one
http://stackoverflow.com/questions/861144/difference-between-has-one-and-belongs-to-in-rails























