- Ruby / Rails関連
週刊Railsウォッチ: Turbo 7.2.0リリース、GitLabのDevSecOpsサーベイ結果ほか(20221011前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
公式更新情報に取り残され気味だったので今回はペースを上げます。
- 公式更新情報: Ruby on Rails — Handling reconnects in Action Cable, no more Coffeescript and a new guide.
- 公式更新情報: Ruby on Rails — Improved assert_redirected_to, improved error messages and more!
以下からも一部取り上げています。
🔗 exclude?
メソッドをActionController::Parameters
に追加
- PR: Add exclude? method to ActionController::Parameters by ianneub · Pull Request #45887 · rails/rails
概要
このプルリクは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ガイド
🔗 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_id
はdom_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 db:system:change
コマンドに--force
オプションが追加
- PR: Fix being able to pass --force to db:system:change by skipkayhil · Pull Request #46086 · rails/rails
動機と背景
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公式ニュースより)
- リリースノート: Release v7.2.0 · hotwired/turbo
つっつきボイス:「Railsかと思ったらTurboの7.2.0だった」「turbo-rails 1.3.0を使うとTurbo 7.2.0が使われるのね」「リリースノートの変更と修正がびっしりですね...」
「そういえばDeviseのTurbo対応はどうなっているかなと思って探して以下を見つけました↓」「そういえばDeviseも422を返すことがありますね」「対応は進んでいるけどまだ完了していないようです」「Deviseはつらみの塊...」
🔗 Phlex: もうひとつのViewComponent(Ruby Weeklyより)
つっつきボイス:「フレックスって読むのかな?」「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のセキュリティベストプラクティス
Ruby Magic by AppSignal ➜ Security Best Practices for Your Rails Application https://t.co/eGYlNuBc2T
— RUBYLAND (@rubylandnews) October 5, 2022
つっつきボイス:「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のload
やrestore
ではなく、安全なJSON.parse
やPsych.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])
みたいにカレントユーザーのスコープから取らない危険な書き方を自動検出するには、アプリケーションに合わせた検証ルールなどをカスタム定義する必要があります」「あ〜たしかに」「こういう記事は一度は読んでおきたい👍」
🔗 GitLabのDevSecOpsサーベイ結果発表
We’re seeing a trend of developers taking on more responsibility for operations, which is a big change from the past.
More findings in our 2022 Global DevSecOps Report.https://t.co/v3eMpAOeB5
— 🦊 GitLab (@gitlab) October 1, 2022
つっつきボイス:「GitLabが今年5月に行ったDevSecOpsサーベイの結果が発表されていました」「70%のDevOpsチームが毎日〜数日おきにデプロイするようになってますね」「DevOpsの完全自動化も年々増えてる↓」
「CI/CDを使わないプロジェクトは随分減ったという実感はありますね」「自動化が進んでリリース速度や頻度が上がればその分失敗する頻度も上がるけど、自動化することで人間に強いロールを割り当てなくて済むようになって結果的に安全度が上がると思います」「最終的にミスするのは人間だから、人間の関わる部分はなるべく減らしたいですよね」
「ところで今回は例年より項目が少なめに見えますが、記事の一番下で登録すれば完全なPDFを無料でダウンロードできるそうです」「お、後でダウンロードして読むか」
🔗 RSpecのletの依存関係グラフをビジュアル表示する
つっつきボイス:「thoughtbotの記事です」「たしかにRSpecはlet
の依存関係がカオスになりがち」「こういうグラフがあるとテストケースの漏れを知るのによさそう」「目の付け所がいい記事👍」
🔗 その他Rails
Supabase、Railsから普通のPostgreSQLとして利用することができたので情報を追記しました。Thanks to @dshukertjrjp !https://t.co/fekerFP5ez pic.twitter.com/Wrnr6OUrGv
— Junichi Ito (伊藤淳一) (@jnchito) October 4, 2022
つっつきボイス:「Herokuの有料プランにどう対応するかをjnchitoさんがまとめてくれて参考になります🙏」「Eco Dynoは月5ドルだけどPostgreSQLも最低で月5ドルか」「円安の今だとこたえますね...」「月10ドルならLightSailの方が安いのでは?」「そうそう、自分もLightSailにしてます」
参考: Amazon Lightsail(月額3.5ドル〜の仮想プライベートサーバー:VPS)| AWS
「記事でも、他のサービスに移行を試みたけど開発体験はHerokuに敵わなかったいう意見もあったそうです」「Herokuは長年改良を続けてきている分一日の長がありますね」「Herokuのスクリプトに依存している商用プロジェクトは、移行してテストする工数やコストなども含めて移行するしないを検討することになるでしょうね」
前編は以上です。
バックナンバー(2022年度第4四半期)
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)