FactoryBot .find_or_create_by
Tags: rspec, ruby, rails, testing
Published: 2019-03-18 05:59:58

---
title: FactoryBot .find_or_create_by
published: true
description: "FactoryBot: How to only create if does not currently exist"
tags: RSpec, Ruby, Rails, testing
cover_image: https://thepracticaldev.s3.amazonaws.com/i/j4fmr4c9w9z01k0hmyf0.jpg
---

### **TL;DR**

```ruby
factory :country do
  to_create do |instance|
    instance.id = Country.find_or_create_by(name: instance.name, code: instance.code).id
    instance.reload
  end
end
```

**How did I come up with that solution? See below.**

There seems to be no clear documentation on how to go about this. So, here's what I tried.

Before we begin, let's prepare our test environment, we will be using a `Country` model, with attributes `name` and `code`:
```ruby
# models/country.rb

class Country
  validates :name, presence: true
  validates :code, presence: true, length: { is: 2 }, uniqueness: { case_sensitive: false } # ISO-3166-ALPHA-2
end
```

```ruby
# spec/factories/countries.rb

FactoryBot.define do
  factory :country do
    name { 'Canada' }
    code { 'CA' }
  end
end
```

Now, we're all good engineers and good engineers write unit tests. This will make verifying results easier. [The RSpec](https://gist.github.com/jooeycheng/a09c4701d1dfb68bdc9d3cc6028c9e24#the-rspec).

### Attempt 1. Overriding the `initialize_with` block

This is the most voted answer on a [question](https://stackoverflow.com/a/11799674/3956879) posted on StackOverflow. We can override a factory's default initialization behavior with:

```ruby
factory :country do
  initialize_with { Country.find_or_create_by(name: name, code: code) }
end
```

While the solution does work, it _breaks the factory_. Calling `build(:country)` will now create a record in database if it does not exist.

Looking at the comments section, we see a [suggestion](https://stackoverflow.com/questions/7145256/find-or-create-record-through-factory-girl-association#comment51187522_11799674) to use `.find_or_initialize_by`:

```ruby
factory :country do
  initialize_with { Country.find_or_initialize_by(name: name, code: code) }
end
```

This partially restores behaviour of `build` as it does not create a record anymore. However, it still returns a persisted record if found.

This approach breaks the original behaviour of `build`, which is to only _initialize a record_, and not return a persisted record. There is also a performance cost of accessing the database to perform a `Country.find_by`. An ideal solution would be to only access the database on `create`, to create the record. We should not need to read or write to database on `build`.

[RSpec Results](https://gist.github.com/jooeycheng/a09c4701d1dfb68bdc9d3cc6028c9e24#attempt-1)

### Attempt 2. Overriding the `to_create` block (v1)

Digging into FactoryBot documentation, we see a section on **[Custom Methods to Persist Objects](https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#Custom_Methods_to_Persist_Objects)**.

> By default, creating a record will call `save!` on the instance; since this may not always be ideal, you can override that behavior by defining `to_create` on the factory

Let's try it out:

```ruby
factory :country do
  to_create { |instance| Country.find_or_create_by(name: instance.name, code: instance.code) }
end
```

[RSpec Results](https://gist.github.com/jooeycheng/a09c4701d1dfb68bdc9d3cc6028c9e24#attempt-2)

From the results, we see that `build` and `create` works as expected.
However, `create` seems to return the _factory-pre-save instance_ instead of the _created instance_, which explains why the `country.id` is `nil`, but `Country.count` increased. 🤔

Alright, we're close. All we need is a way to set the attributes back to it's own instance after calling the `.find_or_create_by` so it can assume identity of the found-or-created object.

### Attempt 3. Overriding the `to_create` block (v2)

Now, we know the `instance` in `to_create { |instance| ... }` is the model object itself, which is an ActiveRecord model. Hence, we should be able to sort-of-force-update-the-attributes like so:

```ruby
factory :country do
  to_create { |instance| instance.attributes = Country.find_or_create_by(name: instance.name, code: instance.code).attributes }
end
```

[RSpec Results](https://gist.github.com/jooeycheng/a09c4701d1dfb68bdc9d3cc6028c9e24#attempt-31)

Oh 💩it worked. But one last hurdle - `country.persisted?` returns false. Easiest way to fix this is by `.reload`-ing the instance:

```ruby
factory :country do
  to_create do |instance|
    instance.attributes = Country.find_or_create_by(name: instance.name, code: instance.code).attributes
    instance.reload
  end
end
```

Yay, all green! 🚦

[RSpec Results](https://gist.github.com/jooeycheng/a09c4701d1dfb68bdc9d3cc6028c9e24#attempt-32)

But, `#reload` is not the ideal solution, as it performs an unnecessary [database read](https://apidock.com/rails/v4.2.7/ActiveRecord/Persistence/reload), when all we want is for `#persisted?` to behave correctly. Looking at the [Rails documentation](https://apidock.com/rails/ActiveRecord/Persistence/persisted%3F), we find that `#persisted?` is the inverse of `#new_record?`, which takes its value from instance variable `@new_record`.

Which means, we could possibly do this:

```ruby
factory :country do
  to_create do |instance|
    instance.attributes = Country.find_or_create_by(name: instance.name, code: instance.code).attributes
    instance.instance_variable_set('@new_record', false)
  end
end
```

Damn it worked 😎

[RSpec Results](https://gist.github.com/jooeycheng/a09c4701d1dfb68bdc9d3cc6028c9e24#attempt-33)

Now, I agree this is _hacky_. Probably should just stick to `.reload` to be safe. If this is not relevant to you, probably could just ignore the `.reload` altogether.

_Cover image by [Freepik](https://www.freepik.com/free-photos-vectors/abstract)._