Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ: Rails 7をRuby 3.1で動かす、クックパッドのRuby 3.1解説記事、Rails 6->7更新ほか(20220112)

あけましておめでとうございます。新年最初の週刊Railsウォッチです。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙏

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Rails: 先週の改修(Rails公式ニュースより)

今回はRails 7のリリース後の更新情報から見繕いました。

なお年末に2021年度の更新まとめ情報も公開されています。ひととおりウォッチで扱ったはずです。

🔗 3-tierコンフィグでのrails dbconsoleの挙動を修正

3-tier(階層)構成のアプリケーションにはデータベースの"primary"エントリがない可能性がある。Railsのdbconsoleを起動するときは、primaryエントリがあることを前提とせずに、最初のデータベースを読み込むべき。
この修正でメッセージがより明確になる。環境にデータベースがないためにデータベースが提供されていない場合は「primaryが存在しない」ではなく「データベースが存在しない」というメッセージを返すべき。
この変更では、エラーメッセージ以外のアプリケーションの挙動は変更されないはずだが、primaryエントリがないアプリではうまくいかないので、マルチプルDBをサポートするバージョンにもバックポートする必要がある。
同PRより


つっつきボイス:「マルチプルデータベース用か」「これはRailsのdbconsoleのメッセージを修正したんですね」

参考: 3階層システム(三階層システム)とは - IT用語辞典 e-Words

「Railsのdbconsole(bin/rails dbconsole)ってほとんど使ったことないかも」「そもそも存在を知りませんでした」「database.ymlのコンフィグを使ってpsqlやmysqlなどのDBクライアントを呼び出すだけのコマンドですが、DBクライアントや必要なヘッダファイルなども入っていないと使えません」「たしかに自分の環境でやってみたら動かなかった😢」

参考: 1.5 bin/rails dbconsole -- Rails のコマンドラインツール - Railsガイド

🔗 change_tableで認識できないオプションの例外を発生するようにした

このプルリクは、change_tableブロック内のテーブルでyieldするTableが、if_existsif_not_existsキーワード引数を持つメソッドを受け取ったときに例外を発生させる。
これにより、bulk: trueオプションを指定してchange_tableブロックを呼び出すと黙ってオプションが無視され、このオプションなしで呼び出すとそのとおりになるという予期しない振る舞いを防げる。
同PR冒頭より


つっつきボイス:「change_tableブロックにサポートされていないオプションを渡すと例外を発生するようになったんですね」「以前は通っちゃったのか」

「ちなみに自分はマイグレーションでif_existsif_not_existsを指定するのは怖いのであまりやりません: 以下みたいにif_existsでインデックスがあればインデックスを消すとか」「書きたくない気持ちわかります」

# 同PRより
change_table(:table) do |t|
    t.column :new_column, if_not_exists: true
    t.remove_index :old_column, if_exists: true
end

「Railsのマイグレーションは、途中で失敗したときにマイグレーションが中途半端に残ってしまう可能性があるんですよね」「そうそう、そうなると手動で残りを反映することになって残念な気持ちになる」「Railsのマイグレーションを使いたがらない人がいるのもちょっとわかります」

参考: Rails migration から ridgepole に移行した - DEV Community 👩‍💻👨‍💻

🔗 HostAuthorizationミドルウェアでIP+ポート番号を利用できるようになった

ホストがIPアドレス+ポート番号形式の場合、IPAddrオブジェクトの比較でエラーが発生してミドルウェアがリクエストを拒否していたので、IPAddrオブジェクトを比較するときにホストからホスト名を抽出するようにした。
#43864のコメント(コメント)が修正されるはず。
同PRより


つっつきボイス:「ActionDispatch::HostAuthorizationといえば少し前にセキュリティ修正されたヤツですね↓」

Railsセキュリティ修正6.0.4.1と6.1.4.1 がリリースされました

127.0.0.1:3000みたいにIPアドレスとポート番号を指定できなかったのが修正されたのね」「へ〜、こんなのがあったとは」「これは動かないと困るヤツ」「そういえばRails 7リリース間近でこれがマージされたのを見た覚えがあります」

# actionpack/lib/action_dispatch/middleware/host_authorization.rb#L18
  class HostAuthorization
    ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
-   PORT_REGEX = /(?::\d+)?/.freeze
+   PORT_REGEX = /(?::\d+)/ # :nodoc:
+   IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
+   IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
+   IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
+   VALID_IP_HOSTNAME = Regexp.union( # :nodoc:
+     /\A#{IPV4_HOSTNAME}\z/,
+     /\A#{IPV6_HOSTNAME}\z/,
+     /\A#{IPV6_HOSTNAME_WITH_PORT}\z/,
+   )

IPAddrクラスをちゃんと使うようになった↓」

# actionpack/lib/action_dispatch/middleware/host_authorization.rb#L39
      def allows?(host)
        @hosts.any? do |allowed|
-         allowed === host
-       rescue
-         # IPAddr#=== raises an error if you give it a hostname instead of
-         # IP. Treat similar errors as blocked access.
-         false
+         if allowed.is_a?(IPAddr)
+           begin
+             allowed === extract_hostname(host)
+           rescue
+             # IPAddr#=== raises an error if you give it a hostname instead of
+             # IP. Treat similar errors as blocked access.
+             false
+           end
+         else
+           allowed === host
          end
        end
      end

🔗Rails

🔗「Rails 7.0 + Ruby 3.1でゼロからアプリを作ってみたときにハマったところあれこれ」


つっつきボイス:「jnchitoさんのこの記事とても参考になりました❤️: TechRacho記事も引用してくださってます🙏」「rails newってなかなか1回で終わらなくて、オプションを足して何回かやり直したりしますよね」「するする: そういえば昨年業務でrails newする機会は1回しかなかった😢」「これはもうしょうがないですね」

「以下の記事にも書きましたが、thor gemが出していたwarningも修正されました↓」「そういえばRails 7.0.0でRuby 3.1がまだ動かないんだったか(編集部注: つっつきの翌日にRails 7.0.1がリリースされてRuby 3.1で動くようになりました↓)」

Rails 7.0.0+Ruby 3.1.0のGemfileは7-0-stableブランチの指定が必要

Rails 7.0.1がリリースされました

「jnchitoさんの記事で今頃知ったんですが、Rails 7ではアセットをビルドするためにbin/railsではなくbin/devでdevelopmentサーバーを起動するんですね」「お、なるほど」「ちなみに私はrails newするときに例のpropshaft↓を有効にしたんですが、その場合はbin/devは生成されませんでした: propshaftはビルド不要なので従来のbin/railsでdevelopmentサーバーを起動するみたいです」

Propshaft gem README(翻訳)

「Rails 7ではpropshaftのときもbin/devで起動するように統一してくれたら、起動コマンドを使い分けなくて済むのでよさそうですけど」「それもそうですね」「bin/devはforemanを起動するらしいので、もしかするとpropshaftでは余分なforemanをインストールしないようにしているのかもしれませんが」

「あ、自分はRails 6を7にアップグレードしたんですが(後述)、bin/devはありませんね」「binstubを生成するみたいにbin/devも生成できそうですけどね🤔」「とりあえずrails assets:precompileは引き続き使えるみたい」

後で調べてみましたがbin/devを単独で生成する方法は見つけられませんでした😢。

「Rails 7のTurboはややこしそう」「このstatus: :unprocessable_entity↓は、Rails 7でscaffoldすると追加されていたので気づきました」「なるほど、Turboがこれを解釈するので必要になるのか」

# 同記事より: コントローラ
  def create
    @project = current_user.projects.new(project_params)

    respond_to do |format|
      if @project.save
        format.html { redirect_to project_url(@project), notice: "Project was successfully created." }
        format.json { render :show, status: :created, location: @project }
      else
        # status: :unprocessable_entity が必須!!
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @project.errors, status: :unprocessable_entity }
      end
    end
  end

「以下のような変更↓はrails-ujsからTurboに変わることで必要になるんだと思いますが、こういうアップグレード作業は地味に大変そう」「ビューの修正が増えそうですね」「確認ダイアログの出し方もrails-ujsのときと変わるのか」「rails-ujsは昔jQuery版があったほど歴史が長くて自分もそれに慣れているので、新しい書き方に慣れないといけなくなるかな」「この感じだと一括置換が効くとも限らなさそうですね...」「こういう部分をヘルパーメソッドに切り出すことも多いので、それにも配慮しないといけなさそう」

<!-- 同記事より: ERB -->
<!-- 従来の書き方(rails-ujsを使っている場合) -->
<%= link_to 'Delete', [task.project, task], method: :delete %>

<!-- Turboを使っている場合 -->
<%= link_to 'Delete', [task.project, task], data: { turbo_method: :delete } %>
<!-- 同記事より: ERB -->
<!-- link_toの場合 -->
<%= link_to 'Delete', [task.project, task], data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %>

<!-- button_toの場合 -->
<%= button_to "Delete", [task.project, task], method: :delete, form: { data: { turbo_confirm: "Are you sure?" } } %>

「destroyアクションでTurboを使うにはこのstatus: :see_otherも必要なのね↓」「これはscaffoldでも生成されなかったので、jnchitoさんの記事を読んでなかったら見落とすところでした」

# 同記事より: コントローラ
  def destroy
    @task.destroy

    respond_to do |format|
      # status: :see_other が必須!!
      format.html { redirect_to @project, notice: "Task was successfully destroyed.", status: :see_other }
      format.json { head :no_content }
    end
  end

「今さらですがTurboって何をするんでしたっけ?」「Rails 7からはHotwireがデフォルトになって、それと一緒に入るTurboがデフォルトのフロントエンド向けライブラリになります」「なるほど、rails-ujsの代わりになる感じですか」「Turboはrails-ujsも置き換えますが、それ以外にも機能があります」「Turbolinksはどうなるんですか?」「Turboに変わったのでRails 7にはデフォルトでは入りません」「ちなみにTurbolinksはもう開発も止まっています

速報: Basecampがリリースした「Hotwire」の概要

「Turboは独立してインストールできるのかな?」「そういえばRails 6.1.1にTurboをインストールする記事↓を見かけたのでできそうです」「HotwireはTurboが必須のようですね」

参考: Rails 6.1.1にTurbo Driveをインストールして困ったこと

「なぜTurboでインターフェイスを変更したんでしょうね?」「おそらくですが、rails-ujsと共存できるようにするためじゃないかな: たしか共存はできるはずですし」「それなら最初は共存させておいて、それから少しずつ書き換えて移行することもできそう」「名前空間が衝突するとそれができなくなるからでしょうね」

「jnchitoさんの記事でもrails-ujsが非推奨になったとある」「いずれTurboに移行しないといけなくなるのか」「その代わりTurboならAjax的なことやturbo_frame_tagのような強力な機能も使えるようになりますけどね↓」「rails-ujsを昔読んでみたときは比較的シンプルだった覚えがあるんですが、Turboの方が大きいから読むのに気合が要りそう」

<!-- 同記事より: app/views/tasks/_completed.html.erb -->
<%= turbo_frame_tag task do %>
  <%= form_with model: task, url: [:toggle, task.project, task] do |f| %>
    <label class="<%= task.completed? ? 'completed' : '' %>">
      <%= f.check_box :completed, onchange: "this.form.requestSubmit()" %>
      <%= task.name %>
    </label>
  <% end %>
<% end %>

「deviseがまだTurboに対応していないとあるけど、これはいずれ対応すると思います」「deviseのような認証部分のコードはなるべく改変したくないのでぜひ対応して欲しい」「そうそう」「いずれにしろRailsは7.0.1が早々に出そうな予感がします」

つっつきの翌日に本当にリリースされました↓。

Rails 7.0.0+Ruby 3.1.0のGemfileは7-0-stableブランチの指定が必要

🔗 RailsでRuby 3.1を使う

なお、Rails 7.0.1がリリースされたことで同Gistは更新されました。


つっつきボイス:「yahondaさんのこのGistも有用な情報がいろいろあって、以下の記事を出すときに助かりました↓」「Rail 7.0.0でRuby 3.1を使うには7-0-stableブランチを指定しないといけないという情報も載っていますね(編集部注: Rails 7.0.1では不要になりました)」

Rails 7.0.0+Ruby 3.1.0のGemfileは7-0-stableブランチの指定が必要

「そうそう、Ruby 3.1のClass#descendantsがリリース直前にいったん外されたことで↓、それに依存していたRails 7.0.0がRuby 3.1で動かなくなった」「RubyのリリースとRailsのリリースが接近していたことによるハプニングでしたね」

参考: Feature #14394: Class.descendants - Ruby master - Ruby Issue Tracking System

🔗 RailsデザインパターンKPT


つっつきボイス:「こうやって社内向けの設計方針を外部向けにも記事として出すのはいいですね👍」「記事では、Service ObjectとDecoratorとForm ObjectとValue Objectを維持する選択をしたそうです」「ちなみに個人的にはDecoratorは好きではなくてForm Objectは割と好き: ただForm Objectをさらに抽象化したDomain Objectの方が柔軟性が高まるのもわかります」

参考: Ruby on Rails Tutorial => Domain Objects (No More Fat Models)

「同記事ではRepositoryパターンは今後なくすそうです」「単に不要になったので削除するのかなるほど」「Repositoryパターンはあってもいいけど、オーバーキル気味な傾向はあるかな: 自分たちはActive Recordにすっかり慣れているので、独自のRepositoryを使うのは不便そうだなという気はする」

「自分は逆にRepositoryパターン大好きで、外部APIを叩くときによく使ってます」「Active Recordみたいにチェインできるオブジェクトかと思ったら実はRepositoryパターンで、チェインしたらめちゃくちゃ重くなったなんてことにもなりそうですけどね」「まあたしかに」「もう自分たちはActive Record脳ですから」

🔗 Rails 6をRails 7にアップグレード


つっつきボイス:「ぼくの記事だ〜❤️」「早速のアップグレードお疲れさまです」「でもさっきTurboの話を聞いていて、自分のアプリにTurboを入れてなかったことに気が付きました😅」

「記事ではbulletとannotate gemがRails 7で動かなかったgemがありますね: 私も2つほど動かないgemがありました」「annotate gemは好みが別れますが、自分は割と好き」「私も」「このgemを入れておくと、他の人がマイグレーションファイルを書くときにちゃんとコメントも書くようになってくれるんですよ」

ctran/annotate_models - GitHub

「記事にもあるけど、Rails APIサーバーならスムーズにRails 7に移行できそうかな」「逆にビューはHotwireとTurbo周りで手間がかかりそうです」「rails-ujsをTurboに書き換えるとなるとそうなるでしょうね」

🔗Ruby

🔗 クックパッドの「プロと読み解く Ruby 3.1 NEWS」


つっつきボイス:「Ruby 3.1リリースの日に公開されたクックパッドさんの記事です」「お〜後で読もうっと」「Rubyコミッターが経緯や使い方を日本語でみっちり解説してくれるのはありがたい🙏」「よく見ると記事すごく長いですね」「休みの日にでもゆっくり読んでみるといいと思います👍」

そういえばRuby 2.6は今年2022年03月31日にEOLを迎えます↓のでお忘れなく。

参考: Ruby Maintenance Branches

🔗 YJITを調べてみた


つっつきボイス:「お、うちの記事が引用されていますね↓」「はてなブックマークは検索対象を引用している記事を見つけられるんですが、その機能で浮かび上がってきた記事です」「YJITをステップバイステップで追いかけていますね: 面白そう👍」

YJIT: CRuby向けの新しいJITコンパイラを構築する(翻訳)

🔗 Faraday 2.0の変更


つっつきボイス:「ruby/debugメンテナーのst0012さんに教えてもらったツイートです」「お、Faradayがつい最近メジャーアップデートされたのか」「しかもnet_http以外のアダプタをすべて外したそうです」「へ〜」「アダプタが多くてサポートがつらくなったみたいなことが書かれてました」「アダプタが多いとそうなるでしょうね」

lostisland/faraday - GitHub

「どうせひとつに絞るならnet_httpよりもhttpclientにして欲しかった↓」「httpclientはよく話題にしているgemですね(ウォッチ20211108)」「httpclientはよくできてます」

nahi/httpclient - GitHub


今回は以上です。

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。