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!