Một số lưu ý khi làm việc với Active Record Migrations
Bài đăng này đã không được cập nhật trong 8 năm
Ruby on Rails database migrations là một giải pháp giúp cải thiện một vấn đề thực tế mà các developer phải đối mặt đó là: Làm thế nào để thay đổi database script một cách đáng tin tưởng để có thể nhân rộng trên môi trường development của team hoặc triển khai lên production server tại thời điểm thích hợp. Trước Rails và các giải pháp backend khác, developer thường viết các script trực tiếp trong database và sử dụng chúng trên tất cả.
Tuy nhiên, cũng như hầu thế các giải pháp cải thiện khác, Migration không phải là không có nhược điểm. Theo thời gian một database migration có thể trở thành một mớ hỗn độn có thể gây nguy hại đến công việc của bạn hơn là sự thích thú mà nó tạo ra. Việc tuân theo một số quy tắc sau có thể đảm bảo migration của bạn không trở lên lộn xộn.
I. Không bao giờ thay đổi một migration đã được commit
Như đã nói ở trên database migration giúp bạn chia sẽ những thay đổi đến database một cách đáng tin cậy đến các thành viên khác của team.
Khi bạn commit một migration mới lên source code repository, ngoại trừ khi có lỗi xảy ra trong migration mà không thể đảo ngược lại(ví dụ lỗi cú pháp) thì bạn không nên sửa trực tiếp một migration đã được commit. Một migration đã được chạy trên máy của thành viên khác hoặc trên máy chủ sẽ không tự động chạy lại. Để chạy nó một lần nữa lập trình viên cần phải rollback lại migration đó và chạy nó 1 lần nữa. Thậm chí nó còn tồi tệ hơn khi bạn làm điều đó là việc mất dữ liệu.
Nếu bạn chắc chắn rằng một migration chưa được chạy trên server, khi đó bạn có thể giao tiếp với phần còn lại của team và re-migration của họ hoặc thực hiện điều đó bằng tay trong database. Nhưng điều đó có thể gây mất thời gian của họ và dễ dẫn đến lỗi. Vì vậy tôi xin nhắc lại một lần nữa là không bao giờ thay đổi một migration đã được commit trừ trường hợp bắt buộc.
Nhưng đôi khi bạn phạm sai lầm và migration không rollback, nó bắt buộc phải được fix. Trong trường hợp đó bạn không có lựa chọn nào khác là phải thay đổi một migration đã được commit. Để hạn chế tối đa trường hợp này bạn luôn phải chắc chắn rằng migration của bạn có thể thực thi được và kiểm tra kết quả trước khi được nó lên source code repository. Bạn cũng không nên giới hạn việc chạy nó không, hãy thử cả rollback nó rồi re-run. Rails cung cấp rake task cho phép bạn thực hiện điều đó như sau:
rake db:migrate
rake db:rollback
rake db:rollback sẽ đảo ngược những thay đổi của migration cuối cùng của bạn. Việc thực hiện 2 lệnh trên đảm bảo migration của bạn có tính 2 chiều, lặp lại và không có lỗi. Khi đó bạn có thể tự tin đưa migration của mình lên source code repository.
II. Không bao giờ sử dụng External Code trong một Migration
Database migrations dùng để quản lý sự thay đổi của database. Khi một cấu trúc của database được thay đổi thì việc thường kèm theo là sự thay đổi về dữ liệu. Khi điều đó xảy ra thì cách khá phổ biến là sử dụng Model trong migration. Ví dụ
class AddJobsCountToUser < ActiveRecord::Migration
def change
add_column :users, :jobs_count, :integer, :default => 0
User.all.each do |user|
user.jobs_count = user.jobs.size
user.save
end
end
end
Trong ví dụ trên bạn thêm một cột để cache giá trị trong bảng users. Migration này đang sử dụng Model để cập nhật giá chị cho jobs_count. Việc này khiến bạn phải đối mặt với 2 vấn đề:
- Đầu tiên, performs của phương pháp này thật sự khủng khiếp, Đoạn mã này load tất cả user vào bộ nhớ, duyệt qua từng bản ghi, đếm số lượng jobs và update vào jobs_count.
- Thứ 2, cái này quan trọng hơn nhiều, đó là migration này sẽ không chạy khi mà Model User đã bị loại bỏ khỏi source code của bạn, khi đó nó trở thành không khả dụng hoặc thay đổi theo cách nào đó mà khiến cho migration này không còn giá trị. Code trong migration có nghĩa vụ phải chạy để quản lý thay đổi trong database, theo thứ tự và bất cứ khi nào. Khi mà một external code được sử dụng trong migration, nó liên quan đến migration và không bị ràng buộc bởi cùng một quy tắc, có thể dẫn đến kết quả là migration không thể chạy.
Vì vậy, cách tốt nhất là sử dụng trực tiếp SQL trong migration của bạn, như ví dụ trên các bạn nên viết như sau:
class AddJobsCountToUser < ActiveRecord::Migration
def change
add_column :users, :jobs_count, :integer, :default => 0
execute <<-SQL
UPDATE users SET jobs_count = (
SELECT count(*) FROM jobs
WHERE jobs.user_id = users.id
SQL
end
end
Khi 1 migration sử dụng SQL trực tiếp, nó sẽ không phụ thuộc vào bất cứ gì ngoại trừ cấu trúc của database tại thời điểm phải thực thi.
Trong trường hợp bạn thực sự cần sử dụng một Model hoặc code Ruby khác trong migration thì mục tiêu là phải không được phụ thuộc vào code ở bên ngoài migration. Vì vậy tất cả code bạn cần phải được định nghĩa ở trong migration.
III.Tài liệu tham khảo
- Rails Guide
- Ebook: Rails Antipatterns
All rights reserved