ハトネコエ Web がくしゅうちょう

プログラミングやサーバー・Web制作、チームマネジメントなど得た技術のまとめ

Rails 5 から Rails 6 へアップデートした際の手順

以前 Rails 4 を Rails 5 に上げる記事を書きましたが、
同様に Rails 5 から Rails 6 へ上げる作業をおこないましたので、詳細を書きます。

1. なぜ上げたかったか

Rails 5 に上げた記事にて『Ruby 2.6 にした場合に deprecation warning が出てしまったこと』をバージョンアップの理由に書きましたが、
Rails 6 に上げた理由も実は同様で、Ruby 2.7 にした場合に deprecation warning が出てしまうため でした。

/bundle/ruby/2.7.0/gems/activemodel-5.2.4.4/lib/active_model/translation.rb:67: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/bundle/ruby/2.7.0/gems/actionview-5.2.4.4/lib/action_view/helpers/form_helper.rb:755: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/bundle/ruby/2.7.0/gems/actionpack-5.2.4.4/lib/abstract_controller/helpers.rb:67: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call

上のように、Ruby 3 で廃止される予定のキーワード引数の記法について deprecated warning が出ます。
(詳しくは Ruby 2.7 のリリースノート をご覧ください)

これに対応する修正は Rails 5 ではおこなわれていなく、
Rails 6.0.3 にておこなわれています。(プルリク: https://github.com/rails/rails/pull/37935

というわけで Ruby 3 が来ることも見据えて、Rails 6 に上げておきたかったわけです。

2. 下準備

  • config/application.rbconfig.load_defaults を 5.2 にまで上げる
    Rails 5 に上げたときでは config.load_defaults が 5.0 だったので、そこから差分を見つつ慎重に上げていった)
  • その当時での最新の Rails 5.2.4.4 にアップデート
  • 各種 gem をできるだけ最新バージョンにアップデート

3. Rails 6 に上げた際の作業内容

3-1. bin/rails app:update

Gemfile 内の rails バージョンを 5.2.4.4 から 6.0.3.3 に書き換えたのち、

bundle update rails

その後、

bin/rails app:update

で config ディレクトリ内のファイルをいくつか更新しました。

3-2. utf8_enforcer_tag メソッドの上書きについて削除

config/initializers/disable_utf8_enforcer_tag.rb として以下のようなファイルを置いていたのですが、
config.load_defaults を 6.0 にしたあとは不要になったので削除しました。

# Internet Explorer 5 対応で utf8=✓ がクエリに付く問題の解消。Rails 6 では default_enforce_utf8 オプションが false (デフォルト)であれば不要になる 
module ActionView  
  module Helpers   
    module FormTagHelper   
      def utf8_enforcer_tag  
        "".html_safe  
      end   
    end 
  end   
end

3-3. Zeitwerk の挙動を考慮し Inflector の設定

こちらの記事がわかりやすいのですが、
Zeitwerk ではファイル名から名前が推測されます

例えば class DailyKPIclass MonthlyKPI などのクラス定義がある場合、
ファイル名が daily_kpi.rb だと Zeitwerk 的には「DailyKpi ってクラス名があるのかな?」って探すのだけれど見つからないのでエラーになります。

このエラーを避けるには、 config/initializers/inflections.rb に以下のように指定してあげる必要があります。

ActiveSupport::Inflector.inflections do |inflect|
  inflect.acronym "KPI"
end

「Kpi って記すところもあるんだよなぁ〜」って場合には、
config/initializers/zeitwerk.rb などの名前でファイルを作って、

Rails.autoloaders.each do |autoloader|
  autoloader.inflector.inflect(
    "daily_kpi" => "DailyKPI",
    "monthly_kpi" => "MonthlyKPI",
  )
end

と、必要なところだけ、ファイル名と名前の関係性を作るということも可能です。

3-4. redis-rails の使用をやめる

You're using a cache store that doesn't support native cache versioning.
Your best option is to upgrade to a newer version of ActiveSupport::Cache::RedisStore
that supports cache versioning (ActiveSupport::Cache::RedisStore.supports_cache_versioning? #=> true).

上記のエラーが出たので調べて、
Rails 5.2 から redis が Rails に内蔵されるようになっていたことを知ったので、
redis-rails の使用をやめて redis_cache_store を使うよう変更しました。

redis-rails の最後のリリースって、2017/04 なんですね……。
https://rubygems.org/gems/redis-rails/versions

redis-rails を Gemfile から取り除いて bundle install したのち、
以下のように config を変更しました。

# before
config.cache_store = :redis_store, ENV["REDIS_URL"], { expires_in: 1.hour }

# after
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"], expires_in: 1.hour }

4. Rails 6 へのアップデートを終えて

Rails 5.2 から Rails 6.0 へのアップデートは、予想していた通り、
Rails 4 → Rails 5 へのアップデートよりもずいぶんとカンタンに終えられました。

Rails 6 へのアップデートを終えたことにより、
今後の Rails バージョンアップだけでなく Ruby のバージョンアップにも追従しやすくなりました。

Rails 5.2 をお使いの方は、ぜひ Railsガイドのアップグレードガイド を読みつつ、
Rails 6.0 へのアップデートをおこなってみてください。
Zeitwerk の挙動と、cookie 情報の後方互換性のない変更あたりに気を付ければ、だいたい大丈夫かと思います。

Materialize は iOS 13 以降でのバグがあるので注意!

React や Vue を使っていないサイトにおいて、
Materialize というCSSフレームワークは、
コンポーネントが充分に用意されていて、マテリアルデザインなサイトを製作する上でとても有用です。

Google が開発しているのにコンポーネントが不足したまま開発が放置されてしまった material-design-lite よりはよっぽど……。
マテリアルデザインを提唱してる大元なのだからがんばってほしかった・・・)

一方で、Materialize も 2018年9月に v1.0.0 を出して以降はリリースが止まってしまったのでバグがあります。
iOS 13 以降で発生するバグです。

iOS 13 でのバグとその原因

実際に見てもらうのが早いので動画にしました。

www.youtube.com

このように、押した位置と選択結果が一致しないことが多々あります。
動画は iOS 14 を公式 Simulator で動かしたもので、iOS 14 でも引き続きこのバグが起こることを確認できました。

なぜ起こるかと言うと、このフォーラムで書かれているように、どうやら
touchstart, touchend, そこから 0.2〜0.3 秒ほどおいて mousedown, click といった順にイベントが発火しているようです。

これにより、押された瞬間に dropdown.js は touchend イベントで反応するので閉じるアニメーションを開始するのですが、
押された箇所がどの要素か判定する _handleOptionClick メソッドは、
その名の通り click イベントによって発火するので、アニメーションが開始してから遅れて判定が走ります。

結果として、ドロップダウンが少し閉じている状態でのユーザーのタップ位置の li 要素が選ばれるので、
少し下の要素が選ばれてしまうのです。もしくは何も選ばれないのです。

作者の対応

これに対し、作者はこのコミットで、
ドロップダウンが閉じ始めるタイミングを要素が選択されたあとにすることで対応しています。
(_selectOption メソッドの最後で this.dropdown.close(); しているのがポイント)

残念ながらこの2019年9月の修正は v1.0.1 としてリリースされればよかったのですが、今のところリリースされていないので、
自らコンパイルして JS ファイルを作らない限りはこの修正の恩恵を受けられません。

また、ドロップダウンも同様の問題を抱えているのですが、その修正はされていません。
ドロップダウンメニューのリンクを踏んだら別のページに飛ばされるのですから、ユーザー体験はかなり悪いですね……。

どう対処するか

select について

3通り考えられます。
現在の https://github.com/Dogfalo/materialize の r1-dev の最新コミットから grunt を用いて JS を生成し、それを使う方法。
または、こちらの記事で紹介されているように、最新コミットから select.js だけを取ってきて materialize.js の後で読み込む方法です。
後者の方法はメソッドの上書きをすることでギリ動いているのではと思うので、materialize.min.js でなく materialize.js を使わないと上手くいかない気がします。

3つ目の方法は次で説明します。

dropdown について

この記事で書かれている方法がおもしろかったです。
touchend イベントを付与してしまってそちらに処理してしまう方法です。
元々の click イベントと二重に発生する可能性を持っていますがページ移動処理なのでそんなに困らないという、力技の解決方法でけっこう好きです。

他の解決方法としては、dropdown のオプションである closeOnClick を false に設定することで、
ドロップダウンメニュー内を選んだときに閉じるアニメーションが発生しなくなるので、
誤った箇所が選択されることが無くなります。

このオプションは select においても dropdownOptions というオプションから設定できます。select の問題の対処法の3つ目はこれを用いる方法です。
ただ、領域外を押すまでドロップダウンが閉じてくれないので、select で使うのはユーザー体験としては悪いでしょうね……。

最近の Materialize の動き

最近は Materialize に大きな動きがありました。

なかなか動きを見せない作者に業を煮やし、DanielRuf さんが新しいリポジトリを作成しました。

作成した5月からはしばらく動きを見せていませんでしたが、
(作者から「新しいコミュニティを作るのもなんだから、ここのリポジトリを手伝ってくれる人を募集するよ!」って声をかけられたからだと思う。その後 DanielRuf さんが「ぜひ」と返したあとで作者からの返信が無くなるんだけど……)
7月から活発に動くようになり、コミットが少しずつ重ねられています。

まだ最初のリリースも作られていないので自分でコンパイルしない限りは使えませんが、
上手く行けば、select バグなどが解消された v1.0.1 がリリースされる日が来るかも知れません。

正直、今のところは Material Design for Bootstrap を選択するほうが安全策と言えるのですが、
ボタンのデザインなど Materialize の方がデザインはカッコいいと思うので、今後の動きに注目です。

追記:メンバーになりました

「dropdown のバグを直すために少しいじってみるか〜」と、このブログを書いたあとでさわってみたら
いろいろ問題を見つけてしまい、プルリクエストをいくつか出してコントリビュートしていたところ、
翌日メンバーに招待されました。

英語苦手だけどレビューがんばらなくちゃな〜と思いました。

指定ディレクトリ以下の node_modules ディレクトリをすべて消す

node_modules ディレクトリは物によっては数百MBもの大きさになるので、
放ったままにしておくのはディスク領域の無駄となる。

そこで node_modules ディレクトリをすべて削除してしまいたいと考えていたところ、
とてもいい記事を見つけた。

How to Delete ALL node_modules folders on your machine and Free up HD space!

ここで、 node_modules にどれだけの容量を使っているのかを知るコマンド
それらを全て削除するコマンドが説明されていた。ありがたい。

node_modules にどれだけの容量を使っているか

find . -name "node_modules" -type d -prune | xargs du -chs

find コマンドで node_modules ディレクトリを探し、
prune オプションによってそれより下層のディレクトリは結果として出力しない。
で、 xargsdu コマンドに渡してあげて、ディレクトリごとの容量を見ているわけだ。

プログラミングデータをまとめているディレクトリで実行してみたところ、
以下のようになった。

 60M   ./isucon/isucon8/backup/isucon/torb/webapp/nodejs/node_modules
 34M    ./isucon/isucon8/backup/isucon/local/node/lib/node_modules
## (中略)
250M    ./practice/react/my-app/node_modules
101M    ./practice/rails/rails6-practice/node_modules
8.0G   total

なんと驚きの 8.0GB も node_modules に使用していた。

必要になったら再び npm install すればいいので、これらは削除してしまいたい。

すべての node_modules ディレクトリを削除

find . -name "node_modules" -type d -prune -exec rm -rf '{}' +

先ほどの探索コマンドに、exec オプションを付けて実行をしている形である。
つまりは以下のコマンドと同義である。

find . -name "node_modules" -type d -prune | xargs rm -rf

xargs の -p オプションを付けて実行すると、何が削除されるのか表示されるのでより安全。
(表示までやや時間がかかります)

find . -name "node_modules" -type d -prune | xargs -p rm -rf

node_modules ディレクトリごと削除されるので、
node_modules ディレクトリは残してその下層のファイルのみ削除したい場合は別のコマンドを考える必要がある。
自分はいくらか試したが、どうにも上手くいかなかったので、特に困らないし node_modules ディレクトリごと削除する方向に落ち着いた。