プログラミング

Rails モデルへのユニークキーやNOT NULLの付け方

プライマリーキーとユニークキーの違い

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が発動します。

こんな感じで、制約のかけ方と検証でした。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です