Data Migrations in Rails

https://robots.thoughtbot.com/data-migrations-in-rails

Bất cứ lúc nào, khi chúng ta cần phải thay đổi dữ liệu thực tế trên môi trường production. Dĩ nhiên tùy chọn đầu tiên xuất hiện trong đầu là sử dụng Rails migration, đặc biệt kể từ khi migration xuất hiện trong các task chính của, data migration. Nhưng chúng ta nên nói chi tiết hơn và hãy để tôi cố gắng ngăn cản bạn làm như vậy.

Hãy nhìn vào Rails Guides for Active Records Migration, mở đầu phần đầu tiên là câu nói:

Migrations là những tính năng của Active Record cho phép bạn phát triển database schema theo thời gian. Thay vì viết thay đổi schema trong SQL thuần, thì migrations sẽ cho phép bạn dễ dàng sử dụng Ruby DSL để mô tả những thay đổi trong các table của bạn.

Bạn có nhận thấy rằng từ data đã bị vắng mặt trong đoạn văn trên? Theo định nghĩa Rails migrations chỉ nên được sử dụng để thay đổi schema và không làm thay đổi dữ liệu thực tế của database.

Nói chung, việc thao tác dữ liệu trong migrations là một ý tưởng tồi cho một số nguyên nhân. Thứ nhất, file data migrations sẽ ở trong thư mục db/migrate và sẽ chạy bất cứ khi nào một developer mới cài đặt cho project của họ trên môi trường development của họ. Và có thể tương lai, chúng ta sẽ thay đổi một class và logic của nó có thể phá vỡ migration sau này. Hơn nữa, đây không phải là business logic, do đó không nên ở lại trong code base mãi mãi.

Vấn đề thứ hai là data migrations có thể bị từ chỗi trong tương lai bởi các nhà phát triển nếu thay vì chạy rake db:migrate các developers chạy rake db:schema:load or rake db:reset . Cả hai lệnh trên chỉ đơn thuần là tải phiên bản mới nhất của cấu trúc database sử dụng file schema.rb mà không hề động gì đến migrations.

Thứ ba là việc triển khai ứng dụng của bạn lên môi trường production phụ thuộc vào data migration được hoàn thành. Điều này không phải là vấn đề khi mà ứng dụng của bạn là mới (lần đầu được triển khai lên production) và dữ liệu của bạn nhỏ. Nhưng làm thế nào đối với một database lớn với hàng triệu bản ghi? Quá trình triển khai của bạn bây giờ phải chờ các thao tacs dữ liệu hoàn thành và có thể sẽ có một số rắc rối xảy ra như là có thể bị treo hoặc migration failed.

Tôi muốn gợi ý một thay thế tốt hơn bằng cách sử dụng cáctemporary rake tasks. Temporary rake tasks cho phép chúng ta tách quá trình deployment ra khỏi quá trình hoàn thiện migrations. Nó cho chúng ta kiểm soát nhiều hơn quá trình thao tác dữ liệu bằng cách đóng gói chúng vào một nơi nào đó. Nhược điểm là chúng ta cần nhớ và add rake task đó vào trong script deploy, hoặc chạy rake task đó thường xuyên sau khi deployment. Chúng ta cũng cần làm sạch sau khi deploy và cần xóa temporary rake task khi các thay đổi đã được deploy và thực hiện.

Khi tạo ra một temporary rake task cho một data migration, có thể bạn sẽ bị cám dỗ viết cái gì đó giống như đoạn code dưới đây:

# lib/tasks/temporary/users.rake
namespace :users do
  task :set_newsletter => :environment do
    User.all.each do |user|
      if user.confirmed?
        user.receive_newsletter = true
        user.save
      end
    end
  end
end

Chúng ta có năm vấn đề với đoạn code như trên:

  1. Task đó sẽ đi qua tất cả các user
  2. Nó gọi validationscallbacks, trong đó có thể gây ra những hậu quả ngoài ý muốn.
  3. Nó sử dụng if block để kiểm tra xem người dùng nào cần update.
  4. Nó không cho chúng ta một chỉ dẫn trực quan rằng nó thực sự làm việc
  5. Nó không bao gồm một mô tả và do đó chúng ta không thể thấy các task khi mà chúng ta chạy rake -T

Đây là một gợi ý cho một rake task tốt hơn

# lib/tasks/temporary/users.rake
namespace :users do
  desc "Update confirmed users to receive newsletter"
  task set_newsletter: :environment do
    users = User.confirmed
    puts "Going to update #{users.count} users"

    ActiveRecord::Base.transaction do
      users.each do |user|
        user.mark_newsletter_received!
        print "."
      end
    end

    puts " All done now!"
  end
end

Trong trường hợp này:

  1. Nó có đính kèm một mô tả, do vậy chúng ta sẽ thấy task đó thực thi và mô tả của nó khi chúng ta chạy rake -T
  2. Nó dùng một scope để lấy hết tất cả các records cần update, vì vậy chúng ta có thể remove được if block và giới hạn được số lượng records cần sử dụng.
  3. Nó nói cho chúng ta biết trước được sẽ có bao nhiêu records sẽ được update, cho chúng ta một cách nhìn trực quan thực tế là nó đang làm việc, và khi nào thì nó thực thi xong.
  4. Nó bao bọc các thay đổi trong một giao dịch (transaction).

Nếu database của bạn hỗ trợ transaction, nó luôn luôn là một ý tưởng tốt để bọc code mà bên trong là những dữ liệu thực sự cần thay đổi vào một transaction. Transactions giúp chúng ta đối phó được với crashes, failures, và data consistency. Chúng rất quan trọng khi làm việc với nhiều object cùng phải thay đổi và chúng tôi muốn đảm bảo tính toàn vẹn của dữ liệu. Ví dụ, khi thực hiện một giao dịch chuyển tiền, chúng tôi muốn đảm bảo rằng sẽ không có một tình huống mà tiền được rút ra từ một tài khoản, nhưng không gửi vào một tài khoản khác.

Những ví dụ trên là rất ngắn. Nếu sự thay đổi dữ liệu đòi hỏi cần nhiều hành động hơn (actions), hãy xem xét việc kéo các hành vi đó vào method của chính nó.

Nếu database là khá lớn, nó cũng được đề nghị chạy data migration theo batches. Bạn có thể tìm hiểu thêm về batchestrên Rails Guides tại Retrieving Multiple Objects in Batches.

Cuối cùng nhưng không kém phần quan trọng, nếu như bạn sử dụng gem Suspenders để khởi tạo base application với tiêu chuẩn mặc định (standard defaults) của thoughtbot, bạn có thể thấy rằng nó đã thêm file lib/tasks/dev.rakerake dev:prime trong rake tasks của bạn. Task này được dùng để add dữ liệu vào môi trường phát triển development vì thế seeds.rb có thể được dành cho data cần thiết cho các môi trường phát triển khác (như staging hay production).

Kết luận, bằng cách tạo ra các temporary rake task, chúng ta có được những lợi ích của việc deployments, migration không bị phá hủy, và chúng ta có thể kiểm soát nhiều hơn trong quá trình thao tác dữ liệu. Và như tôi nhớ đã nhắc nhỡ rằng chúng ta nên nhớ chạy các migration rake task trên môi trường development và staging trước.


All Rights Reserved