- Ruby / Rails関連
週刊Railsウォッチ: Rails 6.1を7.0にアップグレードしてみた、PostgreSQLでジョブキューほか(20220208前編)
こんにちは、hachi8833です。
つっつきボイス:「このSUSEのYouTubeチャネルがめちゃくちゃ面白い↓」「お〜、英語の替え歌がどっさりですね」「映像も凝っていてクォリティが半端ない」
SUSE (openSUSEとかのとこ) のオフィシャルYouTubeチャネルが異常であることが判明しつつあり、こういうMVが無限にある、しかし不当に再生数が少ない https://t.co/hvx7Wjvrxk
— moznion (@moznion) February 2, 2022
🔗Rails: 先週の改修(Rails公式ニュースより)
今回は以下の更新の中からChangelogが更新されたものを中心に見繕いました。引き続きドキュメントの修正が多いようです。
🔗 TestCase#stub_const
を追加
以下のようにブロック内で定数の値を変更する。
# World::List::Import::LARGE_IMPORT_THRESHOLD = 5000
stub_const(World::List::Import, :LARGE_IMPORT_THRESHOLD, 1) do
assert_equal 1, World::List::Import::LARGE_IMPORT_THRESHOLD
end
assert_equal 5000, World::List::Import::LARGE_IMPORT_THRESHOLD = 5000
World::List::Import::LARGE_IMPORT_THRESHOLD = 5000
を無理やり設定する代わりにこのメソッドを使えば、warningも出力されなくなり、そのテストが完了すれば元の値に戻る。
同PRより
つっつきボイス:「お、このstub_const
は便利そう: 個人的に好き👍」
「これと似たような感じで、テストコードでクラスインスタンス変数をスタブする必要が生じることがたまにあるんですが、直接スタブできないので、たとえばクラスインスタンス変数の値にアクセスするメソッドを追加してそれをスタブした覚えがあります」「あ、なるほど」
参考: クラス変数とクラスインスタンス変数を理解する - Qiita
🔗 PostgreSQL generated column関連の修正
# activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L666
- default_function = extract_default_function(default_value, default)
+
+ if attgenerated.present?
+ default_function = default
+ else
+ default_function = extract_default_function(default_value, default)
+ end
つっつきボイス:「この修正はissueを見るのがわかりやすそう↓」「schema.rbでPostgreSQLのgenerated columnが常にas: nil
になってしまっていたんですね」「これは修正が必要なヤツ」
参考: 5.3. 生成列 -- PostgreSQL 13.1
🔗 Websocketsが接続中に閉じないようにする
これは、接続状態のWebSocketを閉じると永久に壊れてしまう問題(Safariによるバグ)を解決する。
この問題は、最近の Safari(15.1+) のNSURLSession Websockets と呼ばれる新しいWebSocketsの実装で発生した。この問題が発生するとWebSocketsが完全におかしくなり、修正するにはSafariを完全に再起動する必要がある(リロードだけでは修正できない)。この問題はBasecampで発生したが、このパッチで問題の修正を確認できた。このパッチは単に接続状態にあるWebSocketを閉じないようにするもの。他のユーザーからも同様の問題が報告されている(これとこれ)。ActionCableに限らないが、Action Cableの監視システムはstaleコネクション検出ロジックの一部で接続状態のコネクションを閉じることがあったためにこの問題が発生した。
Safari固有の問題ではあるものの、Action Cableを使うすべてのアプリに影響するため、パッチをRailsにアップストリームすることにした。この問題はトラブルシューティングが非常に難しい問題でもある (この問題を追いかけるのに数週間かかった)。他のブラウザでは問題を起こさないので、ブラウザ検出を行わずにシンプルに対応した。最悪、接続状態にあるWebsocketsコネクションが閉じていない状態で接続される可能性もあり、その場合ブラウザに2つのコネクションができてしまうが、Action Cableで監視されることも使われることもないので何もせずに勝手に死んでくれる。
Safariが近いうちにこの問題を解決して、この変更を元に戻すことを検討できるようになればと思う。
同PRより
つっつきボイス:「Safariのバグを回避する修正か」「プルリクに書かれているリンクが切れていてSafariのどのバグだかわからない...」「あらま」
🔗 非推奨化されたurlsafe_csrf_tokens
を削除
関連PR: #43817
通常なら非推奨化されたコードを削除するのはもっと先になるが、#44283を進めるのに邪魔なので、今削除する方がよいと判断した。
同PRより
つっつきボイス:「お、少し前に非推奨化されたurlsafe_csrf_tokens
コンフィグが早くも削除された(ウォッチ20211221)」「Shopifyからのプルリクですね」
🔗 PostgreSQLとMySQLの修正1件ずつ
つっつきボイス:「PostgreSQLにクエリをキャンセルする機能があるとは知らなかった」
# activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb#L121
def exec_rollback_db_transaction # :nodoc:
+ @connection.cancel unless @connection.transaction_status == PG::PQTRANS_IDLE
+ @connection.block
execute("ROLLBACK", "TRANSACTION")
end
「ところでテストコードに:<
という顔文字っぽい書き方があるけど↓、Rubyでこんな書き方ができるんですね」「ちょっとぎょっとする感じ」「Rubyのシンボルでしょうか?」「どうやらシンボルですね」
# activerecord/test/cases/adapters/postgresql/transaction_test.rb#195
assert_operator duration, :<, 5
「こちらはMySQLアダプタです」「マルチステートメントのバッチで、abandon_results!
を最後に呼んでいたのをバッチごとに呼ぶように修正したのね」
# activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb#L99
def execute_batch(statements, name = nil)
statements = statements.map { |sql| transform_query(sql) }
combine_multi_statements(statements).each do |statement|
raw_execute(statement, name)
+ @connection.abandon_results!
end
- @connection.abandon_results!
end
max_allowed_packet
設定を超えるフィクスチャをbulk insertするとMysql2::Error: Commands out of sync; you can't run this command now
が発生していたのを修正。
同PRより
🔗 番外: Railsガイドの旧ガイドリンクにdata-turbolinks="false"
を追加
- PR: Add turbolinks=false on links to older guides by jesselawson · Pull Request #44315 · rails/rails
つっつきボイス:「こちらはRailsガイドのテンプレートにあるリンクの修正だそうです」「過去のガイドへのリンクだから、data-turbolinks="false"
を追加して完全にページ遷移する必要があるんでしょうね」「なるほど」「デフォルトでTurbolinksがオンになっていると<a>
タグの挙動が変わるんだった、気をつけよう」
# guides/source/_welcome.html.erb#L18
<p>
The guides for earlier releases:
-<a href="https://guides.rubyonrails.org/v7.0/">Rails 7.0</a>,
-<a href="https://guides.rubyonrails.org/v6.1/">Rails 6.1</a>,
-<a href="https://guides.rubyonrails.org/v6.0/">Rails 6.0</a>,
-<a href="https://guides.rubyonrails.org/v5.2/">Rails 5.2</a>,
-<a href="https://guides.rubyonrails.org/v5.1/">Rails 5.1</a>,
-<a href="https://guides.rubyonrails.org/v5.0/">Rails 5.0</a>,
-<a href="https://guides.rubyonrails.org/v4.2/">Rails 4.2</a>,
-<a href="https://guides.rubyonrails.org/v4.1/">Rails 4.1</a>,
-<a href="https://guides.rubyonrails.org/v4.0/">Rails 4.0</a>,
-<a href="https://guides.rubyonrails.org/v3.2/">Rails 3.2</a>,
-<a href="https://guides.rubyonrails.org/v3.1/">Rails 3.1</a>,
-<a href="https://guides.rubyonrails.org/v3.0/">Rails 3.0</a>, and
-<a href="https://guides.rubyonrails.org/v2.3/">Rails 2.3</a>.
+<a href="https://guides.rubyonrails.org/v7.0/" data-turbolinks="false">Rails 7.0</a>,
+<a href="https://guides.rubyonrails.org/v6.1/" data-turbolinks="false">Rails 6.1</a>,
+<a href="https://guides.rubyonrails.org/v6.0/" data-turbolinks="false">Rails 6.0</a>,
+<a href="https://guides.rubyonrails.org/v5.2/" data-turbolinks="false">Rails 5.2</a>,
+<a href="https://guides.rubyonrails.org/v5.1/" data-turbolinks="false">Rails 5.1</a>,
+<a href="https://guides.rubyonrails.org/v5.0/" data-turbolinks="false">Rails 5.0</a>,
+<a href="https://guides.rubyonrails.org/v4.2/" data-turbolinks="false">Rails 4.2</a>,
+<a href="https://guides.rubyonrails.org/v4.1/" data-turbolinks="false">Rails 4.1</a>,
+<a href="https://guides.rubyonrails.org/v4.0/" data-turbolinks="false">Rails 4.0</a>,
+<a href="https://guides.rubyonrails.org/v3.2/" data-turbolinks="false">Rails 3.2</a>,
+<a href="https://guides.rubyonrails.org/v3.1/" data-turbolinks="false">Rails 3.1</a>,
+<a href="https://guides.rubyonrails.org/v3.0/" data-turbolinks="false">Rails 3.0</a>, and
+<a href="https://guides.rubyonrails.org/v2.3/" data-turbolinks="false">Rails 2.3</a>.
</p>
🔗Rails
🔗 Rails 6.1を7.0にアップグレードしてみた(Ruby Weeklyより)
つっつきボイス:「7.0へのアップグレードをやってみた記事がそろそろ出始めてますね」「Railsはアップグレードガイドが整備されているので、それに従っていれば基本的な部分は大丈夫なはず👍」
参考: Rails アップグレードガイド - Railsガイド
「アップグレードで問題になるのはむしろgemでしょうね」「たしかに...」
🔗 que: PostgreSQLをジョブキューにする(Ruby Weeklyより)
つっつきボイス:「Ruby Weeklyで見かけたんですが、このプルリクにも登場していたのが気になりました↓」
「★は2000超えか」「queはケイと発音するらしいけど何語だろう?」「これはPostgreSQLをジョブキューとして使えるようにするようですね: 以前も似たようなgemを見たことがあるかも」
# 同リポジトリより
# app/jobs/charge_credit_card.rb
class ChargeCreditCard < Que::Job
# Default settings for this job. These are optional - without them, jobs
# will default to priority 100 and run immediately.
self.run_at = proc { 1.minute.from_now }
# We use the Linux priority scale - a lower number is more important.
self.priority = 10
def run(credit_card_id, user_id:)
# Do stuff.
user = User.find(user_id)
card = CreditCard.find(credit_card_id)
User.transaction do
# Write any changes you'd like to the database.
user.update charged_at: Time.now
# It's best to destroy the job in the same transaction as any other
# changes you make. Que will mark the job as destroyed for you after the
# run method if you don't do it yourself, but if your job writes to the DB
# but doesn't destroy the job in the same transaction, it's possible that
# the job could be repeated in the event of a crash.
destroy
# If you'd rather leave the job record in the database to maintain a job
# history, simply replace the `destroy` call with a `finish` call.
end
end
end
「たしかにぽすぐれならジョブキューもやれる」「Redisを立てたくない人が欲しくなるのかも」「新しめのPostgreSQLでないとできないでしょうけど」
以下の記事では方法こそ違うものの、PostgreSQLをジョブキューの永続化に使っています↓。
🔗 パスワード強度をエントロピーで算出する(Ruby Weeklyより)
つっつきボイス:「パスワードの強度を根拠のある形で算出するためにstrong_password gemを使ってエントロピーベースのパスワードチェックを実装した記事、なるほど」「画面ではauto-check-elementを使っているそうです」
# 同記事より
def create
checker = User.password_checker
entropy = checker.calculate_entropy(params[:value] || "")
percentage = (entropy / STRONG_ENTROPY) * 100
percentage = 100 if percentage > 100
render(partial: "users/shared/password_strength_meter", locals: { strength: percentage.to_i })
end
「いつも言っていますが、セキュリティに関連する実装では専門家が推奨するライブラリを使うべき」「セキュリティコアの手作りは避けたいですね」
🔗 rpush: Rubyでプッシュ通知と連携(Ruby Weeklyより)
つっつきボイス:「Apple Push Notification ServiceやAmazon Device Messagingなどのプッシュ通知を共通の方法で書けるgemのようですね」「なるほど」「こういうのは自分で実装するよりもよさそう👍」
app = Rpush::Apnsp8::App.new
app.name = "ios_app"
app.apn_key = File.read("/path/to/sandbox.p8")
app.environment = "development" # APNs environment.
app.apn_key_id = "APN KEY ID" # This is the Encryption Key ID provided by apple
app.team_id = "TEAM ID" # the team id - e.g. ABCDE12345
app.bundle_id = "BUNDLE ID" # the unique bundle id of the app, like com.example.appname
app.connections = 1
app.save!
「プッシュ通知ってあまり好きじゃないかも...」「プッシュ通知はユーザーがオプトアウトできますし、通知を増やしすぎないようにするのがコツでしょうね」
🔗 Sidekiqのコンカレンシーを制御する(Ruby Weeklyより)
つっつきボイス:「タイトルを見たときはSidekiqのEnterprise版の機能を自前で実装するのかと思ったけど、Redisのロック周りやアトミック操作などの話をしてるな: どうやら特定のジョブがコンカレントに実行されないことを保証するようにしたということみたい」「あ、concurrency controlはそっちの意味なんですね」「特定のジョブが実行されているときに特定のジョブが同時に実行されないようにしたいというのはたしかにありそう」
🔗 その他Rails
つっつきボイス:「寿司ビール問題↓が今になってまたバズっているのはともかく、とっくにサポート終了したMySQL 5.6を今の時代に使っている方がヤバいのでは」「ですよね」「以下の記事↓を見た限りでもExtened Supportすら1年前に終了している」
参考: MySQLのEOL - 雑記帳
utf8mb4を使っていなかったJoomla!にリモートコード実行可能な脆弱性がありましてねhttps://t.co/7b0UTmzXEj / “UTF-8のテーブル(MySQL5.6)に竈門禰󠄀豆子が格納できない問題を調べてみた - Qiita” https://t.co/EITc7UaewY
— 徳丸 浩 (@ockeghem) February 2, 2022
前編は以上です。
バックナンバー(2022年度第1四半期)
週刊Railsウォッチ: Rubygems Adoptionフォームが開設、JetBrains Gateway、NGINX Unitほか(20220201後編)
- 20220131前編 Sidekiqが10歳に、BuildKiteのテストを高速化、フィーチャーフラグほか
- 20220126後編 Rubyコンパイラの歴史動画、RubyのWebAssembly対応進む、ぼっち演算子の注意点ほか
- 20220124前編 Webpackerが公式に引退宣言、『Everyday Rails』日本語版がRails 7に対応ほか
- 20220118後編 Ruby 2.5〜3.1ベンチマーク、Opal 1.4、JRubyが20歳に、2022年のCSSほか
- 20220117前編 rails-ujs->Turboアップグレードガイド、RubyとWeb Componentsほか
- 20220112 Rails 7をRuby 3.1で動かす、クックパッドのRuby 3.1解説記事、Rails 6->7更新ほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)