오늘 포스트에서는 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
테이블.
이 마이그레이션이 rails db:migrate
를 사용하여 실행될 때 명령을 실행하면 events
가 생성됩니다. category
가 있는 테이블 string
유형의 열 및 타임스탬프 열 created_at
및 updated_at
.
실제 데이터베이스 열 유형은 데이터베이스에 따라 varchar 또는 text입니다.
마이그레이션 타임스탬프 및 schema_migration 테이블의 중요성
rails g migration
을 사용하여 마이그레이션이 생성될 때마다 명령을 실행하면 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
에 항목이 있는지 확인합니다. 마이그레이션 파일의 타임스탬프가 있는 테이블을 만들고 해당 항목이 없는 경우에만 실행합니다. 이렇게 하면 시간이 지남에 따라 데이터베이스에 변경 사항을 점진적으로 추가할 수 있고 마이그레이션은 데이터베이스에서 한 번만 실행됩니다.
데이터베이스 스키마
더 많은 마이그레이션을 실행함에 따라 데이터베이스 스키마는 계속 발전합니다. Rails는 가장 최근의 데이터베이스 스키마를 db/schema.rb
파일에 저장합니다. . 이 파일은 애플리케이션 수명 동안 데이터베이스에서 실행되는 모든 마이그레이션의 Ruby 표현입니다. 이 파일 때문에 코드베이스에 오래된 마이그레이션 파일을 보관할 필요가 없습니다. Rails는 dump
작업을 제공합니다. 데이터베이스에서 schema.rb
로의 최신 스키마 및 load
schema.rb
에서 데이터베이스로 스키마 . 따라서 이전 마이그레이션은 코드베이스에서 안전하게 삭제할 수 있습니다. 데이터베이스에 스키마를 로드하는 것도 애플리케이션을 설정할 때마다 마이그레이션을 실행할 때보다 빠릅니다.
Rails는 데이터베이스 스키마를 SQL 형식으로 저장하는 방법도 제공합니다. 두 형식을 비교하는 기사가 이미 있습니다. 여기에서 자세한 내용을 읽을 수 있습니다.
마이그레이션의 Rails 버전
우리가 생성하는 모든 마이그레이션에는 수퍼클래스의 일부로 Rails 버전이 있습니다. 따라서 Rails 6 앱에서 생성된 마이그레이션에는 수퍼클래스 ActiveRecord::Migration[6.0]
가 있습니다. Rails 5.2 앱에서 생성된 마이그레이션에는 ActiveRecord::Migration[5.2]
슈퍼클래스가 있습니다. . Rails 4.2 이하의 오래된 앱이 있는 경우 수퍼클래스에 버전이 없음을 알 수 있습니다. 슈퍼클래스는 ActiveRecord::Migration
입니다. .
Rails 버전은 Rails 5의 마이그레이션 슈퍼클래스에 추가되었습니다. 이는 기본적으로 이전 버전의 Rails에서 생성된 마이그레이션을 중단하지 않고 마이그레이션 API가 시간이 지남에 따라 발전할 수 있도록 합니다.
events
생성을 위한 동일한 마이그레이션을 살펴봄으로써 이에 대해 더 자세히 살펴보겠습니다. Rails 4.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
method는 마이그레이션의 기본 방법입니다. 마이그레이션이 실행되면 change
메소드를 실행하고 그 안의 코드를 실행합니다.
create_table
과 함께 , Rails는 또 다른 강력한 방법인 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는 트랜잭션 내에서 eachmigration을 실행하여 이 문제를 해결합니다. 마이그레이션이 실패하면 트랜잭션이 롤백됩니다. 이렇게 하면 데이터베이스가 일관성 없는 상태가 되지 않습니다.
<블록 인용>이것은 데이터베이스 스키마 업데이트를 위한 트랜잭션을 지원하는 데이터베이스에 대해서만 수행됩니다. 이를 데이터 정의 언어(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
를 사용하는 대신 메소드 method.The 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
를 변경합니다. events
열 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
revert
이 메서드는 부분적으로 마이그레이션을 되돌리는 블록도 허용합니다.
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
이 구성은 두 개의 데이터베이스(primary
)를 사용하기를 원한다는 것을 Rails에 알려줍니다. 및 analytics
.앞서 보았듯이 마이그레이션은 db/migrate
에 저장됩니다. 기본적으로 디렉토리. 그러나 이 경우 단일 디렉토리 내에 두 데이터베이스의 마이그레이션을 추가할 수 없습니다. analytics
의 마이그레이션을 실행하고 싶지 않습니다. primary
의 데이터베이스 데이터베이스와 그 반대의 경우도 마찬가지입니다. 여러 데이터베이스를 사용하는 경우 두 번째 데이터베이스에 대한 마이그레이션을 저장하기 위한 경로를 제공하는 데 필요한 마모입니다. 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
만 실행하는 경우 , 모든 데이터베이스에 대해 마이그레이션을 실행합니다.
analytics
데이터베이스에는 자체 schema_migrations
가 있습니다. 어떤 마이그레이션이 실행되고 어떤 마이그레이션이 실행되지 않는지 추적하는 테이블입니다.
배포 중 마이그레이션 실행
마이그레이션은 데이터베이스의 상태를 변경할 수 있고 우리의 코드는 이러한 변경 사항에 따라 달라질 수 있으므로 새 코드를 적용하기 전에 마이그레이션을 먼저 실행하는 것이 매우 중요합니다.
Heroku 기반 배포에서 마이그레이션은 release
에서 실행할 수 있습니다. Procfile
단계 .
# Profile
web: bin/puma -C config/puma.rb
release: bundle exec rake db:migrate
이렇게 하면 앱 dyno가 다시 시작되기 전에 마이그레이션이 실행됩니다.
Capistrano 기반 배포에서는 서버가 다시 시작되기 전에 마이그레이션을 실행해야 합니다.
도커 기반 배포에서는 사이드카 컨테이너를 실행하여 앱이 다시 시작되기 전에 먼저 마이그레이션을 실행할 수 있습니다. 그렇지 않으면 새 코드에 대한 데이터베이스 변경 사항을 적용하기 전에 새 코드를 사용하기 시작하면 새 컨테이너가 일관성 없는 상태가 될 수 있으므로 이는 매우 중요합니다.
결론
이 게시물에서 우리는 Rails에서 데이터베이스 마이그레이션을 작성하는 다양한 측면을 보았습니다. 또한 마이그레이션을 구성하는 요소와 오류를 처리하고 필요한 경우 마이그레이션을 롤백하는 방법도 살펴보았습니다. Rails 6을 사용하면 여러 데이터베이스를 사용할 수 있으며 각각에 대한 마이그레이션을 별도로 추가해야 합니다. 마지막으로 새 코드가 사용하기 전에 데이터베이스 변경 사항이 올바르게 적용되도록 배포하는 동안 마이그레이션을 실행하는 방법을 간략하게 살펴보았습니다.
추신 Ruby Magic 게시물이 언론에 공개되는 즉시 읽고 싶다면 Ruby Magic 뉴스레터를 구독하고 게시물을 놓치지 마세요!