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. ```language-rb # 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. ```language-rb # 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. ```language-rb 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. ```language-rb 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 some ways that you can use `find_or_create_by`. Happy coding!