Tìm hiểu về Migration
Bài đăng này đã không được cập nhật trong 6 năm
Migration
là một tính năng của Active record cho phép bạn thay đổi cả cấu trúc và dữ liệu trong database. Thay vì thay đổi trực tiếp vào database thì Rails cho phép bạn sử dụng Ruby DSL để mô tả việc thay đổi các table.
Tiện gần đây dự án mình gặp vấn đề về migration
nên mình viết bài viết này. Bài toán đặt ra là Khách hàng muốn rollback migration
về thời điểm nào đó chỉ để chạy code ở version nào đó thôi mà không phải code hiện tại. Và lúc đó bên mình đã rollback
nhưng bị lỗi rollback
mà trong lúc code k ai nghĩ đến trường hợp đó cả. Đó cũng là một vấn đề mà ta cần hiểu rõ hơn về migration để tránh những trường hợp như vậy.
Sau khi đọc bài viết bạn có thể biết thêm về:
- Hiểu về migration, các cách tạo
- Các cách chạy migration, rollback chúng
- Hiểu thêm về schema
1. Tổng quan
Migration là một cách thuận tiện để thay đổi cấu trúc bảng và dữ liệu trong database 1 cách dễ dàng. Bằng cách sử dụng Ruby DSL bạn k cần phải viết SQL bằng tay, nó có thể giúp bạn thay đổi database 1 cách độc lập Tưởng tượng mỗi migration tương ứng vs 1 version của database. Ban đầu, schema là rỗng, và mỗi lần migration thì sẽ modify để add hoặc remove table, columns hoặc rows. Active record biết cách để update schema theo thời gian. Và từ bất cứ thời điểm nào trong quá khứ cũng có thể update version của schema đến bản mới nhất. Active record cũng sẽ update file db/schema.rb để làm cho thống nhất với cấu trúc mới nhất của database. Chúng ta cùng nhìn qua ví dụ về 1 migration:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
Đây là 1 migration tạo bảng products trong database. Có 2 trường đó là name
và description
. Một cột khóa chính là id
cũng sẽ được thêm vào sau khi chạy migration này, đây là khóa chính mặc định cho tất cả model của Active Record. timestamps
sẽ thêm vào bảng 2 cột đó là : created_at
và updated_at
. Các cột này sẽ được quản lý tự động bởi ActiveRecord nếu chúng tồn tại
Trước khi thực hiện migration thì không tồn tại table nào cả. Run migration thì table sẽ được sinh ra. Và Active record cũng có cách để back lại cái migration lúc nãy bằng cách là Rollback lại cái migration đó thì bảng được tạo lúc trước sẽ bị xóa.
Ta cũng có thể viết migration theo cách khác để hiểu cách nó rollback
:
class ChangeProductsPrice < ActiveRecord::Migration[5.0]
def change
reversible do |dir|
change_table :products do |t|
dir.up { t.change :price, :string }
dir.down { t.change :price, :integer }
end
end
end
end
dir.up
là chạy migration. dir.down
là chạy rollback lại, quay lại thời điểm trước khi chạy migration
Ví dụ như nếu chúng ta change_column
thì rollback sẽ bị lỗi, buộc phải viết code theo kiểu này.
2. Tạo migration
2.1 Tạo migration
Migration được lưu tại thành các file tại db/migrate
. Tên của các file migration sẽ theo dạng YYYYMMDDHHMMSS_create_products.rb
. File name kèm theo thời gian để phân biệt các version migration. Ví dụ: file name là 20080906120000_create_products.rb
thì class CreateProducts
phải được định nghĩa. Rails sử dụng dấu thời gian này để xác định migration nào cần được chạy và theo thứ tự nào, do đó, nếu bạn đang sao chép migration từ một ứng dụng khác hoặc tự tạo ra một tệp tin, hãy nhận biết vị trí của nó theo thứ tự.
Và tất nhiên, cứ mỗi lần tạo migration chúng ta k thể ngồi tính thời gian để tạo file dc. Active Record cung cấp một cách tạo đơn giản, dễ dàng:
$ bin/rails generate migration AddPartNumberToProducts
Cau lệnh sẽ tạo ra 1 migration mới:
class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
end
end
Nếu migration tên có dạng "AddXXXToYYY" hoặc "RemoveXXXFromYYY" sẽ tạo ra các migration add_column
hoặc remove_column
. Ta có thể thêm vào các column và type của chúng theo sau:
$ bin/rails generate migration AddPartNumberToProducts part_number:string
sẽ tạo ra:
class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :part_number, :string
end
end
Các migration có tên theo dạng CreateXXX
và theo sau là danh sách các tên column và type của chúng sẽ tạo ra table có tên XXX với những column đã liệt kê. Ví dụ:
$ bin/rails generate migration CreateProducts name:string part_number:string
Sẽ tạo ra migration:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.string :part_number
end
end
end
2.2 Tạo model
Khi tạo model có thể tạo migration theo đó. Ví dụ chúng ta tạo 1 model mới tên là Product
.
$ bin/rails generate model Product name:string description:text
Nó sẽ tạo ra 1 migration mới như sau:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
Bạn có thể thêm nhiều cột khác nếu muốn )
3. Chạy migration
Rails cung cấp 1 tập hợp của bin/rails
tasks để chạy migration
Đầu tiên câu lệnh chúng ta hay sử dụng nhất đó là rails db:migrate
. Ở đây nó chỉ chạy các migration chưa chạy,. các migration chạy rồi nó sẽ k chạy lại nữa. Nó sẽ chạy theo thứ tự thời gian
Một lưu ý là khi chạy db:migrate
nó cũng sẽ tự động chạy db:schema:dump
để cập nhập file db/schema.rb
cho trùng khớp với cấu trúc database của bạn
Nếu bạn muốn chạy 1 migration cụ thể. chúng ta có thể chạy bằng cách lấy tên version là dãy số dài ở file migration và chạy:
$ bin/rails db:migrate VERSION=20080906120000
Nếu như version 20080906120000 lớn hơn version hiện tại, nó sẽ chạy những thay đổi (up
method) của tất cả các migration bao gồm cả 20080906120000 và sẽ không chạy bất kì các migration sau nó.
3.1 Rolling back
Rollback được sử dụng khi chúng ta tạo sai 1 migration và muốn sửa chúng, muốn quay lại tại thời điểm chạy migration lỗi đó. Ta có thể quay lại migration trước bằng cách:
$ bin/rails db:rollback
Nó sẽ rollback tại thời điểm mới nhất của migration, nếumuốn rollback lại nhiều version trước đó nữa thì chỉ định parameter STEP. Ví dụ:
$ bin/rails db:rollback STEP=3
Nó sẽ revert 3 migration gần nhất.
Ta dùng db:migrate:redo
để chạy roll back và migration lại:
$ bin/rails db:migrate:redo STEP=3
Đây là các cách giúp chúng ta sửa sai các migration mà không cần phải reset migration.
Một lưu ý khi tạo migration chúng ta nên kiểm tra xem nó có rollback được hay không ^^. Có thể nó sẽ không rollback được do nhiều lý do. Ví dụ như
change_column
thì sẽ không rollback dc, hoặc khi ta thêm điều kiện vào để chạy migration.
3.2 Cài đặt database
Ta dùng db:setup
sẽ giúp tạo ra database và load schema và khởi tạo chúng với seed.
3.3 Reset database
Dùng db:reset
để dropdatabase và cài đặt lại nó. Nó tương đương với 2 câu lệnh: db:drop
và db:setup.
3.4 Chạy 1 migration bất kì
Nếu bạn muốn chạy 1 migration với up hoặc down, ta có thể dùng db:migrate:up
và db:migrate:down
. Và thêm version đằng sau để xác định đó là file migration nào
$ bin/rails db:migrate:up VERSION=20080906120000
sẽ chạy 20080906120000 với những thay đổi (up
method). Nó cũng kiểm tra xem migration này đã chạy chưa và sẽ không làm gì hết nếu Active Record xác định nó đã được chạy
3.5 Chạy migration trên các môi trường khác nhau
Mặc đinh nếu chạy db:migrate
nó sẽ chạy trên môi trường development
. Nếu bạn muốn nó chạy trên các môi trường khác thì chỉ cần thêm biến môi trường vào đằng sau đó là RAILS_ENV
. Ở đây mình muốn chạy migrate trên môi trường test
:
$ bin/rails db:migrate RAILS_ENV=test
4. Schema
4.1 Mục đích
File schema sẽ giúp bạn có thể biết các thuộc tính của đối tượng Active Record có. Những thông tin này nó không có trong code model và thông qua các migration nó được tạo ra.
4.2 Các loại Schema Dump
Có 2 phương pháp để Dump schema
. Cách dump thì được setting trong file config/application.rb
của config.active_record.schema_format
. Chỉ định sql
hoặc là :ruby
Nếu chỉ định :ruby thì schema sẽ được lưu ở db/schema.rb
. Mở file này có chắc chắn có thể nhìn thấy giống như là 1 bảng migration lớn.
ActiveRecord::Schema.define(version: 20080906171750) 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
5. Migration và Seed data
Migration có thể sử dụng để thêm hoặc sửa data. Nó rất hữu dụng với 1 database đã tồn tại sẵn và không thể xóa hoặc tạo lại, ví dụ như production database. Ta có thể viết như sau:
class AddInitialProducts < ActiveRecord::Migration[5.0]
def up
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
end
def down
Product.delete_all
end
end
Để thêm dữ liệu sau khi database được tạo. Rails có một tính năng có thể giúp quá trình này trở nên nhanh chóng. Bằng cách thêm code vào db/seeds.rb
và chạy rails db:seed
.
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
Bài viết đến đây là kết thúc rồi. Hi vọng bài viết có thể giúp ích cho bạn, giúp bạn hiểu hơn về migration.!
Nguồn tham khảo: http://edgeguides.rubyonrails.org/active_record_migrations.html#changing-existing-migrations
All rights reserved