- M1 macで
docker-compose up
するとなぜかexec /usr/local/bin/docker-entrypoint.sh: exec format error
- docker imageはintel用なのでそれが関連している
- と思って色々試したがなにも変わらず
- docker desktopのバージョンを4.31.0 -> 4.32.0に上げたら
docker-compose up
が失敗するようになり"docker-compose"コマンドを使っているのが良くないんじゃ?という事実に気づいた docker compose up
としたらうまくいきました- コマンド履歴で
docker-compose up
を実行していたのが良くなかったという話
E2Eテストでバリデーションを調整してタイムアウトを抑制する
- 関連: system specでNet::ReadTimeoutになったら - おもしろwebサービス開発日記チラシの裏
- やっぱりタイムアウトの時間を伸ばすのではなく、テスト側でバリデーションの条件を緩和してあげたほうがトータルのテスト実行時間が短くなっていいんじゃないか、と思って次のようにした
- (設定はpalkan/anyway_config: Configuration library for Ruby gems and applicationsを使っています)
class DailyReport < ApplicationRecord validates :body, presence: true, length: { maximum: -> { ValidationConfig.daily_report[:body][:maximum] } } end
around do |example| original = ValidationConfig.daily_report[:body][:maximum] ValidationConfig.daily_report[:body][:maximum] = 10 example.run ValidationConfig.daily_report[:body][:maximum] = original end
- rails5.2から、maximumにlambdaを渡して動的に条件を変えられるようになっている
- また、rails7.1からは引数なしのlambdaでも受け付けられるようになっている
flashメッセージが時々表示されないflakyテストを改善した話
Railsで
- cookie sessionを使っている
- 非同期でAPIをよく叩いている
という条件下で、例えば日報を投稿したあとに"投稿しました!"というflashメッセージを表示しているはずなのになぜか"投稿しました!"が表示されないという現象が時々起こっていました。
これは次のようなことが原因だと推測しています。
- 非同期API(例: 日報のプレビューを表示する)が実行される
- 日報投稿ボタンを押す
- 投稿が成功して日報詳細ページへのリダイレクト用のレスポンスが返される
- SetCookiesでflashメッセージを含んだcookie sessionが返される
- 非同期APIのレスポンスが返る
- SetCookiesでflashメッセージを含まないcookie sessionが返される
- 日報詳細ページへのリクエストが実行される
- このとき送信するCookieにはflashメッセージが含まれていないので"投稿しました!"は表示されない
長らくこの問題に悩まされていたのですが、API実行時は次のようにしてSetCookiesヘッダを返さないようにするという方法を思いついたので試してみました。おそらくこれで解決するはず。
class Api::BaseController < ApplicationController class NullCookieJar < ActionDispatch::Cookies::CookieJar def write(*) # nothing end end before_action :null_cookies def null_cookies request.cookie_jar = NullCookieJar.build(request, {}) end end
Rails8.0.0マイルストーンの現状
これはなに
- 8.0.0 Milestoneを見て気になったものをまとめています
- マイルストーンは先週くらいにできたのですがもうマージされているやつもたくさんあります
- DHHが年末年始にめっちゃ働いている
気になったものたち
- Ruby3.3以上のサポート
- DHHは最初3.3以上で、という気持ちだったんだけど流石にみんな大変やろ、という意見が出て結局リリース時(2024年の予定)にサポートされているRubyのバージョン、つまり3.1以上に落ち着いた
- PR: Bump the required Ruby version to 3.1.0 by byroot · Pull Request #50491 · rails/rails
- ↑のPRでは「メジャーバージョンアップ時にRubyのサポートを落とす」だとRails自体のメンテも大変だしアプリケーション開発者も大変なので、毎回マイナーバージョンアップでもその時サポートしているRubyのバージョンだけサポートするようにしようぜ、という意見が出ている
- ついでにセキュリティフィックスバージョンも落とそうぜ、という意見もある
- このへん最終的にどうなるかわからないけど、Railsのバージョンを上げる会社はRubyのバージョンも上げるのでそんなに影響はない気がしますね
- solid_queue, solid_cache, prop_shaft, kamalがデフォルトに
- Action CableのアダプタのデフォルトをDBにする
- solid_queue, solid_cacheと一緒でredisなしをデフォルトにする方針っぽい
- もともとpostgresqlはアダプタとして使えたけれど、MySQLやsqlite3でも使えるようにしてそれをデフォルトにする、という方針
- ONCEではすでにそれでやっている模様
- 古いブラウザでアクセスしたときに古いよ、と出る機能
- Add allow_browser to set minimum versions for your application by dhh · Pull Request #50505 · rails/rails
- マージ済み
- ユーザエージェントの判別用にgshutler/useragent: HTTP User Agent parser が依存に追加されている
- allow_browserメソッドが追加
- versionsオプションで対象バージョンを追加
- 対象を
:modern
で指定するとSafari 17.2+, Chrome 119+, Firefox 121+, Opera 104+扱いになる - 古いブラウザでアクセスしたときにデフォルトだと public/426.html が表示される
block: -> { redirect_to blocked_path }
みたいなオプションで上書きできる
class ApplicationController < ActionController::Base # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has allow_browser versions: :modern end class ApplicationController < ActionController::Base # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+. allow_browser versions: { safari: 16.4, firefox: 121, ie: false } end class MessagesController < ApplicationController # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action. allow_browser versions: { opera: 104, chrome: 119 }, only: :show end
- 認証機能の追加
- 今のところ lazaronixon/authentication-zero: An authentication system generator for Rails applications. のようなコードジェネレータを想定している模様
- lograge的な構造化したログ出力をする機能
Action Notifier(プッシュ通知用コンポーネントの追加)
- Railsでプッシュ通知を簡単に実装できるようになる
- ↑の準備としてPWA用のマニフェストをデフォルトで生成するようになった (こちらはマージ済み)
- Rubocopの追加
- rails/rubocop-rails-omakase: Omakase Ruby styling for Railsが自動でGemfileに入る
- LayoutやStyleなどの、みんなそれほど文句がなさそうなCopだけがデフォルト有効でほかは無効になっている
- standardrb/standard: Ruby's bikeshed-proof linter and formatter 🚲 はどう?という議論があったがDHHは採用しなかった
- マージ済み
- brakemanの追加
- brakemanが自動でGemfileに入る
- マージ済み
- GitHub Action用のファイルの生成
- rubocop, brakeman,
rails test test:system
を実行するGitHub Actionsのworkflowが追加される - マージ済み
- rubocop, brakeman,
- アクセス制限機能の追加
- rails/kredis: Higher-level data structures built on Redisを利用している
- マージ済み
- kredis側のPR
- kredis1.7.0以上で利用できる
- redisを利用した簡単なカウンタによるアクセス制限機能、という印象
- 最初にカウントしてから時間を計測して、withinの時間が過ぎたらリセットされるカウンタ
class SessionsController < ApplicationController rate_limit to: 10, within: 3.minutes, only: :create end class SignupsController < ApplicationController rate_limit to: 1000, within: 10.seconds, by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups!" }, only: :new end
turboからのリクエストに対してリダイレクトするときに気をつけること
- turboを利用していて、destroyアクションでリダイレクトを使うときステータスコードを明示的に303(see other)にしないと、リダイレクト先にDELETEメソッドでアクセスしてしまうというハマりポイントが有る
- 参考: Rails 7.0 + Ruby 3.1でゼロからアプリを作ってみたときにハマったところあれこれ #Ruby - Qiita
- 上記の例ではDELETEメソッドを利用しているけど、PATCH(PUT)でも同じ挙動になる
- Railsの現時点でのデフォルトのリダイレクト時は302なので、
redirect_to @project, status: :see_other
のように明示的にステータスコードを指定するとGETでリダイレクトする - なぜこのような仕様になっているかというと、fetch APIがそういう仕様だから
- fetch APIがなぜこのような仕様になっているかというと、302の仕様になるべく沿おうとしているから(要出典)
- 302 Found - HTTP | MDN
仕様書ではリダイレクトの際にメソッド (と本文) を変更しないよう要求していますが、すべてのユーザーエージェントが準拠している訳ではありません (まだこの種のバグのあるソフトウェアが見つかるでしょう)。従って、
302
コードはGET
またはHEAD
メソッドへのレスポンスのみに使用し、POST
メソッドのままリダイレクトする場合は代わりに307 Temporary Redirect
(こちらでは明確にメソッドの変更が禁止されている) を使用することが推奨されています。- chromeなどのブラウザでは、GETとPOST以外のメソッドでアクセスした場合は同じメソッドをリダイレクト先にも適用する、という実装になっている模様
- なので仕方ないっちゃ仕方ないんだけど、既存のアプリケーションに対してturboを採用しようとした場合には結構な負担になりますね
- Rails側はこの仕様を緩和するための変更がいくつか入っていたり提案されていたりする
- turbo-railsでは、PATCHやDELETEメソッドでリクエストするときにそれをPOSTと
_method
に差し替えるというロジックが入っている- Encode HTTP method into Request body as
_method
by seanpdoyle · Pull Request #370 · hotwired/turbo-rails - turbo-rails gemだとv1.3.0、@hotwired/turbo-rails npm だと7.2.0以降であればこのコミットが入っているので、意図しないリソースが削除されてしまう危険は回避されているはず
- しかしこれはあくまでTurbo発のリクエストだけを差し替えるので、独自にfetchを利用している箇所は依然として(GETでリダイレクトをしたければ)303でリダイレクトしてやる必要がある。
- Encode HTTP method into Request body as
- POST, PUT, DELETEなどのメソッドでリクエストが来たときのリダイレクト時のステータスコードを一律に設定させるための設定が提案されている
- Add
redirect_code_for_unsafe_http_methods
config by jonathanhefner · Pull Request #45393 · rails/rails - これ7.1に間に合ってほしかった…
- でも、これまで302で返しているものが303になったら古いアプリケーションにめっちゃ影響がありそうなので慎重になるのもわからなくはない
- Add
- scaffoldで生成されるアクションでは
status: :see_other
がつくようになっている(Rails7.1.0以降)- Scaffold destroy action returns status code 303 by t27duck · Pull Request #45383 · rails/rails
- が、このテンプレートはjbuilderが上書きしているので、jbuilderを有効にしているRailsアプリケーションでは結局302を返すコントローラが生成されてしまう…
- そしてオプションつけずにrails newするとjbuilderは入っている
- jbuilder側にPRはでている
- turbo-railsでは、PATCHやDELETEメソッドでリクエストするときにそれをPOSTと
ぼくらは結局どうしたらいいんですかね
- turbo-rails経由でPATCHやDELETEを発行しているぶんには気にせず302で問題なさそうではあるけど、気づかずにfetch APIを直接実行したときのことを考えるとサーバ側でも対応しておきたい
- Add
redirect_code_for_unsafe_http_methods
config by jonathanhefner · Pull Request #45393 · rails/rails 相当のものをモンキーパッチして、redirect_to時のステータスコードをデフォルト303に一律で変えてしまうのが楽そうかな〜と思っています
Beats Studio Proを買ったけど返品した
ここ数年はずっとBoseのquiet comfort35を使っていて、音質などには不満は特にありませんでした。
でも複数pcを切り替える時AirPodsなどと比べると面倒*1。あとは2台同時にペアリングしている時に音を出したいデバイスではない方がアクティブになり、そちらのBluetoothを明示的にオフにしないと狙った方で音が出せなくてイライラする状況があったり。だんだんとこの状況を打破したいな、という気持ちが高まってきました。
AirPods max2がリリースされるのをずっと待っていたのですが、あと1年くらいは発売がなさそうな雰囲気。そこでApple傘下のBeats Studio Proなら発売間もないしいいんじゃないか、と思いポチってみました。
使ってみた感じとしては、Boseに感じていた諸々の不満が解消されていていいじゃん、となったのですが頭への締め付けがきつくて長時間つけていると辛い…。これまでヘッドホンの締め付けを気にしたことがなかったので盲点でした。やむなく返品することに。
Apple storeで購入したので返品処理がスムーズにできたのは不幸中の幸いでした。
もうちょっとBoseを使い続けるか、AirPods max1を買ってしまうかが目下の悩みどころです。
*1:仕事柄pcを複数台使っているので切り替える機会が多く面倒に感じやすい
テストでWebsocketのsubscribeが終わるまで待ちたい
- ページを開く
- ActionCableのブロードキャストを実行する
- ブロードキャストの結果画面が変更されるのを確認する
というテストがあるときに、2がActionCableの該当チャンネルのsubscribeより前に実行されてしまいテストが失敗する、という事象があった。
なのでsubscribeを待つためのヘルパーメソッドを書いてみた。asyncアダプタ想定で、subscriber情報を持っているハッシュの個数が0より大きくなればsubscribe完了しているだろう、と判断した。
def wait_for_websocket_connection Timeout.timeout(Capybara.default_max_wait_time) do until ActionCable.server.pubsub.send(:subscriber_map).instance_variable_get(:@subscribers).count > 0 sleep 0.5 end end end
だいぶナイーブな実装なのでうまく動かないケースもありそうだけど、とりあえずこれで。
2024/02/06追記
ActionCableへの購読は、まず内部用のチャンネル(ActionCable::Connection::InternalChannel) を購読してから専用のチャンネルを購読する、という流れになるので、上記のコードだとタイミングによってはまだチャンネルの購読が完了していない事がある。そこを次のようなコードで改善してみた。しばらくこれで様子見
# `stream_for current_user ` のようにして購読している前提 def wait_for_websocket_connection(user) channel = NotificationChannel.broadcasting_for(user) block = lambda do s = ActionCable.server.pubsub.send(:subscriber_map).instance_variable_get(:@subscribers) s.key?(channel) && s[channel].count > 0 end Timeout.timeout(Capybara.default_max_wait_time) do sleep 0.5 until block.call end end