- Ruby / Rails関連
週刊Railsウォッチ: Active Storageのしくみを詳しく解説するDiscussion投稿ほか(20231017前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
公式更新情報に追いついたので、mainブランチのコミットログから見繕いました。多くはドキュメント関連でした。また、7.1.2と7.2.0のマイルストーンもできていました。
参考: 7.1.2 Milestone
参考: 7.2.0 Milestone
🔗 HashWithIndifferentAccess
に#to_proc
を実装
従来は、
HashWithIndifferentAccess
オブジェクトに対して#to_proc
を呼び出すと、Hash
クラスから継承された#to_proc
メソッドが使われていたが、これは文字列キーとシンボルキーを問わず値にアクセスする機能が使えなかった。修正: #48770
同PRより
つっつきボイス:「Rails 7.1.1で一段落したのか今回は小粒の改修が多い感じです」「おや、HashWithIndifferentAccess
に#to_proc
がインスタンスメソッドとして実装されたのね: 自分はあまり使ってない機能ですが」「今までだとHash
の#to_proc
が呼ばれてたんですね」「#to_proc
したものにアクセスするとキーが文字列の場合とシンボルの場合で結果が違ってしまっていたのか」
# activesupport/test/hash_with_indifferent_access_test.rb#968
# @strings = { "a" => 1, "b" => 2 }が設定済み
def test_indifferent_to_proc
@strings = @strings.with_indifferent_access
proc = @strings.to_proc
assert_equal 1, proc["a"]
assert_equal 1, proc[:a]
assert_nil proc[:no_such]
end
参考: Rails API ActiveSupport::HashWithIndifferentAccess
後で上のテストコードを少し変えて手元のRails 7.1.1で動かしてみると、修正前は以下のようになりました。
» proc['a']
#>1
» proc[:a]
#>nil
🔗 send(primary_key)
呼び出しをid
に置き換えた
修正: #49408
primary_key
をレコードにsend
して主キーの値を取得する理由はない。id
メソッドなら正確に行えるし、抽象化としてもより優れている。また、これによって複合主キーの場合の問題も暗黙で修正される。
同PRより
つっつきボイス:「7.1の複合主キー関連でread_attribute(:id)
が主キーを返すのが非推奨化されたときも取沙汰されましたけど(ウォッチ20230906)、こういうのは主キーではなくid
で取るのが正当ですね」「これはRails内部のコード修正だからユーザー側には影響しないやつですね」
# activerecord/lib/active_record/persistence.rb#L1272
def _raise_record_not_destroyed
@_association_destroy_exception ||= nil
key = self.class.primary_key
- raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{send(key)}", self)
+ raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self)
参考: Active Record の複合主キー - Railsガイド
🔗 scaffoldで生成したシステムテストでdatetime
やtime
を正しく扱うよう修正
動機/背景
このコミットより前は、
time
またはdatetime
属性を含むシステムテストをscaffoldで生成すると、update
のテストで失敗していた。キャプチャされたスクリーンショットには、以下のようなクライアントサイドのバリデーションエラーが表示されていた。Please enter a valid value. The two nearest valid values are 10:45:33 AM and 10:46:33 AM.
Please enter a valid value. The two nearest valid values are 09/27/2023, 10:45:33 AM and 09/27/2023, 10:46:33 AM.
詳細
このプルリクでは、システムテストのscaffoldを更新し、
time
とdatetime
の値が条件付きで文字列に変換して正しく入力され、テストがパスするように修正する。
同PRより
つっつきボイス:「scaffoldで生成したシステムテストがいきなり失敗したらびっくりする」「今さらですが、システムテストにもジェネレータがあるんですね」
# railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb#L66
+
+ def datetime?(name)
+ attribute = attributes.find { |attr| attr.name == name }
+ attribute&.type == :datetime
+ end
+
+ def time?(name)
+ attribute = attributes.find { |attr| attr.name == name }
+ attribute&.type == :time
+ end
参考: §6 システムテスト -- Rails テスティングガイド - Railsガイド
# Railsガイドより
$ bin/rails generate system_test users
invoke test_unit
create test/system/users_test.rb
🔗 ActiveSupport::BroadcastLogger
のメソッドにブロックも渡せるようになった
- PR: Delegate block in broadcast logger method_missing by eugeneius · Pull Request #49458 · rails/rails
#49417の続き。
ブロードキャストロガーで呼び出されるメソッドにブロックを渡したら、すべての登録済みのロガーにブロックをforwardするべき。たとえば、
Rails.logger.tagged { }
を呼び出したら、現在は渡したブロックを実行せずに新しいタグ付きロガーを返している。
同PRより
つっつきボイス:「Rails 7.1に駆け込みでマージされたBroadcastLogger
の続きですね(ウォッチ20231011)」「method_missing
に手を加えてブロックも受け取れるようにしたんですね↓: ロガーをカスタマイズしたりしてもブロックがforwardされるようになった感じかな」
# activesupport/lib/active_support/broadcast_logger.rb#L206
- def method_missing(name, *args)
+ def method_missing(name, *args, &block)
loggers = @broadcasts.select { |logger| logger.respond_to?(name) }
if loggers.none?
- super(name, *args)
+ super(name, *args, &block)
elsif loggers.one?
- loggers.first.send(name, *args)
+ loggers.first.send(name, *args, &block)
else
- loggers.map { |logger| logger.send(name, *args) }
+ loggers.map { |logger| logger.send(name, *args, &block) }
end
end
+ def respond_to_missing?(method, include_all)
+ @broadcasts.any? { |logger| logger.respond_to?(method, include_all) }
+ end
参考: Rails API ActiveSupport::BroadcastLogger
参考: BasicObject#method_missing
(Ruby 3.2 リファレンスマニュアル)
🔗 ドキュメントの修正2件
- PR: [ci skip] do not use backticks in docs by nvasilevski · Pull Request #49407 · rails/rails
- PR: Enable raising when running
rubocop-md
against invalid ruby snippets by fatkodima · Pull Request #49412 · rails/rails
つっつきボイス:「2つまとめました: APIドキュメントのRDocフォーマットでmarkdownのバッククォートを書く人があとを絶たないらしく、1つ目の#49407の他に7.1でも何度かRDocの++
記法に修正されていました」「RDocってバッククォート使えないのか」「RDoc書いたことなかった」「こういうのはlinterで自動修正できそうな気もしますけどね」「たしかに」
# activerecord/lib/active_record/associations.rb#L158
- # When the value is set the Array size must match associated model's primary key or `query_constraints` size.
+ # When the value is set the Array size must match associated model's primary key or +query_constraints+ size.
参考: §1 RDoc -- API ドキュメント作成ガイドライン - Railsガイド
参考: class RDoc::MarkupReference
- rdoc 6.5.0 Documentation
「2つ目の#49412はドキュメントのコードブロックに多数修正が入りました」「コードブロックの言語が違っているのとかいろいろ修正されてる」「これはrubocop-mdによる自動修正なのか」「そういえば少し前にRailsで導入されていましたね(ウォッチ20230405)」
# guides/source/action_cable_overview.md#L850
-```ruby
+```js
# guides/source/contributing_to_ruby_on_rails.md#L600
def load_defaults(target_version)
case target_version.to_s
when "7.1"
- ...
+ # ...
if respond_to?(:active_job)
active_job.existing_behavior = false
end
- ...
+ # ...
end
end
# guides/source/active_record_multiple_databases.md#L320
-class MyCookieResolver << ActiveRecord::Middleware::DatabaseSelector::Resolver
+class MyCookieResolver < ActiveRecord::Middleware::DatabaseSelector::Resolver
「rubocop-mdでチェックする対象を追加した、なるほど↓」
# .rubocop.yml#L20
- 'activestorage/test/dummy/**/*'
- 'actiontext/test/dummy/**/*'
- 'tools/rail_inspector/test/fixtures/*'
+ - guides/source/debugging_rails_applications.md
+ - guides/source/active_support_instrumentation.md
🔗Rails
🔗 Phlexを使ってみて(Ruby Weeklyより)
つっつきボイス:「以前話題にしたPhlexというViewComponent的なコンポーネントを使ってみた記事だそうです(ウォッチ20221011)」「そういえばPhlexありましたね: コードの雰囲気はSlimを思わせるものがありそう↓」「言われてみればHTMLタグがメソッド名になっているところとか似てるかも」「アイデアとしては昔からあったんですね」「DSLの一種だと思えばいいので、Phlexの書き方にはそんなに違和感はないかな」
# 同記事より
class ArticlePartial < Phlex::HTML
include Ui::Typography
def initialize(article)
@article = article
end
def template
article(class: "article") do
heading1(@article.title)
subtitle(@article.motto)
div(class: "content") { @article.content }
end
end
end
参考: slim/README.jp.md at main · slim-template/slim
# https://github.com/slim-template/slim/blob/main/README.jp.mdより
doctype html
html
head
title Slim Examples
meta name="keywords" content="template language"
meta name="author" content=author
link rel="icon" type="image/png" href=file_path("favicon.png")
javascript:
alert('Slim supports embedded javascript!')
body
h1 Markup examples
#content
p This example shows you how a basic Slim file looks.
== yield
- if items.any?
table#items
- for item in items
tr
td.name = item.name
td.price = item.price
- else
p No items found. Please add some inventory.
Thank you!
div id="footer"
== render 'footer'
| Copyright © #{@year} #{@author}
🔗 Active Storageのしくみを詳しく解説するDiscussion投稿(Ruby on Rails Discussionsより)
つっつきボイス:「たしかX(元Twitter)で見かけたような気がするんですが、discuss.rubyonrails.orgのこの投稿が絶賛されていました」
💎Everything you should know about ActiveStoragehttps://t.co/cQAY5T9YxX
— Zilvinas Kucinskas (@zilkucinskas) August 9, 2023
「おぉ〜、これはとてもよく書けている記事なのでは: Active Storageのここまで詳しい解説ってないので、今は知りたかったら自分で実装を追いかけるしかないヤツですね」「分量もすごい」
後で見出しだけ取り出して翻訳してみました。投稿ではActive StorageをASTと略しています。
1. はじめに
2. Active Storageを理解する
2.1. 基本的なユースケースをサンプルにする
2.2. Active Storageで作成されるテーブルやカラム
2.2.1. active_storage_blobs
2.2.2. active_storage_attachments
2.2.3. active_storage_variant_records
2.3. 内部のしくみ
2.3.1. 画像を添付すると何が行われるか
2.3.2. 画像を表示すると何が行われるか
2.3.3. ファイルを「リダイレクトモード」「プロキシモード」「パブリックモード」で配信すると何が行われるか
2.3.4. ダイレクトアップロードのフローを解説
2.3.5. PNGのフォールバックのしくみ
2.3.6. AnalyzeJob
のしくみ
2.4. これらが自分のアプリと一体どう関係するか
2.4.1. 添付ファイルのあるレコードを大量に表示するときはN+1クエリに十分注意すること
2.4.2. クライアントが遅くならないために、ファイルアップロードは常にダイレクトアップロードを使うこと
2.4.3. プロキシモードを使う場合は、クライアントが遅くならないためにサーバーとユーザーの間にNginxやCloudflareを配置すること
2.4.4. オンデマンドのバリアント生成機能は素晴らしいが、気をつけないとアプリ全体が詰まってしまう可能性がある
3. production環境で画像を配信する場合の注意事項や愚痴など
「discussに投稿するボリュームと内容じゃないですよね: 公式にRailsガイドに含めてもおかしくないのでは」「Railsガイドに載せてはどうかというレスもついてますね」「翻訳したいけどSNS投稿だとやりにくそう...」
参考: Active Storage の概要 - Railsガイド
前編は以上です。
バックナンバー(2023年度第4四半期)
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)