プライマリーキーとユニークキーの違い
PRIMARY KEY 制約は、あるテーブルの 1つの列または複数の列に1度だけ使用できるが、UNIQUE 制約は何度でも使用できる。
PRIMARY KEY 制約を持つ列には NULL が含まれないが、UNIQUE 制約を持つ列には NULL が含まれる可能性がある。
一意のユーザーIDを実装する
ユーザー登録を実装する上で出てくる課題が、「ユーザーID」です。
twitterでいう、@fumiya_ja
みたいなのですね。
プライマリーキーの付属したテーブルのIDをそもままユーザーIDをにしてしまってもいいのですが、(e.g.@832748 みたいなIDになる)
そうすると、テーブルのレコードの数、つまりユーザー数が丸見えになってしまうとか、単純にユーザビリティが悪いっていうようなデメリットがあります。
なので、ちょっと面倒でも、ユーザーIDは一意のデータとしてユニークキーをつけて、実装するのが良いかと思います。
ユーザーIDをどう実装するか
ユニークキーはNULLが許可されているので、NOT NULL制約を付加します。
- ユニークキー制約をする
# 1つのカラムに
add_index :keywords, :site_id , unique: true
# 複数のカラムに
add_index :keywords, [:site_id, :name, :date], unique: true
- NOT NULL制約をする
t.string :address, :null => false
- NOT NULL+デフォルト設定
t.string :address, :null => false, :default => 'Tokyo'
migrateファイルの一例:
class CreateDevs < ActiveRecord::Migration[5.2]
def change
create_table :devs do |t|
t.string :name , :default => 'NAMELESS'
t.string :userid , :null => false
t.timestamps
end
add_index :devs, [:userid], unique: true
end
end
できたら、rake db:migrate
。
では、default
とかNOT NULL
、ユニークキーの確認をしていきます。
# テーブルのカラムの確認
Dev.column_names
=> ["id", "name", "userid", "created_at", "updated_at"]
# 最初のユーザー作成。
irb(main):004:0> Dev.create(name: 'yuis', userid: 'yuis')
(0.0ms) begin transaction
Dev Create (1.0ms) INSERT INTO "devs" ("name", "userid", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "yuis"], ["userid", "yuis"], ["created_at", "2018-04-28 14:05:28.582549"], ["updated_at", "2018-04-28 14:05:28.582549"]]
(61.7ms) commit transaction
+----+------+--------+-------------------------+-------------------------+
| id | name | userid | created_at | updated_at |
+----+------+--------+-------------------------+-------------------------+
| 1 | yuis | yuis | 2018-04-28 14:05:28 UTC | 2018-04-28 14:05:28 UTC |
+----+------+--------+-------------------------+-------------------------+
1 row in set
# 全く同一のユーザー名でやってみる。
irb(main):005:0> Dev.create(name: 'yuis', userid: 'yuis')
(0.0ms) begin transaction
Dev Create (0.0ms) INSERT INTO "devs" ("name", "userid", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "yuis"], ["userid", "yuis"], ["created_at", "2018-04-28 14:05:31.971999"], ["updated_at", "2018-04-28 14:05:31.971999"]]
(0.0ms) rollback transaction
ActiveRecord::RecordNotUnique: SQLite3::ConstraintException: UNIQUE constraint failed: devs.userid: INSERT INTO "devs" ("name", "userid", "created_at", "updated_at") VALUES (?, ?, ?, ?)
from (irb):5
#=> ユニークキー制約によるエラー。ちゃんと機能してますね。
# useridを別の値にして再検証。
irb(main):006:0> Dev.create(name: 'yuis', userid: 'yuis_2')
(0.0ms) begin transaction
Dev Create (1.0ms) INSERT INTO "devs" ("name", "userid", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "yuis"], ["userid", "yuis_2"], ["created_at", "2018-04-28 14:05:47.363096"], ["updated_at", "2018-04-28 14:05:47.363096"]]
(60.3ms) commit transaction
+----+------+--------+-------------------------+-------------------------+
| id | name | userid | created_at | updated_at |
+----+------+--------+-------------------------+-------------------------+
| 2 | yuis | yuis_2 | 2018-04-28 14:05:47 UTC | 2018-04-28 14:05:47 UTC |
+----+------+--------+-------------------------+-------------------------+
1 row in set
# nameにNULLが通るか検証。
irb(main):007:0> Dev.create(name: '', userid: 'yuis_3')
(0.0ms) begin transaction
Dev Create (2.0ms) INSERT INTO "devs" ("name", "userid", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", ""], ["userid", "yuis_3"], ["created_at", "2018-04-28 14:06:01.369127"], ["updated_at", "2018-04-28 14:06:01.369127"]]
(58.2ms) commit transaction
+----+------+--------+-------------------------+-------------------------+
| id | name | userid | created_at | updated_at |
+----+------+--------+-------------------------+-------------------------+
| 3 | | yuis_3 | 2018-04-28 14:06:01 UTC | 2018-04-28 14:06:01 UTC |
+----+------+--------+-------------------------+-------------------------+
1 row in set
# 間違えました。NULLという定数はRubyにはありませんね。
irb(main):008:0> Dev.create(name: NULL, userid: 'yuis_4')
NameError: uninitialized constant NULL
from (irb):8
# nilにするとdefaultが発動するのか検証。
irb(main):009:0> Dev.create(name: nil, userid: 'yuis_4')
(0.0ms) begin transaction
Dev Create (1.0ms) INSERT INTO "devs" ("name", "userid", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", nil], ["userid", "yuis_4"], ["created_at", "2018-04-28 14:06:24.069276"], ["updated_at", "2018-04-28 14:06:24.069276"]]
(75.2ms) commit transaction
+----+------+--------+-------------------------+-------------------------+
| id | name | userid | created_at | updated_at |
+----+------+--------+-------------------------+-------------------------+
| 4 | | yuis_4 | 2018-04-28 14:06:24 UTC | 2018-04-28 14:06:24 UTC |
+----+------+--------+-------------------------+-------------------------+
1 row in set
#=> 発動しませんね。やっぱりnameにも`NOT NULL`制約が必要そうです。
# nameに値自体を指定しない。
irb(main):010:0> Dev.create(userid: 'yuis_5')
(0.0ms) begin transaction
Dev Create (2.0ms) INSERT INTO "devs" ("userid", "created_at", "updated_at") VALUES (?, ?, ?) [["userid", "yuis_5"], ["created_at", "2018-04-28 14:06:41.119073"], ["updated_at", "2018-04-28 14:06:41.119073"]]
(82.2ms) commit transaction
+----+----------+--------+-------------------------+-------------------------+
| id | name | userid | created_at | updated_at |
+----+----------+--------+-------------------------+-------------------------+
| 5 | NAMELESS | yuis_5 | 2018-04-28 14:06:41 UTC | 2018-04-28 14:06:41 UTC |
+----+----------+--------+-------------------------+-------------------------+
1 row in set
#=> これはdefaultが発動します。
こんな感じで、制約のかけ方と検証でした。