■ Part1で構築したRailsアプリケーションに以下機能を実装.
マイグレーション基礎.
データ内容の制限.
モデルの検証.
コールバック.
■ マイグレーション基礎.
マイグレーション機能はテーブルやカラム、インデックス追加/削除、既存データ修正を楽に行える機能.
■ マイグレーション機能の基本的な2ステップ.
DB構造を変更するコードをRubyで記述したマイグレーションファイルを作成.
マイグレーションファイルをrails db:migrateでデータベースに適用.
# デフォルトでは開発用データベースに適用される.
$ bin/rails db:migrate
# 本番用データベース.
$ bin/rails db:migrate RAILS_ENV=production
# 開発用データベース.
$ bin/rails db:migrate RAILS_ENV=test
Railsでは現在のデータベース構造をdb/schema.rbに自動出力させる.
# 最新までマイグレーションを適用.
$ bin/rails db:migrate
# 特定verまでマイグレーションが適用された状態にする(ファイルの先頭の数字部分を指定).
$ bin/rails db:migrate VERSION=xxxxxx
# バージョンを1つ戻す.
$ bin/rails db:rollback
# 指定したステップ数だけバージョンを戻す.
$ bin/rails db:rollback STEP=2
# バージョンを1つ戻してから1つ上げる.
$ bin/rails db:migrate:redo
マイグレーションに失敗した場合、適用しかけた変更はロールバックされ、元の状態に戻される.
■ データ内容の制限.
Taskテーブルのname(名前)にNOT NULL制約を付与.
# 雛形ファイルの作成.
$ bin/rails g migration ChangeTasksNameNotNull
class ChangeTasksNameNotNull < ActiveRecord::Migration[5.2]
def change
change_column_null :tasks, :name, false
end
end
NOT NULL制約が付与されたかirbで確認.
$ bin/rails c
# nameがnilのTaskオブジェクトを作成し、DB登録を試してみる.
$ Task.new(name: nil).save
[ 実行結果 ]
ActiveRecord::NotNullViolation (PG::NotNullViolation: ERROR: null value in column "name" violates not-null constraint)
DETAIL: Failing row contains (6, null, null, 2019-12-15 13:13:22.582812, 2019-12-15 13:13:22.582812).
: INSERT INTO "tasks" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"
Taskテーブルのname(名前)カラムを30文字以内に制限するマイグレーション実行.
$ bin/rails g migration ChangeTasksNameLimit30
class ChangeTasksNameLimit30 < ActiveRecord::Migration[5.2]
def up
change_column :tasks, :name, :string, limit: 30
end
def down
change_column :tasks, :name, :string
end
end
Taskテーブルのname(名前)カラムをユニークに制限するマイグレーション実行.
$ bin/rails g migration AddNameIndexToTasks
class AddNameIndexToTasks < ActiveRecord::Migration[5.2]
def change
add_index :tasks, :name, unique: true
end
end
■ モデルの検証.
Railsにおけるモデル検証では「モデルオブジェクト(レコード)をデータベースに登録・更新する前に検証を行い、エラーがあれば登録・更新をしないで差し戻す」という仕組みとなっている.
# データベース登録・更新時に自動的に検証を行い、エラーがあればfalse返却.
task.save
# データベース登録・更新時に自動的に検証を行い、エラーがあれば例外生成.
task.save!
# validate: falseで検証スキップも可能.
task.save(validate: false)
# 検証処理を単体実行する場合に利用.
task.valid?
データの検証では以下3つの実装を追加.
Taskモデルのname属性に必須チェック付与.
Taskモデルのname属性に文字列長制限付与.
オリジナル検証コード実装.
① Taskモデルのname属性に必須チェック付与.
class Task < ApplicationRecord
validates: name, presence: true
end
# [ 実行結果 ]
# irb(main):001:0> task = Task.new
# => #<Task id: nil, name: nil, description: nil, created_at: nil, updated_at: nil>
# irb(main):002:0> task.save
# (0.2ms) BEGIN
# (0.2ms) ROLLBACK
# => false
# [ 実行結果 ]
# task.errors.full_messages
# => ["名称を入力してください"]
Taskモデルでの検証結果を元に、コントローラとビュー側に制御ロジックを追加.
def create
@task = Task.new(task_params) # Taskモデルのnameに値が未設定の場合はfalse返却.
if @task.save
redirect_to @task, notice: "タスク「#{@task.name}」を登録しました。"
else
render :new # 検証エラーの場合は登録用フォームを再表示して再入力を促す.
end
end
- if task.errors.present?
ul#error_explanation
- task.errors.full_messages.each do |message|
li= message
② Taskモデルのname属性に文字列長制限付与.
class Task < ApplicationRecord
validates :name, presence: true
validates :name, length: { maximum: 30 }
end
③ オリジナル検証コード実装.
# オリジナル検証コードの書き方は以下の2パターン.
# 1. 検証を行うメソッドを追加して、そのメソッドを検証用のメソッドとして指定.
# 2. 自前のValidatorを作って利用.
class Task < ApplicationRecord
validate :validate_name_not_including_comma
private
# ぼっち演算子(&.):レシーバがnilであった場合でもエラーが発生しない.
# object = nil
# object.name [実行結果] NoMethodError ※RailsがException生成.
# object&.name [実行結果] nil
def validate_name_not_including_comma
# 「,(カンマ)」が含まれるかはinclude?を利用.
errors.add(:name, 'にカンマを含めることはできません') if name&.include?(',')
end
end
■ コールバック.
コールバックはモデル内で登録・更新など重要なイベント前後に任意の処理を挟む仕組みのこと.
今回はTaskモデルのname属性に値が未設定の場合は「名前なし」の値を自動的に設定.
class Task < ApplicationRecord
before_validation :set_nameless_name
...
private
def set_nameless_name
self.name = '名前なし' if name.blank?
end
end
■ 参考文献.
現場で使える Ruby on Rails 5速習実践ガイド.