こんにちは、hachi8833です。今回はRailsウォッチでもご紹介したRein gemのREADME翻訳をお送りします。
多くの機能がPostgreSQL寄りなので、PostgreSQLで使うとさらに幸せになれるかもしれません。
概要
README末尾のMITライセンスおよびリポジトリのライセンスに基づき翻訳・公開いたします。
- 英語README: rein
- 更新日: 2017/06/06
- 著者: Joshua Bassett
- サイト: https://joshbassett.info/
Rein: RailsのActiveRecordにDB制約を追加するgem(README翻訳)
データ完全性(data integrity)はよいものです。
値の制約は、アプリのレベルよりもデータベースのレベルでかける方が、データを正しく保つ方法としてより強力になります。
残念なことに、SQLを手書きせずにデータ完全性を実現しようとしても、ActiveRecordではそうしたサポートについて冷淡であり、許してすらくれません。Rein(発音はrainと同じ)は、データベース上のデータを正しく保つのに役立つさまざまなメソッドをActiveRecordのマイグレーションに追加するgemです。
ReinのDSLで使えるメソッドはすべて逆操作が可能なので、Railsのマイグレーションで可逆的な操作を利用できます。
クイックスタート
- 1: gemをインストールします。
gem install rein
- 2: マイグレーションに制約(constraint)を追加します。
class CreateAuthorsTable < ActiveRecord::Migration
def change
create_table :authors do |t|
t.string :name, null: false
end
# authorには必ずnameがあること
add_presence_constraint :authors, :name
end
end
利用できる制約
外部キー制約
外部キー制約は、カラム内の値が別のテーブル内の行(row)の値を一致しなければならないことを指定します。
たとえば、「books
テーブルのauthor_id
は、authors
テーブルのid
にある値に限定したい」場合は、次のように書きます。
add_foreign_key_constraint :books, :authors
外部キー制約を追加しても、参照されるカラムにインデックスが自動で追加されるわけではありません。一般に、インデックスを追加することで外部キーのJOINを高速化できます。インデックスを作成するには、index
オプションを使います。
add_foreign_key_constraint :books, :authors, index: true
Reinは、テーブルに対応するカラム名を自動で推測します。明示的に指定したい場合は、referenced
オプションやreferencing
オプションを利用できます。
add_foreign_key_constraint :books, :authors, referencing: :author_id, referenced: :id
参照先の行のひとつが更新または削除されたときの動作も指定できます。
add_foreign_key_constraint :books, :authors, on_delete: :cascade, on_update: :cascade
DELETEやUPDATEで指定できる動作の全オプションを以下に示します。
no_action
: 制約チェック時に、参照元の行がまだ存在している場合にはエラーを出力します。オプションを指定しない場合はデフォルトでこの動作になります。cascade
: 参照先の行が削除されたときに、参照元の行も同時に削除されなければならないことを指定します。set_null
: 参照先の行が削除されたときに、参照元のカラムにNULLを設定します。set_default
: 参照先の行が削除されたときに、参照元のカラムにデフォルト値を設定します。restrict
: 参照先の行の削除を禁止します。
外部キー制約を削除するには、次を使います。
remove_foreign_key_constraint :books, :authors
inclusion制約
inclusion制約は、カラムに設定できる値のリストを指定します。
たとえば、「state
カラムの値はavailable
かon_loan
の2つのみを取れる」ようにするには、次のようにします。
add_inclusion_constraint :books, :state, in: %w[available on_loan]
inclusion制約を削除するには、次を使います。
remove_inclusion_constraint :books, :state
if
オプションも併用すると、次のように特定の条件を満たす場合にのみ制約をかけることもできます。
add_inclusion_constraint :books, :state,
in: %w[available on_loan],
if: "deleted_at IS NULL"
name
オプションで名前をカスタマイズすることもできます。
add_inclusion_constraint :books, :state,
in: %w[available on_loan],
name: "books_state_is_valid"
長さ制約
長さ制約は、文字列カラムの値が取れる長さの範囲を指定します。
たとえば、「call_number
の長さを1から255の間にする」には次のようにします。
add_length_constraint :books, :call_number,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 255
長さ制約の全オプションを以下に示します。
equal_to
not_equal_to
less_than
less_than_or_equal_to
greater_than
greater_than_or_equal_to
if
オプションも併用すると、次のように特定の条件を満たす場合にのみ制約をかけることもできます。
add_length_constraint :books, :call_number,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 12,
if: "status = 'published'"
name
オプションで名前をカスタマイズすることもできます。
add_length_constraint :books, :call_number,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 12,
name: "books_call_number_is_valid"
長さ制約を削除するには、次を使います。
remove_length_constraint :books, :call_number
数値制約
数値制約は、数値カラムが取れる値の範囲を指定します。
たとえば、「publication_month
の値は1から12の間だけを取れる」ようにするには、次のようにします。
add_numericality_constraint :books, :publication_month,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 12
数値制約の全オプションを以下に示します。
equal_to
not_equal_to
less_than
less_than_or_equal_to
greater_than
greater_than_or_equal_to
if
オプションも併用すると、次のように特定の条件を満たす場合にのみ制約をかけることもできます。
add_numericality_constraint :books, :publication_month,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 12,
if: "status = 'published'"
name
オプションで名前をカスタマイズすることもできます。
add_numericality_constraint :books, :publication_month,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 12,
name: "books_publication_month_is_valid"
数値制約を削除するには次のようにします。
remove_numericality_constraint :books, :publication_month
presence制約
presence(存在)制約は、文字列カラムの値が空にならないよう指定します。
いわゆるNOT NULL
制約は空文字列でも満たされますが、文字列に(NULLでない)何らかの値があることを保証するには、次のようにします。
add_presence_constraint :books, :title
特定の条件が満たされる場合にのみ制約をかけたい場合は、if
オプションを渡します。
add_presence_constraint :books, :isbn, if: "status = 'published'"
name
オプションで名前をカスタマイズすることもできます。
add_presence_constraint :books, :isbn, name: "books_isbn_is_valid"
presence制約を削除するには、次のようにします。
remove_presence_constraint :books, :title
NULL制約
NULL制約は、カラムにNULL値が含まれていないことを保証する制約です。これはカラムにNOT NULL
制約を加えることと同じですが、条件を指定できる点が異なります。
たとえば、「本がon_loan
(貸出中)の場合のみdue_date
が必ずあるようにする」には、次のようにします。
add_null_constraint :books, :due_date, if: "state = 'on_loan'"
NULL制約を削除するには、次のようにします。
remove_null_constraint :books, :due_date
データ型
列挙型
列挙型(enum)は、静的かつ順序の変わらない値のセットを表します。
create_enum_type :book_type, %w[paperback hardcover]
データベースから列挙型を削除するには、次のようにします。
drop_enum_type :book_type
ビュー
データベース・ビュー(以下単にビュー)は、通常のテーブルと同じように参照できる「名前付きクエリ(named query)」です。Reinを使うと、データベースでビューをサポートするActiveRecordモデルも作成できるようになります。
たとえば、「現在貸出可能な本のリストを返すavailable_books
というビューを定義する」には、次のようにします。
create_view :available_books, "SELECT * FROM books WHERE state = 'available'"
ビューをデータベースから削除するには、次のようにします。
drop_view :available_books
スキーマ
ひとつのデータベースには、名前付きスキーマ(複数可)を含めることができます。この名前スキーマにはテーブルが含まれることになります。データベースを複数のスキーマに分割して、複数のテーブルを論理的にグループ化すると便利な場合があります。
create_schema :archive
スキーマをデータベースから削除するには、次のようにします。
drop_schema :archive
例
簡単な図書館貸出アプリを用いて、データベースの値に制限を追加するマイグレーションの実例をいくつか見てみましょう。
class CreateAuthorsTable < ActiveRecord::Migration
def change
# The authors table contains all the authors of the books in the library.
create_table :authors do |t|
t.string :name, null: false
t.timestamps, null: false
end
# 制約: authorには必ずnameがあること
add_presence_constraint :authors, :name
end
end
class CreateBooksTable < ActiveRecord::Migration
def change
# booksテーブルには図書館のすべての本や状態(貸出中・貸出可など)が収録されている
create_table :books do |t|
t.belongs_to :author, null: false
t.string :title, null: false
t.string :state, null: false
t.integer :published_year, null: false
t.integer :published_month, null: false
t.date :due_date
t.timestamps, null: false
end
# book 1冊につき、authorが1人いること
# authorを1人削除すると、authorのbookもデータベースから自動削除されること
add_foreign_key_constraint :books, :authors, on_delete: :cascade
# book 1冊につき、空でないtitleが1つあること
add_presence_constraint :books, :title
# stateは"available"(貸出可)、"on_loan"(貸出中)、"on_hold"(保留)のいずれかだけを取ること
add_inclusion_constraint :books, :state, in: %w[available on_loan on_hold]
# 古典はこの図書館の対象外
add_numericality_constraint :books, :published_year,
greater_than_or_equal_to: 1980
# 月は常に1〜12であること
add_numericality_constraint :books, :published_month,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 12
# 貸出中の本1冊には、due_date(返却期限)が1つあること
add_null_constraint :books, :due_date, if: "state = 'on_loan'"
end
end
class CreateArchivedBooksTable < ActiveRecord::Migration
def change
# archiveスキーマは、(非公開の)書庫に関するデータをすべて含む
# このスキーマは、一般公開用スキーマと別にしておきたい
create_schema :archive
# archive.booksテーブルは、非公開書庫にあるすべての本を含む
create_table "archive.books" do |t|
t.belongs_to :author, null: false
t.string :title, null: false
end
# book 1冊につき、authorが必ず1人いること
# このデータベースでは、著書があるauthorの削除を禁止すること
add_foreign_key_constraint "archive.books", :authors, on_delete: :restrict
# book 1冊につき、空でないtitleが1つあること
add_presence_constraint "archive.books", :title
end
end
ライセンス
ReinはMIT Licenseに基いて公開しています。