Rails 升級到 7.1, ruby 升級到 3.3.4

by Aaron • 12/25/2024, 6:21:12 PM

最近產品開發的進度比時程來的快一些, 想當然在工程師的事件只有準時做完和需要多一點時間做完, 沒有提早做完這件事, 讓我可以偷一點點時間可以對產品進行一些升級與優化。

Notice: 這不是教學, 而是升級過程的 murmur

現況

至於其他版本

Ruby 3.4
status: normal maintenance
release date: 2024-12-25
normal maintenance until: TBD
EOL: TBD

Ruby 3.3
status: normal maintenance
release date: 2023-12-25
normal maintenance until: TBD
EOL: 2027-03-31 (expected)

Ruby 3.2
status: normal maintenance
release date: 2022-12-25
normal maintenance until: TBD
EOL: 2026-03-31 (expected)

Ruby 3.1
status: security maintenance
release date: 2021-12-25
normal maintenance until: 2024-04-01
EOL: 2025-03-31 (expected)
Ruby VersionRequired Ruby VersionRecommended Ruby Version
8.0.Z>= 3.2.03.3
7.2.Z>= 3.1.03.3
7.1.Z>= 2.7.03.2.Z
7.0.Z>= 2.7.03.1.Z
7.0.0>= 2.7.0 < 3.13.0.Z
6.1.Z>= 2.5.03.0.Z

看起來低標應該是 rails 7.1, ruby 3.2

Separation of positional and keyword arguments

詳細可以看官方文件, 差異有點大… 不過最主要需要更改的的是 arguments 的部分, 測試覆蓋率如果不高的話會有點難過

def foo(a, b = 2,  *args, c: 3, **kwargs)
  pp a
  pp b
  pp args
  pp c
  pp kwargs
end

foo(1, 2, 3, {c: 4, d: 5})

# ruby 2.7
# 1
# 2
# [3]
# 4
# {:d=>5}

# ruby 3
# 1
# 2
# [3, {:c=>4, :d=>5}]
# 3
# {}

File.exist? and File.exists?

這個在 ruby 3.2 被更改了… 但這個比較好查找, 以下這行就可以解決了

grep -r "File.exists?" $(bundle show --paths)

升級跟 ruby 3 不相容的 gem

這個有很多工具可以自動幫你檢查哪些不相容, 但是… 應該能升的 gem 都升上去看看好了

結果一路升到了 ruby 3.3.4

升級 rails 版本

看了一下官方升級指南, 改動其實蠻多的, 不過條列得很清楚, 應該可以順利吧?

  1. rails app:update 這要仔細看 diff, neovim 也有需多 plugin 可以很容易的更新 config files

  2. Zeitwerk

在之前如果有些 class name 不想要 nest 太多層, 例如 errors 相關的, 我們可能會

# /libs/errors/errors.rb
module Errors
  class FooError < CustomError; end
  class BarError < CustomError; end
end

# others

raise Errors::FooError

但是這違反命名規則

解法

# config/initializers/zeitwerk.rb
SUBDOMAINS = [
]

Rails.autoloaders.each do |autoloader|
  SUBDOMAINS.each do |sub|
    autoloader.ignore(Rails.root.join(sub))
  end
end

rails_event_store

  1. 這按照升級指令的話會生成 5 個 migration file, 部分是不能 rollback 的, 需要自己寫 rollback 的反向 sql, 不然沒辦法切 branch 回去開發
  2. event_store_events 整張表照道理來說會蠻大的, 這個生成的 migration file 其實蠻危險的, 畢竟更改表的結構, 需要做好 migration plan
class CreatedAtPrecision < ActiveRecord::Migration[4.2]
  def up
    #...
  end

  def down
    change_column :event_store_events,            :created_at, :datetime, precision: nil
    change_column :event_store_events_in_streams, :created_at, :datetime, precision: nil
  end
end

class NoGlobalStreamEntries < ActiveRecord::Migration[4.2]
  def up
    #...
  end

  def down
    remove_index :event_store_events, :event_id
    execute "ALTER TABLE event_store_events DROP PRIMARY KEY, MODIFY id INT NOT NULL"
    remove_column :event_store_events, :id
    rename_column :event_store_events, :event_id, :id
    execute "ALTER TABLE event_store_events ADD PRIMARY KEY (id)"
  end
end

class AddForeignKeyOnEventIdToEventStoreEventsInStreams < ActiveRecord::Migration[7.0]
  def up
    # ...
  end

  def down
    remove_foreign_key :event_store_events_in_streams, column: :event_id if foreign_key_exists?(:event_store_events_in_streams, :event_store_events, column: :event_id)
  end
end

最後

內容減少了許多痛苦的過程, 最深刻的感想是寫測試很重要!!!

Reference

© 2025 Aaron Li. All Rights Reserved.