- Ruby / Rails関連
週刊Railsウォッチ: AR::Relation#destroy_allがバッチ分割に変更、Active Record暗号化解説、sidekiq-unique-jobsほか(20210712前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
今回は以下の更新情報から見繕いました。
🔗 ActiveRecord::Relation#destroy_all
の処理をデフォルトでバッチに分割
ActiveRecord::Relation#destroy_all
が処理をバッチに分割するようになった。
destroy_all
は実際には全リレーションを読み込んでからレコードのdestroyを1件ずつ繰り返すのでメモリがすぐ吹っ飛びがち。これを正しく行うために、デフォルトでは100件ずつのバッチに小分けするようにし、#destroy_all(batch_size: 100)
のようにバッチサイズも指定できるようにする。
アプリを7.0にアップグレードするときにdeprecation warningが表示される。Rails 7.1までにdestroy_all
はdestroy対象のオブジェクトのコレクションを返さなくなる。
新しい振舞いに移行するには以下のイニシャライザを設定する。
config.active_record.destroy_all_in_batches = true
このオプションは、今後新規作成するRailsアプリではデフォルトでオンになる。イニシャライザで設定しておくことで環境ごとの違いを生じないようにできる。
Genadi Samokovarov, Roberto Miranda
同Changelogより
つっつきボイス:「find_in_batches
的なバッチ分割処理がRails 7.1のdestroy_all
に取り入れられるようですね」「今後はdestroy_all
がデフォルトでバッチ分割されるようになるのか: 割と大きな変更かも」「コンフィグでオフにできるんですね」
- Rails API:
find_in_batches
--ActiveRecord::Batches
「find_in_batches
のようなバッチ分割は、途中に別のトランザクションがはさまると結果が変わる可能性があるんですよ: destroy_all
もその点は同じだと思いますが、心配な人はdestroy_all
をトランザクションで囲むだろうし、destroy_all
が遅いと思う人はActiveRecord::Relation
のdelete_all
を使うと思うので、気にする人は少ないのかも」「なるほど」
「destroy_all
が途中でコケたらどうするんでしょう?」「destroy_all
はデフォルトでorder: :asc
が付いているので、コケた場合には順序を頼りに追うことになるでしょうね」
- Rails API:
delete_all
--ActiveRecord::Relation
🔗 ActiveSupport::TimeZone.iso8601
でordinal date値をサポート
Date._iso8601
で'21087'
のようなordinal dateの文字列の値をパースしようとすると「28th March 2021」になる。
Rubyの標準ライブラリでDate._iso8601
をサポートしているように、ActiveSupport::TimeZone.iso8601
でもordinal date値をサポートすべき。
「年」と「年日数」({ year: 2021, yday: 87 }
など)の値のパースをサポートし、Date.ordinal
で有効となる日付の生成を試みる。このとき、パースされた値のうち、対応する年の:yday
がサニタイズされる。
同PR Summaryより
参考: Date._iso8601 (Ruby 3.0.0 リファレンスマニュアル)
つっつきボイス:「ordinal dateって初めて聞きました」「'21087'
が2021年の87日目を表す、つまりその年の日付を3桁の日数で表せるのか、へ〜!」「あ、1月1日が001みたいな感じでカウントするんですね」「YYYY-DDD形式って...」
ordinal dateが「年間通算日(年日付)」と訳されているケースを見つけましたが、正式な名前かどうかは不明です。
「ordinal dateって日本だと見かけないかも」「普通あんまり使わなさそう」「ordinal dayの表を見つけた↓」「面白いけど使いたくないな〜😅」「うるう年は数字がズレるんですって」
# https://www.atmos.anl.gov/ANLMET/OrdinalDay.txtより
TABLE OF ORDINAL DAY NUMBER FOR VARIOUS CALENDAR DATES.
(After February, add 1 on leap years).
JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
1 1 32 60 91 121 152 182 213 244 274 305 335
2 2 33 61 92 122 153 183 214 245 275 306 336
3 3 34 62 93 123 154 184 215 246 276 307 337
4 4 35 63 94 124 155 185 216 247 277 308 338
5 5 36 64 95 125 156 186 217 248 278 309 339
6 6 37 65 96 126 157 187 218 249 279 310 340
7 7 38 66 97 127 158 188 219 250 280 311 341
8 8 39 67 98 128 159 189 220 251 281 312 342
9 9 40 68 99 129 160 190 221 252 282 313 343
10 10 41 69 100 130 161 191 222 253 283 314 344
11 11 42 70 101 131 162 192 223 254 284 315 345
12 12 43 71 102 132 163 193 224 255 285 316 346
13 13 44 72 103 133 164 194 225 256 286 317 347
14 14 45 73 104 134 165 195 226 257 287 318 348
15 15 46 74 105 135 166 196 227 258 288 319 349
16 16 47 75 106 136 167 197 228 259 289 320 350
17 17 48 76 107 137 168 198 229 260 290 321 351
18 18 49 77 108 138 169 199 230 261 291 322 352
19 19 50 78 109 139 170 200 231 262 292 323 353
20 20 51 79 110 140 171 201 232 263 293 324 354
21 21 52 80 111 141 172 202 233 264 294 325 355
22 22 53 81 112 142 173 203 234 265 295 326 356
23 23 54 82 113 143 174 204 235 266 296 327 357
24 24 55 83 114 144 175 205 236 267 297 328 358
25 25 56 84 115 145 176 206 237 268 298 329 359
26 26 57 85 116 146 177 207 238 269 299 330 360
27 27 58 86 117 147 178 208 239 270 300 331 361
28 28 59 87 118 148 179 209 240 271 301 332 362
29 29 *60 88 119 149 180 210 241 272 302 333 363
30 30 89 120 150 181 211 242 273 303 334 364
31 31 90 151 212 243 304 365
* Feb 29 exists only on a leap year.
「ordinal dateはISO 8601でも定義されていますし、この機能がActive Supportに入るということは使いたい人がいるということでしょうね」「ordinal dateは月替りを考えたくないときに使うのかな?」「週を数字にして2021年の何週目みたいに表すのは英語圏のアプリとかでたまに見かけますけど」
「ordinal dateを使うことがあるとすれば、組み込みのように少しでも桁数を減らしたい分野かもしれませんね」「あ〜たしかに」「後何日で保証が切れるみたいなタイマーが作りやすそう」
Ordinal dates
異なるカレンダーの日付を比較するなど、週や月の定義が任意だと障害になりやすい場合のためのシンプルなフォーマットです。(中略)このフォーマットは、日付システムを必要とするが完全なカレンダー計算ソフトウェアを含めるのが難しい単純なハードウェアシステムで使われます。
ISO 8601 - Wikipedia(英語版)より
🔗 remove_foreign_key
やadd_foreign_key
でif_exists:
とif_not_exists:
オプションをサポート
remove_foreign_key
/add_foreign_key
でif_exists:
とif_not_exists:
オプションをサポートする。以下のようにアプリケーションのマイグレーション中に、既に存在する外部キーを追加したときの例外や、存在しない外部キーを削除したときの例外を無視できるようになる。
class AddAuthorsForeignKeyToArticles < ActiveRecord::Migration[7.0]
def change
add_foreign_key :articles, :authors, if_not_exists: true
end
end
class RemoveAuthorsForeignKeyFromArticles < ActiveRecord::Migration[7.0]
def change
remove_foreign_key :articles, :authors, if_exists: true
end
end
同Changelogより
つっつきボイス:「Railsのマイグレーションで使うことのあるremove_foreign_key
やadd_foreign_key
でif_exists:
オプションとif_not_exists:
オプションがサポートされたんですね: 個人的にはマイグレーションにこういうオプションをあまり付けたくない気はしますけど」
参考: Active Record マイグレーション - Railsガイド
「たしかschema.rbには最終的にDDLの形で抽出したスキーマが反映されるはずだと思うので、このオプション部分で成功しても失敗してもschema.rbの一貫性は保てそうかな」「自分もたしかそうだったと認識してます」「マイグレーションの蓄積を直接schema.rbに反映していたらschema.rbが不定になる可能性があるので、そういう方法ではやっていないはず」
参考: データ定義言語 - Wikipedia -- DDL
「普段は使わないと思いますが、たとえば外部キーを追加した後に何らかの理由でマイグレーションに失敗したときのリトライにはこういうオプションがあるといいかも」「あ〜たしかに」「マイグレーションが途中で失敗すると再実行でもコケるので、それをリカバリーしたいというニーズに応えるための改修だとしたら理解できる👍」「そういう状況はあって欲しくないけど、そうなったときには欲しいかも」「外部キーのマイグレーションにif_exists:
が書かれているのを見かけたら不安な気持ちになりそうですけどね」
🔗 Action Mailboxで使うデフォルトのActive Storageサービスをカスタマイズ可能に
生メールソースの保存に使うActiveStorageサービスをコンフィグできる機能を追加。
# config/storage.yml
incoming_emails:
service: Disk
root: /secure/dir/for/emails/only
config.action_mailbox.storage_service = :incoming_emails
Yurii Rashkovskii
同Changelogより
つっつきボイス:「今まではAction Mailboxの保存先のActive Storageサービスを選べなかったのがyamlに記述することで選べるようになった: これは必要ですね👍」「Action Mailboxまだ使ったことなかったな〜」「そもそもメールを普段使っていません😆」
参考: Action Mailbox の基礎 - Railsガイド
🔗 Active Storageのコントローラでstrict_loading_by_default
コンフィグをサポート
つっつきボイス:「これもコンフィグ追加ですね」「今までActive Recordでstrict loadingをデフォルトでオンにするとActive Storageのコントローラでエラーになっていたのを修正したらしい」「ActiveStorage::Representations
というコントローラがあったとは知りませんでした」「ActiveStorage::Representations
はActiveStorage::Preview
と関連しているみたい: そのコントローラがビューの中でstrict loading違反していたんでしょうね」
- Rails API: ActiveStorage::Preview
新しくedgeで生成したアプリケーションで
active_record.strict_loading_by_default = true
を設定すると、ActiveStorage::Representations
コントローラでstrict loadingエラーが発生する。このプルリクではその問題を修正し、Active Storageのモデルでstrict loadingを無効にせずに済むようにした。
同PR Summaryより
🔗 uglify-jsをterserに置き換え
- PR: Replace uglifier with terser in dummy applications by SkipKayhil · Pull Request #42622 · rails/rails
つっつきボイス:「ターサー?」「terserが一瞬teaser(いじめっ子)に見えてしまいましたが、terserはterse(簡潔な)の比較級だそうです」「JSコードのminifyや難読化などに使うuglify-jsを新しいterserライブラリに置き換えたんですね: 改修の差分を見てもgemの差し替えぐらいしかやっていない↓」「ホントだ」「こんなにキレイに移行できるとは」「後発なだけにインターフェースも互換性があるんでしょうね」
# Gemfile#L26
gem "uglifier", ">= 1.3.0", require: false
gem "terser", ">= 1.1.4", require: false
「uglifier、そんなgemもありましたね(遠い目)」「この記事は2019年だけどterserが伸びているらしい↓」「JSのライブラリは移り変わりが激しいので、こういう置き換えもケアしているんですね」
参考: 2019年のJavaScript minifier "terser" - Qiita
後で現在の比較を見るとterserが上回っています↓。
参考: terser vs uglify-js | npm trends
🔗Rails
🔗 Active Recordの暗号化機能解説(RubyFlowより)
つっつきボイス:「Rails 7に標準で搭載されるActive Record暗号化(ウォッチ20210412)の解説記事が出たんですね」
「extend_queries
コンフィグをオンにすると以下ができるらしい: uniquenessバリデーションは、まさにこの間話したdeterministic encryptionに関連するヤツ(ウォッチ20210628)」「なるほど」「暗号化方式がdeterministicでない場合は、暗号化データを復号しないとuniquenessバリデーションができなくなります」
- 暗号化カラムで暗号化なしの平文データもクエリできる(
config.active_record.encryption.support_unencrypted_data
もオンにする必要あり)- 暗号化スキームを複数利用可能になる
- uniquenessバリデーションのサポートが有効になる
同記事より
「検索はこういう感じになるのね↓」
# 同記事より
> dog = Dog.find_by!(toy_location: 'top secret')
Dog Load (2.1ms) SELECT "dogs".* FROM "dogs" WHERE "dogs"."toy_location" = ? LIMIT ? [["toy_location", "{\"p\":\"oVgEJvRaX6DJvA==\",\"h\":{\"iv\":\"WYypcKysgBY05Tum\",\"at\":\"OaBswq+wyriuRQO8yCVD3w==\"}}"], ["LIMIT", 1]]
#=> #<Dog id: 1, name: "Bruno", toy_location: "top secret", created_at: "2021-05-28 22:41:23.142635000 +0000", updated_at: "2021-05-28 22:41:23.142635000 +0000">
「記事末尾のlimitation解説もよさそう」「以下のmultiple keysはたぶんキーのローテーションを指していると思います」「なるほど」
Deterministic searching does not support multiple keys - Something good to be aware of going in - if using deterministic encryption/searching, we won't have the ability to use more than one key at a time. If we need to change keys, we'll likely need to do something fancy.
同記事より
「Railsコンソールだと生データが見えるそうです」「当然そうなりますね」「deterministic encryptionだとセキュリティが下がる、これもごもっとも」
「コード例も載っていて要点を押さえた記事、よさそう👍」「この記事翻訳してくださ〜い」「はい、聞いてみます」
その後Honyebadgerより翻訳を許可いただきました🙇。
🔗 Railsアンチパターンシリーズ記事最終回
つっつきボイス:「AppSignalブログのRailsアンチパターンシリーズ記事の最終回だそうです」
「最初はデメテルの法則↓: このコード例は、song
下のlabel
に直接アクセスさせるとlabel
で何でもできてしまうので、Railsのdelegate
ヘルパーを使って隠蔽しましょうという話のようですね」「なるほど」「コンポジションしたオブジェクトを直接公開するべきではない、たしかに」
# 同記事より
# Bad
song.label.address
# Good
song.label_address
参考: Module#delegate
参考: デメテルの法則 - Wikipedia
「次は"そのGem、本当に必要?"的なトピック」「あまりに簡単な機能ならgemより直接実装する方が早いしgemのバージョンアップとかも気にしなくて済むので、自分もよくそう思います」「たしかに」
「このグラフのnpmのモジュールの増え方がヤバい↓」「自動生成してそうな勢い」「大半は使われていないモジュールでしょうけど、勢いがあるのは言語としては望ましいんですよね」「そうですね」「欲しいモジュールを検索するのは大変ですけど」
「次は"例外を握りつぶすな"トピック」「以下のコード例を見ていて思ったんですが、return
で最後に返す値をあまり考慮していないrescue
はたまに問題になりますね」「あ〜」「Rubyは最後に評価したものを値として返す仕様なので、rescue
をメソッドの最後に書くときはちょっと気をつけておかないと、どこからreturn
するかで想定外の値が返される可能性もあります」「なるほど」
# 同記事より
begin
song.upload_lyrics
rescue
puts 'Lyrics upload failed'
end
「なかなかよさそうな記事👍」「これも翻訳してみたいです」
🔗 sidekiq-unique-jobs(Ruby Weeklyより)
つっつきボイス:「Sidekiqに重複したジョブが入らないように一意性を確保できるgemだそうです」「Sidekiqで全く同じパラメータのジョブが複数実行されるのを排除できるようですね」「ふむふむ」「割と欲しい機能に見えるので後でチェックしてみようかな」
「lock: :until_execute
を指定できる↓:、実行完了するまでは同じジョブを投入できないけど実行が終われば投入できるということか、へ〜!」
# 同リポジトリより
class UntilExecuted
include Sidekiq::Workers
sidekiq_options lock: :until_executed
def perform(id)
# Do work
end
end
「lock: :until_expired
の場合はジョブが期限切れになれば同じジョブを再投入できる: これ賢い!」「おぉ〜」
# 同リポジトリより
class UntilExpired
include Sidekiq::Workers
sidekiq_options lock: :until_expired, lock_ttl: 1.day
def perform
# Do work
end
end
「Webアプリの注文ボタンにたとえると、注文ボタンを押してもすぐに反応がないとつい何回も連打してしまうことがありますよね」「そうそう」「この場合なら:until_execute
を指定すれば、実行中は同じジョブをキューに入れられないようにできる」「なるほど!」「READMEをさっと見た限りではロックやexpireなどをいろいろ制御できるみたい」
「このgemを使えば、ジョブの重複制御を自分でやらなくても投機的にジョブを投入できるということになりますね」「それすごいじゃないですか」
「思いついた範囲だと、たとえば時間課金の外部コンピューティングリソースを使いたいときにこのgemが合いそう: 投機的にジョブを積んでおくだけでCPU時間を効果的に使い切れるようになる」「あ〜なるほど」
「ジョブのステータスをチェックして重複を排除したりリトライしたりする機能は自分で実装すると複雑になりがちなので、今度機会があったら使ってみよう👍」「意識することが減るのはありがたい🙏」
🔗 Hanami Mastery: Hanami Frameworkのブログ
Hanami公式かどうかは確かめきれませんでした。
From this month #hanamimastery community reaches the level, where we do support all core team members of @hanamirb sustainably!
(however on the minimal layers yet)
Excited about the next steps! It's a bold idea but YOU make it possible!#opensource #ruby #hanamimasterysupport https://t.co/5keVZYwypn
— Hanami Mastery (@HanamiMastery) July 7, 2021
つっつきボイス:「Hanamiフレームワークのブログサイトを立ち上げたそうです」「Hanami Masteryというタイトルがなかなかポイント高いですね」「花見名人的な」
🔗 その他Rails
つっつきボイス:「Gmailなどで見かける2文字のアバターSVGをERBで生成する記事です」「SlackのWorkspaceもデフォルトでこういう2文字アバターが表示されますね」「たしかにアバターがないと識別が面倒」
前編は以上です。
バックナンバー(2021年度第3四半期)
週刊Railsウォッチ: GitHub CopilotのAI補完、Pure Ruby実装のRuby JIT rhizome、PostgreSQLのPG-Strom拡張ほか(20210706後編)
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)