- Ruby / Rails関連
週刊Railsウォッチ: GitLab 14.0のbreaking changes、Railsのセキュリティ脅威解説シリーズ記事ほか(20210628前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
今回は、以下の公式更新情報から見繕いました。今回載せきれないほど更新がありましたので、来週も追ってみたいと思います。
- 公式更新情報: Rails 6.0.4, Lots of Active Storage goodies, and many Quality-of-Life improvements! | Riding Rails
つっつきボイス:「今回の公式更新情報、すごく多いですね」「Railsウォッチの先週の改修とのかぶりも少なかったので、もしかして追いつかれないように気合い入れてたりして」「ないない😆」
🔗 set_pool_config
のpool_config
がnilの場合にエラーを出すようにした
つっつきボイス:「コネクションプールでマルチプルデータベースのロールやシャーディングなどを設定するときに、pool_config
がnilならエラーをraiseするようにしたようですね」
# activerecord/lib/active_record/connection_adapters/pool_manager.rb#L38
def set_pool_config(role, shard, pool_config)
- @name_to_role_mapping[role][shard] = pool_config
+ if pool_config
+ @name_to_role_mapping[role][shard] = pool_config
+ else
+ raise ArgumentError, "The `pool_config` for the :#{role} role and :#{shard} shard was `nil`. Please check your configuration. If you want your writing role to be something other than `:writing` set `config.active_record.writing_role` in your application configuration. The same setting should be applied for the `reading_role` if applicable."
+ end
end
参考: Shard (database architecture) - Wikipedia
#41549で、
pool_config
がnil
だったのでall_connection_pools
メソッドがエラーになったユーザーがいた。再現用アプリを入手すると、アプリケーションのコンフィグをミスったときにこれが発生することがわかった。たとえば、アプリケーションでwritingロールに:all
を使ってもconfig.active_record.writing_role = :all
が設定されず、setup_shared_connection_pool
でwriting_pool_config
の値がnil
になり、それがset_pool_config
に設定されていた。
setup_shared_connection_pool
を直接修正してエラーを出すようにすることも検討したが、外部gemやアプリケーションがprivate APIを使っているとこのエラーが発生する可能性がある。現実には、Railsであるかどうかに関わらず、どのコードもプールのproolコンフィグにnil
を設定して欲しくない。注: テストでは別途コネクションハンドラを作成して、テスト対象に別のプールを持たせるようにした。そうでないと既存のプールがテストされてしまうので、そちらに影響を与えたくない。
同PRより大意
🔗 forced_encoding_for_deterministic_encryption
オプションの追加など
つっつきボイス:「deterministic?」「"決定論的な"と訳されることが多いですね」「元が同じ文字列なら暗号化した結果も常に同じになる暗号化をdeterministic encryptionと呼びますね: この場合、暗号化済み文字列同士で同値かどうかを比較できます」「なるほど、そういう意味ですか」「deterministic encryptionでも元の文字列のエンコードが違えば同じにならなくなるので、同じになるはずのものがならないことがある問題を修正したようですね: テストでもエンコードをUS-ASCIIとUTF-8にして比較している↓」
# activerecord/test/cases/encryption/encryptable_record_api_test.rb#94
test "encrypt will honor forced encoding for deterministic attributes" do
ActiveRecord::Encryption.config.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
book = ActiveRecord::Encryption.without_encryption { EncryptedBook.create!(name: "Dune".encode("US-ASCII")) }
book.encrypt
assert Encoding::UTF_8, book.reload.name.encoding
end
「いつも暗号化済みで同値比較できる方がよさそうですけど?」「カーディナリティの低いデータ(年齢や性別、誕生日など)を想定したときに、Aさんの暗号化前データを知っていればAさんと同じ情報を持つ人を特定できることになります」「あ、それはマズそう」「いえ、それだけでマズいというものではなく、そういう方式の暗号化もあるということです: 暗号化方式を選ぶときにはそうした使い勝手や運用も含めて検討する必要があります」「なるほど」
参考: Deterministic encryption - Wikipedia
ActiveRecord::Encryption
で"決定論的"暗号化を使う場合には値をUTF-8でエンコードするようになった。このエンコードは暗号化済みペイロードの一部なので、値によってエンコード方式が変わると暗号文も異なってしまう。これによってunique制約やクエリが壊れる可能性がある。
新しい振舞いはactive_record.encryption.forced_encoding_for_deterministic_encryption
でコンフィグ可能。デフォルトはEncoding::UTF_8
で、nil
を設定すると無効にできる。
Jorge Manrubia
Changelogより大意
他にも以下が追加されています。
- 暗号化済み属性で
exists?
をサポート。
EncryptedBook.exists?(name: "Dune")
ignore_case: true
オプションを指定しても再暗号化で大文字小文字を区別するようになった。
🔗 strict_loading
がthrough関連付けの中間レコードへカスケードするようになった
つっつきボイス:「ここで言うカスケードって何だろう?」「このテスト↓を見るとDeveloper.strict_loading.includes(:firms)
でstrict_loading
したものがdev.firms.first.contracts.first
などのメソッドチェーンでも効くようにしたということみたい」「なるほど」「明示的にstrict_loading
するならこういうふうに動いて欲しいでしょうね👍」
# activerecord/test/cases/strict_loading_test.rb#256
def test_strict_loading_with_has_many_through_cascade_down_to_middle_records
dev = Developer.first
firm = Firm.create!(name: "NASA")
contract = Contract.create!(developer: dev, firm: firm)
dev.contracts << contract
dev = Developer.strict_loading.includes(:firms).first
assert_predicate dev, :strict_loading?
[
proc { dev.firms.first.contracts.first },
proc { dev.contracts.first },
proc { dev.ship }
].each do |block|
assert_raises ActiveRecord::StrictLoadingViolationError do
block.call
end
end
end
- Rails API:
strict_loading
--ActiveRecord::QueryMethods
🔗 package.jsonでカレントのRails->npm_version
を使うようになった
つっつきボイス:「ちょうどさっきセマンティックバージョニングの話をしましたけど(後述)、まさにそれに通じる改修かも」「5.0.0.rc1
や5.0.0.beta1.1
というバージョン表記だとnpmのバージョニングシステムに合致しないのか」「Rubygemは5.0.1.1
のような4桁表示を認識できますけど、npmだと認識できないから5.0.1-1
のような表記に置き換えるようですね」
参考: Semantic Versioningの闇 - knqyf263's blog
# railties/lib/rails/generators/app_base.rb#L301
+ # This "npm-ifies" the current version number
+ # With npm, versions such as "5.0.0.rc1" or "5.0.0.beta1.1" are not compliant with its
+ # versioning system, so they must be transformed to "5.0.0-rc1" and "5.0.0-beta1-1" respectively.
+ #
+ # "5.0.1" --> "5.0.1"
+ # "5.0.1.1" --> "5.0.1-1" *
+ # "5.0.0.rc1" --> "5.0.0-rc1"
+ #
+ # * This makes it a prerelease. That's bad, but we haven't come up with
+ # a better solution at the moment.
+ def npm_version
+ # TODO: support `options.dev?`
+
+ if options.edge? || options.main?
+ # TODO: ideally this would read from Github
+ # https://github.com/rails/rails/blob/main/actioncable/app/assets/javascripts/action_cable.js
+ # https://github.com/rails/rails/blob/main/activestorage/app/assets/javascripts/activestorage.js
+ # https://github.com/rails/rails/tree/main/actionview/app/assets/javascripts -> not clear where the output file is
+ "latest"
+ else
+ Rails.version.gsub(/\./).with_index { |s, i| i >= 2 ? "-" : s }
+ end
+ end
+
+ def turbolinks_npm_version
+ # since Turbolinks is deprecated, let's just always point to main.
+ # expect this to be replaced with Hotwire at some point soon.
+ if options.main? || options.edge?
+ "git://github.com/turbolinks/turbolinks.git#main"
+ else
+ "^5.2.0"
+ end
+ end
「ついでにドキュメントも更新されてる↓」「Rails 7という文字を見てちょっとゾクゾクしました」
# guides/source/upgrading_ruby_on_rails.md#19
### Ruby Versions
Rails generally stays close to the latest released Ruby version when it's released:
* Rails 7 requires Ruby 2.7.0 or newer.
* Rails 6 requires Ruby 2.5.0 or newer.
* Rails 5 requires Ruby 2.2.2 or newer.
It's a good idea to upgrade Ruby and Rails separately. Upgrade to the latest Ruby you can first, and then upgrade Rails.
🔗 セマンティック バージョニングよもやま
「半年前の記事ですが、RubyやNode.jsを例に出していました」「記事冒頭の要約に大事なことは書かれていますね: バージョンの比較とバージョン制約は別の話」
- Semantic Versioning 2.0.0にはバージョン"比較"の定義はあるが、バージョン"制約"(>= 2.1.3みたいなやつ)の定義がない
- その結果、同じsemver準拠ライブラリでも制約の解釈が異なり結果が真逆になる
- というかそもそもsemver使ってるエコシステムが少なすぎる
Semantic Versioningの闇 - knqyf263's blogより
「Semantic Versioningはひと頃かなりメジャーになりましたね↓」「お〜、こんなガイドラインもあるんですか」「こうしたルールを何らかの形で決めておかないとRubyのbundlerのようなものが作れません」「それもそうか」
参考: セマンティック バージョニング 2.0.0 | Semantic Versioning
「v1.2.3
みたいにvを付けるのはセマンティックバージョンではないそうです↓」「え、vダメなのか」「この2.0ドキュメントではBNFまで使ってバージョンの書き方決めてる」「そうしないと壊れるからでしょうね」
『v1.2.3』はセマンティック バージョンでしょうか?
いいえ、『v1.2.3』はセマンティック バージョンではありません。しかしながら、セマンティック バージョンに接頭辞の『v』を付けるのは英語ではバージョン番号であることを示す一般的な方法です。バージョン管理では、『バージョン』を『v』と略すことがよくあります。たとえば git tag v1.2.3 -m" Release version 1.2.3 " では『v1.2.3』はタグ名であり、セマンティック バージョンは『1.2.3』です。
semver.orgより
「Semantic Versioningに沿っていると謳っているソフトウェアでも実際に厳密に沿っているとは限らないことが割とありますよ」「へ〜」「X.Y.Z
(Xがメジャー、Yがマイナー、Zがパッチ)の3桁形式は取り入れていても、細かい点が違っていたりするのも見かけます: Railsもここで言うSemantic Versioning (SemVer)2.0.0には従っていないと言えますが『意味付けをしたバージョニング』という意味ではバージョンの付け方はちゃんと管理されているので、広義ではセマンティクスのあるバージョニング、という言い方もできると思います」「なるほど」
参考: Maintenance Policy for Ruby on Rails — Ruby on Rails Guides
Rails follows a shifted version of semver:
edgeguides.rubyonrails.orgより
「Railsのバージョンアップのインパクトとしては、Semantic Versioningで言うYのバージョンアップが事実上メジャーバージョンアップに近いものを感じますね」「まあたしかに😁」「Railsではセキュリティパッチのリリースに4桁目も付けますけど、こちらの方がパッチバージョンに近い気がしています」
「記事にもRubygemsのバージョニングは独特とありますね」「Rubygemのバージョニングとnpmパッケージのバージョニングなどもそうですけど、単に表記が違うだけでなく意味づけも違ったりすることがあるんですよ」「バージョニングって大変...」「固有名詞としての『Semantic Versioning(SemVer)』は厳密に定義を決めたものであるのに対し、世の中ではSemVerを参考にした『セマンティクスのあるバージョニングポリシー』の方言が色々あって、それらが混在してしまっているのが現状ですね」
参考: Representational State Transfer - Wikipedia
後で仕様のリポジトリを見つけました。
オンラインのsemverバリデータも見つけました(公式ではないようです)。
🔗 Active StorageでGCSのcache_control:
にデフォルト値の設定がサポートされた
つっつきボイス:「これはわかりやすいですね: Google Cloud Storage(GCS)のcache_control
にデフォルト値を書けるようになった」
gcs:
service: GCS
...
cache_control: "public, max-age=3600"
参考: Cloud Storage | Google Cloud
🔗Rails
🔗 GitLab 14.0のbreaking changes
つっつきボイス:「お、GitLabのメジャーバージョンアップきた: 近々にアップグレードしようかな」「GitLabのバージョンアップはmorimorihogeさんがやってるんですか?」「1〜2か月に1回ぐらいのペースで気が向いたときにやってます: GitLabのOmnibusパッケージで上げるだけなので随分楽になりましたよ」「へ〜、どんなふうにやってます?」「そんなに大変ではないですね、Ubuntuのパッケージがあるので基本的にはapt-get upgrade
しますが、公式のアップグレードガイドにも推奨手順が書いてある↓のでそれに沿って進めます: 注意すべきはアップグレードパスで、基本的にマイナーバージョンを1つずつアップグレードします」
「GitLab 14.0にはbreaking changesがあるようなのでチェックするか: GraphQLフィールドの一部がdeprecatedになるのね」
「初期ブランチがmaster
からmain
に変わるんですね」「ついにGitLabもか」「最初のうちmain
って打つときに違和感ありましたけど最近慣れてきました」
「"WIP merge request"の呼び方が"draft merge request"に変わるのは、GitHubの命名に寄せた感じかな」「タイトルがWIP
や[WIP]
で始まるとマージボタンが押せなくなるGitLabの機能: ちなみにこの機能自体はGitLabの方がGitHubよりも前から搭載していて、後から追いかけたGitHubではdraft pull requestという名称なんですよ」「へぇ〜!」
参考: Draft Pull Requestをリリースしました - GitHubブログ
「WIPという略語よりdraftの方が非英語話者とかにもわかりやすいからとも書かれてますね」「WIPとかLGTMって何の略かもあまり考えずに使ってたかも」「たしかに略語だと通じる範囲が狭まるので、ちょっとわかる」
「GitLab OAuthのimplicit grantも非推奨化: 今は明示的にやるのが普通なのであまりやらなさそう」「CI_PROJECT_CONFIG_PATH
もCI_CONFIG_PATH
に変わる」
参考: GitLab as an OAuth2 provider | GitLab
「期限切れのsshキーを追加するとデフォルトで無効にするようになった」「GitLab 13.9でsshキーを管理者が強制的に期限切れにするオプションが入っていたのね」「うっかりするとCIが止まったりして」
参考: Optional enforcement of SSH key expiration (#250480) · Issues · GitLab.org / GitLab · GitLab
「Code Quality?」「あぁ、Code QualityはGitLabの機能名で↓、そこでサポートするデフォルトのRuboCopバージョンを変更したのね」「Ruby 2.4〜3.0をサポートして2.1〜2.3のサポートは終了するけど、コンフィグで引き続きサポート可能なところがさすがのGitLabですね👍」
「最近のGitLabはメジャーバージョンアップを以前よりも頻繁にするポリシーになっているんですけど、今回のGitLab 14.0はbreaking changesが割とあるので、後でじっくりチェックしておこうっと」
🔗 fx: Railsで使うPostgreSQLの関数やトリガーを管理
以下の記事で知りました。
つっつきボイス:「関数だからfxなのかな」「PostgreSQLの関数やトリガーを別ファイルに書いてマイグレーションで管理するようですね」「マイグレーションにdrop_function
やdrop_trigger
も書けるらしい」
# 同リポジトリより
% rails generate fx:function uppercase_users_name
create db/functions/uppercase_users_name_v01.sql
create db/migrate/[TIMESTAMP]_create_function_uppercase_users_name.rb
# 同リポジトリより
def change
drop_function :uppercase_users_name, revert_to_version: 2
end
「この書き方どこかで見たな、たしかデータベースVIEWを扱えるgem...scenicだ」「あ、たしかに」「scenicはSQLファイルを別途作ってそこに生SQLを書いて使うんですけど、このあたりとかfxととても似てる↓」「ホントだ」
# scenic-views/scenicより
$ rails generate scenic:view search_results
create db/views/search_results_v01.sql
create db/migrate/[TIMESTAMP]_create_search_results.rb
「インターフェイスがこんなに似てるということは、fxとscenicは同じ人が作ってるのかな?: コントリビュータを見ると、fxの作者もscenicにコミットしてる↓」「なるほど納得」「どちらのgemもやっていることは似ているので、fxの機能がscenicに入ったらいいかも」
🔗 DatabaseCleaner設定の見直し
つっつきボイス:「truncation
をdeletion
に変えて速くなる場合がある、なるほど」
「DatabaseCleanerは使いこなしが大変」「システムテストだとDatabaseCleanerがなくてもよくなったんでしたっけ?」「最初からDatabaseCleanerなしで動くようにテストが書かれていればいいんですけど、データベース書き込みをテストするようになると何らかの形でrewinder的なものが必要になって、気をつけないとテストの実行順序で結果が変わったりすることもあるんですよ」「あ〜」「テスト数が多くなると原因を特定するだけでも時間がかかるので、DatabaseCleanerを入れて様子を見たりしましたよ」
🔗 Railsのセキュリティ脅威を学ぶ: 認証編
つっつきボイス:「ざっと見た感じでは、項目ごとに具体的なコードもあって丁寧に書かれていそうですね👍」「お〜!」「この記事には他のシリーズもあるみたいですね↓: OWASPトップテンと同じ見出しなので今後もトップテンを順に追いかけて記事にしていくみたい」「なるほど」「これ全部追いかけたら凄い」「この記事翻訳したいです」
参考: Rails Security Threats: Injections - Honeybadger Developer Blog
- Injection
- Broken authentication(上の記事)
- Sensitive data exposure
- XML external entities (XXE)
- Broken access control
- Security misconfigurations
- Cross-site scripting (XSS)
- Insecure deserialization
- Using components with known vulnerabilities
- Insufficient logging and monitoring
シリーズ見出しより
「ちなみに記事の冒頭にあるOWASP(Open Web Application Security Project)はこういうセキュリティ上の脅威トップテンみたいなものを定期的に発表しています↓」
前編は以上です。
バックナンバー(2021年度第2四半期)
週刊Railsウォッチ: childprocess gemで子プロセスを制御、Ruby 2.6〜3.0で動くdelegationほか(20210623後編)
- 20210621前編 Active Storageのvariantsをeager loadingするメソッドが追加、Hotwire専用Discuss、AnyCable Proほか
- 20210615後編 RubyのRBSを理解する、シンボルがGCされないとき、Terraform 1.0リリースほか
- 20210608後編 RubyでAppleのLZFSE圧縮データ解凍、AWS Lambda Extensionsが正式リリース、unixgame.ioほか
- 20210607前編 ActiveRecord::Relationのone?とmany?が高速化、RubyKaigi Takeout 2021登壇者募集開始ほか
- 20210601後編 Python使いから見たRuby、MySQLのインデックス解説、GitHubが採用したOpenTelemetryほか
- 20210531前編 RailsConf 2021の動画が公開、GraphQLのN+1を自動回避、Ruby 3のJITとRailsほか
- 20210525後編 Rubyのオブジェクトアロケーション改善、RubyKaigi Takeout 2021開催日発表、AWS App Runnerほか
- 20210524前編 Active Supportの知られてなさそうな機能5つ、RSpecの歴史、書籍『Practicing Rails』ほか
- 20210518後編 RubyのGCを深掘りする、Psych gemのbreaking change、11月のRubyConf 2021ほか
- 20210517前編 Bootstrap 5リリース、productionでSQLiteがwarning表示、rails-ujsの舞台裏ほか
- 20210511後編 AWS Lambda関数ハンドラをDSLで書けるyake gem、VPC Peeringが同一AZ転送量無料化ほか
- 20210510前編 属性メソッドをキャッシュして最適化、Railsのガバナンスに関する声明、bundle install高速化ほか
- 20210427後編 RactorでUDPサーバーを作る、JSONシリアライザalba gem、AppleのAirTagほか
- 20210420後編 ShopifyのJITコンパイラYJIT、PicoRuby、DynamoDBの3つの制約ほか
- 20210419前編 RailsのN+1クエリを定番以外の方法で修正する、GitLabのセキュリティ修正リリースほか
- 20210413後編 RubyMineのRBSサポートとCode With Me、GitHub ActionとDockerレイヤキャッシュほか
- 20210412前編 Active Record属性暗号化機能がRails 7にマージ、RailsNew.ioでrails newオプションを生成ほか
- 20210407後編 エイプリルフールのRuby構文プロポーザル、AWSのVPC Reachability Analyzerほか
- 20210406前編 GitHubが修正したRailsセッションハンドリングの競合、erb/haml/slimの速度比較ほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)