依頼いただいて久しぶりに言語とFWのバージョンアップの対応を行ったので、苦労した点などを。
PHP(4->5)などのバージョンアップぐらいしか経験がなかったので不安でしたが、なんとかなりました。
バージョン情報
元のバージョン
- Ruby
- 2.7.4
- Rails
- 6.1.4
アップしたバージョン
- Ruby
- 3.2.2
- Rails
- 7.1.3
やる前に想定していたこと
- 最近参画したプロダクトで知らない箇所しかないので苦労するだろうなぁ
- メジャーバージョンを上げるから、コードの書き方が変わる箇所も多々あるだろうなぁ
- メソッドとかのDeprecateのWarningとかもすごいだろうなぁ
- 設定とかも大きく変わるだろうから、精査しないといけないな
- Gemとかそのままだと動かないものがあるからバージョンアップ必要だろうなぁ
- Gemもそもそも更新止まっていて、更新できないものもあるんじゃないかなぁ
- リリース後にトラブルがあった時に戻すの大丈夫かな
いざ開始!そして見えてくる苦労
幸いなことに
Ruby2.7.4 -> 3.2.2でアプリケーションのコードはそれほど大きく変わりませんでした
to_s(:delimiter)
などがto_formatted_s(:delimiter)
に変わったとか、
ハッシュ引数の渡し方が変わったとか
tt(str, options = {})
end
options = { a: 1, b:1 }
tt(str, options)
# ではなく
tt(str, **options)
そんな程度でした
いざGemのアップデート!
今後のことを考えて、今回は全体的にgem updateを実行して、なるべく最新にしていきました。
その時、当然アプリケーションコードそのままだと動かないとか、設定方法が変わるとかは想定していましたが、想定以上に時間がかかってしまいました
Ransack5系からransackable_attributesの設定が必要
Ransackの検索項目が自由すぎたせいか、検索とソートできる内容を制限する機能が追加されました。
paramsのpermitのような設定ですね。
def self.ransackable_attributes(auth_object = nil)
["id", "memo", "name", "updated_at"]
end
乱暴にやると、
def self.ransackable_attributes(auth_object = nil)
# 全attributesを検索対象にするよ!という設定
authorizable_ransackable_attributes
end
で今まで通りの挙動になりますが、確かに制限はあったほうが良いかなと思い、全モデルに設定していきました。
しかし、これを行わないと、エラーが発生し、画面確認ができないので、
- 検索している箇所を特定する
- 検索しているカラムを特定する
- 検索に使用されているカラムを設定していく
という工程を全画面で確認しつつ登録していくことに・・・。
検索はまだエラーがでるので良いのですが、ソートは設定されていなくてもエラーが出ず、最初ソートも対象だと気が付かず検索のみ対象に行っていたところ、後ほどソートができないことがわかり
- ソート箇所を使用している箇所を特定する
- ソートしているカラムを特定する
- ソートで使用されているカラムを設定していく
という二度手間な作業となりました。
管理画面系でテーブルデータが多く、検索やソートも多用されているので、辛かったです。
一通り追加後、確認していきましたが、多分ソート効かない箇所があるんではないかと思っています。
また、関連するモデルでの検索を使用する場合は、 ransackable_associations
で検索対象とするモデルの指定も必要です。
Deviseでユーザー招待ができなくなった!
次にユーザーの編集や新規ユーザー追加をしていて確認していたところ、
ユーザー追加を行うとエラーやメッセージが何も出ず、編集画面に戻ってくるという症状が出ました。
Deviseの問題なのか、別の問題なのか、拡張を外したりしつつ確認していると、
DeviseInvitable関連の一部であるvalidate_on_invite: true
を外した時に動くことを確認できました。
validate_on_invite: true
はメールアドレスのバリデーションを行ってくれる機能で、
メールアドレスが不正であれば送信されないという仕組みです。
deviseとの問題か?など設定方法が変わったのか、などを調べていくと、パスワードがないからバリデーションエラーになるという仕様になったようです。
いや、招待の時パスワードいらなくない?
と思いながら、
invitations_controller.rbで
protected
def invite_resource(&block)
# パスワードをinvite_paramsハッシュに追加(Deviseのバリデーションで弾かれるため)
params = invite_params.to_h
params[:password] = SecureRandom.base64
resource_class.invite!(params, current_inviter, &block)
end
と、無理やり初期パスワードを設定して対応しました。
こちらの記事に救われました。
突然ユーザー追加だけできなくなり、最初は全く原因がわからなかったので焦りました。
CarrierWaveでアップロードした画像 xxx.jpgが、xxx(2).jpg となる
アップロードした画像がなんかおかしいので、調べていたら、
xxx.jpg
をアップロードしたのに、画像名がxxx(2).jpg
になってしまう問題が発生しました。
どうも重複した画像を上げる際に自動で接尾辞をつけてくれるようになったのですが、
問題としては重複した画像などないという事です。
オンリーワンかつナンバーワンの画像たちをアップロードしても、(2).jpgとしてくれるのです。
issueやPRなどを見ても全くわからず、
バージョンを上げたり下げたりすると、直る時があるとなり、バージョンの問題と思いました。
3.0.0以降ではアップロードされたファイルに接尾辞がついてしまうのです。
Cange logを見てみると、それっぽい内容がありました
Support adding suffix to filename on store when path collides with the existing ones (@mshibuya 07a5632, #1855)
どうしたら直るのか、悩みながら、該当箇所のアップローダの設定を、ふと何の気もなしに
class Image< ApplicationRecord
#....
# belongs_toやvalidateなど、色々な設定
#....
mount_uploaders :images, ImageUploader
とされていた箇所を、上に上げてみました。
class Image< ApplicationRecord
mount_uploaders :images, ImageUploader
これによりアップロードされる画像は xxx.jpg
と正常になりました。
以前の処理では画像名の重複チェックのバリデーションとかが上にあったので、それらとの兼ね合いなのかなと考えながら、こちらは無事、迷宮入りとなりました。
迷宮入りさせようかと思ったのですが、もう少し調べてみると
と同時アップロード時の仕様だよとのことだったのですが、1枚でアップロードしても xxx(2).jpg
となるので、やはりvalidationかattributesとかの兼ね合いっぽいのと、 ImageUploader
に
class ImageUploader < CarrierWave::Uploader::Base
...
def deduplicated_filename
filename
end
end
とすることで、重複ファイル名は解消できました。
FakerとPublicSuffixの戦い
とある処理で、URLを登録するという箇所があり、
RSpecで検証していた時に、RSpecの失敗する箇所がありました。
URL登録が失敗するんです。
ただ、該当箇所を画面で確認してみると、すんなり登録できてしまい、原因がわかりませんでした。
バリデーションの問題だろうか、と思い、save
をsave!
に変えたところ、想定通りURLが不正です
とバリデーションで失敗していて登録ができない模様です。
ではURLが問題なのだろうか、とURLの確認をしてみると、 https://hogehgoe.example
やhttps://hogehoge.to
など、特に問題なさそうな感じがしました。
うーむ、なぜだろう、とバリデーション箇所を調べていると、
def validate_each(record, attribute, value)
return if value.blank? || (PublicSuffix.valid?(value, default_rule: nil) && EXCLUDE_CARACTERS.all? { |caracter| value.exclude?(caracter) })
record.errors.add(attribute, 'error_dayo')
end
と特に問題なさそうな気が
ただ、PublicSuffix
を知らなかったので、こちらを調べてみると、
Public Suffix for RubyというGemのようです。
こちらはドメインパーサーで、パースしたドメインが有効か無効かを判定してくれるありがたい機能のようです。
しかしこちらにドメインを入れてみると
value = 'hogehgoe.example'
[5] pry(main)> PublicSuffix.valid?(value, default_rule: nil)
=> false
と**このドメインは有効ではないよ!**という判定になりました。
今まで通っていたのに、なぜ。。。
そもそもこの、default_rule: nil
とは一体・・・?
試しに、default_rule: nil
を外してみると、
value = 'hogehgoe.example'
[5] pry(main)> PublicSuffix.valid?(value)
=> true
true
になりました!
これにて一件落着!
ともいかず、原因を追ってみると、 default_rule: nil
は
Strict validation (without applying the default * rule)
と、厳格なルールの指定のようです。
厳格なルールとは・・・? とコードを追ってみると
# @example Parse an invalid (unlisted) domain with strict checking (without applying the default * rule)
# PublicSuffix.parse("x.yz", default_rule: nil)
# # => PublicSuffix::DomainInvalid: `x.yz` is not a valid domain
default_rule: list.default_rule
のように、引数があり且つ、nilである場合は、こちらのリストを使用してチェックするよ!というような内容の処理がありました。
リストとは・・・?
とさらにコードを追っていくと、data
ディレクトリに存在していて、確かにトップレベルドメイン(comとかjpとか)の一覧があるではありませんか!
ここに、当たり前ですが、example
などは入っておらず、バリデーションエラーとなったようです。
しかし今までは動作していたテストなので、
そうなると、以前使っていたバージョン、2.0.0の使用していたバージョンにはexample
が存在していたのでは!と思ったのですが、別にありませんでした
なぜ動いていたのかは永遠の謎として墓場に持って行こうと決意し、
RSpecで使用するURLを生成している箇所を確認すると
link_url { Faker::Internet.url }
となっていました。
このFaker::Internet.url
は FakerというGemで
架空のURLを生成してくれたり、色々と偽のデータを作成してくれるとて便利なGemのようです
ただ、今回の問題となっているURL生成の際に example
などの存在しないドメインで生成してしまうようです。
しかし、ホスト名は指定できるようで
link_url { Faker::Internet.url(host: 'faker-domain.com') }
などにすると、 https://hogehoge.faker-domain.com
と生成されるURLのホスト名が指定できるので無事、テストが通るようになりました。
こうして、FakerとPublicSuffixの戦いは私の一人負けで幕を閉じました。
以上がGem関連で苦労した点です、他にも発見できず、表に出ていないだけで、隠れGem設定ミス&変更していて問題になっているなどが存在しているかもしれません。
しかし全てのGem使用箇所などをチェックは到底不可能なので、今回は全て100%達成というよりは、
- 事業としてクリティカルな箇所については動作するかどうかをしっかり確認する
- 今後発覚する問題についてはアップデートで対応する
- 問題が大きいようであればロールバックする
という形で進めているため、今後更新する可能性があります。