- Ruby / Rails関連
週刊Railsウォッチ: Arel::Nodes::NodeにAPIドキュメントが追加、rubocop-mdほか(20230405前編)
こんにちは、hachi8833です。RubyKaigi 2023の各種イベントが発表されていますね。
People are returning back to in-person conference. What about social events? Check out https://t.co/ubqYPF2oKo for tentative schedule of parties and related events! #rubykaigi
— RubyKaigi (@rubykaigi) March 10, 2023
参考: Ruby on Rails | endoflife.date
ひさびさにLightning Talkも #rubykaigi に帰ってきた〜 🤩 日/英可, 4/19まで!!! :gong: "Your talk can be spoken in either English or Japanese", "The CFP form will remain open until Apr 19th" ⚡️ RubyKaigi 2023 LT CFP https://t.co/TdZ9DtZrF0
— Kakutani Shintaro (@kakutani) April 4, 2023
🔗Rails: 先週の改修(Rails公式ニュースより)
🔗 複合キー関連の改修2件
以下のような(
query_constraints
による)複合キー制約を定義した関連付けがあるとする。BlogPost.has_many :comments, query_constraints: [:blog_id, :blog_post_id]
このとき、以下のように
comments
オブジェクト全体でブログ記事をクエリできるようになる。comments = Comment.first(2) BlogPost.where(comments: comments).to_a
残りの作業
関連付け名を用いて制約をビルド可能になるこの機能は非常に強力なので、少なくとも今後以下のようなサブクエリのユースケースでカバーする関連付けの種類を増やす必要があるだろう(belongs_to
、has_one
、has_many through
、ポリモーフィック関連付け)。BlogPost.where(comments: Comment.where(id: 1, blog_id: 1))`
そこで今後は必要に応じてカバレッジと修正を増やしていく予定。ただし他の作業がパラレルに完了するのをブロックしないよう、最も一般的なユースケースについて修正を行いたいと思う。以下のような抽象的な利用法に依存する機能がブロックされないようにする。
.where(association_name => association_records)
今回の例では、
association_name
がcomments:
に、association_records
がComment
オブジェクトの配列に相当する。
#47692より
ActiveRecord::FinderMethods#find
で、以下のように複合主キーをセットで渡せるようになった。Cpk::Book.find([1, 1]) Cpk::Book.find([[1, 1]]) Cpk::Book.find([1, 1], [1, 2]) Cpk::Book.find([[1, 1], [1, 2]])
値は複合主キーカラムの値として扱われるが、これはモデルの設定として
primary_key
クラスインスタンス変数に配列として複数のカラムが指定された場合のみが対象となる。以前から、
find
メソッドは以下のように常に"識別子"を受け付けるシグネチャを持っている。Model.find(ID) Model.find([ID]) Model.find(ID, ID) Model.find([ID, ID])
上の
ID
にはモデルを識別するものが入る。Railsがシングルカラムの識別子だけをサポートする限り、この点はさほど明確ではなかった。しかし複合主キーがサポートされたことで、識別子としてのID
の意味が拡張された。値のセットでモデルを識別可能になったので、それをサポートするためにfind
のシグネチャを拡張する必要がある。実装の詳細
primary_key
を持つモデルについてのみ振る舞いが変更されるので、is_a?(Array)
の分岐が複数になり、そのいくつかは重複する。primary_key.zip(values_set)
が反復するパターンになったり、.inject(&:or)
を使うバッチクエリのビルド方法も反復する。
しかし現時点では、重複を導入してできるだけ多くのユースケースを集めておき、これらを1つの概念に抽象化してリファクタリングしたい。冗長なユースケースについては、当面その概念にふさわしい名前と置き場所を見つけるためにのみ役立てることになるだろう。
#47664より
つっつきボイス:「複合キーに関連する改修が増えてきましたね」「#47692は、query_constraints
を指定した関連付けをwhere
で絞り込む際、個別のキーを指定せずに関連名だけで絞り込めるようにした: これはできて欲しいヤツ👍」「今回はhas_many
関連付けで、今後はbelongs_to
関連付けとかでも順次使えるようにするんですね」「複合主キー関連の改修はもっと大規模になるかと思っていたけど、思ったより変更範囲が小さいのがちょっと驚き」
参考: Edge API query_constraints
-- ActiveRecord::Persistence::ClassMethods
「次の#47664はfind
メソッドに[1, 1]
や[[1, 1], [1, 2]]
のような形で複合主キーを渡せるようになった、なるほど」「find
に配列で渡すの何でだろうと思ったら複合主キーの話だったんですね、やっと理解しました」
参考: Rails API ActiveRecord::FinderMethods
「ところで、渡す複合主キーの順序ってどうやって規定するんだろう?」「あ、それもそうか」「関連付けの定義で書いた順に適用されそうに思えるけど、複合主キーを渡すときの順序については気をつけておく必要はあるかも」
🔗 Railsアプリをディレクトリの外から再起動可能になった
bin/rails restart
をアプリのディレクトリの外から呼び出せるようになった。
従来は、以下を実行すると"No Rakefile found"で失敗した。blog/bin/rails restart
Petrik de Heus
同Changelogより
つっつきボイス:「アプリの外部から/bin/rails restart
を呼べるようにするためにthor gemを使ったんですね」「restartタスクが追加されてますが、内容としてはtmp/restart.txt
ファイルを置くだけで、既存の再起動の仕組みを使っているんですね」
module Rails
module Command
class RestartCommand < Base # :nodoc:
desc "restart", "Restart app by touching tmp/restart.txt"
def perform
require "fileutils"
FileUtils.mkdir_p Rails::Command.application_root.join("tmp")
FileUtils.touch Rails::Command.application_root.join("tmp/restart.txt")
end
end
end
end
🔗 Arel::Nodes::Node
にAPIドキュメントが追加された
動機/背景
現在のArelはprivate APIであり、ドキュメントがほぼ存在しないが、APIとして非常に有用であり、アプリケーションでもgemでも同じぐらい使われている。最終的にはArelをpublic APIにできればと思っている。このプルリクは、Arelにドキュメントを追加して最終的にpublicにする取り組みの出発点である。
詳細
このプルリクでは、SQL文生成でよくあるノードの利用方法について簡単な説明文を追加する。網羅的なドキュメントではないが、今後ドキュメントを増やす取り組みの出発点となる。
追加情報
Ruby on Rails Discordで@matthewdがドキュメント作成を提案してくれた。
同PRより
つっつきボイス:「これはArelを愛するkazzさんが喜びそう」「お〜、これは嬉しいヤツです😂」「Arelは公式には使って欲しくないというスタンスだったから今までAPIドキュメントがなかったんでしたっけ」「想像ですけど、Arelもそろそろ枯れてきたしAPIを公開してもいいのではという流れなのかも」「期待しちゃいます」
「Arel怖いです...」「Arel大好き😋」「Arelの他のドキュメントはまだ空ですが、今後も増やすようですね」「Arelがないと書けないようなActive Recordコードは今でもいろいろあるので、Arelを使う人のためにドキュメントを整備する方向に行くのはわかる」
ちなみにArelはActive Relationとも呼ばれるそうです。
🔗 fixtureパスをRailsエンジン単位で指定可能になった
動機/背景
このプルリクを作成した理由は、fixture_path
をRailsエンジンレベルで設定することで、アプリを複数のデプロイ可能な単位に分割しやすくするため。詳細
このプルリクはTestFixtures#fixture_paths
(複数形)を導入する。これはクラス属性で空配列である。これを用いることで、Railsのデフォルト以外のfixtureパスをアプリケーションごとに追加可能になる。
たとえば、Railsエンジンを複数利用しているアプリケーションがあり、それぞれが独自のテストとfixtureを持っている場合、エンジンが独自のfixtureへのパスをfixtureパスに追加できるようになる。module UserManagement class Engine < Rails::Engine initializer("user_management.fixture_path) do ActiveSupport.on_load(:active_support_test_case) do self.fixture_path << "#{Rails.root}/user_management/test/fixtures}" end end end end
追加情報
単数形のfixture_path
は非推奨とした。古い振る舞いとの後方互換性を維持するために、fixture_path
のリーダーとライターを書き直した。
同PRより
つっつきボイス:「単数形のfixture_path
が非推奨になって複数形のfixture_paths
になったんですね」「fixtureパスを追加したい場合って何だろうと思ったら、Railsエンジンが独自のfixtureを持ってる場合か」「これはできる方が嬉しいですね👍」
参考: Rails API ActiveRecord::TestFixtures
参考: Rails エンジン入門 - Railsガイド
🔗 Delegated Typesでカスタムカラム名を指定するforeign_type
オプションが追加された
delegated_type
の{role}_type
カラム名をforeign_type
オプションでカスタマイズ可能になった。概要
Delegated TypesはRailsの既存のポリモーフィック関連付けに強く依存している。delegated_type
で作成される主要な関連付けはポリモーフィックbelongs_to
関連付けである。ポリモーフィック関連付けを使っているが、型やクラスを含むカラム名をカスタマイズする必要があるアプリでは、
foreign_type
オプションが利用可能である。Delegated Typesでも同様にカスタマイズできれば便利だろう。
delegated_types
は既に背後のbelongs_to
関連付けに沿ってオプションを渡すので、比較的素直に変更できる。単にtypeカラムを"#{role}_type"
で派生させるのではなく、foreign_type
オプションに沿って派生させることが可能になる(ポリモーフィック関連付けで使われるのと同じオプションである)。
例:delegated_type :entryable, types: %w[ Message Comment ], foreign_type: :entry_class
同PRより
つっつきボイス:「"#{role}_type"
というRails wayな命名に沿っていないカラム名をDelegated Typesで直接指定できるようになったんですね」「なるほど」「既存のデータベースを使いたい場合はこれができないと困る」
:foreign_type
関連付けられるオブジェクトの型を保存するカラムを指定する。デフォルトでは、渡されたrole
に“_type”
サフィックスを追加した名前を推論する。たとえば、delegated_type :entryable, types: %w[ Message Comment ]
の関連付けを定義したクラスは、“entryable_type”という名前をデフォルトの:foreign_type
として使うようになる。
edgeガイドActiveRecord::DelegatedType
より
「Delegated TypesはAPIドキュメントの翻訳があります↓」
🔗 番外: rubocopでmarkdownドキュメントをチェックするようになった
- PR: Rubocop markdown snippets by zzak · Pull Request #47186 · rails/rails
- PR: Introduce markdownlint for guides by zzak · Pull Request #47779 · rails/rails
Railsのmarkdownファイルを対象にrubocop-mdを導入する。
これにより、ガイド内のコードスニペットもRailsのコーディング規約に準拠するようになり、変更の旅に手作業で確認せずに済むようになる。
なお、Style/StringLiterals
による変更量は非常に大きいので、ガイドでは意図的に無効にしてある。その変更は、このプルリクがマージされた後で1件のプルリクでまとめて行おうと思っている。
もっと細かな変更についてはzzak#2やzzak#3で見られる。
同PRより
つっつきボイス:「ドキュメント関連の改修ですが、一番うれしいのは私かも😂」「Railsガイドのmarkdownファイルに対してrubocop-mdやmarkdownlintでスタイルのCIチェックをかけるようになったんですね↓」「rubocop-mdもmarkdownlintも知らなかった、今度使ってみようっと」
「ところで、原文のmarkdownでパラグラフがしょっちゅう80文字で強制改行されていたりするんですが、あれがあるとRailsガイド↓の翻訳がとてもやりにくいんですよ😢」「そのあたりを修正するとなると修正量が増えそうですね」「プルリクを投げたくても、強制改行はHTMLでの表示には影響しないので本質的ではないし、スタイル修正のコミットを増やすとgit blame
する人の邪魔になりそうで、ずっとためらっていました」
参考: Ruby on Rails ガイド:体系的に Rails を学ぼう
参考: Git - git-blame Documentation
「markdownドキュメントを大勢でメンテナンスすると、rubocop-mdのようなlintツールなしでは書式がばらついてしまいますよね」「ツールで書式を整えるようにすれば今後ドキュメントのプルリクを減らす効果もあるし、書式をどうするかで争うことも減ると思うのでいいと思います👍」
🔗Rails
🔗 AnyCableをRails以外のものにも使う(Ruby Weeklyより)
つっつきボイス:「AnyCable gemとAnyCable-GoというWebSocketサーバーを使ってTwilioをHanamiに接続するという記事だそうです」「そういえばAnyCableもAnyCable-GoもEvil Martiansが手掛けていますね」
参考: Hanami | The web, with simplicity
「そういえばTwilioはこれまでもよく話題に登場していますね(ウォッチ20200901)」「Twilioは電話/SMS/メールなどのAPIを多数提供している大規模メッセージングサービスで、この種のサービスとしては最古参の部類でしょうね」
参考: Twilio(トゥイリオ) | SMS、音声、ビデオ、二要素認証用のコミュニケーションAPI | Twilio
参考: ショートメッセージサービス - Wikipedia -- SMS
「商用メール配信サービスで有名なSendGridもTwilioの傘下です」
参考: Email Delivery, API, Marketing Service | SendGrid
参考: SendGrid - Wikipedia
「Twilioは歴史が長いし、エンタープライズ向けの大規模メッセージング機能がいろいろ充実しているんですよ: マルチアカウントにも対応していて、プロジェクトを作ってその中でマルチアカウントの権限を管理するといったAWS Organization的なこともできる(それなりの費用がかかりますが)」「なるほど」「KDDIが代理店になっているので請求書が日本語なのもいい」「先ごろKDDIがTwilioの代理店をやめると発表したのが話題になっていましたね」
参考: 弊社によるTwilioサービス提供の終了とお手続きについて|Twilio - KDDI Web Communications
🔗 RailsからPostmark経由でメール送信する(Ruby Weeklyより)
# 同記事より
# config/environments/production.rb
config.action_mailer.perform_deliveries = true
config.action_mailer.delivery_method = :postmark
config.action_mailer.postmark_settings = {
api_token: ENV["POSTMARK_API_TOKEN"]
}
# or use Rails Encrypted Credentials
config.action_mailer.postmark_settings = {
api_token: Rails.application.secrets.postmark_api_token
}
つっつきボイス:「Postmarkというメール配信サービスを使う記事だそうです」「SendGrid的なサービスのひとつかな: 「Postmarkはメール配信をトランザクションストリームとブロードキャストストームに分けているのが特徴らしい↓」「最大で10のストリームが使えるんですね」「同じIPアドレスから大量にメール送信すると詰まったりすることがありますけど、ここではジョブキューを分けるような感じでストリームを分ける形にしているのかもしれませんね」
参考: A better way to send all your application email | Postmark
🔗 RubyやActive Supportでファイルを暗号化する(Ruby Weeklyより)
つっつきボイス:「SecureRandom
はActive Supportに前からあるけど、encrypted_fileというのもあるのか」「記事ではinvisible_inkというgemも使っていますね」
# 同記事より
def build_encrypted_file(file)
ActiveSupport::EncryptedFile.new(
content_path: file,
key_path: "invisible_ink.key",
env_key: "INVISIBLE_INK_KEY",
raise_if_missing_key: true
)
end
参考: Rails API SecureRandom
モジュール
参考: Active Support コア拡張機能 - Railsガイド
「記事では、暗号化済みファイルをエディタで開くときに復号して、保存するときに暗号化している↓: Railsのcredential編集機能みたいなことをやろうとしているっぽいですね」「たしかに」
# 同記事より
when "write"
file = ARGV.shift
handle_missing_file_argument if file.nil?
if ENV["EDITOR"].to_s.empty?
puts "No $EDITOR to open file in"
exit 1
end
begin
encrypted_file = build_encrypted_file(file)
encrypted_file.write(nil) unless File.exist?(file)
encrypted_file.change do |tmp_path|
system(ENV["EDITOR"], tmp_path.to_s)
rescue Interrupt
puts "File not saved"
end
rescue ActiveSupport::EncryptedFile::MissingKeyError => error
handle_missing_key(error)
end
参考: §10.1 独自のcredential -- Rails セキュリティガイド - Railsガイド
🔗 RailsチュートリアルのCodeSpacesリポジトリ
つっつきボイス:「RailsチュートリアルがGitHub CodeSpacesにチュートリアル用のリポジトリを作ったそうです: チュートリアル1〜3章の環境構築をスキップできます」「教育分野だと環境構築周りでトラブルが起きやすくてインストラクターの時間が食われがちですよね」「かといってツールが増えすぎても時間を取られるし」「今どきGitHubアカウントは必ず作るでしょうから、GitHub Codespaceならツールを増やさなくても使えるのがいいですね👍」
参考: GitHub Codespaces の概要 - GitHub Docs
つっつき後に以下の予告動画(セルフ切り抜き)が公開されました。
前編は以上です。
バックナンバー(2023年度第1四半期)
週刊Railsウォッチ: 書籍『Rebuilding Rails』、OpenSSL 1.1.1が今年9月でEOLほか(20230330後編)
- 20230328前編 Active SupportにObject#withが追加、カスタム名前空間のサポートほか
- 20230322 Rubyに新しくRJITがマージされた、Shopifyのタスク管理gem maintenance_tasksほか
- 20230315後編 Wasm Workers Server 1.0、mruby 3.2.0リリース、irbtoolsほか
- 20230314前編 Devise 4.9のHotwire/Turbo統合に対応する、英国政府のViewComponentほか
- 20230308後編 Ruby30周年記念イベント、37signalsのデプロイツールmrskほか
- 20230307前編 Action Mailerプレビューで全メールヘッダーを表示可能に、rubocop-graphqlほか
- 20230222後編 Ruby 3.2のData#initializeの設計、ruby-openai gemほか
- 20230221前編 Ruby30周年記念イベント、ActiveRecord APIクイズほか
- 20230215後編 Bundler 2.4リリース、RubyKaigi 2023参加募集開始ほか
- 20230214前編 AssumeSSLミドルウェア追加、Fly.ioとRails 7.1のDocker対応ほか
- 20230202後編 ShopifyのYJIT記事、RubyGemsのgem execコマンドほか
- 20230201後編 Ruby 3.2のベンチマーク記事、dry-cliで高度なCLIを作るほか
- 20230131前編 Evil Martiansが使っているgem、JavaScriptガイドが更新ほか
- 20230125前編 2022年のRails振り返り記事、RailsにDocker関連ファイルが追加ほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)