Hit the books!!

プログラミング学習記録

マイグレーションとシード機能

前回の続きで、モデルのより詳しい話。

ud-ike.hatenablog.com

参考

モデルの生成

モデルの作成手順

  1. モデルを生成する
  2. マイグレーションを行いモデルに対応するテーブルを作成する
  3. モデルの基本の検証を行う
  4. 動作検証に基づいて必要な機能を追加する

モデル生成コマンド

書式:$ rails g model モデル名 [モデル属性] [オプション] ...

モデル属性の書式:属性名[:データ型][:オプション]

モデル名は小文字単数形で、先頭が大文字のキャメル形式。

図書アプリに対して、以下の属性を持つUserモデルを生成・追加してみる。

  • 名前(name)
  • 住所(address)
  • メールアドレス(email)
  • 誕生日(birthday)
% bin/rails g model user name:string address:string email:string birthday:date
Running via Spring preloader in process 2111
      invoke  active_record
      create    db/migrate/20201012031057_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml

マイグレーションファイル

db/migrateディレクトリに生成された「数字14桁_create_users.rb」という名前のファイルがマイグレーションファイル。

# db/migrate/20201012031057_create_users.rb

class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :address
      t.string :email
      t.date :birthday

      t.timestamps
    end
  end
end

t.timestampsという属性は、登録日(created_at)と更新日(updated_at)を生成する属性である。

rails db:migrateの実行

rails db:migrateを実行することで、このマイグレーションファイルに対応するusersテーブルを生成することができる。

% bin/rails db:migrate
== 20201012031057 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0026s
== 20201012031057 CreateUsers: migrated (0.0028s) =============================

マイグレーション

マイグレーションとは

Railsマイグレーションとは、マイグレーションファイルを使ってRailsとは別の世界にあるデータベースを作成・更新するための作業である。

本来、データベースは十分理解した上で作成するべきであるが、Railsではマイグレーション機能によって、データベースの深い知識がなくても簡単に生成することができる。また、マイグレーションファイルを使うと、データベースの種類に依存せずにテーブルやスキーマなどを簡単に管理できる。

マイグレーションのコマンド

コマンド 内容
rails db:migrate 未反映のマイグレーションファイルをもとにデータベースおよびschema.rbを更新する。オプションを指定して開発環境以外でも実行できる。バージョン指定も可
rails db:version 現在の実行済のマイグレーションのバージョンを表示する
rails db:migrate:status マイグレーションの実行状況を一覧で表示する
rails db:migrate:reset データベースやスキーマを一度削除し、すべてのマイグレーションを再実行する
rails db:setup データベースの作成(db:create)、スキーマからのテーブル作成(db:schema:load)、初期データの登録(db:seed)を一連の作業として行う
rails db:reset データベースの削除(db:drop)、データベースの再作成(db:setup)を一連の作業として行う
rails db:rollback [STEP=戻す数] マイグレーションを1つ前のバージョンの状態に戻す(ロールバック)。データは削除される。STEPパラメータを指定すると、指定した数だけロールバックできる
rails db:migrate:redo [STEP=戻す数] ロールバックと再マイグレーションを一度に実行する。STEPパラメータで指定した数だけ戻して再実行する。戻したテーブルは再構成されるため初期状態になる
rails db:schema:load 現在のスキーマファイルからデータベースを作成する。データベースは削除され作り直される
rails db:schema:dump 現在のデータベースからスキーマを作成する
rails db:drop 現在のデータベースをすべて削除する

マイグレーション名の付け方

モデルに必要になった属性を既存のテーブルに追加するなど、例外的な対応を行う場合には独自にマイグレーションファイルを作成する必要がある。

マイグレーション名は任意につけることが可能だが、以下の例のようにRailsの標準的な規約に従ってつけよう。

  • テーブルを新規に作成する:create_テーブル名
  • 既存テーブルに新しいカラムを追加する:addカラム名to_テーブル名
  • 既存テーブルからカラムを削除する:removeカラム名from_テーブル名
  • 多対多の関係の仲介テーブル「(has_belongs_to_many)用のテーブル」を作成する:create_join_tableモデル名モデル名

新規テーブルを作成する

特に支障がない限りは、create_テーブル名を使用せずにモデルから生成するほうがおすすめ。

例えば、Foodモデルを生成する。

% bin/rails g model food name:string
Running via Spring preloader in process 2924
      invoke  active_record
      create    db/migrate/20201012044713_create_foods.rb
      create    app/models/food.rb
      invoke    test_unit
      create      test/models/food_test.rb
      create      test/fixtures/foods.yml
# db/migrate/20201012044713_create_foods.rb 

class CreateFoods < ActiveRecord::Migration[6.0]
  def change
    create_table :foods do |t|
      t.string :name

      t.timestamps
    end
  end
end

既存テーブルにカラムを追加する

Foodモデル(foodsテーブル)に新しい属性(カラム)を追加する。

% bin/rails g migration add_description_to_foods description:string
Running via Spring preloader in process 3286
      invoke  active_record
      create    db/migrate/20201012045306_add_description_to_foods.rb

生成されたマイグレーションファイルは以下の通り。

# db/migrate/20201012045306_add_description_to_foods.rb

class AddDescriptionToFoods < ActiveRecord::Migration[6.0]
  def change
    add_column :foods, :description, :string
  end
end

カラム属性を変更する

Foodモデルに追加したdescriptionを複数行の文字列(text)に対応させるようにする。

変更するための厳密な名前ルールはないが、次のような名前でマイグレーションファイルを生成することにする。

% bin/rails g migration change_datatype_description_of_foods
Running via Spring preloader in process 3339
      invoke  active_record
      create    db/migrate/20201012045854_change_datatype_description_of_foods.rb

以下のようなマイグレーションファイルが生成されるため、自分で記述する必要がある。

# db/migrate/20201012045854_change_datatype_description_of_foods.rb

class ChangeDatatypeDescriptionOfFoods < ActiveRecord::Migration[6.0]
  def change
  end
end

change_columnメソッドを使用し、以下のように書き換える。

class ChangeDatatypeDescriptionOfFoods < ActiveRecord::Migration[6.0]
  def change
    change_column :foods, :description, :text, default: "食事の内容"
  end
end

書式:change_column :テーブル名, :カラム名, :データ型[, オプション]

descriptionの属性タイプをtextとし、初期値として"食事の内容"を設定している。

カラムを差し替える

カラム属性を変更する場合、古いカラムを削除して新しいカラムを追加する方法もある。

% bin/rails g migration change_attributes_of_foods
Running via Spring preloader in process 3760
      invoke  active_record
      create    db/migrate/20201012055302_change_attributes_of_foods.rb
# db/migrate/20201012055302_change_attributes_of_foods.rb

class ChangeAttributesOfFoods < ActiveRecord::Migration[6.0]
  def up
    change_table :foods do |t|
      t.change :name, :string, default: "食事の名前"
    end
  end

  def down
    change_table :foods do |t|
      t.change :name, :string
    end
  end
end

nameの初期値に"食事の名前"を設定している。

既存のカラム属性をインデックスにする

% bin/rails g migration add_index_email_to_users
Running via Spring preloader in process 3953
      invoke  active_record
      create    db/migrate/20201012061555_add_index_email_to_users.rb
# db/migrate/20201012061555_add_index_email_to_users.rb

class AddIndexEmailToUsers < ActiveRecord::Migration[6.0]
  def change
    add_index :users, :email, unique: true
  end
end

マイグレーションを実施すると、usersテーブルのemail属性に基づくユニークなインデックスが生成される。

スキーマ

マイグレーションを実行することで、データベースのテーブル、インデックスの生成、テーブル内のカラム追加などが行われ、最新のデータベースに基づいてスキーマファイルというファイルが作成・更新される。

スキーマファイル(schema.rb)

スキーマファイルには最新のデータベースのテーブル構造などを反映した情報が記載されている。

# db/schema.rb

ActiveRecord::Schema.define(version: 2020_10_12_055302) do

  create_table "foods", force: :cascade do |t|
    t.string "name", default: "食事の名前"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.text "description", default: "食事の内容"
  end
  
end

なお、idはスキーマ内には記載がないが、実際のテーブルには追加されている。

シード機能

マイグレーションによってテーブルを作成してもテーブルにはまだ何も登録されていない。アプリケーションを確認するため、テーブルにデータリソースを登録する必要があるとき、シード機能を使ってあらかじめテーブルに入れておきたい固有情報を登録することができる。

# db/seeds.rb 

Food.create(name: "ラーメン", description: "キングオブ麺")
Food.create(name: "カレー", description: "スリランカカレー食べたい")

createメソッドを使って2件のデータを登録。

シード機能を実行し、結果をRailsコンソールで確認すると、

% bin/rails db:seed
irb(main):001:0> Food.all
   (0.5ms)  SELECT sqlite_version(*)
  Food Load (0.2ms)  SELECT "foods".* FROM "foods" LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Food id: 1, name: "ラーメン", created_at: "2020-10-12 06:44:18", updated_at: "2020-10-12 06:44:18", description: "キングオブ麺">, #<Food id: 2, name: "カレー", created_at: "2020-10-12 06:44:18", updated_at: "2020-10-12 06:44:18", description: "スリランカカレー食べたい">]>
irb(main):002:0> Food.count
   (0.2ms)  SELECT COUNT(*) FROM "foods"
=> 2

2件のデータが呼ばれていることがわかる。

例えば以下のようにseed.rbを記述すれば、100件のデータを簡単に入力することができる。

...(省略)
100.times do |n|
  Food.create!(
    name: "寿司#{n}",
    description: "寿司#{n}は日本文化です",
  )
end