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

週刊Railsウォッチ: Turbo 7.2.0リリース、GitLabのDevSecOpsサーベイ結果ほか(20221011前編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

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

公式更新情報に取り残され気味だったので今回はペースを上げます。

以下からも一部取り上げています。

🔗 exclude?メソッドをActionController::Parametersに追加

概要
このプルリクはexclude?メソッドをActionController::Parametersに追加する。このメソッドは背後にパラメータデータを持つハッシュに委譲するもので、include?と逆の動作。
その他の情報
このメソッドが追加されることでHashの一貫性が少し増すようになる。
同PRより


つっつきボイス:「言われてみればinclude?があるならexclude?があってもいいですよね」「やりたいことはわかる👍」

# actionpack/lib/action_controller/metal/strong_parameters.rb#L217
-   delegate :keys, :key?, :has_key?, :member?, :empty?, :include?,
+   delegate :keys, :key?, :has_key?, :member?, :empty?, :exclude?, :include?,
      :as_json, :to_s, :each_key, to: :@parameters

参考: Rails API include? -- ActionController::Parameters

🔗 Action Cableのconnected()コールバックに再接続の制御を追加

概要
サブスクライバがconnected()コールバックで再接続を扱えるようにするのが目的。

import consumer from "./consumer"

consumer.subscriptions.create("ExampleChannel", {
  connected({reconnected}) {
    if (reconnected) {
      ...
    } else {
      ...
    }
  }
})

これは、コネクションが切断されてサブスクライバのメッセージの一部が失われたときに便利。
同PRより


つっつきボイス:「データベースのコネクションかと思ったらAction Cableのコネクションだった」「同じこと思いました」「修正されたのはJavaScriptのコードですね」「Railsサーバーを再起動したときとかにこうやって接続が切れるのかな」「こういう再接続を暗黙でやってくれるのは嬉しい👍」

参考: § 6.1 例1: ユーザーアピアランスの表示 -- Action Cable の概要 - Railsガイド

🔗 Gemfileにpumaアプリサーバーを追加してtest/dummyを起動可能にした

概要
エンジンのGemfileにアプリサーバー(puma、unicorn、webrickなど)がないと、ガイドに記載されているようにtest/dummyアプリケーションを./bin/railsサーバーで起動できなくなる。./bin/railsを実行すると"Could not find server"エラーが返される。
同PRより


つっつきボイス:「たしかに./bin/railsでエンジンのtest/dummyを動かすにはWebサーバーが必要ですね」「WebrickがRubyコアから削除されたのは2年ぐらい前だったかな↓」「随分前でしたよね」

参考: Feature #17303: Remove webrick from stdlib - Ruby master - Ruby Issue Tracking System
参考: Rails エンジン入門 - Railsガイド

ruby/webrick - GitHub

🔗 dbconsoleコマンドとMySQLDatabaseTasksに--ssl-modeオプションを追加

  • MySQLDatabaseTasksでMySQLのssl-modeオプションをサポート

データベースサーバーのアイデンティティを検証するには、ssl-modeオプションをVERIFY_CAまたはVERIFY_IDENTITYにする必要がある。従来このオプションはデータベース作成やstructureダンプなどのMySQLタスクで無視されていた。
Petrik de Heus
同PRより


つっつきボイス:「dbconsoleがMySQLのssl-modeオプションに対応したんですね: sslポートでリッスンしている場合はこのオプションを使います」「ありがたい🙏」「dbconsoleは自分はたまに使うぐらいかな」

参考: §1.5 bin/rails dbconsole -- コマンドラインツール - Railsガイド
参考: MySQL :: MySQL 8.0 リファレンスマニュアル :: 6.3.1 暗号化接続を使用するための MySQL の構成

「ちなみにdbconsoleは単に内部でmysqlコマンドやpsqlコマンドなどを呼んでいるだけです」「たしかにプロンプトも同じですよね」

🔗 assert_redirected_toにHTTPステータスコードも指定可能になった

従来、このメソッドはステータスが常に:redirectであるというアサーションで、任意の3XXレスポンスが容認されていた。しかしリダイレクトのステータスコードを正確に把握する必要が生じる場合もある。たとえば、Railsアプリケーションでリダイレクトが302(Found)ではなく301(Moved Permanently)であることを確認したい場合がある。この新しいメソッド引数を使うと1つのアサーションでチェックできるようになるので便利。
同PRより抜粋


つっつきボイス:「assert_redirected_toアサーションに引数が追加されたそうです」「リダイレクトのステータスコードが304か301かなども知りたいときは確かにありますね」「従来もレスポンスコードを追加で調べればできるけど、リダイレクトのアサーションでまとめてチェックできるようになったのはいい👍」

# actionpack/test/controller/action_pack_assertions_test.rb#445
  def test_assert_redirection_with_status
    process :redirect_to_path
    assert_redirected_to "http://test.host/some/path", status: :found
    assert_raise ActiveSupport::TestCase::Assertion do
      assert_redirected_to "http://test.host/some/path", status: :moved_permanently
    end
    assert_raise ActiveSupport::TestCase::Assertion, "Custom message" do
      assert_redirected_to "http://test.host/some/path", { status: :moved_permanently }, "Custom message"
    end

    process :redirect_permanently
    assert_redirected_to "http://test.host/some/path", status: :moved_permanently
    assert_raise ActiveSupport::TestCase::Assertion do
      assert_redirected_to "http://test.host/some/path", status: :found
    end
    assert_raise ActiveSupport::TestCase::Assertion, "Custom message" do
      assert_redirected_to "http://test.host/some/path", { status: :found }, "Custom message"
    end
  end

参考: Rails API assert_redirected_to -- ActionDispatch::Assertions::ResponseAssertions

🔗 dom_idにクラスを渡せるようになった

  • dom_idにクラスを渡せるようになる

dom_idを呼び出す前のnew呼び出しが不要になった。これにより、dom_iddom_classのように振る舞う。コード入力も減らせるし、文字列を生成するためだけにRubyがまったく新しいオブジェクトをインスタンス化する必要もなくなる。

# 変更前:
dom_id(Post) # => NoMethodError: undefined method `to_key' for Post:Class
# 変更後
dom_id(Post) # => "new_post"

Goulven Champenois
同Changelogより


つっつきボイス:「dom_idにクラスを直接渡すときはPost.newする必要があったのがPostのまま渡せるようになった、なるほど」「dom_idはRails wayに沿ったDOM idを安定して取得できるメソッドですね↓」

Rails: Action Viewのdom_idヘルパーは実は有能(翻訳)

🔗 rails db:system:changeコマンドに--forceオプションが追加

動機と背景
db:system:changeを実行すると常にdatabase.ymlを上書きするプロンプトが表示されるので、--forceで上書きできるとよさそう。
同PRより


つっつきボイス:「db:system:changeって使ったことないけど何だろう?」「データベースを切り替えるのに使うみたい」「こんなコマンドがあったとは」「ちょっと怖い」「そこにさらに--forceオプションがついたんですね」

# railties/test/commands/db_system_change_test.rb#L57
  test "change can be forced" do
    output = `cd #{app_path}; bin/rails db:system:change --to=postgresql --force`

    assert_match "force  config/database.yml", output
    assert_match "gsub  Gemfile", output
  end

「database.ymlとGemfileを更新するだけのようなので、rails newした直後にデータベースの指定を間違えたことに気づいたときの修正に使う前提なのかも」「そんな感じですね」「データベースが変わればマイグレーションの互換性が失われる可能性もあるので、大きく育ったRailsアプリでいきなり実行する感じではなさそうかな」

後で情報を表示してみました↓。

root@c53bd8c09bce:/app# bundle exec rails db:system:change -h
Usage:
  rails change [options]

Options:
  [--to=TO]  # The database system to switch to.

参考: The Rails db:system:change command (Example) | GoRails

🔗 属性のシリアライズで型の二重キャストを回避して高速化

モデルのほとんどの属性型はシリアライズ前に指定の型のキャストを試みる。これにより、キャストされていない値をfinderメソッドに渡しても適切にシリアライズされる。しかしモデルを永続化する場合、値は既にActiveModel::Attribute#valueによってキャスト済みなので、このキャストは不要。

このコミットでは、この第2のキャストによるオーバーヘッドを排除するためにActiveModel::Type::SerializeCastValueを導入する。型はこのモジュールをインクルード可能で、キャスト済みの値をシリアライズするときは、シリアライズではなくそれらのserialize_cast_valueメソッドが呼び出される。

Railsの型をサブクラス化した任意のユーザー型の既存の振る舞いを変えないようにするため、(その型のスーパークラスではなく)その型自身がActiveModel::Type::SerializeCastValueをインクルードしている場合にのみserialize_after_castが呼び出される。これはDelegateClassで実装された型デコレータについても適用される。
同PR冒頭より


つっつきボイス:「これは最適化だそうです」「二重に呼び出されていた型キャストを頑張って消したんですね」「プルリクのベンチマークを見るとかなり速くなってますね」「シリアライザを使う場面でよく効きそう👍」

🔗Rails

🔗 Turbo 7.2.0リリース(Rails公式ニュースより)


つっつきボイス:「Railsかと思ったらTurboの7.2.0だった」「turbo-rails 1.3.0を使うとTurbo 7.2.0が使われるのね」「リリースノートの変更と修正がびっしりですね...」

「そういえばDeviseのTurbo対応はどうなっているかなと思って探して以下を見つけました↓」「そういえばDeviseも422を返すことがありますね」「対応は進んでいるけどまだ完了していないようです」「Deviseはつらみの塊...」

🔗 Phlex: もうひとつのViewComponent(Ruby Weeklyより)

joeldrapper/phlex - GitHub


つっつきボイス:「フレックスって読むのかな?」「ViewComponent↓のオルタナだそうです」

ViewComponent/view_component - GitHub

参考: ViewComponent を試してみた

「なるほど、こういうふうにRubyで書いてHTMLを生成するんですね↓」「こう書きたい気持ちはとてもよくわかる」「ビューを全部Rubyで書きたい人にとっては魅力的でしょうね」「リポジトリではViewComponentより速いと謳っていますね」「ケースによるでしょうけどね」

# 同ドキュメントより
class Nav < Phlex::View
  def template
    nav do
      ul do
        li { a "Home", href: "/" }
        li { a "About", href: "/about" }
        li { a "Contact", href: "/contact" }
      end
    end
  end
end
<nav>
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

「ところで今日の社内勉強会でPofEAA(Patterns of Enterprise Application Architecture)の"レイヤ"の話をしたんですが、ちょうどこのあたりの話もありました↓」「そうでしたね」

「Railsでプレゼンテーション層に相当するのは主にビューですが、これがサーバーサイドにあるとデプロイしやすくなります: 逆にフロントエンドとバックエンドに分かれていると、どちらかのコードを書くときに他方も常に意識する必要があるし、デプロイが別々になるとタイミングによっては不整合が起きる可能性もある」「そう思うと面倒ですね」「プレゼンテーション層がサーバーサイドにあると、ユーザー側のコードやアセットのバージョンを気にしなくてよいというメリットがあると自分は思っています」


「それと同じようにViewComponentやこのPhlexなどにも一定のメリットがあると思いますし、実際個人的にも好きな方なので、個人プロジェクトあたりなら使ってみたい気持ち👍」「なるほど」「ただ現実問題として、これらを業務で使うのはまだ趣味に走りすぎかなという気持ちもあります: 業務プロジェクトで使うと自分の書いたコードを引き継ぐコストが上がってしまうので」「それもそうか」「業務で導入するなら十分検討してからということですね」

🔗 Railsのセキュリティベストプラクティス


つっつきボイス:「AppSignalのセキュリティ記事です」「セキュリティのチェックリストというよりはコーディングやセットアップの注意点をまとめた感じですね」

「Force SSLはcookie周りの設定などもやってくれるので、ロードバランサやリバースプロキシでTLSを使う場合でもtrueにしておくのがいい↓」

config.force_ssl = true

「秘密情報をcredentials.yml.encで扱うか環境変数で扱うかはプロジェクトの方針によりますね: ちなみに自分は環境変数でやりたい派」「私もです」

「認証ロジックは1箇所にまとめるのが理想、シングルユーザーではなくロールベースでグループ化は定番ですね」

「入力パラメータのフィルタ: こういうコードはレビューで見かけたら即指摘しています↓」「そうそう」「paramsがビューやコントローラで生書きされていたらレビューで必ずチェックすべき」

# 同記事より
class UsersController < ApplicationController
  def update
    current_user.update(params[:user])
  end
end

「これもレビューで指摘される超定番ですね: current_userじゃないものでfindとかすべきでない↓」「そうそう、業務コードに慣れていないとやりがち(ウォッチ20181015)」「こういうのは見つけたら何度でも指摘するしかないと思います」

# 同記事より: 悪例
class MessagesController
  before_action :authenticate_user!

  def show
    @message = Message.find(params[:id])
  end
end

# 修正
class MessagesController
  before_action :authenticate_user!

  def show
    @message = current_user.messages.find(params[:id])
  end
end

「ところで、current_userを付けない悪例は、言ってみればコンソールでSQLのWHEREを書かずにいきなりUPDATEやDELETEを書くのとノリが似ているかもしれませんね」「あ〜わかります」「逆にSQLに慣れている人ほど、万一の失敗を避けるためにUPDATEやDELETEより先にWHERE句を書くようになりますよね」「誤ってEnterキーを押したときに備えて最初にWHERE 1=0を書いたりしますね」「そもそもクリティカルなSQLクエリは普通はコンソールで直接書かずにエディタでチェックしてから貼るようにしています」

「危険なURLリダイレクトやSQLインジェクションは、最近だとこれも含めてRuboCopやbrakemanが危険な書き方の多くを指摘してくれるようになっていますね」「そうそう、助かってます🙏」

# 同記事より: URLリダイレクト
redirect_to params[:url]
# 同記事より: SQLインジェクション
User.joins(params[:table]).order('id DESC')

「コマンドインジェクションを防ぐために、Kernelモジュールからヤバそうなシステムコマンドを削除している↓」「なかなか過激ですね」「使ってるgemによっては動かなくなりそう」

# 同記事より
module Kernel
 remove_method :exec
 remove_method :system
 remove_method :`
 remove_method :eval
 remove_method :open
end

Binding.send :remove_method, :eval
RubyVM::InstructionSequence.send :remove_method, :eval

「データのシリアライズは最近よくトピックにあがりますね: JSONやMarshalのloadrestoreではなく、安全なJSON.parsePsych.safe_loadを使うこと」

# 同記事より
# bad
JSON.load(data)
JSON.restore(data)

# good
JSON.parse(data)
# 同記事より
# bad
Marshal.load(data)
Marshal.restore(data)

# good
Psych.safe_load(data)

参考: JSON.#parse (Ruby 3.1 リファレンスマニュアル)
参考: Psych.safe_load (Ruby 3.1 リファレンスマニュアル)

「CSSインジェクションって何だろうと思ったらユーザー入力の値をそのままCSSで使っているんですね↓」「これをやるとユーザーがCSSを改ざんできてしまう可能性がありますね」

<-- 同記事より -->
<body style="background: <%= profile.background_color %>;"></body>

「今なら記事に書かれていることの多くはRuboCopやbrakemanなどが指摘してくれると思いますが、たとえばさっきの@message = Message.find(params[:id])みたいにカレントユーザーのスコープから取らない危険な書き方を自動検出するには、アプリケーションに合わせた検証ルールなどをカスタム定義する必要があります」「あ〜たしかに」「こういう記事は一度は読んでおきたい👍」

presidentbeef/brakeman - GitHub

🔗 GitLabのDevSecOpsサーベイ結果発表


つっつきボイス:「GitLabが今年5月に行ったDevSecOpsサーベイの結果が発表されていました」「70%のDevOpsチームが毎日〜数日おきにデプロイするようになってますね」「DevOpsの完全自動化も年々増えてる↓」


同記事より

「CI/CDを使わないプロジェクトは随分減ったという実感はありますね」「自動化が進んでリリース速度や頻度が上がればその分失敗する頻度も上がるけど、自動化することで人間に強いロールを割り当てなくて済むようになって結果的に安全度が上がると思います」「最終的にミスするのは人間だから、人間の関わる部分はなるべく減らしたいですよね」

「ところで今回は例年より項目が少なめに見えますが、記事の一番下で登録すれば完全なPDFを無料でダウンロードできるそうです」「お、後でダウンロードして読むか」

🔗 RSpecのletの依存関係グラフをビジュアル表示する


つっつきボイス:「thoughtbotの記事です」「たしかにRSpecはletの依存関係がカオスになりがち」「こういうグラフがあるとテストケースの漏れを知るのによさそう」「目の付け所がいい記事👍」


同記事より

🔗 その他Rails

つっつきボイス:「Herokuの有料プランにどう対応するかをjnchitoさんがまとめてくれて参考になります🙏」「Eco Dynoは月5ドルだけどPostgreSQLも最低で月5ドルか」「円安の今だとこたえますね...」「月10ドルならLightSailの方が安いのでは?」「そうそう、自分もLightSailにしてます」

参考: Amazon Lightsail(月額3.5ドル〜の仮想プライベートサーバー:VPS)| AWS

「記事でも、他のサービスに移行を試みたけど開発体験はHerokuに敵わなかったいう意見もあったそうです」「Herokuは長年改良を続けてきている分一日の長がありますね」「Herokuのスクリプトに依存している商用プロジェクトは、移行してテストする工数やコストなども含めて移行するしないを検討することになるでしょうね」


前編は以上です。

バックナンバー(2022年度第4四半期)

週刊Railsウォッチ: ヒアドキュメント拡張の提案、『組織に自動テストを根付かせる戦略』ほか(20221004後編)

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

Rails公式ニュース

Ruby Weekly


CONTACT

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