- Ruby / Rails関連
週刊Railsウォッチ: Railsリポジトリで進行中のPropshaft、inverse_ofを自動推論ほか(20211018前編)
こんにちは、hachi8833です。Kaigi on Rails 2021が今週の金曜と土曜に開催されますね。
皆さんこんにちは!最近のアップデートについてお知らせします。
公式ページでタイムテーブルが公開されました。お気に入りのトークをチェックしてみてください✨https://t.co/P4V1eq6uGX
参加登録も開始しました。以下のリンクからどうぞ、参加費は無料です😄https://t.co/2NtX9Am0Zl#kaigionrails— Kaigi on Rails (@kaigionrails) September 29, 2021
🔗Rails: 先週の改修(Rails公式ニュースより)
以下の公式更新情報を中心に見繕いました。
- 更新情報: Automatic inverse_of, performance improvements and more! | Riding Rails
- 更新情報: Autumn is here, and so is Rails 7 Alpha 2! 🍂 | Riding Rails
🔗 コントローラのコールバックでinstance_exec
を回避
procが(条件またはコールバック自身として)コールバックに渡されると、この
proc
がコントローラのインスタンス上でinstance_exec
を用いて評価される。instance_exec
を呼ぶとそのオブジェクトのシングルトンクラスが新たに作成され、インラインメソッドキャッシュが新たに要求される。このコミットは、
:only
か:except
がコールバックに渡された場合にコントローラで余分なシングルトンクラスが作成されるのを回避する。
この効果を誇張した以下のベンチマークも作った。
https://gist.github.com/jhawthorn/bded5bc1d5f1afd4cdd7fb5b800312e1
同PRより
つっつきボイス:「なるほど、改修前のようにproc
で処理するとオブジェクトのシングルトンクラスが生成されてしまう↓」
# actionpack/lib/abstract_controller/callbacks.rb#L77
def _normalize_callback_option(options, from, to) # :nodoc:
if from = options.delete(from)
- _from = Array(from).map(&:to_s).to_set
- from = proc { |c| _from.include? c.action_name }
+ from = ActionFilter.new(from)
options[to] = Array(options[to]).unshift(from)
end
end
「代わりにActionFilterクラスを定義しておいてそれで処理する方が、proc
で回すよりも生成を削減できて高速化につながるということのようですね」「なるほど、そういえばJeremy Evansさんの『Polished Ruby Programming』にもこんな話がどこかにあった気がします」「考え方としてはValue Objectを連想しますね」
「呼び出される回数がものすごく多いコードをこうやって最適化すると、改善がたとえ数%であっても顕著な差が出る」「たしかにコールバックは呼び出しが多そう」
# actionpack/lib/abstract_controller/callbacks.rb#L38
+ class ActionFilter
+ def initialize(actions)
+ @actions = Array(actions).map(&:to_s).to_set
+ end
+
+ def match?(controller)
+ @actions.include?(controller.action_name)
+ end
+
+ alias after match?
+ alias before match?
+ alias around match?
+ end
🔗 スコープ付き関連付けでinverse_of
を自動推論
- PR: Automatically infer inverse_of with scopes by composerinteralia · Pull Request #43358 · rails/rails
- スコープ付き関連付けで
inverse_of
を自動的に検出できるようになった
inverse_of
の自動検出がスコープ付き関連付けで動くようになった。たとえば、以下のcomments
関連付けで自動的にinverse_of: :post
が検出されるので、このオプションを渡す必要がなくなる。
class Post < ActiveRecord::Base
has_many :comments, -> { visible }
end
class Comment < ActiveRecord::Base
belongs_to :post
end
ただし、まだこの自動検出は逆関連付けにスコープがあると動作しない。この例では、
post
関連付けにスコープがあると、Railsがcomments
関連付けの逆関連付けを探索できなくなる。これはRails 7の新規アプリでデフォルトになる。オプトインするには以下の設定を用いる。
config.active_record.automatic_scope_inversing = true
Daniel Colson, Chris Bloom
同Changelogより
つっつきボイス:「お、can_find_inverse_of_automatically
というメソッドを改修することで、スコープ付きの関連付けでinverse_of
を自動検出するようにしたんですね↓」「こんなメソッドがあったとは」
このコミットは
can_find_inverse_of_automatically
を変更して、関連付けにスコープがあるが逆の関連付けにスコープが存在する可能性がない場合にinverse_of
を自動検出するようにした(can_find_inverse_of_automatically
は関連付けのリフレクションで最初に呼び出され、true
が返されれば逆のリフレクションを探索し、最終的に逆のリフレクションでそのメソッドを再び呼び出すことでそのメソッドを確実に呼び出せるようにする)。
同PRより抜粋
「inverse_of
を毎回手動で書くのは割と手間だったので、副作用がないならこういうのはありがたい👍」「Changelogには逆の関連付けにスコープがあると動かないと書かれていますね」「単純なものなら手動で書けば動かせますけど、逆の関連付けにスコープが付いていると自動でコードを生成するのは簡単ではなさそう」
「コミットメッセージによるとGitHubには関連付けが171個もあるので、これを使ってinverse_of
を自動追加したいとありますね: 確かにこれだけたくさんあると手動でやってたら間違えそう」「コミットしたcomposerinteraliaさんはGitHubのスタッフなんですね」
🔗 コネクションのスキーマキャッシュをlazy loadingできる機能を追加
- コネクションのスキーマキャッシュをlazy loadingするオプションを追加
従来は、Active Recordでスキーマキャッシュを読み込むには起動時にRailtieを用いる方法しかなかった。このオプションは、コネクションが確立した後でコネクションのスキーマキャッシュを読み込める機能を提供する。コネクションの確立後にキャッシュが読み込まれるので、コネクションのキャッシュを遅延読み込みする機能はマルチプルデータベースを用いるRailsアプリケーションで重宝するだろう。現時点では、Railtiesは起動前にコネクションにアクセスできない。
このキャッシュを用いるには、アプリケーションのコンフィグで
config.active_record.lazily_load_schema_cache = true
を設定する。さらに、デフォルトの"db/schema_cache.yml"パスを使いたくない場合は、データベースコンフィグにschema_cache_path
も設定するべき。
Eileen M. Uchitelle
同Changelogより
つっつきボイス:「コネクションのスキーマキャッシュをlazy loadingする機能、通常はそれほど必要なさそうに見えるけど、マルチプルデータベースで便利な可能性か、なるほど」「あくまでオプションなので、使いたければ使えるという感じですね」
🔗 pg:dump
でCOMMENTの出力を回避
このプルリクは、PostgreSQL利用時に
COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
のような行がdb:structure:dump
実行時に出力されないようにする。
この種のCOMMENTがあってもあまり価値はないように思える。
しかしこのCOMMENTがあると、#36816で指摘されているようにdb:structure:load
がDBのスーパーユーザーでないと動かなくなり、ActiveRecord::StatementInvalid: PG::InsufficientPrivilege: ERROR: must be owner of extension
のような一般的なエラーが出力されてデバッグがやりにくくなる点がよくない。
同PRより
つっつきボイス:「PostgreSQLバージョンが11以上のときはdb:structure:dump
でCOMMENTを出力しないようにしたんですね」
「なるほど、COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
みたいな行があるとスーパーユーザー以外はdb:structure:load
が失敗するので、失敗しないようにCOMMENTを抑制したということか」「なるほど」「作ったダンプが読み込めないのかと思って焦りそうなので、余分なエラーがなくなるのはいい👍」「ですよね」
🔗 PostgreSQLのクエリパラメータ構文を利用できるようになった
従来は以下を実行すると
ActiveRecord::DatabaseConfigurations::UrlConfig.new(:production, :production, 'postgres:///?user=user&password=passwd&dbname=theapp_production', {}).configuration_hash
以下が返された。
{ :user=>"user", :dbname=>"theapp_production", :adapter=>"postgresql" }
uri.password
がnil
なのでpassword
属性がnil
を返し、このハッシュをマージしたものが上の結果となる。このプルリクはこの点を修正して以下を返すようになる。
{ :user=>"user", :password=>"passwd", :dbname=>"theapp_production", :adapter=>"postgresql" }
この問題は#42797で指摘された。なお同issueでは、
postgres://user:passwd@/theapp_production
はPostgreSQLのURIとして有効という指摘もあった。現在は、以下を実行すると
ActiveRecord::DatabaseConfigurations::UrlConfig.new(:production, :production, 'postgres://user:passwd@/theapp_production', {})
以下のエラーが出力される。これはURIが有効なRFC 2396実装でないことが原因。これを修正するアイデアがあれば求む。
/home/abeid/.rbenv/versions/3.0.1/lib/ruby/3.0.0/uri/generic.rb:207:in 'initialize': the scheme postgres does not accept registry part: user:passwd@ (or bad hostname?) (URI::InvalidURIError)
同PRより
つっつきボイス:「postgres:///?user=user&password=passwd&dbname=theapp_producti
みたいにpostgres:///
で始まるURIを指定できるようになったんですね」「こんなふうにuserやpasswordを指定するのか」「これはたぶん本当のURI形式とは違うんじゃないかな?」「あくまでURI風ということなのかも」「JDBCとかでこの形式のURIを使った覚えがあります」「そうそう」
参考: Java Database Connectivity - Wikipedia
🔗Rails
🔗 RailsリポジトリにあるPropshaft
"Propshaft is an asset pipeline library for Rails. It's built for era where bundling assets to save on HTTP connections is no longer urgent, where JS and CSS is either compiled by dedicated Node.js bundlers or served directly to the browsers, ..." https://t.co/OHrIYVxl5Z
— DHH (@dhh) September 20, 2021
つっつきボイス:「この間Railsウォッチをレビューいただいたときに(ウォッチ20211004)このPropshaftの存在を教わったので取り上げてみました」「そうそう、アセットパイプラインの新しい選択肢のひとつが作り中のはずと思って探してみたら、Railsのリポジトリの中にこのProfshaftがあったんですよね」
「アセットパイプラインで従来のSprocketsの代わりにProfshaftも使えるようになるということみたい」「Propshaftって造語かと思ったらプロペラシャフトでした↓」「Sprocketsもそうですけど、機械の部品っぽい命名にしてるんでしょうね」「くるくる回り続ける部品感ある」
参考: propshaftとは何? Weblio辞書
参考: sprocketの意味・使い方・読み方 | Weblio英和辞書
「PropshaftのREADMEを見ると、アセットのバンドルを頑張らなくてよくなった時代のためのアセットパイプラインライブラリという感じの触れ込みですね」「Rails 7ではアセットのプリコンパイルを避ける方向に持っていきたいはずなので、Propshaftはその一環ということでしょうね: Rails 7で例のimport mapが導入されることで、Sprocketsのときよりも機能を軽量化できた感じ」「なるほど」
「READMEの末尾にある『PropshaftはRails 7に入るのか?』というFAQに『入る可能性は高いけど当面Sprocketsのサポートも必要』とありますね」「まだしばらくかかりそうかな」
🔗 Active Supportの#descendants
メソッドを深掘りする(Ruby Weeklyより)
参考: ActiveSupport::DescendantsTracker
つっつきボイス:「descendants
メソッドやancestors
メソッドは1〜2年おきぐらいに話題になる感じ」「クラスやモジュールの読み込みリストをチェックするメソッドですね」「記事にもあるように、モジュールをinclude
やprepend
した結果の順序などが罠になることがあります」
🔗 SeleniumによるシステムテストをCupriteに移行してみた(Ruby Weeklyより)
つっつきボイス:「ChromeでテストするならCupriteでいいんじゃないかな: 手順が丁寧に説明されていてよさそう👍」
「記事でも取り上げていますけど、Ajax/Fetch周りが割と複雑」「記事ではテストを書き直さないといけなかったそうです」「イベントを取得するとか、通信が発生して書き換えを待つような動作のテストは難しい部分ですね」
「そういえば少し前にもCupriteに変えてみた記事があったのを思い出しました↓」「こういう実際に動かしてみた記事が増えてくると助かります🙏」
参考: 2021年6月現在、Cupriteで"正しい"システムテストはできるのか?
🔗 SidekiqをActive Job経由ではなく直接使う(Ruby Weeklyより)
# 同記事より: Sidekiqを直接使う場合
class DoThingsInBackgroundJob
include Sidekiq::Worker
Sidekiq_options queue: "default"
def perform(id)
an_active_record_object = ActiveRecordObject.find_by(id: id)
an_active_record_object.do_things
end
end
つっつきボイス:「タイトルで言いたいことはだいたい理解できた😆」「気持ちわかります」「Active Jobは抽象化されている分機能が少な目なので、生のSidekiqを使う方が話が早い」
「記事の末尾に『SidekiqはPro版にするのがおすすめ』とありました」「Sidekiqの有料版を使ったことはないけど、十分普及していて経営も順調なサービスならサポートなどの面を考えてもお金を払う価値はあるでしょうね」「たしかに」
参考: Sidekiq: Simple, efficient background jobs for Ruby.
「お、Sidekiqの価格表に支払い方法が書かれているのがちょっと珍しいかも: Enterprise版は請求書払いができるとありますよ」「ほんとだ」「請求書払いに対応していないと日本企業でなかなか決済が通らなかったりしますよね」「そういう傾向はありますね」「最近はさすがに減りつつあると思いますけど」
🔗 RSpecのsubject
おとといに公開したブログ記事です。RSpec初心者からよく聞こえてくる「RSpecわからん、難しい」の約80%はこのsubjectに起因するものです(要出典)。
僕がRSpecでsubjectを使わない理由 - give IT a try https://t.co/A3JHfPNgfP
— Junichi Ito (伊藤淳一) (@jnchito) October 10, 2021
つっつきボイス:「RSpecのsubject
は、うまく適合するケースの方が少ないなと自分も思います: it
がたくさんある場合にはsubject
があると明らかに繰り返しが減りますけど、記事にもあるように以下のような使い方はたしかに最悪↓」「う、subject
の中でincrement
してる」「subject
の中での改変はやめて欲しい」
# 同記事より: subjectが向いてないケース
class Counter
attr_reader :count
def initialize
@count = 0
end
def increment
@count += 1
end
end
describe 'Counter#increment' do
let(:counter) { Counter.new }
subject { counter.increment }
it do
subject
expect(counter.count).to eq 1
end
end
「行数の多いテストにsubject
があると、このテストのsubject
はどこだっけと探さないとわからなくなりますよね」「たしかに」「subject
には名前を付けて呼び出せる機能もあるので、それを使うならsubject
があってもいいかなと思います: 名前付きsubject
は嫌いじゃない」「なるほど」
参考: Explicit Subject - Subject - RSpec Core - RSpec - Relish
🔗 その他Rails
Railsアップグレードを何回か経験してるけど、本当にこれが大事。ただ、Git、Ruby、Rails力がないと実現できないので難易度も高い。
> 最終的にはライブラリのバージョン変更の差分だけにして本番に反映するhttps://t.co/FU73XHnM4S
— 神速 (@sinsoku_listy) September 26, 2021
つっつきボイス:「上はr7kamuraさんのRailsアップグレード記事ですね」
「r7kamuraさんはたしか以前からRailsアップグレード作業を請け負っていますね↓」「お〜」「Railsのアップグレードは社内の人がなかなかやりたがらないこともあったりするので、Railsアップグレードに特化して業務を請け負うのはひとつの生存戦略だなと思いました」「そうそう、アップグレードのノウハウも蓄積できますよね」
前編は以上です。
バックナンバー(2021年度第4四半期)
週刊Railsウォッチ: Ruby 3.1にYJITマージのプロポーザル、Rubyのmagic historyメソッド、JSのPartytownほか(20211012後編)
- 20211011前編 ServerTimingミドルウェア追加、paramsで数値キーを許可、Railsで多要素認証ほか
- 20211006後編 ruby/debug 1.2.0リリース、Railsにはthorが入っている、tendejitほか
- 20211004前編 Rails 7でbyebugがruby/debugに変更、GitHub Codespacesをサポートほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)