Ruby on Rails Explained
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
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">
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