- Ruby / Rails関連
週刊Railsウォッチ: Turbo Railsのチュートリアル、Active Recordの「Leaky Abstraction」を削減ほか(20220411前編)
こんにちは、hachi8833です。17歳の誕生日おめでとうございます🎉
Today we're celebrating 17 years of Git! 🎉
How has Git changed your life? pic.twitter.com/urNVDk1Q6U
— 🦊 GitLab (@gitlab) April 7, 2022
🔗Rails: 先週の改修(Rails公式ニュースより)
今回は以下のコミット差分より見繕いました。
🔗 ActionController::Parameters
のキーにSymbol
とString
のみを許可
ActionController::Parameters
ではキーにSymbol
とString
のみを許可するようにする。
これは#44813の実験。
同PRより
# actionpack/lib/action_controller/metal/strong_parameters.rb#L271
def initialize(parameters = {}, logging_context = {})
+ raise InvalidParameterKey unless parameters.keys.all? { |key| key.is_a?(String) || key.is_a?(Symbol) }
@parameters = parameters.with_indifferent_access
@logging_context = logging_context
@permitted = self.class.permit_all_parameters
end
参考: Rails API ActionController::Parameters
つっつきボイス:「Symbol
とString
以外のものをキーにできないようにするということは、これまでは任意のオブジェクトを渡せてしまっていたということなんでしょうね」「その発想はなかったかも」「禁止されていないと使う人はどうしても出てきてしまいますよね」「RubyではハッシュのキーにSymbol
とString
しか使わないのが普通ですけど、仕様上は任意のオブジェクトをキーにできる↓」
参考: class Hash (Ruby 3.1 リファレンスマニュアル)
ハッシュテーブル(連想配列とも呼ぶ)のクラスです。ハッシュは任意の種類のオブジェクト(キー)から任意の種類のオブジェクト(値)への関連づけを行うことができます。
https://docs.ruby-lang.org/ja/latest/class/Hash.htmlより
🔗 CSRFトークンをセッションストレージ以外の任意の場所に保存できるようになった
- PR: Allow CSRF tokens to be stored outside of session. by simbasdad · Pull Request #44283 · rails/rails
つっつきボイス:「csrf_token_storage_strategy
というコンフィグが追加されて、CSRFトークンをセッションの外にオプションで保存できるようになったんですね」「どういうときに欲しくなるんでしょうか?」「サマリー↓にも、認証されてないユーザーのセッションが大量に作成されて、しかも中身はCSRFトークンだけという状態を避けたいときに便利とありますね」「なるほど、これはありがち」「フォームを作成した時点でCSRFトークンが必ず作成されるのでセッションストアがゴミだらけになるヤツ」「セッションをcookieに保存するなら問題ないですね」「ユースケースによって欲しくなる設定👍」
概要
CSRFトークンをカスタムの置き場所に保存できるlambdaを使える設定オプションを新たに追加。これはセッションをcookieに保存しない場合に有用。たとえば、Redisセッションストアを使う場合、認証されていないユーザー用のセッションが数百万件作成され、しかもそのセッションにはCSRFトークンしか含まれていないという状態になると、キャッシュがスラッシングして常に古いセッションが退避される可能性がある。
この新しい設定パラメータを使うと、CSRFトークンをセッションの外(暗号化済みcookieなど)に保存可能になり、認証済みユーザーのセッションのみをセッションストアに保存しておけるようになる。
同PRより
参考: §3.1 CSRFへの対応策 -- Rails セキュリティガイド - Railsガイド
🔗 ActionController::Parameters#values
でネステッドハッシュの値をActionController::Parameters
にキャスト
ActionController::Parameters#values
がネステッドハッシュの値をActionController::Parameters
にキャストするようにした。また、8af86c9で導入された非推奨警告もこのコミットで修正した。
同PRより
つっつきボイス:「これもActionController::Parameters
の改修」「ネステッドハッシュもparamsで使えるようになったということかな」「このテストがわかりやすいかも↓: ActionController::Parameters
の中にさらにActionController::Parameters
があっても正しく展開できるようになったんですね」
# actionpack/test/controller/parameters/accessors_test.rb#L314
test "values returns an array of the values of the params" do
params = ActionController::Parameters.new(city: "Chicago", state: "Illinois")
assert_equal ["Chicago", "Illinois"], params.values
params = ActionController::Parameters.new(city: "Chicago", state: "Illinois", person: ActionController::Parameters.new(first_name: "David"))
assert_equal ["Chicago", "Illinois", ActionController::Parameters.new(first_name: "David")], params.values
end
「values
メソッドもこう変わった↓」
# actionpack/lib/action_controller/metal/strong_parameters.rb#L398
+ # Returns a new array of the values of the parameters.
+ def values
+ to_enum(:each_value).to_a
+ end
「ところで、ActionController::Parameters
はRubyのHash
クラスを継承していないんですよ」「形だけ見るととてもハッシュっぽいのに違うんですね」
参考: Rails API ActionController::Parameters
「たしかEnumerable
ですらないはず」「意外でした」「以前ハッシュのつもりで使おうとしたら使えないメソッドがあったときに気づきました: Railsのmainブランチで見るとActionController::Parameters
は素のクラスになっている↓」「to_h
やto_hash
なども自前実装なんですね」
# https://github.com/rails/rails/blob/main/actionpack/lib/action_controller/metal/strong_parameters.rb#L142
class Parameters
cattr_accessor :permit_all_parameters, instance_accessor: false, default: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
# 略
def to_h(&block)
if permitted?
convert_parameters_to_hashes(@parameters, :to_h, &block)
else
raise UnfilteredParameters
end
end
# 略
def to_hash
to_h.to_hash
end
# 略
end
🔗 legacy_connection_handling
を削除
つっつきボイス:「Rails 6.1から互換性のためにあったlegacy_connection_handling
がついに削除された」「名前からしてそんな感じ」「Rails 6.1でコネクションプール周りが大きく変わったので、おそらくgemの都合などでこれまで残されていたんでしょうね」
参考: §2.1 データベース単位のコネクション切り替え -- Ruby on Rails 6.1 リリースノート - Railsガイド
Rails 6.1でデータベース単位のコネクション切り替え機能が使えるようになりました(#40370。6.0の場合は、ロールを
reading
に切り替えるとすべてのデータベースコネクションもreadingロールに切り替わりました。6.1からは、Railsの設定でlegacy_connection_handling
をfalse
に指定しておけば、対応する抽象クラスでconnected_to
を呼び出すことでデータベースへのコネクションを切り替えられます。
railsguides.jpより
参考: Implement granular role and shard swapping by eileencodes · Pull Request #40370 · rails/rails -- legacy_connection_handling
はこのプルリクで入りました
🔗 db:schema
のダンプや読み込みのスキーマフォーマットを環境変数で指定できるようになった
rails db:structure:dump
やrails db:structure:load
が非推奨になったので、SQLフォーマットとRubyフォーマットの両方でシンプルにダンプする方法がなかった。
このプルリクは#39470のレビューでの@jeremyのアドバイスを実装したので、以下のように環境変数で指定できるようになった。
SCHEMA_FORMAT=sql rake db:schema:dump
同PRより
つっつきボイス:「SCHEMA_FORMAT
環境変数でスキーマのフォーマットを指定できるようになった」「今までもコンフィグでSQLかRubyかを設定できたけど↓、この環境変数で上書きすれば柔軟に変更できますね」「CIでスキーマフォーマットを一時的に変更したいときとかに便利そう: これはいい話👍」
参考: §3.7.10 config.active_record.schema_format
-- Rails アプリケーションを設定する - Railsガイド
「Railsの環境変数が使えるのはもちろん便利だけど、環境変数が増えてくるとだんだん管理が複雑になったりしますよね」「あるあるですね」
🔗Rails
🔗 hotrails.dev: Turbo Railsのチュートリアル
If you want to learn about Turbo Rails/Hotwire, I just finished writing my open-source Turbo Rails tutorial!
We will learn about Turbo Drive, Turbo Frames, Turbo Streams, and more! All those new concepts are illustrated with sketches!https://t.co/PG1m6t9pKA pic.twitter.com/wJvOm2dPc2
— Alexandre Ruban (@alexandre_ruban) March 18, 2022
つっつきボイス:「Matzがリツイートしていたのを見て知りました」「なるほど、Turbo Railsのチュートリアルサイトですか」「章立ても充実してる感じですね↓」「トップページの下にある『What people are saying』にチュートリアルの喜びの声が集められてますね」「手を動かしながらひととおり読み進めればTurbo Railsの基本的なユースケースに対応できそう👍」
- 1: Turbo Rails tutorial introduction
- 2: Organizing CSS files in Ruby on Rails
- 3: Turbo Drive
- 4: Turbo Frames and Turbo Stream templates
- 5: Real-time updates with Turbo Streams
- 6: Turbo Streams and security
- 7: Flash messages with Hotwire
- 8: Two ways to handle empty states with Hotwire
- 9: Another CRUD controller with Turbo Rails
- 10: Nested Turbo Frames
- 11: Adding a quote total with Turbo Frames
「トップページからたどれるQuote editor↓をTurboで作ってみましょうという趣向のようです」「この種のSPA的な機能を試すときって、とりあえずToDoやRemember The Milk的なものを作ったりしますよね」「そうそう」
参考: Remember The Milk: オンラインでTodoやタスクを管理しよう
🔗 Active Recordの「Leaky Abstraction」を削減する(Ruby Weeklyより)
つっつきボイス:「thoughtbotの記事だ」「記事は、クエリの詳細をコントローラに書いてしまうと後で修正箇所が増えてつらくなるよという話↓」「この記事ではそれをleaky abstractionと呼んでいるんですね」
# 同記事より
class AuthorsController < ApplicationController
def index
@published_authors = Person.distinct.joins(:posts).where(posts: { published: true })
end
end
「記事での解決法はスコープ的なself.published
クラスメソッドを作る: これもよく使われます↓」
# 同記事より
class Post < ApplicationRecord
# other methods
def self.published
where("published_at < ?", Time.current)
end
end
「これでpublished
の意味も適切に抽象化されるし、Post.published
のようにわかりやすく書ける↓」
# 同記事より
class PostsController < ApplicationController
def index
- @newest_posts = Post.where(published: true).order(created_at: :desc).limit(10)
+ @newest_posts = Post.published.order(created_at: :desc).limit(10)
end
end
class AuthorsController < ApplicationController
def index
- @published_authors = Person.distinct.joins(:posts).where(posts: { published: true })
+ @published_authors = Person.distinct.joins(:posts).merge(Post.published)
end
end
「ところで、このpublished_at
のように多義的に使われるカラムは、ここでやっているpublished
のようになるべくビジネスロジックに沿ったメソッドで抽象化しておかないと後でつらくなりがち」「使う場所ごとにpublished_at
の意味を使い分けるのはたしかにつらそう...」
unpublished
(published_at
がnil
に設定される)published
(published_at
には現在またはそれ以前のタイムスタンプが設定される)enqueued
(published_at
には未来のタイムスタンプが設定される)
同記事より
「ただ、こういうのはどこまでやるかというさじ加減がなかなか難しいんですよ: コントローラの1箇所でしか使わないなら抽象化なしでも容認できることもあるけど、あちこちで使われるようになってくると読むのがしんどくなる」「たしかに」
参考: Leaky abstraction - Wikipedia
参考: Leaky Abstraction について - 新山
🔗 『【2022年版】HerokuでRailsの画像が表示されないときの適切な対処法(と間違った対処法)』
この件、「"config.assets.compile = true"にしたら直りました!って書いてる記事のなんと多いことよ😭」と以前から嘆いておりました。これで少しは改善されるかな〜?https://t.co/FfCOJ5r48E
— Junichi Ito (伊藤淳一) (@jnchito) February 13, 2022
つっつきボイス:「jnchitoさんが以前から繰り返し指摘していた問題を解決すべく検索でトップに出る記事を書いたそうです」「ウォッチでも取り上げたことありますね」「そうそう、Herokuで画像が表示されないからといってconfig.assets.compile = true
をproductionで設定してはいけない」「ネットでこれをやってしまっている記事、あちこちにありますよね」
「原因についても丁寧に解説されている」「image_tag
の画像名に拡張子付け忘れるの、ありがち」「development環境だと拡張子なしでも通るのにproductionだと効かないのって罠ですよね」
# 同記事より
-<%= image_tag "bread" %>
+<%= image_tag "bread.jpg" %>
「jpg拡張子をjpegと書いてしまうのもあるある」「4文字の拡張子ってなかなか見かけないですよね」
参考: 拡張子 - Wikipedia
「初歩的な問題ではあるけど、おそらくRailsの環境が以前より多様化してきたこともあって、アセット周りでときどき原因がすぐにわからないような問題に出くわすことがありますね」「記事にもあるstylesheet_pack_tag
とかもそう」「Herokuの問題なのかどうかの切り分けとかも割と面倒だし、いろいろやっているうちになぜか動くようになってしまうことすらある」「そうそう」「初心者だとなおさらググって目についた方法に飛びつきがち」「そういう人たちを助けるための記事👍」
🔗 Herokuよもやま
「ちなみに、この記事で取り上げているのはRailsに特化したHeroku環境なので特殊だけど、今のHerokuはDockerコンテナでデプロイもできるので、そちらを使っていれば普通にDockerコンテナでやれるでしょうね」「HerokuのDockerデプロイ、まだやったことなかった」
参考: Docker によるデプロイ | Heroku Dev Center
「ところで、昨今はユーザーエクスペリエンスを重視する文化が広まったことで、たとえばFirebase+フロントエンドフレームワークのようなセットアップをよく見かけるようになってきましたね」「たしかにそんな感じ」「でも個人的には、そういうのでない業務システムのラピッドプロトタイピングなら、今でもRails+Herokuのようなセットアップでやるのが一番手っ取り早いと思います」「ふむふむ」「特に業務システムだと、データベースを手堅く使えるのは圧倒的なメリット」「たしかに」
参考: Firebase
🔗 その他Rails
つっつきボイス:「そうそう、OmniAuthとの関連でcriticalなセキュリティ修正が出てた: BPSのGitLabサーバーではOmniAuthは使っていないけど、週末にアップデートしておかないと(その後完了しました)」
前編は以上です。
バックナンバー(2022年度第2四半期)
週刊Railsウォッチ: RBS関連記事、Ruby formatterプロジェクト、Google Cloud Runほか(20220406後編)
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)