Rails Migration 資料庫變動的利器

紅寶鐵軌客
Join to follow...
Follow/Unfollow Writer: 紅寶鐵軌客
By following, you’ll receive notifications when this author publishes new articles.
Don't wait! Sign up to follow this writer.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.
寫程式中、折磨中、享受中 ......
1.2K   0  
·
2019/10/07
·
9 mins read


如果問我當初為什麼選擇用 Rails 開發後台,Rails 的 Migration 絕對是一個重要的原因,我很喜歡這個設計,清楚簡單又好用,當然啦,我後來對其他後台開發平台也沒有深入研究,也許其他的也很厲害,但是選了後就用到現在,沒有怨言。

這篇文章是 Migration 的使用流程、重點與一些特殊用法的記錄,如果要全面性的了解,還是要看以下的 Rails 指南的:Active Record Migrations,另外高見龍的文章也很生動有趣,很值得一看:Model Migration | 高見龍

 

 

什麼是 Rails migration ~ 就是讓 Rails 更新資料庫啦
  1. 你用很像英文的說法,告訴 rails 笨笨的自動 Migration 產生器,你要怎麼更改資料庫
  2. rails 會自動寫 migration 程式,但是一般來說,不夠好,你還是要手動修改,
  3. 執行這 migration 程式後,資料庫就會被修改,不管他是 mySQL 還是 PG,Rails 也會產生最新的資料庫綱要到 schema.rb,Rails 會盡力做到與實際 database 的 schema 相同。

 

我的 Migration 標準流程

我所有的開發文件上,只要有用到 Migration 都會有以下的註記,也就是我的標準作業流程:

增加 *欄位* 到 *Model*.
    欄位名稱: 說明所要加入的欄位理由與原因.
    執行 "rails g migration add_column_name_to_models column_name:type"
    修改檢查 Migration (通常是為了增加 default 及說明), 我會把改變的地方記錄下來
      class AddColumnToModels < ActiveRecord::Migration[5.0]
        def change
          add_column :models, :column_name, :type, default: "", comment: "this is comments"
        end
      end
    執行 rake db:migrate
      如果有錯,執行 rake db:rollback
    檢查 schema.rb, 要確定無誤.
    完成

所以,可以很清楚的看到我的流程就是以下的幾個步驟:

  1. 清楚的註記資料庫修改的欄位與目的
  2. 用 Rails 的自動 Migration 產生器,建立一個 Rails Migration 程式,我不會自己寫,太麻煩了,也容易錯
  3. 修改 Migration 程式,通常就是要加入預設的 default 值與 comment,當然還有其他用處
  4. 執行 Migration 程式,就是那行 rake db:migrate,如果有錯,就改,如果執行順利,但是又發現不對,可以用 rollback 回到 migration 前的資料庫狀態
  5. 最好就是要好好的檢查 schema.rb,看看所加入的欄位與預設等都有沒有錯,都對,就恭喜完成,任何問題就 rollback!
  6. 完成

我很喜歡各種 Rails 的自動產生器,如果要新增一個資料表,我會用 rails g scaffold,這樣一次到齊所有 MVP,所以我幾乎不會用到 migration 來新增資料表,大部分都是用在新增與刪除資料欄位,我想這也是大家會使用 migration 的地方。

以下是每一個點的一些使用細節。

 

註記資料庫修改說明與目的

這個我覺得是最重要的,相信我,資料庫的新增與修改當下都很清楚,但是一段時間後,保證全部忘光光,如果沒有清楚的文件記錄,往往就要花很多的時間來閱讀舊的程式碼,但是大家不要忘了,Rails 的程式碼可是分散在各處的,一個資料欄位的使用可以是在 Model 裡,也可以是在 controller 或是 view 裡面,重新閱讀程式碼是一件很痛苦的事,而且一定會有遺漏,最好的方法就是清楚的文件記錄,我建議的資料庫註記方式如下,註記在兩個地方:

精萃版註記:

特別是未來開發程式要注意的地方,就把簡潔關鍵的說明放在 migration 程式裡,Rails 5 以前要用 migration_comments 或是其他的 gem,Rails 5 以後就可以直接加在 migration 裡了,你只要在 migration 的欄位中加入 comment: 就好,例如:

t.integer :block_type, comment: '0-no block, 1-block by user, 2-block by customer, any other is no blocking'

這種用法只支援 mySQL 跟 PostgreSQL,有點可惜,加上後,就可以在 schema.rb 中看到這些精萃版的註記,非常的方便查詢,聽說 MySQL Workbench 跟 PgAdmin III 也可以讀到,我沒用就是了。

理由與思緒:

紀錄當初選擇這樣做的原因,特別是為什麼不用其他方式的理由,把它寫在 model 的頭,或是開發文件裡,選一個地方放就好,不要兩個地方都寫,不同步更亂。  理由與思緒的過程是很重要的,如果沒有詳細的紀錄,一是太可惜了,二是忘記了後,一碰到問題,又要再想一遍。

 

用 Rails 的自動 Migration 產生器

我看好多人都喜歡自已寫,我好怪,我就是喜歡用自動 Migration 產生器,我覺得電腦會寫最好,自己寫好容易出錯,而且電腦好快,以下是 Migration 產生器的常用方法:

# 標準寫法:用大小寫區分,Add Column To Models,連起來就對了
rails generate migration AddSummaryToProducts summary:text
# hey hey,用底線 _,也可以,更容易看懂
rails g migration add_summary_to_products summary:text
# 別忘了,一次可以加好幾個欄位,不需要一次寫一個
rails g migration add_detail_to_products summary:text exp_date:datetime
# Add...To 是增加,Remove...From 是刪除
rails g migration RemoveSummaryFromProducts summary:text
# 所以這中間就是為了指定 Model 名稱? 不見得,也可以建立(Create)model,
#   這樣就可以新增一個資料 table 
rails g migration CreateProducts name:string desc:string
# 也可以新增一個 reference,也就是一個 belong_to 的欄位啦,
rails g migration AddUserRefToProducts user:references
# 好像我就只用過這些,其他自己看 Rails guide 吧。

references 跟 belongs_to 是一樣的東西,選一個你喜歡的用。 

 

加入預設

有兩種預設,一是資料欄位的預設,另一個是要讀取別的 Model。

資料欄位加入預定(modifiers)及 index

一下是一些常用的 Migration 程式修改點:

:text, default: "" 預設空白
:integer, :default => 0 預設為 0
:boolean, default: true 預設為 true
:datetime, default: -> {'CURRENT_TIMESTAMP'} 預設為 migration 的時間
:date, :default => nil 預設為 nil
:decimal, precision: 5, scale: 2 小數點
:null => true null 允許
:comment 說明,前面講過了
t.belongs_to :column, foreign_key: true 設定 column 是 belong_to,建立 foreign key
t.references :column, foreign_key: true 設定 column 是 reference,建立 foreign key
add_index :table, :column, :unique => true 增加不重複 index,這是要加在 migration 程式後面的
刪除 timestamp 就沒有 created_at 及 updated_at

 

欄位的預設值來自別的 Model 

有時,你會想要新增一個 column,它的值是來自另外一個相關聯的 table,雖然這樣做有違背資料庫的一致性,但是可能是為了速度或是其他理由,這時,你的 migration 程式內就會需要新增一個讀取功能,基本上,migration 程式就是一個 ruby 程式,所以,就是加一段 ruby 進去。

我們來看這個 migration 例子,我們要在書本的 table 中新增一個語言別欄位,它的值來自作者:

class AddLanguageToBooks < ActiveRecord::Migration[5.0]

  # 避免更新時驅動 model 的 callbacks 或 validation
  class Book < ActiveRecord::Base; end

  def up
    add_column :books, :language, :string

    # reset 新增 column 的 table
    Book.reset_column_information
    # 更新資料,用 update_columns 來避免 callback.
    Book.find_each do |bk|
      authour = Writer.find(bk.writer_id)
      bk.update_columns(language: authour.language)
    end

  end

  def down
    remove_column :pc_main_folders, :language
  end
end

這是一個用 up/down 的方法,我想大家一定注意到,change 被改成 up/down 了,是的,這是為了要讓 rollback 能運做,大家可以想想,新增時 migration 程式中會讀取資料寫入新增的欄位,如果 rollback 也這樣做,一定死掉。

另一個不同是增加了 Book.reset_column_information,這基本就是將 table 重讀,確定等待欄位新增成功。

class Book < ActiveRecord::Base; end 有點爭議性,將新增的 model 關聯methods/callback/validation 完全暫停好嗎?這真的要看需要了,一般來說,新增的資料必須要與舊的資料關聯一致,這樣做有風險,自己做判斷吧。

更新資料就是執行一段 ruby / rails 程式碼,你可以讀取任何 active records 或是運算,甚至執行 SQL,很方便的,注意一下資料更新的時間,migration 本來就不快。

我喜歡改用 up/down 這個老方法的方法,但是如果你堅持,你可以續用 change,這時就要將更新資料的程式碼用 reversible 包起來,如下:

class AddLanguageToBooks < ActiveRecord::Migration[5.0]

  # 避免更新驅動 model 的 callbacks 或 validation
  class Book < ActiveRecord::Base; end

  def change
    add_column :books, :language, :string

    reversible do |dir|
      dir.up do
        # reset 新增 column 的 table
        Book.reset_column_information
        # 更新資料,用 update_columns 來避免 callback.
        Book.find_each do |bk|
          authour = Writer.find(bk.writer_id)
          bk.update_columns(language: authour.language)
        end
      end
      dir.down do
        # 沒事
      end
    end
  end

end

兩個都差不多啦,自取所好。

 

執行 Migration

不要怕,小心就好,Migration 程式如果寫錯了,一執行,就會出現錯誤,改到對就好,執行後,如果查看 schema.rb 有誤,就給他 rake db:rollback,當資料庫越來越大時,執行起來真的很有壓力,特別是要設定預設值時,又很慢,我的建議是,一次增加少少的欄位就好,仔細地在開發環境下好好的測試,特別是 validation 部分,一定要確定無誤後,才可以上傳到 production site,畢竟,如果客戶資料被搞掉了,那就問題太大了,上傳前務必要再三測試確認,真的不要急。

這篇文章沒有寫到一大堆更深入的 migration 用法,例如:增加刪除 foreign/index keys,改變 default,join_table,create table 等等,也沒提到種子資料的建立,這些部分就請看 rails guide 吧,我還不熟,不敢亂寫。

BTW,rails 5 以後都是用 rails db:xxx 了,我還在寫 rake db:xxx,大家別介意啦,就用習慣了。

就這樣,希望對大家有幫助。

 


WriterShelf™ is a unique multiple pen name blogging and forum platform. Protect relationships and your privacy. Take your writing in new directions. ** Join WriterShelf**
WriterShelf™ is an open writing platform. The views, information and opinions in this article are those of the author.


Article info

This article is part of:
Categories:
Tags:
Date:
Published: 2019/10/07 - Updated: 2019/10/20
Total: 2422 words


Share this article:
About the Author

很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
就,這會一點點,那會一點點⋯⋯




Join the discussion now!
Don't wait! Sign up to join the discussion.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.