- Ruby / Rails関連
週刊Railsウォッチ: Railsコミュニティアンケート結果発表、書籍『Sustainable Web Development with Ruby on Rails』ほか(20220531)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
🔗 Action Textでパーシャルが何度もレンダリングされる問題を修正
rich_text
フィールドが更新されたときにコンテンツのレイアウトが何度もレンダリングされる問題を修正。
Jacob Herrington
同Changelogより
つっつきボイス:「Action Textの==
を修正してますね」「2019年にも#37818が上がっているので割と前から起きていたみたい」「Action Textを使わなければ影響はなさそう」
# actiontext/lib/action_text/content.rb#L106
def ==(other)
- if other.is_a?(self.class)
+ if self.class == other.class
+ to_html == other.to_html
+ elsif other.is_a?(self.class)
to_s == other.to_s
end
end
🔗 convert_to_model
呼び出しをform_for
からform_with
に移動
convert_to_model
呼び出しをform_for
からform_with
に移動する
form_for
は今やform_with
の形で実装されているので、このコミットではconvert_to_model
呼び出しもform_for
の実装から削除する。
同Changelogより
# actionview/lib/action_view/helpers/form_helper.rb#L433
def form_for(record, options = {}, &block)
raise ArgumentError, "Missing block" unless block_given?
case record
when String, Symbol
model = nil
object_name = record
else
- model = convert_to_model(record)
+ model = record
# actionview/lib/action_view/helpers/form_helper.rb#L754
def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
if model
if url != false
url ||= if format.nil?
polymorphic_path(model, {})
else
polymorphic_path(model, format: format)
end
end
- model = _object_for_form_builder(model)
+ model = convert_to_model(_object_for_form_builder(model))
scope ||= model_name_from_record_or_class(model).param_key
end
つっつきボイス:「form_for
はRails 7にもまだ一応あるんですね」「convert_to_model
呼び出しを移動したリファクタリングか」「form_with
のmodel:
引数はもう要らないかもねというコメントに、やるなら別のプルリクにするかもとレスが付いていますね」
🔗 バリデーションのin
やwithin
でbeginless/endless rangeをサポート
- PR: Support infinite ranges for
LengthValidator
s:in
/:within
options by fatkodima · Pull Request #45138 · rails/rails - PR: Add beginless range support to clusivity by bjeanes · Pull Request #45123 · rails/rails
つっつきボイス:「1つ目は、validates_length_of
でもin
やwithin
の範囲を以下の..30
のように書けるようになった」「Ruby 2.6のbeginless/endless range機能はRails 6.1から入ったんだったかな」
# 同Changelogより
validates_length_of :first_name, in: ..30
参考: 演算子式 (Ruby 3.1 リファレンスマニュアル)
「2つ目は、validates_inclusion_of
やvalidates_exclusion_of
の..
でも開始を省略できるようになったんですね」
# 同Changelogより
validates_inclusion_of :birth_date, in: -> { (..Date.today) }
参考: validates_inclusion_of
-- ActiveModel::Validations::HelperMethods
参考: validates_exclusion_of
-- ActiveModel::Validations::HelperMethods
「これは嬉しい改修でしょうか?」「<
や>
とかで書くよりはわかりやすいのかも : 個人的な趣味としては{ (..Date.today) }
の内側の丸かっこ()
があまり好きじゃないぐらいかな」
「ところでこのin: -> { (..Date.today) }
は、要するに未来の日付ではないという意味ですね」「たしかに誕生日に未来の日付は入力しませんよね」
「そういえば、以前英語圏の人の記事で<
や>
よりbeforeやafterの方がわかりやすいと書かれてました↓」「ボクも<
や>
が2つ以上出てくるとよく向きを間違えちゃいます」
🔗 uniqueness指定のフィールドが変更されていない場合のバリデーションを回避
従来はActive Recordのレコードを保存するときに
uniqueness: true
が指定されているバリデーションを属性ごとにチェックするための余分なクエリが、属性が変更されていない場合にも実行されていた。
データベースにUNIQUEインデックスがある場合は、このバリデーションが永続化済みレコードで失敗することはありえないので、安全にスキップ可能。
fatkodima
同Changelogより
つっつきボイス:「パフォーマンスがよくなりそうな改修」「uniquenessバリデーションはデータベースクエリが発生して遅くなるので、無駄なクエリを投げないのはいい👍」「不要なクエリを投げないようにする改修はこの間もありましたね(ウォッチ20220516)」
「割とコードやテストの追加量が多いかも」「この種の改修は、ほぼ起きなさそうなレアケースの対応も含めてちゃんとやるのは割と難しいと思うので、テストでしっかり押さえておかないといけないでしょうね」「なるほど」
🔗 ActiveSupport::Cache::Store#fetch_multi
でforce
オプションをサポート
# activesupport/lib/active_support/cache.rb#L495
def fetch_multi(*names)
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
options = names.extract_options!
options = merged_options(options)
instrument :read_multi, names, options do |payload|
- reads = read_multi_entries(names, **options)
+ if options[:force]
+ reads = {}
+ else
+ reads = read_multi_entries(names, **options)
+ end
+
writes = {}
ordered = names.index_with do |name|
reads.fetch(name) { writes[name] = yield(name) }
end
payload[:hits] = reads.keys
payload[:super_operation] = :fetch_multi
write_multi(writes, options)
ordered
end
end
つっつきボイス:「force
オプションをキャッシュストアに渡せるようにしたらしい」「force
はメモリ上のキャッシュを使わずに、保存済みのキャッシュストアから強制的に読み直すということでしょうか?」「そうだと思います」
- Rails API:
fetch_multi
--ActiveSupport::Cache::Store
- Rails API:
ActiveSupport::Cache::RedisCacheStore
「複数キーの取り出しでキャッシュ有効期限内のキーと失効したキーが混じっているときに、force
で有効期限を無視して取り直すのかな?」「テストコード↓を見るとexpires_in
に60や100を指定しているので、そのようなユースケースを想定していそうですね」
# 同PRより
def test_fetch_multi_race_condition_protection
time = Time.now
key = SecureRandom.uuid
other_key = SecureRandom.uuid
@cache.write(key, "foo", expires_in: 60)
@cache.write(other_key, "bar", expires_in: 100)
Time.stub(:now, time + 71) do
result = @cache.fetch_multi(key, other_key, race_condition_ttl: 10) do
assert_nil @cache.read(key)
assert_equal "bar", @cache.read(other_key)
"baz"
end
assert_equal({ key => "baz", other_key => "bar" }, result)
end
end
🔗Rails
🔗 書籍『Sustainable Web Development with Ruby on Rails』
このレビューを読むだけで、それですよマジで!みたいな気持ちになったので、これからもRails使う人は絶対読んだ方が良さそう! / “『Sustainable Web Development with Ruby on Rails』はRails使ってるなら絶対面白いと思う” https://t.co/GI1lDQaqQY
— joker1007 (アルフォートおじさん) (@joker1007) May 20, 2022
つっつきボイス:「ツイートでも引用されている以下の記事でこの本を知ってポチりました」「この本話題になってますね」「紙の書籍の定価は$49.95ですが、国によってディスカウントがあって、Amazon.co.jpでKindleだと1,880円で買えました」「お、リーズナブルな値段」
参考: 『Sustainable Web Development with Ruby on Rails』はRails使ってるなら絶対面白いと思う
参考: Sustainable Web Development with Ruby on Rails -- 公式サイト
「そういえば@r7kamuraさんも早速読んでブログに書いていますね↓」
参考: 『Sustainable Web Development with Ruby on Rails』を読んだ
「まだ半分ぐらいしか読んでませんが、英文も思った以上に読みやすくて、Railsチュートリアルを終えた人やRails開発1年目ぐらいの人によさそうかなと思いました」
その後読み終わりました。
🔗 Serviceクラスよもやま
「同書を巡って日本語記事でも話題になっているServiceクラスは、以下の記事で知ってたコマンドパターン的なService Objectと少し違う感じでした: クラスメソッドにせずにnew
して使うとか、汎用的なcall
を使わずに具体的なメソッド名にするとか」「コマンドパターン的なService Objectでもそういう書き方をすることはありますよ: ただ個人的にはそうした詳細部分の違いはあまり重要ではない気もします」
「設計の好みの話で言うと、自分はService Objectがクラスメソッドなのは特に気にならない方です: 逆にインスタンス化してステートを持たせたり使い回されたりするのが好きではないので、クラスメソッドを呼んだらそれでおしまいという形の方が好み」「自分はコンテキストとして使いたいので、逆にクラスメソッドにするのがあまり好きではない派かも」「自分はそれをさせたくない気持ちなんですよ」「なるほど、それもわかります」
「細かな設計はつまるところ実装者の好み次第なので、明らかに問題があるとか既存の設計と調和しないということがない限り、コードレビューでは通すことが多いと思います」「そうそう、結局は好みというか宗派ですよね」「もちろん決済周りのような非常にクリティカルな部分の設計だったらレビューで指摘することもあると思いますが、たとえばService Objectが既にプロジェクトで使われているなら同じように書く方が望ましいぐらいの気持ちなので、このあたりの設計に強いこだわりはないですね」
🔗 Railsコミュニティアンケート2022年度版の結果発表
2022 Ruby on Rails Community Survey Results showing what version of Rails apps are running and why they're not up to date. https://t.co/G86rdQjCiB via @planetargon
What's the overlap between no budget/not a priority and <= v.5.2 because EOL is in 6 days 😬 pic.twitter.com/ubb9ESAGLS
— Thomas Countz 🤍 (@ThomasCountz) May 27, 2022
つっつきボイス:「以前取り上げた2022年度版アンケート(ウォッチ20220228)の結果が発表されていました」
「CIでGitHub Actionsが2022年でランク外から突然トップに上がっているのが興味深い: GitHub Actionsで足りるなら連携も楽だし、たしかに便利」「Circle CIが2位でGitLabが3位か」「BPSでメインとして使っているGitLabがトップ3に入っているのを見たら、GitLabはマイナーじゃないと思えて安心しました」
「CDNはCloudFrontを使っているという回答の方が、コストの安いCloudflareより多いところを見ると、アンケートの母集団にはエンタープライズ系の回答者が多そうな気がしますね」
「デプロイ周期がほぼ毎日という回答数が上昇しているのも興味深い」「めったにデプロイしないという回答数はめっきり減っていますね」
「新人にRailsの学習と構築を2022年も推奨するかはYesが94%」「あくまでこの母集団での結果ですけどね」
🔗 DeviseとTurbo
- PR: Return 422 status when login fails + treat
turbo_stream
as a navigational format by ghiculescu · Pull Request #5340 · heartcombo/devise -- オープン - PR: Disable turbo by default by n-studio · Pull Request #5487 · heartcombo/devise -- オープン
- PR: turbo_stream fix bugs for rails 7 and above apps by Salanoid · Pull Request #5467 · heartcombo/devise -- オープン
つっつきボイス:「DeviseのRails 7対応ってどうなっているんだろうと思って探してみると、Rails 7ではTurboをオフにしようというプルリクがオープンされているのが目に付きました」「Deviseを使う人はSessionsController
を継承してカスタマイズする人が多いと思いますし、仮にTurboを使うとしてもログイン画面までSPA的にしなくてもよさそうに思えるので、実際に使うときは自力でログイン画面のTurboをオフにして部分更新しないようにしている人が多いかもしれませんね」「なるほど」
「ついでに気づいたんですが、Jeremy Evans作のrodaをベースにしたrodauth-railsは↓Rails 7に対応しているものの、こちらもTurboをオフにする必要があるそうです」
「Turboみたいなものは認証と相性が良くないんでしょうか?」「Turboというよりasync/await的な非同期処理と相性が良くないというのはあるでしょうね: ログインセッションの状態を更新するリクエストとログイン後にリクエストする前提のリクエストを同時に非同期で実行すると、認証されていることを前提とするリクエストが認証されてなくてエラーになったりする」「あ、そうか」
「その意味では、Webアプリの認証は非同期ではなく同期的にやりたいものですね: 認証をTurboで非同期に行おうとすると、同じページ内で認証後のセッションcookieありのリクエストとセッションcookieなしのリクエストが混じって扱いにくそうなので、Deviseの標準では対応しにくいかも」「たしかに」「ログアウトについても同じようなことが考えられますね」
🔗 Railsリポジトリにあるrequest.js
いつも愛読させていただいてます!
rails-ujs、Turbo関連で、あまり情報が出てない無いRails公式ライブラリについての記事を書いたので、よかったらご参考くださいm(_ _)mhttps://t.co/hqdyPQQ9n8— gnattali (@gnattali) May 19, 2022
つっつきボイス:「お便りいただきました😂」「お、rails/request.jsというのが一応標準であるんですね」「これでAjaxリクエスト時にCSRFトークンを含められるそうです」「ページのmetaタグにあるCSRFを取り出して使うのかな: 知ってたら使うかも👍」
🔗 Railsでカウンタキャッシュを使う前に試したい4つの方法(Ruby Weeklyより)
つっつきボイス:「記事は見出しどおりの内容かな: Rails標準のcounter_cache
はだいぶ昔からありますが、あくまで自前で実装するよりは便利という程度の機能なので、これをベストプラクティスと信じ込まない方がいいと思います」「なるほど」
参考: §4.1.2.3 :counter_cache
-- Active Record の関連付け - Railsガイド
🔗Ruby
🔗 bundler-alive: Gemfile.lockのgemをチェック
つっつきボイス:「ruby-jp Slackで見かけたgemです」「なるほど、Gemfile.lockに記載されているgemがリポジトリから取得可能かどうかをチェックするんですね、欲しくなる気持ちわかる👍」
「gemが消えていることってたまにありますね」「あるバージョン以前のmimemagic gemが諸事情で消されたことは以前も話題にしましたね(ウォッチ20210329)」
🔗 Rubyのfilter_map
つっつきボイス:「短い記事です」「Ruby 2.7から入ったfilter_map
だと簡潔に書ける、そうそう」「RuboCopでもfilter_map
使えって言われるぐらいRubyらしい書き方」
# 同記事より
>> [1, 2, 3, 4, 5, 6].filter_map { |i| i * 2 if i.even? }
#=> [4, 8, 12]
参考: Enumerable#filter_map
(Ruby 3.1 リファレンスマニュアル)
参考: Performance/MapCompact -- Performance :: RuboCop Docs
「kazzさんといえば以下の記事ですが、きっとfilter_map
好きですよね?」「まだあまり使ってないけど好き❤️」
🔗 Rubyのdig
How would you call a method that works like Hash#dig, but raises an error when a key is missing? The feature is acceptable, but blocked until we find a good name https://t.co/yLoLhO42OV
— Jean Boussier (@_byroot) May 24, 2022
参考: Hash#dig
(Ruby 3.1 リファレンスマニュアル)
参考: Array#dig
(Ruby 3.1 リファレンスマニュアル)
参考: Struct#dig (Ruby 3.1 リファレンスマニュアル)
つっつきボイス:「上のissueはまだオープン中ですが、エラーをraiseするバージョンのdig
を提案しています」「Hash#dig
とかはキーが見つからなくてもエラーを出さないところが便利なので、dig!
的な別メソッドみたいなオプションとして追加されるならいいかな」
🔗 その他Ruby
Check out the new and improved "Contributing to Ruby" guides @_st0012, @JemmaIssroff and I wrote! It covers reporting bugs, building Ruby, testing Ruby, making changes, and ways to contribute!
Check it out here: https://t.co/XoeBcIJ9a3
PR: https://t.co/NeCrcE4pCN— Peter Zhu (@peterzhu2118) May 12, 2022
つっつきボイス:「小ネタですが、上のRubyコントリビューションガイドをruby/debugでおなじみの@_st0012さんが他の方と共同でがっつり書き直したそうです」「こういうのは最新になっていることが重要ですね👍」
参考: contributing - Documentation for Ruby 3.2
Ruby書いてると、変数名にも"?"を付けたくなることってありますよね。
go_for_a_walk? = weather.fine? && temperature > 16
みたいな。
— Junichi Ito (伊藤淳一) (@jnchito) May 23, 2022
「変数名に?
を付けたくなる気持ち、ちょっとわかるかも」「自分は思わないなぁ」
「今のRubyは識別子に?
が付いていたら必ずメソッド名だとわかるから、?
が付いているのに変数だと戸惑いそう」「Rubyの識別子は1種類じゃないということなんでしょうか?」「そう考えてみると、?
がメソッド名には使えるのに変数名には使えない歴史的な理由があるのかも」「探せばありそうですね」
「ここは想像ですが、変数の方が使える文字が多いのは、たとえば代入の=
演算子は変数名の直後にスペースなしで置けないけどメソッド名の直後には置けるからかもしれませんね」
参考: 字句構造 (Ruby 3.1 リファレンスマニュアル)
今回は以上です。
バックナンバー(2022年度第2四半期)
週刊Railsウォッチ: Railsコアチームとコミッターに新メンバー、ruby-buildでのRust YJITサポートほか(20220524後編)
- 20220523前編 Hotwireの用途解説記事、RubyKaigi 2022プロポーザル募集開始ほか
- 20220517後編 rubygemsに「scoped gems」の提案、RSpecのブロック構文ほか
- 20220516前編 Active Modelで属性のパターンマッチをサポート、猫でもわかるHotwire入門ほか
- 20220511後編 Ruby 3.2.0devにRust版YJITがマージ、Docker Compose V2ほか
- 20220510前編 Active RecordにPromiseと非同期集計メソッドがマージ、climate_control gemほか
- 20220419後編 RubyのGCコンパクション改修、jemalloc、ReDoSの自動検出修正ほか
- 20220418前編 RailsConf 2022が5月17〜19日開催、認可機能解説記事ほか
- 20220412後編 HashieでRubyのハッシュを強化、最近のRubyコア解説記事ほ
- 20220411前編 Turbo Railsチュートリアル、Active Recordの「Leaky Abstraction」を削減ほか
- 20220406後編 RBS関連記事、Ruby formatterプロジェクト、Google Cloud Runほか
- 20220404前編 Ruby 3.2.0 Preview 1リリース、Rails向けDocker環境ジェネレータ、scientist gemほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)