ActiveRecord 的 default_attributes
- Backend
- 13 Aug, 2020
在 Rails server 沒有重開的情況下,更動資料庫表的欄位預設值,會有意想不到的事情發生
以下是用 Rails 6.0.3.2 + MySQL 5.6 實驗
事情是這樣的
有一張 tasks 表,有個欄位 completed,預設值為 false,且不能為 NULL
create_table "tasks", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.boolean "completed", default: false, null: false
end
- 開起第一個 Rails console(console1), 可以成功新增一個
task> Task.create(completed: false) - 開啟第二個 Rails console(console2),將
tasks表的completed預設值拿掉:> ActiveRecord::Base.connection.execute('ALTER TABLE tasks MODIFY COLUMN completed tinyint(1) NOT NULL;') - 在 console1,再次新增一次 `task
Task.create(completed: false)
結果竟然噴了ActiveRecord::NotNullViolation (Mysql2::Error: Field ‘completed’ doesn’t have a default value)`
為什麼呢
仔細觀察了一下第三步 console1 裡的 log,可以發現 raw SQL 其實是長這樣 Task Create (0.4ms) INSERT INTO tasks VALUES () 。又因為目前資料庫的completed 欄位其實沒有預設值了,但是又不能是空值,所以才會噴錯。
至於為什麼 Task.create(completed: false) 產生的 SQL 會沒有我要插入的值,是因為 console1 的 Task class 裡面其實存了一個 @default_attributes (可以這樣看
> Task._default_attributes.to_h # => {“id”=>nil, “completed”=>false} )
所以在 console1 的 Task 仍然認知 completed 欄位的預設值是 false ,因此 Task.create(completed: false) 時,Rails 認為completed 的預設值跟你要新增上去的值一樣,所以不需要將它放進 SQL 裡面,所以才會發生錯誤。
只要在 console1 將 cache 清除,就可以成功新增了:
> Task.reset_column_information
> Task._default_attributes.to_h # => {“id”=>nil, “completed”=>nil}