AR/MySQLでの"default"の変更について

MySQLで以下のようなカラムがあったとして

t.integer  "foo",        limit: 4,     default: 0, null: false
#`foo` int(11) NOT NULL DEFAULT '0',

以下のようなマイグレーションを実行したとすると

change_column("articles", "foo", :integer, null: false)

実行されるALTER文はこうなる。

ALTER TABLE `articles` CHANGE `foo` `foo` int(11) DEFAULT 0 NOT NULL

明示的にdefault: nilを設定すると…

change_column("articles", "foo", :integer, null: false, default: false)
ALTER TABLE `articles` CHANGE `foo` `foo` int(11) DEFAULT 0 NOT NULL

DEFAULTを削除するためには「null: true」または「nullを付けない」ようにする必要がある。

change_column("articles", "foo", :integer, null: true, default: nil)
ALTER TABLE `articles` CHANGE `foo` `foo` int(11) DEFAULT NULL

ActiveRecordのコード

ActiveRecordのコードでは明示的にdefaultが付けられていない場合、既存のカラムの値を使うようになっている。

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#L868

      def change_column_sql(table_name, column_name, type, options = {})
        column = column_for(table_name, column_name)

        unless options_include_default?(options)
          options[:default] = column.default
        end

options_include_default?のコードは以下の通り。

          def options_include_default?(options)
            options.include?(:default) && !(options[:null] == false && options[:default].nil?)
          end

options:defaultが含まれていて」かつ「options[:null]falseではない」とき、:defaultが含まれていると見なす。

change_columnの動作としては正しい気がするが、NULL DEFAULT 0のカラムをNOT NULLに一気に変更することができないと思うけど、そういうパターンは少ないからいいのだろうか、、、

default: nilについて

ActiveRecordのコードを読むとdefault: nilは、null:false以外のとき有効なよう。

https://github.com/rails/rails/blob/v4.2.1/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb#L84

          def add_column_options!(sql, options)
            sql << " DEFAULT #{quote_value(options[:default], options[:column])}" if options_include_default?(options)
            # must explicitly check for :null to allow change_column to work on migrations
            if options[:null] == false

ridgepoleでの扱い

「未定義はnil」としました。 が、null: false以外のときの動作が気になる、、、

https://github.com/winebarrel/ridgepole/commit/971aec10ef086f40b8b820b7f8a33bfe29c6f281#diff-5a06f7bcdc0cee42290e2d9c68ade46cR262

if not opts.has_key?(:default)
  opts[:default] = nil
end