We write about Ruby on Rails, React.js, React Native, remote work, open source, engineering and design.
Rails 6.0 was recently released.
Rails 6 added create_or_find_by and
create_or_find_by!. Both of these methods rely on unique constraints
on the database level. If creation fails, it is because of the unique constraints on one or all of the given columns, and
it will try to find the record using find_by!
.
create_or_find_by is an improvement over find_or_create_by because find_or_create_by first queries for the record, and then inserts it if none is found. This could lead to a race condition.
As mentioned by DHH in the pull request, create_or_find_by has a few cons too:
create_or_find_by! raises an exception when creation fails because of the validations.
Let's see how both methods work in Rails 6.0.0.beta2.
1
2> > class CreateUsers < ActiveRecord::Migration[6.0]
3> > def change
4> > create_table :users do |t|
5> > t.string :name, index: { unique: true }
6> >
7> > t.timestamps
8> > end
9> >
10> > end
11> > end
12
13> > class User < ApplicationRecord
14> > validates :name, presence: true
15> > end
16
17> > User.create_or_find_by(name: 'Amit')
18> > BEGIN
19> > INSERT INTO "users" ("name", "created_at", "updated_at") VALUES ($1, $2, \$3) RETURNING "id" [["name", "Amit"], ["created_at", "2019-03-07 09:33:23.391719"], ["updated_at", "2019-03-07 09:33:23.391719"]]
20> > COMMIT
21
22=> #<User id: 1, name: "Amit", created_at: "2019-03-07 09:33:23", updated_at: "2019-03-07 09:33:23">
23
24> > User.create_or_find_by(name: 'Amit')
25> > BEGIN
26> > INSERT INTO "users" ("name", "created_at", "updated_at") VALUES ($1, $2, \$3) RETURNING "id" [["name", "Amit"], ["created_at", "2019-03-07 09:46:37.189068"], ["updated_at", "2019-03-07 09:46:37.189068"]]
27> > ROLLBACK
28
29=> #<User id: 1, name: "Amit", created_at: "2019-03-07 09:33:23", updated_at: "2019-03-07 09:33:23">
30
31> > User.create_or_find_by(name: nil)
32> > BEGIN
33> > COMMIT
34
35=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
36
37> > User.create_or_find_by!(name: nil)
38
39=> Traceback (most recent call last):
401: from (irb):2
41ActiveRecord::RecordInvalid (Validation failed: Name can't be blank)
Here is the relevant pull request.
Also note, create_or_find_by can lead to
primary keys running out, if the type of primary key is int
. This happens because each time
create_or_find_by hits ActiveRecord::RecordNotUnique
,
it does not rollback auto-increment of the primary key. The problem is discussed in this
pull request.