Ruby on Rails Explained
find_or_create_by

Jul 20, 2020

I have previously written a post about Find or create by and its usages in Rails 3 but Rails have gotten several version updated since then and some of the things I mentioned have even been deprecated at this time, so I wanted to revisit this very useful method again.

As the name implies, when called it will try to retrieve a record if it exists and will create it instead if it does not exist.

The main thing that was changed in Rails 4 was the dynamic method names were deprecated in favour of passing the column names as parameter keys instead.

 # Previous syntax
User.find_or_create_by_name "Jake"
=> #<User id: 1, name: "Jake", age: 30, role: "super">

 # New syntax
User.find_or_create_by name: "Jake"
=> #<User id: 1, name: "Jake", age: 30, role: "super">

The same thing goes when using multiple attributes and in this scenario, it is a definite improvement in terms of readability because of the order it is written.

 # Previous syntax
User.find_or_create_by_name_and_age("Jake", 31)
=> #<User id: 2, name: "Jake", age: 31, role: nil>

 # New syntax
User.find_or_create_by name: "Jake", age: 31
=> #<User id: 2, name: "Jake", age: 31, role: nil>

Other features of find_or_create_by

When it fails

What happens when no record could be found but the creation failed for some reason (validation, DB constraints, etc.)? By default it will return the newly instantiated record which will not be persisted. The id column will be nil, calling new_record? will return true and persisted? will be false. In addition, calling errors should hopefully return an explanation as to why it failed.

An alternative to the above behaviour is to add a bang at the end find_or_create_by! and that will cause an exception to be raised if the record cannot be created. Both ways have its merits and it's up to you which way you want to handle it.

Setting additional attributes on create only

By using a block when calling this method you can exclude attributes that should not be used to finda record, but will still be used to set default attributes if a new record needs to be created.

User.find_or_create_by name: "Jake" do |user|
  user.role = "basic"
end
=> #<User id: 1, name: "Jake", age: 30, role: "super">

User.find_or_create_by name: "John" do |user|
  user.role = "basic"
end
=> #<User id: 3, name: "John", age: nil, role: "basic">

Similar methods

Another method that behaves almost the same is find_or_initialize_by which does the exact same thing except when it could not find a matching record, uses new instead of create to return a non-persisted record.

User.find_or_initialize_by name: "Jane"
=> #<User id: nil, name: "Jane", age: nil, role: nil>

There is also the create_or_find_by (reversed order) that usually behaves exactly the same as find_or_create_by with the exception that it takes another approach to tackle potential race conditions and stale database reads. I wont' go into details in this post, but I have written about it before in the following post.

Rails 6 adds new finder method create_or_find_by

That's it for now

These are just some of the way that you can use find_or_create_by.

Happy coding!

Part of the Series: Ruby on Rails Explained