액티브 레코드 마이그레이션
마이그레이션은 Active Record의 기능으로, 시간이 지남에 따라 데이터베이스 스키마를 진화시킬 수 있습니다. 순수 SQL로 스키마 수정을 작성하는 대신, 마이그레이션을 통해 Ruby DSL을 사용하여 테이블 변경을 설명할 수 있습니다.
이 가이드를 읽고 나면 다음을 알 수 있습니다:
- 마이그레이션을 생성하는 데 사용할 수 있는 생성기.
- 데이터베이스를 조작하기 위해 Active Record가 제공하는 메서드.
- 마이그레이션과 스키마를 조작하는 Rails 명령어.
- 마이그레이션과
schema.rb
의 관계.
마이그레이션 개요
마이그레이션은 시간이 지남에 따라 데이터베이스 스키마를 일관되게 변경할 수 있는 편리한 방법입니다. 데이터베이스에 독립적인 Ruby DSL을 사용하므로 SQL을 직접 작성할 필요가 없습니다.
각 마이그레이션을 데이터베이스의 새로운 ‘버전'으로 생각할 수 있습니다. 스키마는 처음에는 아무것도 없지만, 각 마이그레이션은 테이블, 열 또는 항목을 추가하거나 제거하여 스키마를 수정합니다. Active Record는 이 타임라인을 따라 스키마를 업데이트하는 방법을 알고 있으며, db/schema.rb
파일을 데이터베이스의 최신 구조와 일치하도록 업데이트합니다.
다음은 마이그레이션의 예:
class CreateProducts < ActiveRecord::Migration[7.2] def change create_table :products do |t| t.string :name t.text :description t.timestamps end end end
이 마이그레이션은 name
이라는 문자열 열과 description
이라는 텍스트 열이 있는 products
테이블을 추가합니다. 기본 키 열인 id
도 암시적으로 추가됩니다. timestamps
매크로는 created_at
과 updated_at
이라는 두 개의 열을 추가합니다. 이러한 특수 열은 Active Record에 의해 자동으로 관리됩니다.
우리는 앞으로 시간이 지남에 따라 변경하고자 하는 내용을 정의합니다. 이 마이그레이션이 실행되기 전에는 테이블이 존재하지 않습니다. 실행된 후에는 테이블이 존재합니다. Active Record는 이 마이그레이션을 되돌릴 수도 있습니다: 이 마이그레이션을 롤백하면 테이블이 제거됩니다.
데이터 정의 언어(DDL) 문을 지원하는 데이터베이스의 경우 각 마이그레이션은 트랜잭션 내에서 실행됩니다. 데이터베이스가 이를 지원하지 않는 경우 마이그레이션이 실패하면 성공한 부분은 롤백되지 않습니다. 수동으로 변경 사항을 되돌려야 합니다.
참고: 일부 쿼리는 트랜잭션 내에서 실행할 수 없습니다. 어댑터가 DDL 트랜잭션을 지원하는 경우 disable_ddl_transaction!
을 사용하여 단일 마이그레이션에 대해 이를 비활성화할 수 있습니다.
되돌릴 수 없는 것을 가능하게 만들기
Active Record가 알 수 없는 방식으로 마이그레이션을 수행하려는 경우 reversible
을 사용할 수 있습니다:
class ChangeProductsPrice < ActiveRecord::Migration[7.2] def change reversible do |direction| change_table :products do |t| direction.up { t.change :price, :string } direction.down { t.change :price, :integer } end end end end
이 마이그레이션은 price
열의 데이터 형식을 문자열로 변경하거나, 마이그레이션이 되돌려질 때 정수로 변경합니다. direction.up
및 direction.down
블록에 각각 전달되는 것에 주목하세요.
또는 up
과 down
대신 change
를 사용할 수 있습니다:
class ChangeProductsPrice < ActiveRecord::Migration[7.2] def up change_table :products do |t| t.change :price, :string end end def down change_table :products do |t| t.change :price, :integer end end end
정보: reversible
에 대해 자세히 알아보세요.
마이그레이션 생성
독립형 마이그레이션 생성
마이그레이션은 db/migrate
디렉토리에 파일로 저장되며, 각 마이그레이션 클래스마다 하나의 파일이 있습니다. 파일 이름은 YYYYMMDDHHMMSS_create_products.rb
형식이며, 즉 마이그레이션을 식별하는 UTC 타임스탬프와 마이그레이션 이름이 언더스코어로 구분됩니다. 마이그레이션 클래스의 이름(CamelCased 버전)은 파일 이름의 후반부와 일치해야 합니다. 예를 들어 20080906120000_create_products.rb
는 CreateProducts
클래스를 정의하고, 20080906120001_add_details_to_products.rb
는 AddDetailsToProducts
를 정의합니다. Rails는 이 타임스탬프를 사용하여 마이그레이션을 실행할 순서를 결정하므로, 다른 애플리케이션에서 마이그레이션을 복사하거나 직접 파일을 생성하는 경우 순서에 유의해야 합니다.
물론 타임스탬프를 계산하는 것은 재미없는 일이므로, Active Record에서는 이를 처리하는 생성기를 제공합니다:
$ bin/rails generate migration AddPartNumberToProducts
이렇게 하면 적절한 이름의 빈 마이그레이션이 생성됩니다:
class AddPartNumberToProducts < ActiveRecord::Migration[7.2] def change end end
이 생성기는 파일 이름에 타임스탬프를 추가하는 것 이상의 기능을 할 수 있습니다. 명명 규칙과 추가(선택 사항) 인수를 기반으로 마이그레이션을 시작할 수 있습니다.
새 열 추가
마이그레이션 이름이 “AddColumnToTable” 또는 “RemoveColumnFromTable” 형식이고 열 이름과 유형 목록이 뒤따르는 경우 적절한 add_column
및 remove_column
문을 포함하는 마이그레이션이 생성됩니다.
$ bin/rails generate migration AddPartNumberToProducts part_number:string
이렇게 하면 다음과 같은 마이그레이션이 생성됩니다:
class AddPartNumberToProducts < ActiveRecord::Migration[7.2] def change add_column :products, :part_number, :string end end
새 열에 인덱스를 추가하려면 다음과 같이 할 수 있습니다.
$ bin/rails generate migration AddPartNumberToProducts part_number:string:index
이렇게 하면 적절한 add_column
및 add_index
문이 생성됩니다:
class AddPartNumberToProducts < ActiveRecord::Migration[7.2] def change add_column :products, :part_number, :string add_index :products, :part_number end end
마법적으로 생성된 열은 하나로 제한되지 않습니다. 예를 들어:
$ bin/rails generate migration AddDetailsToProducts part_number:string price:decimal
이렇게 하면 products
테이블에 두 개의 추가 열을 추가하는 스키마 마이그레이션이 생성됩니다.
class AddDetailsToProducts < ActiveRecord::Migration[7.2] def change add_column :products, :part_number, :string add_column :products, :price, :decimal end end
열 제거
마찬가지로 명령줄에서 열을 제거하는 마이그레이션을 생성할 수 있습니다:
$ bin/rails generate migration RemovePartNumberFromProducts part_number:string
이렇게 하면 적절한 remove_column
문이 생성됩니다:
class RemovePartNumberFromProducts < ActiveRecord::Migration[7.2] def change remove_column :products, :part_number, :string end end
새 테이블 생성
마이그레이션 이름이 “CreateXXX” 형식이고 열 이름과 유형 목록이 뒤따르는 경우 XXX 테이블을 생성하고 나열된 열을 포함하는 마이그레이션이 생성됩니다. 예를 들어:
$ bin/rails generate migration CreateProducts name:string part_number:string
다음과 같이 생성됩니다.
class CreateProducts < ActiveRecord::Migration[7.2] def change create_table :products do |t| t.string :name t.string :part_number t.timestamps end end end
항상 그렇듯이 생성된 것은 시작점일 뿐입니다. db/migrate/YYYYMMDDHHMMSS_create_products.rb
파일을 편집하여 원하는 대로 추가하거나 제거할 수 있습니다.
참조를 사용하여 연관 생성
또한 생성기는 references
(또한 belongs_to
로 사용 가능) 열 유형을 허용합니다. 예를 들어,
$ bin/rails generate migration AddUserRefToProducts user:references
다음과 같은 add_reference
호출을 생성합니다:
class AddUserRefToProducts < ActiveRecord::Migration[7.2] def change add_reference :products, :user, foreign_key: true end end
이 마이그레이션은 user_id
열을 생성합니다. 참조는 열, 인덱스, 외래 키 또는 심지어 다형성 연관 열을 생성하는 간편한 방법입니다.
'JoinTable'이 이름의 일부인 경우 조인 테이블을 생성하는 생성기도 있습니다:
$ bin/rails generate migration CreateJoinTableCustomerProduct customer product
다음과 같은 마이그레이션을 생성합니다:
class CreateJoinTableCustomerProduct < ActiveRecord::Migration[7.2] def change create_join_table :customers, :products do |t| # t.index [:customer_id, :product_id] # t.index [:product_id, :customer_id] end end end
모델 생성기
모델, 리소스 및 스캐폴드 생성기는 새 모델에 대한 적절한 마이그레이션을 생성합니다. 이 마이그레이션에는 관련 테이블을 생성하는 지침이 이미 포함되어 있습니다. 원하는 열을 알려주면 이러한 열을 추가하는 문도 생성됩니다. 예를 들어 다음을 실행하면:
$ bin/rails generate model Product name:string description:text
다음과 같은 마이그레이션이 생성됩니다:
class CreateProducts < ActiveRecord::Migration[7.2] def change create_table :products do |t| t.string :name t.text :description t.timestamps end end end
원하는 만큼마이그레이션 작성하기
마이그레이션 생성기 중 하나를 사용하여 마이그레이션을 만들었다면 이제 작업할 시간입니다!
테이블 생성
create_table
메서드는 가장 기본적이지만 대부분의 경우 모델, 리소스 또는 스캐폴드 생성기를 사용하여 생성됩니다. 일반적인 사용 예는 다음과 같습니다:
create_table :products do |t| t.string :name end
이 메서드는 name
이라는 열이 있는 products
테이블을 생성합니다.
기본적으로 create_table
은 암시적으로 id
라는 기본 키를 생성합니다. :primary_key
옵션을 사용하여 열 이름을 변경할 수 있으며, 복합 기본 키의 경우 :primary_key
배열을 전달할 수 있습니다. 기본 키가 필요 없는 경우 id: false
를 전달할 수 있습니다.
데이터베이스 특정 옵션을 전달해야 하는 경우 :options
옵션에 SQL 조각을 배치할 수 있습니다. 예를 들어:
create_table :products, options: "ENGINE=BLACKHOLE" do |t| t.string :name, null: false end
이렇게 하면 테이블 생성 SQL 문에 ENGINE=BLACKHOLE
이 추가됩니다.
create_table
블록 내에서 생성된 열에 인덱스를 생성할 수 있습니다. index: true
또는 옵션 해시를 :index
옵션에 전달하면 됩니다:
create_table :users do |t| t.string :name, index: true t.string :email, index: { unique: true, name: 'unique_emails' } end
또한 :comment
옵션을 사용하여 데이터베이스 자체에 저장되고 MySQL Workbench 또는 PgAdmin III와 같은 데이터베이스 관리 도구에서 볼 수 있는 테이블에 대한 설명을 지정할 수 있습니다. 대규모 데이터베이스가 있는 애플리케이션의 경우 마이그레이션에 주석을 지정하는 것이 매우 좋습니다. 현재 MySQL 및 PostgreSQL 어댑터만 주석을 지원합니다.
조인 테이블 생성
마이그레이션 메서드 create_join_table
은 HABTM(has and belongs to many) 조인 테이블을 생성합니다. 일반적인 사용 예는 다음과 같습니다:
create_join_table :products, :categories
이 마이그레이션은 category_id
와 product_id
라는 두 개의 열이 있는 categories_products
테이블을 생성합니다.
기본적으로 이러한 열의 :null
옵션은 false
로 설정되어 있어, 이 테이블에 레코드를 저장하려면 값을 제공해야 합니다. :column_options
옵션을 지정하여 이를 재정의할 수 있습니다:
create_join_table :products, :categories, column_options: { null: true }
기본적으로 조인 테이블의 이름은 create_join_table
에 제공된 첫 두 인수의 알파벳순 조합에서 파생됩니다.
테이블 이름을 사용자 정의하려면 :table_name
옵션을 제공하세요:
create_join_table :products, :categories, table_name: :categorization
이렇게 하면 조인 테이블 이름이 요청한 대로 categorization
이 됩니다.
또한 create_join_table
은 블록을 허용하며, 여기에서 인덱스(기본적으로 생성되지 않음) 또는 추가 열을 추가할 수 있습니다.
create_join_table :products, :categories do |t| t.index :product_id t.index :category_id end
테이블 변경
기존 테이블을 변경하려면 change_table
을 사용하세요.
create_table
과 유사하게 사용되지만 블록 내에서 반환된 객체에는 특수 기능에 대한 액세스 권한이 있습니다. 예를 들어:
change_table :products do |t| t.remove :description, :name t.string :part_number t.index :part_number t.rename :upccode, :upc_code end
이 마이그레이션은 description
과 name
열을 제거하고, part_number
라는 새 문자열 열을 만들며, 해당 열에 인덱스를 추가합니다. 마지막으로 upccode
열의 이름을 upc_code
로 변경합니다.
열 변경
앞서 다룬 remove_column
및 add_column
메서드와 유사하게 Rails는 change_column
마이그레이션 메서드도 제공합니다.
change_column :products, :part_number, :text
이렇게 하면 products 테이블의 part_number
열이 :text
필드로 변경됩니다.
참고: change_column
명령은 되돌릴 수 없습니다.
앞서 설명한 대로 자체적인 reversible
마이그레이션을 제공해야 합니다.
change_column
외에도 change_column_null
및 change_column_default
메서드를 사용하여 열의 null 제약 조건과 기본값을 특별히 변경할 수 있습니다.
change_column_null :products, :name, false change_column_default :products, :approved, from: true, to: false
이렇게 하면 products의 :name
필드가 NOT NULL
열이 되고 :approved
필드의 기본값이 true에서 false로 변경됩니다. 이러한 변경 사항은 향후 트랜잭션에만 적용되며, 기존 레코드에는 적용되지 않습니다.
null 제약 조건을 true로 설정하면 해당 열이 null 값을 허용하게 되며, 그렇지 않으면 NOT NULL
제약 조건이 적용되어 레코드를 데이터베이스에 저장하려면 값을 전달해야 합니다.
참고: 위의 change_column_default
마이그레이션은 change_column_default :products, :approved, false
와 같이 작성할 수 있지만, 이전 예와 달리 이 마이그레이션은 되돌릴 수 없습니다.
열 수정자
열을 생성하거나 변경할 때 다음과 같은 열 수정자를 적용할 수 있습니다:
comment
열에 대한 주석을 추가합니다.collation
string
또는text
열의 collation을 지정합니다.default
열에 대한 기본값을 설정합니다. 동적 값(날짜 등)을 사용하는 경우 기본값은 마이그레이션이 적용된 첫 번째 날짜에만 계산됩니다.NULL
의 경우nil
을 사용하세요.limit
string
열의 최대 문자 수와text/binary/integer
열의 최대 바이트 수를 설정합니다.null
열에NULL
값을 허용할지 여부를 지정합니다.precision
decimal/numeric/datetime/time
열의 정밀도를 지정합니다.scale
decimal
및numeric
열의 소수점 자릿수를 지정합니다.
참고: add_column
또는 change_column
에는 인덱스를 추가하는 옵션이 없습니다. 별도의 add_index
를 사용해야 합니다.
일부 어댑터는 추가 옵션을 지원할 수 있습니다. 자세한 내용은 어댑터별 API 문서를 참조하세요.
참고: null
과 default
는 마이그레이션을 생성할 때 명령줄에서 지정할 수 없습니다.
참조
add_reference
메서드를 사용하면 하나 이상의 연관 관계를 연결하는 적절한 이름의 열을 생성할 수 있습니다.
add_reference :users, :role
이 마이그레이션은 users 테이블에 role_id
열을 생성합니다. 이 열에 대한 인덱스도 생성되지만, index: false
옵션을 명시적으로 지정하여 비활성화할 수 있습니다.
정보: Active Record 연관에 대한 가이드를 참조하여 자세히 알아보세요.
add_belongs_to
메서드는 add_reference
의 별칭입니다.
add_belongs_to :taggings, :taggable, polymorphic: true
다형성 옵션은 taggings 테이블에 taggable_type
과 taggable_id
라는 두 개의 열을 생성하여 다형성 연관을 사용할 수 있게 합니다.
정보: 다형성 연관에 대해 자세히 알아보세요.
외래 키는 foreign_key
옵션을 사용하여 생성할 수 있습니다.
add_reference :users, :role, foreign_key: true
add_reference
에 대한 자세한 옵션은 API 문서를 참조하세요.
참조는 제거할 수도 있습니다:
remove_reference :products, :user, foreign_key: true, index: false
외래 키
필수는 아니지만 참조 무결성을 보장하기 위해 외래 키 제약 조건을 추가하는 것이 좋습니다.
add_foreign_key :articles, :authors
이 add_foreign_key
호출은 articles
테이블에 새 제약 조건을 추가합니다. 이 제약 조건은 authors
테이블의 id
열과 articles.author_id
가 일치하는 행이 있음을 보장합니다.
from_table
열 이름을 to_table
이름에서 유추할 수 없는 경우 :column
옵션을 사용할 수 있습니다. 참조되는 기본 키가 :id
가 아닌 경우 :primary_key
옵션을 사용하세요.
예를 들어 articles.reviewer
가 authors.email
을 참조하도록 외래 키를 추가하려면 다음과 같이 하세요:
add_foreign_key :articles, :authors, column: :reviewer, primary_key: :email
이렇게 하면 articles.reviewer
필드가 authors
테이블의 email
열과 일치하는 행이 있음을 보장하는 제약 조건이 articles
테이블에 추가됩니다.
name
, on_delete
, if_not_exists
, validate
및 deferrable
과 같은 다른 옵션도 add_foreign_key
에서 지원됩니다.
remove_foreign_key
를 사용하여 외래 키를외래 키도 제거할 수 있습니다:
# Active Record가 열 이름을 파악하도록 합니다 remove_foreign_key :accounts, :branches # 특정 열에 대한 외래 키 제거 remove_foreign_key :accounts, column: :owner_id
참고: Active Record는 단일 열 외래 키만 지원합니다. 복합 외래 키를 사용하려면 execute
및 structure.sql
이 필요합니다. 스키마 덤프 및 당신을 참조하세요.
복합 기본 키
때로는 단일 열의 값만으로는 테이블의 모든 행을 고유하게 식별할 수 없지만, 두 개 이상의 열 조합으로는 고유하게 식별할 수 있습니다. 이는 단일 id
열이 없는 레거시 데이터베이스 스키마를 사용하거나, 샤딩 또는 멀티테넌시를 위해 스키마를 변경할 때 발생할 수 있습니다.
:primary_key
옵션에 배열 값을 전달하여 복합 기본 키가 있는 테이블을 생성할 수 있습니다:
class CreateProducts < ActiveRecord::Migration[7.2] def change create_table :products, primary_key: [:customer_id, :product_sku] do |t| t.integer :customer_id t.string :product_sku t.text :description end end end
정보: 복합 기본 키가 있는 테이블에는 배열 값을 전달해야 하며, 정수 ID가 아닙니다. Active Record 쿼리 가이드에서 자세히 알아보세요.
도우미로는 부족할 때
Active Record가 제공하는 도우미로는 충분하지 않은 경우 execute
를 사용하여 임의의 SQL을 실행할 수 있습니다:
Product.connection.execute("UPDATE products SET price = 'free' WHERE 1=1")
개별 메서드에 대한 자세한 내용과 예제는 API 문서를 참조하세요.
특히 ActiveRecord::ConnectionAdapters::SchemaStatements
의 문서를 참조하세요. 이 문서에는 change
, up
및 down
메서드에서 사용할 수 있는 메서드가 나와 있습니다.
create_table
에서 반환된 객체에 대한 메서드는 ActiveRecord::ConnectionAdapters::TableDefinition
에 나와 있습니다.
그리고 change_table
에서 반환된 객체에 대한 메서드는 ActiveRecord::ConnectionAdapters::Table
에 나와 있습니다.
change
메서드 사용하기
change
메서드는 마이그레이션을 작성하는 주요 방법입니다. Active Record가 마이그레이션 작업을 자동으로 되돌릴 수 있는 대부분의 경우에 작동합니다. 아래는 change
가 지원하는 작업 중 일부입니다:
add_check_constraint
add_column
add_foreign_key
add_index
add_reference
add_timestamps
change_column_comment
(:from
및:to
옵션 제공 필요)change_column_default
(:from
및:to
옵션 제공 필요)change_column_null
change_table_comment
(:from
및:to
옵션 제공 필요)create_join_table
create_table
disable_extension
drop_join_table
drop_table
(테이블 생성 옵션 및 블록 제공 필요)enable_extension
remove_check_constraint
(원래 제약 조건 식 제공 필요)remove_column
(원래 유형 및 열 옵션 제공 필요)remove_columns
(원래 유형 및 열 옵션 제공 필요)remove_foreign_key
(다른 테이블 및 원래 옵션 제공 필요)remove_index
(열 및 원래 옵션 제공 필요)remove_reference
(원래 옵션 제공 필요)remove_timestamps
(원래 옵션 제공 필요)rename_column
rename_index
rename_table
change_table
도 되돌릴 수 있습니다. 단, 블록에서 위에 나열된 되돌릴 수 있는 작업만 호출하는 경우에 한합니다.
다른 메서드를 사용해야 하는 경우 reversible
을 사용하거나 change
메서드 대신 up
과 down
메서드를 작성해야 합니다.
reversible
사용하기
복잡한 마이그레이션의 경우 Active Record가 되돌릴 수 없는 처리가 필요할 수 있습니다. reversible
을 사용하여 마이그레이션을 실행할 때 수행할 작업과 되돌릴 때 수행할 작업을 지정할 수 있습니다. 예를 들어:
class ExampleMigration < ActiveRecord::Migration[7.2] def change create_table :distributors do |t| t.string :zipcode end reversible do |direction| direction.up do # distributors 뷰 생성 execute <<-SQL CREATE VIEW distributors_view AS SELECT id, zipcode FROM distributors; SQL end direction.down do execute <<-SQL DROP VIEW distributors_view; SQL end end add_column :users, :address, :string end end
reversible
을 사용하면 작업 순서도 올바르게 실행됩니다. 이전 예제 마이그레이션을 되돌리면 down
블록이 users.address
열이 제거된 후 distributors
테이블이 삭제되기 전에 실행됩니다.
up
/down
메서드 사용하기
change
메서드 대신 up
과 down
메서드를 사용하는 이전 스타일의 마이그레이션을 사용할 수도 있습니다.
up
메서드는 스키마에 대한 변환을 설명해야 하며, 마이그레이션의 down
메서드는 up
메서드에 의해 수행된 변환을 되돌려야 합니다. 즉, up
과 down
을 차례로 실행하면 데이터베이스 스키마가 변경되지 않아야 합니다.
예를 들어 up
메서드에서 테이블을 생성하는 경우 down
메서드에서 해당 테이블을 삭제해야 합니다. up
메서드에서 수행한 변환을 정확히 역순으로 수행하는 것이 현명합니다. reversible
섹션의 예제와 동일합니다:
class ExampleMigration < ActiveRecord::Migration[7.2] def up create_table :distributors do |t| t.string :zipcode end # distributors 뷰 생성 execute <<-SQL CREATE VIEW distributors_view AS SELECT id, zipcode FROM distributors; SQL add_column :users, :address, :string end def down remove_column :users, :address execute <<-SQL DROP VIEW distributors_view; SQL drop_table :distributors end end
되돌릴 수 없음을 알리기 위해 오류 발생시키기
때로는 마이그레이션이 되돌릴 수 없는 작업을 수행할 수 있습니다. 예를 들어 데이터를 삭제할 수 있습니다.
이러한 경우 down
블록에서 ActiveRecord::IrreversibleMigration
을 발생시킬 수 있습니다.
누군가가 마이그레이션을 되돌리려고 하면 작업을 수행할 수 없다는 오류 메시지가 표시됩니다.
이전 마이그레이션 되돌리기
revert
메서드를 사용하여 Active Record의 마이그레이션 롤백 기능을 활용할 수 있습니다:
require_relative "20121212123456_example_migration" class FixupExampleMigration < ActiveRecord::Migration[7.2] def change revert ExampleMigration create_table(:apples) do |t| t.string :variety end end end
revert
메서드는 지침 블록도 허용합니다. 이를 통해 이전 마이그레이션의 일부만 되돌릴 수 있습니다.
예를 들어 ExampleMigration
이 커밋되었고 Distributors 뷰가 더 이상 필요하지 않다고 결정되었다고 가정해 보겠습니다.
class DontUseDistributorsViewMigration < ActiveRecord::Migration[7.2] def change revert do # ExampleMigration에서 복사한 코드 reversible do |direction| direction.up do # distributors 뷰 생성 execute <<-SQL CREATE VIEW distributors_view AS SELECT id, zipcode FROM distributors; SQL end direction.down do execute <<-SQL DROP VIEW distributors_view; SQL end end # 마이그레이션의 나머지 부분은 괜찮았습니다 end end end
이와 동일한 마이그레이션을 revert
를 사용하지 않고 작성할 수도 있지만, 몇 가지 추가 단계가 필요합니다:
create_table
과reversible
의 순서를 반대로 합니다.create_table
을drop_table
로 바꿉니다.- 마지막으로
up
을down
으로,down
을up
으로 바꿉니다.
revert
는 이 모든 작업을 처리합니다.
마이그레이션 실행
Rails는 특정 마이그레이션 집합을 실행하기 위한 일련의 명령을 제공합니다.
아마도 사용할 첫 번째 Rails 마이그레이션 관련 명령은 bin/rails db:migrate
일 것입니다. 가장 기본적인 형태에서 이 명령은 아직 실행되지 않은 모든 마이그레이션의 change
또는 up
메서드를 실행합니다. 그런 마이그레이션이 없는 경우 종료합니다. 마이그레이션 날짜를 기준으로 순서대로 실행됩니다.
db:migrate
명령을 실행하면 db:schema:dump
명령도 실행되어 db/schema.rb
파일이 데이터베이스 구조와 일치하도록 업데이트됩니다.
대상 버전을 지정하면 Active Record는 지정된 버전(마이그레이션 파일의 숫자 접두사)까지 필요한 마이그레이션(change, up, down)을 실행합니다. 예를 들어 버전 20080906120000으로 마이그레이션하려면 다음을 실행합니다:
$ bin/rails db:migrate VERSION=20080906120000
버전 20080906120000이 현재 버전보다 크면(즉, 위쪽으로 마이그레이션하는 경우) 이 명령은 20080906120000까지의 모든 마이그레이션의 change
(또는 up
) 메서드를 실행하지만 이후의 마이그레이션은 실행하지 않습니다. 아래쪽으로 마이그레이션하는 경우 20080906120000 이전의 모든 마이그레이션의 down
메서드가 실행됩니다.
롤백하기
가장 일반적인 작업 중 하나는 마지막 마이그레이션을 롤백하는 것입니다. 예를 들어 실수를 했고 이를 수정하려는 경우입니다. 이전 마이그레이션의 버전 번호를 추적할 필요 없이 다음을 실행할 수 있습니다:
$ bin/rails db:rollback
이렇게 하면 최신 마이그레이션이 롤백됩니다. change
메서드를 되돌리거나 down
메서드를 실행합니다. 여러 마이그레이션을 되돌리려면 STEP
매개변수를 사용할 수 있습니다:
$ bin/rails db:rollback STEP=3
마지막 3개의 마이그레이션이 되돌려집니다.
db:migrate:redo
명령은 롤백한 다음 다시 마이그레이션하는 단축키입니다. db:rollback
명령과 마찬가지로 한 번에 여러 버전을 되돌리려면 STEP
매개변수를 사용할 수 있습니다:
$ bin/rails db:migrate:redo STEP=3
이러한 Rails 명령은 db:migrate
로 수행할 수 없는 작업을 수행하지 않습니다. 편의를 위해 제공되는 것이므로 명시적으로 마이그레이션 버전을 지정할 필요가 없습니다.
데이터베이스 설정
bin/rails db:setup
명령은 데이터베이스를 생성하고, 스키마를 로드하며, 시드 데이터로 초기화합니다.
데이터베이스 준비
bin/rails db:prepare
명령은 bin/rails db:setup
과 유사하지만 idempotent하게 작동합니다.
- 데이터베이스가 아직 생성되지 않은 경우
bin/rails db:setup
과 동일하게 작동합니다. - 데이터베이스는 있지만 테이블이 생성되지 않은 경우 스키마를 로드하고, 보류 중인 마이그레이션을 실행하고, 업데이트된 스키마를 덤프하고, 마지막으로 시드 데이터를 로드합니다.
- 데이터베이스와 테이블은 있지만 시드 데이터가 로드되지 않은 경우 시드 데이터만 로드합니다.
- 데이터베이스, 테이블 및 시드 데이터가 모두 준비된 경우 아무 작업도 수행하지 않습니다.
참고: 데이터베이스, 테이블 및 시드 데이터가 모두 설정되면 이전에 로드된 시드 데이터나 기존 시드 파일이 변경되거나 삭제되어도 시드 데이터를 다시 로드하지 않습니다. 시드 데이터를 다시 로드하려면 수동으로 bin/rails db:seed
를 실행할 수 있습니다.
데이터베이스 재설정
bin/rails db:reset
명령은 데이터베이스를 삭제하고 다시 설정합니다. 이는 bin/rails db:drop db:setup
과 기능적으로 동일합니다.
참고: 이는 모든 마이그레이션을 실행하는 것과 동일하지 않습니다. 현재 db/schema.rb
또는 db/structure.sql
파일의 내용만 사용합니다.
마이그레이션을 롤백할 수 없는 경우 bin/rails db:reset
이 도움이 되지 않을 수 있습니다. 스키마 덤프에 대해 자세히 알아보려면 스키마 덤프 및 당신 섹션을 참조하세요.
특정 마이그레이션 실행
특정 마이그레이션을 위 또는 아래로 실행해야 하는 경우 db:migrate:up
과 db:migrate:down
명령을 사용할 수 있습니다. 적절한 버전을 지정하면 해당 마이그레이션의 change
, up
또는 down
메서드가 실행됩니다. 예를 들어:
$ bin/rails db:migrate:up VERSION=20080906120000
이 명령을 실행하면 버전 “20080906120000"의 마이그레이션의 change
(또는 up
) 메서드가 실행됩니다.
먼저 이 명령은 마이그레이션이 존재하고 아직 실행되지 않았는지 확인하며, 그렇지 않은 경우 아무 작업도 수행하지 않습니다.
지정된 버전의 마이그레이션이 없는 경우 Rails에서 예외를 throw합니다.
$ bin/rails db:migrate VERSION=zomg rails aborted! ActiveRecord::UnknownMigrationVersionError: No migration with version number zomg.
다른 환경에서 마이그레이션 실행하기
기본적으로 bin/rails db:migrate
를 실행하면 development
환경에서 실행됩니다.
다른 환경의 마이그레이션을 실행하려면 명령을 실행할 때 RAILS_ENV
환경 변수를 사용하여 지정할 수 있습니다. 예를 들어 test
환경의 마이그레이션을 실행하려면 다음을 실행할 수 있습니다:
$ bin/rails db:migrate RAILS_ENV=test
마이그레이션 실행 출력 변경하기
기본적으로 마이그레이션은 정확히 무엇을 하고 있는지, 그리고 얼마나 걸렸는지 알려줍니다. 테이블을 생성하고 인덱스를 추가하는 마이그레이션의 출력은 다음과 같습니다:
== CreateProducts: migrating ================================================= -- create_table(:products) -> 0.0028s == CreateProducts: migrated (0.0028s) ========================================
마이그레이션에서 출력을 제어할 수 있는 여러 메서드가 제공됩니다:
메서드 | 목적 |
---|---|
suppress_messages |
블록의 출력을 억제합니다. |
say |
메시지를 출력합니다. 두 번째 boolean 인수를 전달하여 들여쓰기 여부를 지정할 수 있습니다. |
say_with_time |
텍스트와 실행 시간을 출력합니다. 블록이 정수를 반환하면 영향을 받은 행 수로 간주합니다. |
예를 들어 다음과 같은 마이그레이션이 있습니다:
class CreateProducts < ActiveRecord::Migration[7.2] def change suppress_messages do create_table :products do |t| t.string :name t.text :description t.timestamps end end say "Created a table" suppress_messages { add_index :products, :name } say "and an index!", true say_with_time 'Waiting for a while' do sleep 10 250 end end end
이 마이그레이션은 다음과 같은 출력을 생성합니다:
== CreateProducts: migrating ================================================= -- Created a table -> and an index! -- Waiting for a while -> 10.0013s -> 250 rows == CreateProducts: migrated (10.0054s) =======================================
Active Record에서 어떤 출력도 표시하지 않게 하려면 bin/rails db:migrate VERBOSE=false
를 실행하면 됩니다.
기존 마이그레이션 변경하기
때때로 마이그레이션을 작성할 때 실수를 할 수 있습니다. 이미 마이그레이션을 실행한 경우 마이그레이션을 편집하고 다시 실행할 수 없습니다. Rails는 이미 마이그레이션이 실행되었다고 생각하므로 bin/rails db:migrate
를 실행해도 아무 작업도 수행하지 않습니다. 마이그레이션을 롤백(예: bin/rails db:rollback
으로)하고, 마이그레이션을 편집한 다음 bin/rails db:migrate
를 실행하여 수정된 버전을 실행해야 합니다.
일반적으로 기존 마이그레이션을 편집하는 것은 좋지 않습니다. 자신과 동료에게 추가 작업을 만들고 해당 마이그레이션이 이미 프로덕션 시스템에서 실행된 경우 큰 혼란을 초래할 수 있습니다.
대신 필요한 변경 사항을 수행하는 새 마이그레이션을 작성해야 합니다. 아직 소스 제어에 커밋되지 않은(또는 일반적으로 개발 머신 외부로 전파되지 않은) 새로 생성된 마이그레이션을 편집하는 것은 상대적으로 무해합니다.
이전 마이그레이션 되돌리기에서 설명한 대로 revert
메서드를 사용하면 이전 마이그레이션의 전체 또는 일부를 되돌리는 데 도움이 될 수 있습니다.
스키마 덤프 및 당신
스키마 파일은 무엇을 위한 것인가?
강력하기는 하지만 마이그레이션은 데이터베이스 스키마의 권위 있는 소스가 아닙니다. 데이터베이스가 진실의 원천입니다.
기본적으로 Rails는 현재 데이터베이스 스키마를 캡처하려는 db/schema.rb
를 생성합니다.
bin/rails db:schema:load
를 통해 스키마 파일을 로드하여 애플리케이이션의 새 인스턴스를 만드는 것이 마이그레이션 기록 전체를 재생하는 것보다 빠르고 오류가 적습니다.
오래된 마이그레이션은 외부 종속성이 변경되거나 마이그레이션과 별도로 진화하는 애플리케이션 코드에 의존하는 경우 올바르게 적용되지 않을 수 있습니다.
스키마 파일은 Active Record 객체에 어떤 속성이 있는지 빠르게 확인할 수 있어 유용합니다. 이 정보는 모델 코드에 없으며 여러 마이그레이션에 걸쳐 있지만, 스키마 파일에 잘 요약되어 있습니다.
스키마 덤프 유형
Rails에서 생성하는 스키마 덤프의 형식은 config/application.rb
에 정의된 config.active_record.schema_format
설정에 의해 제어됩니다. 기본적으로 형식은 :ruby
이며, 대안으로 :sql
로 설정할 수 있습니다.
기본 :ruby
스키마 사용하기
:ruby
가 선택되면 스키마는 db/schema.rb
에 저장됩니다. 이 파일을 보면 매우 큰 마이그레이션과 매우 유사한 것을 알 수 있습니다:
ActiveRecord::Schema[7.2].define(version: 2008_09_06_171750) do create_table "authors", force: true do |t| t.string "name" t.datetime "created_at" t.datetime "updated_at" end create_table "products", force: true do |t| t.string "name" t.text "description" t.datetime "created_at" t.datetime "updated_at" t.string "part_number" end end
많은 면에서 이것이 정확히 그것입니다. 이 파일은 데이터베이스를 검사하고 create_table
, add_index
등을 사용하여 구조를 표현합니다.
:sql
스키마 덤퍼 사용하기
그러나 db/schema.rb
는 데이터베이스가 지원할 수 있는 모든 것(트리거, 시퀀스, 저장 프로시저 등)을 표현할 수 없습니다.
마이그레이션이 Ruby 마이그레이션 DSL에서 지원되지 않는 구조를 만들기 위해 execute
를 사용할 수 있지만, 이러한 구조는 스키마 덤퍼에 의해 재구성될 수 없습니다.
이러한 기능을 사용하는 경우 스키마 형식을 :sql
로 설정하여 정확한 스키마 파일을 얻어야 합니다. 이 파일은 새 데이터베이스 인스턴스를 생성하는 데 유용합니다.
스키마 형식이 :sql
로 설정되면 데이터베이스 구조가 데이터베이스별 도구를 사용하여 db/structure.sql
에 덤프됩니다. 예를 들어 PostgreSQL의 경우 pg_dump
유틸리티가 사용됩니다. MySQL과 MariaDB의 경우 이 파일에는 다양한 테이블에 대한 SHOW CREATE TABLE
출력이 포함됩니다.
db/structure.sql
에서 스키마를 로드하려면 bin/rails db:schema:load
를 실행하세요.
이 파일의 내용을 실행하여 데이터베이스 구조를 완벽하게 복사할 수 있습니다.
스키마 덤프와 소스 제어
스키마 파일은 새 데이터베이스를 생성하는 데 일반적으로 사용되므로 소스 제어에 스키마 파일을 체크인하는 것이 강력히 권장됩니다.
두 개의 브랜치가 스키마를 수정하면 스키마 파일에서 병합 충돌이 발생할 수 있습니다. 이러한 충돌을 해결하려면 bin/rails db:migrate
를 실행하여 스키마 파일을 다시 생성하세요.
정보: 새로 생성된 Rails 앱에는 이미 마이그레이션 폴더가 git 트리에 포함되어 있으므로, 추가한 새 마이그레이션을 추가하고 커밋하기만 하면 됩니다.
Active Record와 참조 무결성
Active Record 방식은 지능이 모델에 있어야 한다고 주장하므로 트리거 또는 제약 조건과 같은 기능은 권장되지 않습니다.
validates :foreign_key, uniqueness: true
와 같은 유효성 검사는 모델이 데이터 무결성을 보장할 수 있는 한 가지 방법입니다. 연관에 대한 :dependent
옵션을 통해 모델은 부모가 삭제될 때 자식 객체를 자동으로 삭제할 수 있습니다. 애플리케이션 수준에서 작동하는 이러한 기능은 참조 무결성을 보장할 수 없으므로 일부 사람들은 외래 키 제약 조건을 데이터베이스에 추가합니다.
Active Record는 이러한 기능을 직접 제공하지 않지만 execute
메서드를 사용하여 임의의 SQL을 실행할 수 있습니다.
마이그레이션과 시드 데이터
Rails의 마이그레이션 기능의 주요 목적은 일관된 프로세스를 사용하여 스키마를 수정하는 명령을 발행하는 것입니다. 마이그레이션을 사용하여 데이터를 추가하거나 수정할 수도 있습니다. 이는 삭제하고 다시 생성할 수 없는 기존 데이터베이스(예: 프로덕션 데이터베이스)에 유용합니다.
class AddInitialProducts < ActiveRecord::Migration[7.2] def up 5.times do |i| Product.create(name: "Product ##{i}", description: "A product.") end end def down Product.delete_all end end
데이터베이스가 생성된 후 초기 데이터를 추가하려면 Rails에 내장된 '시드’ 기능을 사용하면 프로세스를 가속화할 수 있습니다. 이는 개발 및 테스트 환경에서 데이터베이스를 자주 다시 로드하거나 프로덕션에 대한 초기 데이터를 설정할 때 특히 유용합니다.
이 기능을 시작하려면 db/seeds.rb
를 열고 일부 Ruby 코드를 추가한 다음 bin/rails db:seed
를 실행하세요.
참고: 여기의 코드는 모든 환경에서 언제든 실행할 수 있도록 idempotent해야 합니다.
["Action", "Comedy", "Drama", "Horror"].each do |genre_name| MovieGenre.find_or_create_by!(name: genre_name) end
이는 일반적으로 새 애플리케이션의 데이터베이스를 설정하는 훨씬 더 깨끗한 방법입니다.
오래된 마이그레이션
db/schema.rb
또는 db/structure.sql
은 현재 데이터베이스 상태의 스냅샷이며 해당 데이터베이스를 다시 빌드하는 권위 있는 소스입니다. 따라서 오래된 마이그레이션 파일을 삭제하는 것이 가능합니다.
db/migrate/
디렉토리에서 마이그레이션 파일을 삭제하면 해당 파일이 여전히 존재할 때 bin/rails db:migrate
가 실행된 모든 환경에서 내부 Rails 데이터베이스 테이블인 schema_migrations
에 해당 마이그레이션 타임스탬프가 저장됩니다. 이 테이블은 특정 환경에서 마이그레이션이 실행되었는지 여부를 추적하는 데 사용됩니다.
bin/rails db:migrate:status
명령을 실행하면 각 마이그레이션의 상태(up 또는 down)가 표시됩니다. 이 때 삭제된 마이그레이션 파일 중 특정 환경에서 실행되었지만 더 이상 db/migrate/
디렉토리에 없는 파일 옆에 ********** NO FILE **********
이 표시됩니다.
엔진의 마이그레이션
그러나 엔진과 관련된 문제가 있습니다. 엔진에서 마이그레이션을 설치하는 Rake 작업은 idempotent하므로 여러 번 호출해도 동일한 결과가 나옵니다. 부모 애플리케이션에 이전 설치로 인해 존재하는 마이그레이션은 건너뛰고, 누락된 마이그레이션은 새 선행 타임스탬프로 복사됩니다. 엔진 마이그레이션을 삭제하고 설치 작업을 다시 실행하면 새 타임스탬프로 새 파일이 생성되고 db:migrate
가 다시 실행을 시도합니다.
따라서 일반적으로 엔진에서 오는 마이그레이션은 보존하는 것이 좋습니다. 다음과 같은 특수 주석이 있습니다:
# This migration comes from blorgh (originally 20210621082949)