12/08/2018, 16:22

Migration trong rails

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. Sau khi đọc xong bài này các bạn có thể biết: Active record là gì và cách ...

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. Sau khi đọc xong bài này các bạn có thể biết:

  • Active record là gì và cách tạo
  • Các method cung cấp trong Active Record để manipulate database
  • Bin/rails task
  • Schema

Migration mô tả cách thuận tiện để thay đổi cấu trúc bảng và dữ liệu trong DB một cách dễ dàng. Có thể hình dung mỗi migration chính là một 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. Ví dụ về migration

class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :name
      t.text :description
 
      t.timestamps
    end
  end
end

Nếu run đoạn migration bên trên thì sẽ tạo ra table products với 2 column name với kiểu string và description với kiểu text. Primary key id sẽ được tự động thêm vào, id là khóa chính default trong model active record. timestamps sẽ thêm vào trong table 2 column created_at và updated_at, và 2 cột này nếu tồn tại thì sẽ được quản lý tự động bởi active record. 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. Trong trường hợp không biết cách để rollback lại migration thì có thể dùng reversible

class ChangeProductsPrice < ActiveRecord::Migration
  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

Thay vì dùng change thì cũng có thể dùng up & down

class ChangeProductsPrice < ActiveRecord::Migration
  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

Create migration

Migration thì được lưu trong folder db/migrate và tương ứng với mỗi file thì sẽ là mỗi class migration. File migration sẽ có định dạng YYYYMMDDHHMMSS_create_products.rb. File name có kèm theo thời gian đề phân biệt các version migation. Và tên class phải thống nhất với tên file theo định dạng ví dụ 20080906120000_create_products.rb thì sẽ định nghĩa class CreateProducts.

$ bin/rails generate migration AddPartNumberToProducts

Command sẽ sinh ra migration với nội dung như bên dưới.

class AddPartNumberToProducts < ActiveRecord::Migration
  def change
  end
end

Trong TH tên migration có định dạng "AddXXXToYYY" & "RemoveXXXFromYYY"., sau đó nếu nhập tiếp tên column thì migration sẽ tự động add thêm các trường add_colum và remove_column trong class. VD:

$ bin/rails generate migration AddPartNumberToProducts part_number:string

Thì class sinh ra bên dưới

class AddPartNumberToProducts < ActiveRecord::Migration
  def change
    add_column :products, :part_number, :string
  end
end

Phần remove cũng tương tự. Còn trong TH bên file có định dạng CreateXXX, tiếp theo đó nhập tên và định nghĩa các column thì cũng sẽ được sinh ra bên trong class. Vd

$ bin/rails generate migration CreateProducts name:string part_number:string

Thì bên trong class sẽ là

class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :name
      t.string :part_number
    end
  end
end

Tạo model

$ bin/rails generate model Product name:string description:text

Đoạn command sẽ sinh ra migration như bên dưới

class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :name
      t.text :description
 
      t.timestamps
    end
  end
end

Run Active record

Trong rails có một số class Rake để chạy migration. Class rake run migration nhanh nhất trong hầu hết các trường hợp là bin/rails db:migrate. Thứ tự thực hiện migrate là dựa theo thời gian. Khi chạy command db:migrate thì task db:schema:dump cũng đồng thời được gọi ra, task này sẽ update file schema db/schema.rb và schema sẽ làm thống nhất cấu trúc của db. Nếu chỉ định version của migration thì active record sẽ thực hiện việc migration (change/up/down) đến version migation được chỉ định. Ví dụ, Trong trường hợp muốn migration đến version 20080906120000 thì chạy command như dạng bên dưới

$ bin/rails db:migrate VERSION=20080906120000

Rollback

Sẽ có nhiều trường hợp bạn muốn rollback lại version ngay trước. Ví dụ như trong trường hợp migration bị sai và muốn back lại. Thì trong trường hợp này cho dù không chỉ chính xác version của bản cần rollback thì chỉ cần run command bên dưới thì sẽ ok

$ bin/rails db:rollback

Trong TH muốn rollback lại nhiều version trước đó nữa thì chỉ định parameter step. Vd:

$ bin/rails db:rollback STEP=3

Command trên sẽ rollback lại 3 version cuối cùng. Setting database Task bin/rails db:setup thì sẽ create db, đọc schema và thực hiện các thiết lập đầu tiên của db sử dụng seed database. Reset database Task bin/rails db:reset sẽ drop db và setting lại db. Task này thì tương đương với task bin/rails db:drop db:setup. Chỉ run bản migration đặc định Trong trường hợp cần thiết phải up or down các migration đặc định thì sử dụng db:migrate:up hoặc db:migrate:down. Giống như bên dưới, chỉ cần chỉ định version thích hợp thì lần lượt từng method change, up, down bao gồm cả migration tương ứng.

$ bin/rails db:migrate:up VERSION=20080906120000
Run đoạn command bên trên thì method change nằm bên trong migration 20080906120000 sẽ được thực thi. Đầu tiên, nó sẽ check migration này đã được hoàn thành hay chưa. Trong trường hợp đã được Active record xác định đã hoàn thành thì sẽ k chạy gì cả. 

Schema

Vì Rails migration không thật sự mạnh mẽ, nên việc tạo ra nguồn thông tin đủ tin tưởng để tạo schema của database là không phù hợp lắm. Khi deploy new instance của application thì không cần thiết phải thực hiện lại tất cả những bảng migration cũ. Nếu thực hiện thì rõ ràng là sẽ dễ phát sinh nhiều lỗi. Và đơn giản và nhanh chóng hơn là đọc vào database những cái đã ghi trong file schema hiện tại. File schema nó sẽ biết được ở object của Active record thì có những thuộc tính như thế nào, và thông tin schema thì không có trong code model, phần lớn được chia ra trong các bảng migration và tồn tại trong đó. Sử dụng gem annotate_models thì những comment thông tin tóm tắt schema sẽ được tự động add, update vào đầu của model file.

Các loại Schema dumps

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ở thử 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

Cũng có trường hợp sử dụng migration để thêm data và database.

class AddInitialProducts < ActiveRecord::Migration
  def up
    5.times do |i|
      Product.create(name: "Product ##{i}", description: "A product.")
    end
  end
 
  def down
    Product.delete_all
  end
end

Tuy nhiên, ở rails thì có tính năng seed để mang data vào database lúc đầu tiên. Thêm code ruby vào file db/seeds.rb và run bin/rails db:seed thì sẽ có data ngay lập tức.

5.times do |i|
  Product.create(name: "Product ##{i}", description: "A product.")
end
0