Open6

ridgepole が updated_at で差分を出してしまう

Ohkubo KOHEIOhkubo KOHEI
  t.datetime   :updated_at,
               null: false, default: -> { 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' }
  t.datetime   :created_at,
               null: false, default: -> { 'CURRENT_TIMESTAMP' }

created_at は問題ないが、 updated_at はダメ

Ohkubo KOHEIOhkubo KOHEI

以下は ridgepole の diff.rb で default の Proc を call して文字列に変換している箇所。
上が現在のDBのダンプで下が Schema ファイル。

{:default=>"CURRENT_TIMESTAMP", :null=>false, :unsigned=>false}
{:null=>false, :default=>"CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", :unsigned=>false}

ON UPDATE 以降が消えている。

Ohkubo KOHEIOhkubo KOHEI
+------------+--------------+------+-----+-------------------+-----------------------------+
| Field      | Type         | Null | Key | Default           | Extra                       |
+------------+--------------+------+-----+-------------------+-----------------------------+
| updated_at | datetime     | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| created_at | datetime     | NO   |     | CURRENT_TIMESTAMP |                             |
+------------+--------------+------+-----+-------------------+-----------------------------+

sql からテーブル定義を見ると、 Extra というところに入ってしまっているものが dump されていないのがわかる。

Ohkubo KOHEIOhkubo KOHEI

ridgepole は多分、 rails の db:dump の機能を使って現DBから Schema を生成し、 Schemafile と差分検出して適用しているのだと思うが、 db:dump が extra を出してくれないのだと思う。
(rails 標準の migration 機能は db:dump を見ないので問題にはならない)

Ohkubo KOHEIOhkubo KOHEI

updated_at の default: -> { 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' } が dump されないので比較ロジックを上書きして無視する
https://github.com/ridgepole/ridgepole/blob/v2.0.1/lib/ridgepole/diff.rb#L616-L621

module RidgepoleDiffPatch
  def normalize_default_proc_options!(opts1, opts2)
    if opts1[:default].is_a?(Proc) && opts2[:default].is_a?(Proc)
      opts1[:default] = "#<Proc>" # opts1[:default].call
      opts2[:default] = "#<Proc>" # opts2[:default].call
    end
  end
end
class Ridgepole::Diff
  prepend RidgepoleDiffPatch
end
Ohkubo KOHEIOhkubo KOHEI

bin/ridgepole に外からパッチを当てる方法がない。とりあえず必要なのは applydry-run なので、使うとこだけ適当にコピーして rake task から呼ぶようにした。これで patch を当てられる。

def ridgepole_apply(schemafile_, spec_name, db_name: nil, env: Rails.env, dry_run: false)
  schemafile = Rails.root.join("db/schemas/#{schemafile_}")
  logger = Ridgepole::Logger.instance
  options = { dry_run:, debug: false, color: $stdout.tty? }

  # Ridgepole::Config.load は PORTAL_DATABASE_URL による上書きを処理しない
  # config = Ridgepole::Config.load('config/database.yml', env, spec_name)
  config = ActiveRecord::Base.configurations.configs_for(env_name: env, name: spec_name).configuration_hash

  config = config.merge(database: db_name) if db_name
  Mysql2::Client.new(config.except(:database)).query("CREATE DATABASE IF NOT EXISTS `#{config[:database]}` CHARACTER SET utf8mb4")

  client = Ridgepole::Client.new(config, options)
  delta = client.diff(schemafile.read, path: schemafile.to_s)
  differ, out = delta.migrate(noop: dry_run)

  logger.info('No change') unless differ
  puts out if out # rubocop:disable Rails/Output
end