Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Railsの移行を分析する

今日の投稿では、Railsの移行について詳しく説明します。移行をさまざまな部分に分解し、その過程で、効果的な移行を作成する方法を学びます。複数のデータベースの移行を作成する方法、失敗した移行を処理する方法、およびロールバックを実行する手法について学習します。

投稿全体を理解するには、データベースとRailsの基本を理解している必要があります。

移行101

Railsでの移行により、アプリケーションの存続期間にわたってデータベースを進化させることができます。移行により、エレガントなDSLを提供することにより、データベースの状態を変更するためのプレーンなRubyコードを記述できます。移行により、データベースを操作し、DSLをデータベース固有のSQLクエリに変換する際の詳細を処理するための抽象化が提供されるため、データベース固有のSQLを作成する必要はありません。移行も邪魔にならず、そのような必要が生じた場合、データベースで生のSQLを実行する方法を提供します。

2万リーグからRailsデータベースへの移行

移行を使用して、テーブルの作成、列の追加または削除、列へのインデックスの追加を行うことができます。

すべてのRailsアプリには、 db / migrateという特別なディレクトリがあります。 —すべての移行が保存される場所。

テーブルeventsを作成する移行から始めましょう データベースに追加します。

$ rails g migration CreateEvents category:string

このコマンドは、タイムスタンプ付きのファイル 20200405103635_create_events.rbを生成します db / migrate内 ディレクトリ。ファイルの内容は次のとおりです。

class CreateEvents < ActiveRecord::Migration[6.0]
  def change
    create_table :events do |t|
      t.string :category
 
      t.timestamps
    end
  end
end

この移行ファイルを分解してみましょう。

  • Railsが生成するすべての移行ファイルには、ファイル名に存在するタイムスタンプがあります。このタイムスタンプは重要であり、後で説明するように、移行が実行されたかどうかを確認するためにRailsによって使用されます。
  • 移行には、 ActiveRecord ::Migration [6.0]から継承するクラスが含まれています 。 Rails 6を使用しているため、移行スーパークラスには [6.0]があります。 。 Rails 5.2を使用していた場合、スーパークラスは ActiveRecord ::Migration [5.2]になります。 。後で、Railsバージョンがスーパークラス名の一部である理由について説明します。
  • 移行にはchangeメソッドがあります これには、データベースを操作するDSLコードが含まれています。この場合、 change メソッドはeventsを作成しています 列がcategoryのテーブル タイプstring
  • 移行ではコードt.timestampsを使用します タイムスタンプを追加するにはcreated_at およびupdated_at eventsへ テーブル。

この移行がrailsdb:migrateを使用して実行される場合 コマンドを実行すると、 eventsが作成されます カテゴリのテーブル タイプstringの列 およびタイムスタンプ列created_at およびupdated_at

実際のデータベースの列タイプは、データベースに応じてvarcharまたはtextになります。

移行タイムスタンプとschema_migrationテーブルの重要性

rails gmigrationを使用して移行が生成されるたび コマンドを実行すると、Railsは一意のタイムスタンプを持つ移行ファイルを生成します。タイムスタンプはYYYYMMDDHHMMSSの形式です。 。移行が実行されるたびに、Railsは移行タイムスタンプを内部テーブル schema_migrationsに挿入します 。このテーブルは、最初の移行を実行するときにRailsによって作成されます。テーブルにはversion列のみがあります 、これは主キーでもあります。これはschema_migrationsの構造です テーブル。

CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY);

これで、 eventsを作成するための移行が実行されました。 表では、Railsがこの移行のタイムスタンプを schema_migrationsに保存しているかどうかを確認しましょう。 テーブル。

sqlite> select * from schema_migrations;
20200405103635

移行を再度実行すると、Railsは最初に schema_migrationsにエントリが存在するかどうかを確認します。 移行ファイルのタイムスタンプを含むテーブル。そのようなエントリがない場合にのみ実行します。これにより、時間の経過とともにデータベースに変更を段階的に追加でき、移行はデータベースで1回だけ実行されます。

データベーススキーマ

ますます多くの移行を実行するにつれて、データベーススキーマは進化し続けます。 Railsは、最新のデータベーススキーマをファイル db / schema.rbに保存します。 。このファイルは、アプリケーションの存続期間中にデータベースで実行されるすべての移行のRuby表現です。このファイルがあるため、古い移行ファイルをコードベースに保持する必要はありません。 Railsはdumpするタスクを提供します データベースからschema.rbへの最新のスキーマ およびload schema.rbからデータベースへのスキーマ 。そのため、古い移行をコードベースから安全に削除できます。また、データベースへのスキーマのロードは、アプリケーションをセットアップするたびにすべての移行を実行する場合に比べて高速です。

Railsは、データベーススキーマをSQL形式で保存する方法も提供します。 2つの形式を比較する記事がすでにあります。詳細については、こちらをご覧ください。

移行中のRailsバージョン

生成するすべての移行には、スーパークラスの一部としてRailsバージョンがあります。したがって、Rails 6アプリによって生成される移行には、スーパークラス ActiveRecord ::Migration [6.0]があります。 一方、Rails 5.2アプリによって生成された移行には、スーパークラス ActiveRecord ::Migration [5.2]があります。 。 Rails 4.2以下の古いアプリをお持ちの場合は、スーパークラスにバージョンがないことに気付くでしょう。スーパークラスは単なるActiveRecord::Migration

RailsバージョンがRails5の移行スーパークラスに追加されました。これにより、基本的に、古いバージョンのRailsによって生成された移行を中断することなく、migrationAPIを時間の経過とともに進化させることができます。

events を作成するための同じ移行を見て、これをさらに詳しく見ていきましょう。 Rails4.2アプリのテーブル。

class CreateEvents < ActiveRecord::Migration
  def change
    create_table :events do |t|
      t.string :category
 
      t.timestamps null: false
    end
  end
end

eventsのスキーマを見ると Rails 6の移行によって生成されたテーブルでは、 NOT NULLであることがわかります。 タイムスタンプ列に制約があります。

sqlite> .schema events
CREATE TABLE IF NOT EXISTS "events" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "category" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);

これは、Rails 5以降、移行APIが自動的に NOT NULLを追加するためです。 移行ファイルに明示的に追加する必要のないタイムスタンプ列への制約。スーパークラス名のRailsバージョンは、移行が生成されたRailsバージョンの移行APIを使用することを保証します。これにより、Railsは古い移行との下位互換性を維持すると同時に、移行APIを進化させることができます。

データベーススキーマの変更

変更 メソッドは、移行の主要なメソッドです。移行が実行されると、 changeが呼び出されます。 メソッドを実行し、その中のコードを実行します。

create_tableと一緒に 、Railsは、もう1つの強力なメソッド change_tableも提供します。 。名前が示すように、既存のテーブルのスキーマを変更するために使用されます。

def change
  change_table :events do |t|
    t.remove :category
    t.string :event_type
    t.boolean :active, default: false
  end
end

この移行により、 categoryが削除されます eventsの列 テーブルに、新しい文字列列 events_typeを追加します 新しいブール列active デフォルト値はfalseです 。

Railsは、次のような移行内で使用できる他の多くのヘルパーメソッドも提供します。

  • change_column
  • add_index
  • remove_index
  • rename_table

などなど。変更で使用できるすべての方法はここにあります

タイムスタンプ

t.timestampsを見ました Railsによる移行に追加され、列 created_atが追加されました およびupdated_at eventsへ テーブル。これらの特別な列は、レコードがいつ作成および更新されるかを追跡するためにRailsによって使用されます。Railsは、レコードが作成されるときにこれらの列に値を追加し、レコードが更新されるときに必ず更新します。これらの列は、データベースレコードの存続期間を追跡するのに役立ちます。

updated_at updated_all を実行しても、列は更新されません Railsのメソッド。

処理の失敗

移行は防弾ではありません。彼らは失敗する可能性があります。理由は、構文が間違っているか、データベースクエリが無効である可能性があります。理由が何であれ、データベースが不整合な状態にならないように、障害を処理して回復する必要があります。 Railsは、トランザクション内で各移行を実行することにより、この問題を解決します。移行が失敗した場合、トランザクションはロールバックされます。これにより、データベースが不整合な状態にならないようにします。

これは、データベーススキーマを更新するためのトランザクションをサポートするデータベースに対してのみ実行されます。これらは、データ定義言語(DDL)トランザクションとして知られています。 MySQLとPostgreSQLはどちらもDDLトランザクションをサポートしています。

トランザクション内で特定の移行を実行したくない場合があります。簡単な例は、PostgreSQLで同時インデックスを追加する場合です。このような移行は、PostgreSQLがテーブルのロックを取得せずにインデックスを追加しようとするため、DDLトランザクション内で実行できないため、データベースを停止せずに本番データベースにインデックスを追加できます。 Railsは、 disable_ddl_transactions!の形式で、移行内のトランザクションをオプトアウトする方法を提供します。 。

def change
  disable_ddl_transactions!
 
  add_index :events, :user_id, algorithm: :concurrently

これにより、トランザクション内での移行は実行されません。このような移行が失敗した場合は、自分で回復する必要があります。この場合、 REINDEX のいずれかを実行できます または、インデックスを削除して、もう一度追加してみてください。

リバーシブルマイグレーション

Railsを使用すると、次のコマンドを使用してデータベースへの変更をロールバックできます。

rails db:rollback

このコマンドは、データベースで実行された最後の移行を元に戻します。移行によって列event_typeが追加された場合 その後、ロールバックによってその列が削除されます。移行によってインデックスが追加された場合、ロールバックによってそのインデックスが削除されます。

以前の移行をロールバックして実行するためのコマンドもあります。 rails db:redoです 。

Railsは、ほとんどの移行を元に戻す方法を知っているほど賢いです。ただし、 up を提供することで、移行を元に戻す方法のヒントをRailsonに提供することもできます。 およびdown changeを使用する代わりにメソッド メソッド。up down に対して、移行が実行されるときにメソッドが使用されます 移行がロールバックされるときにメソッドが使用されます。

def up
  change_table :events do |t|
    t.change :price, :string
  end
end
 
def down
  change_table :events do |t|
    t.change :price, :integer
  end
end

この例では、 priceを変更しています イベントの列 integerから stringへ 。 downでロールバックする方法を指定します メソッド。

これと同じ移行は、 changeを使用して作成することもできます。 メソッド。

def change
  reversible do |direction|
    change_table :events do |t|
      direction.up { t.change :price, :string }
      direction.down { t.change :price, :integer }
    end
  end
end

Railsは、 revertを使用して以前の移行を完全に元に戻す方法も提供します メソッド。

def change
  revert CreateEvents
 
  create_table :events do
   ...
  end
end

元に戻す メソッドは、移行を部分的に元に戻すためのブロックも受け入れます。

def change
  revert do
    reversible do |direction|
      change_table :events do |t|
        direction.up { t.remove :event_type }
        direction.down { t.string :event_type }
      end
    end
  end
end

生で実行する

移行内で複雑なSQLを実行したい場合があります。このような場合、通常の移行DSLを忘れて、代わりに次のように生のSQLを実行できます。

def change
  execute <<-SQL
    ....
  SQL
end

複数のデータベースと移行

Rails 6では、単一のRailsアプリケーション内で複数のデータベースを使用するためのサポートが追加されました。複数のデータベースを使用する場合は、 database.ymlで構成します。 ファイル。

development:
  primary:
    <<: *default
    database: db/development.sqlite3
  analytics:
    adapter: sqlite3
    database: db/analytics_dev.sqlite3

この構成は、2つのデータベース( primary )を使用することをRailsに通知します。 およびanalytics 。前に見たように、移行は db / migrateに保存されます デフォルトではディレクトリ。ただし、この場合、単一のディレクトリ内に両方のデータベースの移行を追加することはできません。 analyticsの移行を実行したくない primaryのデータベース データベースおよびその逆。複数のデータベースを使用している場合、weareは2番目のデータベースの移行を保存するためのパスを提供する必要があります。これは、 migrations_pathsを提供することで実行できます。 database.yml内 。

development:
  primary:
    <<: *default
    database: db/development.sqlite3
  analytics:
    adapter: sqlite3
    database: db/analytics_dev.sqlite3
    migrations_paths: db/analytics_migrate

次に、 analyticsの移行を作成できます 次のようなデータベース。

rails generate migration AddExperiments rule:string active:boolean --db=analytics

これにより、 db / analytics_migrate内に移行が作成されます 、次のように実行できます。

rails db:migrate --db=analytics

rails db:migrateのみを実行する場合 、すべてのデータベースの移行を実行します。

分析 データベースには独自のschema_migrationsがあります 実行されている移行と実行されていない移行を追跡するためのテーブル。

展開中に移行を実行する

移行によってデータベースの状態が変わる可能性があり、コードはそれらの変更に依存する可能性があるため、新しいコードを適用する前に、最初に移行を実行することが非常に重要です。

Herokuベースのデプロイメントでは、移行は releaseで実行できます。 Procfileのフェーズ 。

# Profile
web: bin/puma -C config/puma.rb
release: bundle exec rake db:migrate

これにより、アプリdynoが再起動される前に移行が確実に実行されます。

Capistranoベースの展開では、サーバーを再起動する前に移行を実行する必要があります。

Dockerベースのデプロイでは、サイドカーコンテナーを実行して、アプリを再起動する前に最初に移行を実行できます。これは非常に重要です。そうしないと、新しいコードにデータベースの変更を適用する前に新しいコードの使用を開始すると、新しいコンテナが一貫性のない状態になる可能性があります。

結論

この投稿では、Railsでデータベース移行を作成する際のさまざまな側面について説明しました。また、移行の構成要素と、障害を処理し、必要に応じて移行をロールバックする方法についても説明しました。 Rails 6では複数のデータベースを使用でき、それぞれの移行を個別に追加する必要があります。最後に、新しいコードがデータベースの変更を使用し始める前にデータベースの変更が適切に適用されるように、展開中に移行を実行する方法を簡単に説明しました。

P.S。 Ruby Magicの投稿をマスコミから離れたらすぐに読みたい場合は、Ruby Magicニュースレターを購読して、投稿を1つも見逃さないでください。


  1. React on Rails:シンプルなアプリの構築

    アプリケーションのフロントエンド側全体を構築する企業は、多くの場合、バックエンドを構築するためにRailsなどの同じフレームワークを選択します。長年にわたり、これは最良かつ最も信頼できるオプションでした。 今日、絶えず進化するフロントエンドユニバースにある多数のライブラリとフレームワークにより、開発者はバックエンドとフロントエンドの両方に異なるプラットフォームを選択し、それらを簡単に統合できます。 Reactはフロントエンドのパンゲアの巨人になりました。 Ruby on Railsを使用している場合は、デフォルトのRailsページをReactコード(またはその他のフロントフレームワーク)に

  2. Rails5でのAngularの使用

    あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた