- Ruby / Rails関連
週刊Railsウォッチ: orderでコレーション指定をサポート、awesome_nested_set、GitHub Copilotほか(20220221前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
🔗 Active Recordのdestroy_association_async_job
コンフィグが効くように修正
アプリケーションで
dependent: :destroy_async
オプションを指定したhas_many関連付けでは、バックグラウンドで関連レコードをdestroyするジョブをconfig.active_record.destroy_association_async_job
コンフィグで指定できるはずだが、無視される。つまり、常にデフォルトのActiveRecord::DestroyAssociationAsyncJob
がバックグラウンドでレコードをdestroyする。このPRは
config.active_record.destroy_association_async_job
の設定を無視しなくなる。
同PRより
つっつきボイス:「非同期でレコードをdestroyするジョブをカスタマイズするコンフィグが常にデフォルトで上書きされていたのか」「これはバグ」「=
を||=
にすることで修正されてますね↓」
# activejob/lib/active_job/railtie.rb#L45
- ActiveSupport.on_load(:active_record) do
self.destroy_association_async_job = ActiveRecord::DestroyAssociationAsyncJob
+ self.destroy_association_async_job ||= ActiveRecord::DestroyAssociationAsyncJob
end
参考: §3.7.34 config-active-record-destroy-association-async-job
-- Rails アプリケーションを設定する - Railsガイド
🔗 order
でCOLLATE
を安全なSQL文字列として使えるようになった
#36448でORDER BYに関数を渡せるようになった。
COLLATE
も利用できるようにすべき。
Post.order('title COLLATE "C"')
その他
PostgreSQLではコレーション名を引用符で囲む必要がありそうだが、MySQLやSQLiteはそうではない。
同PRより
つっつきボイス:「お〜、ついにorder
でコレーションが書けるようになった🎉」「今までできなかったのは何ででしょう?」「単にこれまでActive Recordのインターフェイスではこの書き方が許可されていなかったんですよ」「あ、そういうことですか」
「たしか以前はselect
するカラムにコレーションを書いて、それをorder
したような覚えがありますが、order
の中でコレーションを直接書けるようになったのはありがたい👍」「なるほど」「今までも回避方法はありましたけど、そもそもコレーションの構文がRDBMSによって違うんですよ」
「言われてみればコレーション(照合順序)は標準のSQLじゃなくてRDBMSの拡張だから違っててもおかしくないか」「テストコードでもRDBMSによってコレーションに書けるものも違ってくるのがわかりますね↓」
# activerecord/test/cases/unsafe_raw_sql_test.rb#169
test "order: allows valid arguments with COLLATE" do
collation_name = {
"PostgreSQL" => "C",
"Mysql2" => "utf8mb4_bin",
"SQLite" => "binary"
}[ActiveRecord::Base.connection.adapter_name]
🔗 mysql2アダプタでActiveSupport::Duration
を適切に扱うよう修正
- PR: Quote ActiveSupport::Duration in mysql2 adapter by kmcphillips · Pull Request #44408 · rails/rails
#44404 の作業中にテストカバレッジを拡大しようとしたところ、MySQLアダプタで
ActiveSupport::Duration
オブジェクトもquote
で処理する必要があることに気づいた。これは#42440や#16069の「一般的なRailsのプラクティスとMySQLの組み合わせでクエリを外部から操作される可能性の緩和」の続き。このケースでは、
ActiveSupport::Duration
オブジェクトで.to_s
を呼ぶとセクション数を整数で返す。これがMySQLアダプタでquote
されないままだと、抽象のConnectionAdapters
に渡され、そこで文字列ではなく数値として処理されてしまう。これは技術的にはセキュリティ修正という形になるが、(攻撃の)ターゲットベクタ(媒介者)とするのは難しそうではある。自分はその点について概念実証(PoC: Proof of Concept)を行っていない。
同PRより
つっつきボイス:「mysql2アダプタでActiveSupport::Duration
が文字列にキャストされていなかったのを修正したようですね」「他のアダプタのテストも足したみたい」「コメントによるとキャストの修正前はPostgreSQLだとエラーになったけどMySQLだと通っちゃったのか」
# activerecord/lib/active_record/connection_adapters/mysql/quoting.rb#L8
module Quoting # :nodoc:
def quote_bound_value(value)
case value
- when Numeric
+ when Numeric, ActiveSupport::Duration
quote(value.to_s)
when BigDecimal
quote(value.to_s("F"))
when true
"'1'"
when false
"'0'"
else
quote(value)
end
end
# activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb#75
def test_where_with_duration_for_string_column_using_bind_parameters
count = Post.where("title = ?", 0.seconds).count
assert_equal 0, count
end
後で読み返すと、rafaelfrancaが「びっくりさせないで〜😂」「このissueを攻撃に転用するのは難しそう、それ以外の問題はありそうだけど」みたいな感じでコメントしていました。
🔗 DBのrakeタスクでVERSION envの数値に_
が使えるようになった
つっつきボイス:「これはコミットのみでした」「環境変数のVERSION
の数値にアンスコを使えるようになった」「これはマイグレーションのバージョンっぽいですね」「バージョン指定でこんな書き方ができるのか」「Rubyは数値のアンスコでこうやって桁区切りできますよね」「マイグレーションの日時みたいな数値をフラットに書くと読みづらいので、アンスコを使えるようにしたい気持ちはワカル」
# activerecord/test/cases/tasks/database_tasks_test.rb#1430
ENV["VERSION"] = "2000_01_01_000042"
assert_equal 20000101000042, ActiveRecord::Tasks::DatabaseTasks.target_version
🔗 ルーティングのtrailing_slash: true
オプションを修正
つっつきボイス:「こちらはルーティングの修正だそうです」「trailing_slash: true
って、ルーティングでURLの末尾にスラッシュを付けるという意味なんですか」「ヘルパーでURLを取得したときにスラッシュを付けられるようですね: これがやりたくなるときもたまにありそう👍」
# 同Changelogより
get '/test' => "test#index", as: :test, trailing_slash: true
test_path() # => "/test/"
🔗Rails
🔗 RailsのRubyはRails方言(Ruby Weeklyより)
つっつきボイス:「RailsのActive Supportのことかな」「記事にもあるように、Active SupportはRubyにたくさんパッチを当てていますね↓」「こうして見ると多い」
# 同記事より
{Array=>{:original_methods=>196, :as_methods=>251, :added_by_as=>55},
Class=>{:original_methods=>117, :as_methods=>172, :added_by_as=>55},
Date=>{:original_methods=>132, :as_methods=>240, :added_by_as=>108},
DateTime=>{:original_methods=>142, :as_methods=>277, :added_by_as=>135},
File=>{:original_methods=>236, :as_methods=>279, :added_by_as=>43},
Hash=>{:original_methods=>182, :as_methods=>246, :added_by_as=>64},
Integer=>{:original_methods=>148, :as_methods=>206, :added_by_as=>58},
Module=>{:original_methods=>114, :as_methods=>165, :added_by_as=>51},
Object=>{:original_methods=>63, :as_methods=>83, :added_by_as=>20},
Range=>{:original_methods=>133, :as_methods=>171, :added_by_as=>38},
String=>{:original_methods=>188, :as_methods=>256, :added_by_as=>68},
Symbol=>{:original_methods=>92, :as_methods=>114, :added_by_as=>22},
Time=>{:original_methods=>121, :as_methods=>257, :added_by_as=>136}}
「記事の下の方では、Active Support出身のメソッドがRuby本体にもたくさん取り込まれたとありますね」「そうそう」「自分の中では出世メソッドと呼んでます😆」
「Railsを動かしているRubyはあくまでRails方言、たしかに」「素のRubyを書いていると、たまにActive Supportにしかない機能を使いそうになっちゃうことある」「そうそう、present?
と書いた直後にないことに気づいたり」「blank?
はRubyにありますけどね」「どちらにもあるけど挙動が違うメソッドもありますね: Rubyだと引数が使えないけどRailsだと使えるように拡張されていたり」
「Active Supportはちと大きいので、HanamiのようにActive Supportがない環境なら記事にもあるようにdry-rbを使う手もありますね↓: dry-rbは必要最小限かつよくできてる👍」
参考: dry-rb - Home
🔗 awesome_nested_set: 入れ子集合を扱うgem(Ruby Weeklyより)
つっつきボイス:「awesome_nested_setは、acts_as_nested_setやBetterNestedSetを置き換えられるそうです」「acts_as_で始まるのは古いライブラリに多いですね」
「lft
とかrgt
というこの書き方は見覚えある↓: ネストした集合を扱える感じ」「lft
とrgt
は左と右なんですね」
# 同リポジトリより
class CreateCategories < ActiveRecord::Migration
def change
create_table :categories do |t|
t.string :name
t.integer :parent_id, null: true, index: true
t.integer :lft, null: false, index: true
t.integer :rgt, null: false, index: true
# optional fields
t.integer :depth, null: false, default: 0
t.integer :children_count, null: false, default: 0
t.timestamps
end
end
end
なお、この記事ではacts_as_nested_setが使われています↓。
🔗 SQLでネステッドセットを扱うパターン
「ネストしたデータセットをデータベースでやろうとすると大変そうですよね」「実はそのような場合のベストプラクティスはある程度確立しているんですよ: こういう構造を適切に作ると、たとえばネストの上の層のデータを短いSQLクエリで書けて、かつインデックスが効くようにできたりします」「お〜」
「たとえばこのancestryというgem↓の場合は、内部でスラッシュ区切りの階層データ構造を持つ形で多階層カテゴリを扱っている」
参考: 多階層カテゴリでancestryを使ったら便利すぎた - Qiita
「awesome_nested_setのようにlft
やrgt
を指定する方法は、たしかSQLのパターンにあったはず: そうそう、このQiita記事↓にあるNested set model(入れ子集合モデル)やNested intervals(入れ子区間)なんかがそうですね」「こういうパターンがあるのか〜」
参考: 階層構造(入れ子集合モデル)について - Qiita
参考: Nested set model - Wikipedia
参考: Nested intervals - Wikipedia
「lft
やrgt
というとツリー構造みたいなものでしょうか?」「Nested set modelはツリーとは違って、たしかid空間を効率よく配置する戦略ですね」
「ちょうどこの記事の図↓にあるように、上のベン図に合うように下の区間でidが設定される: このようにデータを配置することでidの区間の指定にインデックスが効くようになります」「お〜なるほど!」「SQLのテクニック集的な書籍には必ず載っていると言ってもいいと思います: naive treeと一緒に紹介されることが多いですね」
参考: 第5回 SQLで木構造を扱う~入れ子集合モデル (1)入れ子集合モデルとは何か :SQLアタマアカデミー|gihyo.jp … 技術評論社
参考: SQLアンチパターン ナイーブツリー - Qiita
🔗 Railsアプリの脆弱性警告対策
Link: ECR拡張スキャンでRailsアプリを診断した際の脆弱性警告(偽陽性)への対策 - メドピア開発者ブログ https://t.co/KqOWRcZo81
— Yukihiro Matzmotto (@yukihiro_matz) February 16, 2022
つっつきボイス:「Railsにインストールしたgemに含まれているGemfile.lockで警告が出たのか」「gemの問題なのですぐ対応しにくいヤツですね」「記事では可能なものについてはgem作者に対応してもらいつつ、すぐ対応できないものはDockerからgemのGemfile.lockを削除した、なるほど」
🔗 JetBrains IDEのGitHub Copilotプラグイン
つっつきボイス:「JetBrains IDEのGitHub Copilotプラグインは今使っていてなかなか便利👍」「私はVSCodeでGitHub Copilotを使ってます: VSCodeのIntelliSenseとケンカするっぽいので後者はとりあえず外してますが」
「GitHub Copilotってそんなにいいんですか?」「いいですよ〜❤️、ぜひTechnical Previewに申し込んで使ってみてください↓」
参考: GitHub Copilot · Your AI pair programmer
「GitHub Copilotの補完が邪魔になることもないではないけど、かなり賢い(JetBrains IDEではEscでキャンセルできます)」「空のファイルだとRubyにJSのコードを突っ込んできたりすることもありましたけど、コードを書く前に自然言語で構わないのでコメントに概要を書いたりして、なるべくヒントを与えてあげるとどんどんよくなりますね」「そうそう、下手するとコメントまで補完してくれる」
「単に一部を補完させるより、コメント以外のコードをいったん全部消してGitHub Copilotになるべく書かせるぐらいの気持ちで使うといいみたいです」「使ってみたい〜、早速申し込んでみました」「この間GitHub Copilotの使いこなしを見せてくれた人は、GitHub Copilotの振る舞いを完全に掌握したら生産性が3倍は向上したと話してました(個人の感想です)」
GitHub Copilot、ただコード予測してくれるだけかと思ったら翻訳までしてきてびびってる pic.twitter.com/cAnXpIWQs4
— どっかのげんちゃん。+ (@SomeoneMentsuyu) February 12, 2022
「GitHub Copiloはtコード以外にも、このツイートみたいにロケール情報なんかもビシバシ補完してくれますね↑」「GitHub Copilotは自分のプロジェクト内のコードを優先的に扱うので、補完されるメソッドチェーンなどもちゃんとプロジェクトに沿ってくれる」「いいな〜」「もちろん補完されたコードは調べますけど、メソッド名を探さなくて済むのが楽」「変数名やメソッド名をちゃんと付けるモチベも高まりそうですね」「楽しみに待ってます」
🔗 その他Rails
「小技記事です」「fixtureのデータをdb:seedでも使えるようにする、誰もが一度はやりたくなるヤツ」「同じデータを2つも作りたくないですよね」「もちろんdb:seedで入れるデータが開発・テスト用限定で、productionには入れないものでないといけないと思いますが」「たしかにseedの扱いってプロジェクトで違うことがありますよね」
「お、Hanami v2.0.0のアルファ版」「Ruby 3.0以上が必須ですって」「Hanamiを使っている人ならきっと大丈夫」
🔥 Our “Hotwire for Rails Developers” course is now available in early access!
You’ll learn exactly how to use Hotwire to improve the speed and responsiveness of a real Rails app. No hype or hand-waving here.https://t.co/NnI7gxoygI pic.twitter.com/xWr6Fcjfdy
— Mike & Nicole: The Pragmatic Studio (@pragmaticstudio) February 2, 2022
Rails 7で作り直し中のアプリで、ビューに渡す配列をハッシュに変えたらそれだけでアロケーションがごっそり減ってレスポンスタイムも改善された
(そういうことで感激するレベル)
— ハングリィ・ライク・カネゴン (@hachi8833) February 11, 2022
「正確には配列の配列をハッシュの配列に置き換えたんですが、アロケーションはたしかに減っていました」「<<
で配列に追加してループで回したりするとハッシュよりアロケーションが増えそうな気もしますね」「今回はJeremy Evansさんの教え↓にしたがってハッシュに変えたんですが、たしかに前はそれをやってました😅」「何でも配列にしてよかったのはRuby 1.8までですね」「そういえば元々Ruby 2.0のときに書いたコードでした」
前編は以上です。
バックナンバー(2022年度第1四半期)
週刊Railsウォッチ: Bundler自身のバージョンロック機能、gem署名メカニズムの提案ほか(20220216後編)
- 20220214前編 Rails 7.0.2の改修内容、receipts gemでレシートを作成ほか
- 20220209後編 Rubygems.orgのAPIキーに権限スコープが追加、RailsのDBパフォーマンス改善ほか
- 20220208前編 Rails 6.1を7.0にアップグレードしてみた、PostgreSQLでジョブキューほか
- 20220201後編 Rubygems Adoptionフォームが開設、JetBrains Gateway、NGINX Unitほか
- 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ウォッチタグ)