やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

Railsのマイグレーションファイルでt.stringを使うときはlimitを指定してほしい

Railsではマイグレーションファイルを作成してマイグレートを実行することで、テーブルが作成されます。
非常に便利で手軽にテーブルの定義・作成ができるのですが、マイグレートされた結果、どのような型が選択されるのかが隠蔽化されるという問題があると思っています。一例として、t.stringt.textでそれぞれカラムを定義した場合に実際にテーブルで採用される型はどうなるでしょうか。

class CreateUserTable < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :string_name
      t.text :text_name
      t.timestamps
    end
  end
end

postgresqlに接続した状態で実行すると、以下のような結果になりました。
気になるのはt.string :string_nameで定義したカラムでは型がcharacter varying(varchar)(制限付き可変長文字列)となり、t.text :text_nameで定義したカラムではtext(制限無し可変長文字列)が選択されるという点です。

(※ここでいう制限の有無は最大文字数のこと)

postgres=# \d users
                                          Table "public.users"
   Column    |              Type              | Collation | Nullable |              Default              
-------------+--------------------------------+-----------+----------+-----------------------------------
 id          | bigint                         |           | not null | nextval('users_id_seq'::regclass)
 string_name | character varying              |           |          | 
 text_name   | text                           |           |          | 
 created_at  | timestamp(6) without time zone |           | not null | 
 updated_at  | timestamp(6) without time zone |           | not null | 
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)

何が気になるのか

特定の上限を設けずに長い文字列を保存したい場合は、任意の上限を設けるよりも長さの指定が無い text もしくは character varying を使用します。
Tip: 空白でパッドされた型を使用した場合の保存領域の増加を別にして、これら 3 つの型の間でパフォーマンスに関する差異はありません

https://www.postgresql.jp/document/7.3/user/datatype-character.html

postgresqlの公式のドキュメントを見る限りはcharacter varying型とtext型で大きな違いはないそうですが、僕としてはcharacter varying型を選択するのであれば最大文字数の制限はするべきだと思っています。character varying型とtext型の違いは最大文字数の制限が可能かどうかという点です。であれば明確に使い分けるために最大文字数の制限は行った方が役割が明確になります。 制限をしないのであれば、最初からtext型を選択すれば良いだけの話かなと思います。

t.stringに最大文字数を制限するには

非常に簡単でマイグレーションファイルで最大文字数の指定をしてあげれば良いだけです。

class AddColumnVarcharToUser < ActiveRecord::Migration[7.0]
  def up
    add_column :users, :vchar_name, :string, limit: 50 # limitで最大文字数を50とする
  end

  def down
    remove_column :users, :vchar_name
  end
end

マイグレーション後

postgres=# \d users
                                          Table "public.users"
   Column    |              Type              | Collation | Nullable |              Default              
-------------+--------------------------------+-----------+----------+-----------------------------------
 id          | bigint                         |           | not null | nextval('users_id_seq'::regclass)
 string_name | character varying              |           |          | 
:
 vchar_name  | character varying(50)          |           |          | 

先ほどとは違いcharacter varying(50)とあるように、最大文字数の制限がされていることが分かります。

postgres=# INSERT INTO users (
  created_at,
  updated_at,
  vchar_name
) VALUES (
  NOW(),
  NOW(),
  /* 51文字の文字列 */
  'ああああああああああああああああああああああああああああああああああああああああああああああああああい'
);
ERROR:  value too long for type character varying(50)

最後に

僕は基本的にユーザーに自由な選択を与えすぎないということを意識しています。
今回の場合でいえば、t.stringlimit: Nを指定しなければ何文字(正確には最大1GB)でもデータの登録が可能になります。例えばユーザーの名前を登録するカラムであるならば可変長に何文字も登録する必要はありません。日本人しか使わないシステムであるとすれば、どれだけ長くても50文字まで登録できれば十分でしょう。

適度に制限をするというのはテーブル設計・UXにおいても非常に重要になります。t.stringでマイグレーションを行うのであれば、必ずlimit: Nの記述もしたいところです。

少しでも「ええな〜」と思ったらイイネ!・シェア!・はてなブックマークを頂けると励みになります。