こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
参考: Comparing @{2022-06-23}...main@{2022-06-30} · rails/rails
🔗 マイグレーションをExecutionStrategy
でカスタマイズ可能にする
概要
このプルリクはマイグレーションの実行周りにStrategyパターンを導入する。現在のMigrationオブジェクトは、スキーマステートメントのコマンドをコネクションアダプタに委譲するのにmethod_missing
を使う。中間的なストラテジーオブジェクトを導入してアプリケーションが独自のストラテジーオブジェクトを構成可能にすることで、アプリケーション作者がマイグレーションを実行するときの柔軟性が大きく向上する。たとえばユーザーは以下を行いたいことがあるだろう。
- スキーマステートメントに
#create_table
などのキーワード引数を追加する- 特定の操作を禁止する(特定メソッドが呼び出されたらそのストラテジーがraiseできるようにするなど)
- dbスキーマに実際の変更を加えない以下のようなマイグレーションの「dry run」を可能にする
- マイグレーションを実行して、呼び出された操作を出力する
- 操作や変更に対して何らかのチェックを行う
Shopifyでは特定のユースケースがある。productionのマイグレーションでSQLをすぐに実行せず、代わりにマイグレーションをJSON DDLに変換して、複数のdbシャードすべてに渡って実行するサービスに送信するというもの。ストラテジーオブジェクトをカスタマイズしてMigrationクラスで利用できれば、JSONコマンドを生成する独自のオブジェクトを作成してAPI呼び出しで別サービスに送信できるようになる。
このプルリクでは以下を導入する。
- すべてのストラテジーが継承すべき
ExecutionStrategy
ベースクラス- 現状のマイグレーションの振る舞いを維持する
DefaultStrategy
(メソッドをコネクションに委譲する)その他の情報
コンフィグオプションも1つ追加してRailsガイドのコンフィグに追記したが、時期尚早かもしれない。現時点では、ストラテジーがマイグレーションとコネクションを両方とも保持する(これなら暫定的に動かしやすい)が、このあたりを変更するかもしれないし、ドキュメントでもストラテジーがどんなコードになるかをもっと明確にしたい気持ちもある。取りあえずRailsガイドにこれ以上追記はしないことにしてもよいし、残しておいてこれを元に本格的なドキュメントを書いてもよい。
同PRより
つっつきボイス:「マイグレーションに機能が追加されたのかな」「Strategyパターンをマイグレーションに導入して、デフォルトの挙動をDefaultStrategy
にしつつカスタマイズ可能にしたようですね」
「よく見たらShopifyのプルリクだ」「ここは想像ですけど、Shopifyがやりたい特定のストラテジーでマイグレーションを拡張するプルリクをそのままRailsに投げてもすんなりとマージされないだろうから、Strategyパターンを導入して誰でもストラテジーで柔軟にカスタマイズ可能にしたのかもしれませんね」
「ドキュメントの利用例↓ではdrop_table
を利用禁止にしている: こんなふうにカスタムストラテジーを定義すれば、Rails本体に手を加えずに既存のDSLも変更できるし、やりたければ独自のDSLを追加してもいい」「あると嬉しい機能かも」「Shopifyが必要としている機能ですけど、他のプロジェクトでもマイグレーションでこういったカスタムストラテジーを実装したいケースはありそう👍」
# guides/source/configuring.md
class CustomMigrationStrategy < ActiveRecord::Migration::DefaultStrategy
def drop_table(*)
raise "Dropping tables is not supported!"
end
end
config.active_record.migration_strategy = CustomMigrationStrategy
🔗 関連付け先の単数形の名前をwhere
内から複数形で参照すると警告を出す
これは
where
ですべてのキー(キーにはリフレクションやカラム名が入る可能性もある)にexpand_from_hash
を実行して名前を一律で単数形に変換する処理を削除する。これによって、カラム名を単数形にするのにかかる時間を大幅に節約できる。
以前の#45163では単数形化処理全体が削除されたが、その後少し考え直した。where
は非常によく使われるAPIであり、誤ったリレーションによる問題はデバッグが難しいので、少なくとも1リリースをかけて警告を表示するのがよいと思う。この挙動は、
ActiveRecord::Base.allow_deprecated_singular_assocaitions_name = false
やconfig.active_record.allow_deprecated_singular_assocaitions_name = false
で設定可能。cc: @byroot 手のひらを返して申し訳ない。自分がこの変更に気が付かずに振る舞いが変わり始めたときのことを想像して、同じようなRailsユーザーのことが心配になったので。
同PRより
関連付け先の単数形の名前を
where
内から複数形で参照すると警告を出すようにする。config.active_record.allow_deprecated_singular_associations_name = false
とすることで、パフォーマンスのよい新しい書き方を選択できるようになる。
Adam Hess
同Changelogより
つっつきボイス:「リレーション名が単数形のpost
の場合にwhere(posts: { id: 1 })
で複数形のposts
として参照するのが非推奨になったらしい」「修正細かいな〜: 最初このposts
がテーブル名に見えちゃいました」「自分も最初そう思った」
# guides/source/configuring.md
class Post
self.table_name = "blog_posts"
end
class Comment
belongs_to :post
end
Comment.join(:post).where(posts: { id: 1 }) # テーブル名がpostsでない場合は非推奨
Comment.join(:post).where(post: { id: 1 }) # 代わりにリレーション名を使うこと
「普段の自分は非推奨の方法で書いていた気もする」「自分はArel大好きなのでArelで書いちゃいますけど」「Arel嫌いです〜😭」「Arel楽しいですよ😋」
参考: Arel の使い方 [Rails] – Site-Builder.wiki
🔗 development環境用gemのインストールをスキップするオプションを追加
つっつきボイス:「以前#39282でrails new
に--minimal
オプションを付けられるようになっていましたけど↓、そのセットアップにskip_dev_gems
も追加されたようですね」「ミニマルならdevelopment環境用gemは不要というのはわかる」
🔗 Action Cableサーバーをanchor: true
でマウントするようになった
Action Cableサーバーが
anchor: true
でマウントされるようになった。
/cable
で始まるルーティングがAction Cableと衝突しなくなる。
Alex Ghiculescu
同Changelogより
つっつきボイス:「#45489を見ると、Action Cableを使ったときに以下のような/cable-
で始まるルーティングが今までAction Cableの予約語みたいになってて使えなかったのか」「anchor: true
で使えるようになるんですね」「Action Cableあまり使ってなくて気が付かなかった」
# #45489より
get "/cable-hyphenated-slug", to: "test#index"
get "/cable_underscored_slug", to: "test#index"
🔗 database.ymlでforeign_keys: false
を指定可能になった
つっつきボイス:「コンフィグレベルで外部キーを無効にできるようになったんですね」「スキーマ生成のときに外部キーを生成しないようにできるらしい」
# 同PRより
development:
<<: *default
database: db/development.sqlite3
foreign_keys: false
「SQLite3向けの機能でしょうか?」「ぐぐってみると、SQLite3はデフォルトで外部キーを使えないけどPRAGMA foreign_keys=true;
を実行すれば使えるようになるみたい↓: SQLite3以外でも外部キーをサポートしていない環境で使いたいことがあるのかもしれませんね」
参考: sqlite3で外部キーを有効にする | プロサバメモ
🔗Rails
🔗 Vite RubyでRails 7のアセットをバンドルする
- 元記事: Vite-lizing Rails: get live reload and hot replacement with Vite Ruby — Martian Chronicles, Evil Martians’ team blog
- サイト: Vite Ruby
つっつきボイス:「Evil MartiansでAnyCableのメンテナーをやっているVladimir Dementyevさんの記事です」「Vite Rubyは、Vite.jsというバンドラーをRubyから使えるようにするものなんですね: Rails標準のjsbundling-railsなどの代わりにこれを使ってRails 7でライブリロードやHMR(Hot Module Reload)をできるようにした感じかな」「Webpackerを使わずにanycable_rails_demoをRails 7にアップグレードしたかったそうです」
参考: Home | Vite
「Vite Rubyにはこんな感じのコードジェネレータがあるのね↓」
# 同記事より
Building with Vite ⚡️
vite v2.9.13 building for development...
transforming...
✓ 13 modules transformed.
Could not resolve './**/*_controller.js' from frontend/controllers/index.js
error during build:
Error: Could not resolve './**/*_controller.js' from frontend/controllers/index.js
at error (/app/node_modules/rollup/dist/shared/rollup.js:198:30)
at ModuleLoader.handleResolveId (/app/node_modules/rollup/dist/shared/rollup.js:22508:24)
at /app/node_modules/rollup/dist/shared/rollup.js:22471:26
Build with Vite failed! ❌
「vite-plugin-full-reloadやstimulus-vite-helpersとかいろいろ使ってる」「こうやって新しいものを切り開いていくところがEvil Martiansらしい👍」「Rails標準でないものを組み合わせて動かすにはRailsのコンポーネントを深く理解する必要があるので、なかなか簡単にはやれないですよね」
🔗 motion: JSを書かずにインタラクティブなUIコンポーネントを作る(Ruby Weeklyより)
- デモアプリ: Motion Demos
つっつきボイス:「motionは、見たところRailsでインタラクティブなUIコンポーネント的なものを実現するライブラリかな: map_motion
みたいなDSLを書くとAction Cable経由でERBのdata: { motion: "add" }
のところに出したりできるようですね↓」
# 同リポジトリより
class MyComponent < ViewComponent::Base
include Motion::Component
attr_reader :total
def initialize(total: 0)
@total = 0
end
map_motion :add
def add
@total += 1
end
end
<!-- 同リポジトリより -->
<div>
<span><%= total %></span>
<%= button_tag "Increment", data: { motion: "add" } %>
</div>
「ViewComponentの上でAction Cableと連携する感じで、ちょっとしたUIコンポーネントやWebSockets通信などをJavaScriptを書かずに作れるのがポイントかな: 制約もいくつかあるようだし、小さなアプリで使う分にはいいかも👍」
参考: WebSocket API (WebSockets) - Web API | MDN
🔗 Rubyのfetch
メソッド(Ruby Weeklyより)
つっつきボイス:「記事にも書かれているように、Rubyのfetch
は第2引数を渡さないとキーがない場合にKeyError
例外になる↓」
# 同記事より
h = { "a" => "AAA", "b" =>"BBB", "c" => nil }
h.fetch("c") # 'nil'が返る
h.fetch("z") # KeyErrorが発生
参考: Hash#fetch
(Ruby 3.1 リファレンスマニュアル)
「第2引数でデフォルト値を渡すと、キーが存在しない場合はデフォルト値を返し、キーに対応する値がnil
の場合はそのnil
を返す↓: fetch
ではデフォルト値を渡すのがベストプラクティス的ではありますね」
# 同記事より
h = { "a" => "AAA", "b" =>"BBB", "c" => nil }
h.fetch("a", "Not here") # 'AAA`が返る
h.fetch("c", "Not here") # 'nil'が返る
h.fetch("z", "Not here") # 'Not here'が返る(KeyErrorにならない)
「||
だとデフォルト値についてはfetch
と同じ結果にならない↓: ハッシュに[]
でアクセスする場合、キーが存在しなくてもnil
が返されるだけでエラーにならないのはRubyの仕様ですね」
# 同記事より(編集部で変更)
h["a"] || default_value # 'AAA'が返る
h["c"] || default_value # 'Not here'が返る
h["z"] || default_value # 'Not here'が返る
「そういえばPythonのdictだと[]
で存在しないキーにアクセスしたときにエラーになりますね」「他の言語だとそういう仕様の方をよく見かける気はしますね: ハッシュの[]
アクセスでキーが存在しない場合にnil
を返すかエラーにするのかは、どちらがよいという話ではなく言語設計者がその仕様を選んだに過ぎないんですが、他の言語からRubyに来た人はそういうところで少し戸惑うかも」「いわゆるデザイナーズチョイスですね」「Rubyでエラーにしたければfetch
を使うのが普通でしょうね」
参考: pythonのdictionaryでKeyErrorを出さないようにする - Qiita
「お、Rubyのfetch
でキーが存在しないときだけ処理を変えたい場合は、こうやってブロックを渡すと、キーが存在しない場合だけブロックが評価されるのか↓」「fetch
にブロックを渡せるって知らなかったかも」「記事は値がnil
の場合とキーが存在しない場合で処理を分けたかったんですね」
# 同記事より
h.fetch("a"){ default_value } # 'AAA'が返る(メッセージは出ない)
h.fetch("c"){ default_value } # 'nil'が返る(メッセージは出ない)
h.fetch("z"){ default_value } # 'Not here'メッセージが出力される
「同じことを&
によるProc渡しでも書けるのね↓」「自分はここまでしないかな〜」「ただしMethod
オブジェクトのアロケーションが重いからブロックの方がいいと追記されてますね」
# 同記事より
h.fetch("a", &method(:default_value)) # 'AAA'が返る(メッセージは出ない)
h.fetch("c", &method(:default_value)) # 'nil'が返る(メッセージは出ない)
h.fetch("z", &method(:default_value)) # 'Not here'メッセージが出力される
「fetch
は普段あんまり使ってなかったかも」「自分はよく使います」
🔗 Active Recordに隠れているStore機能
つっつきボイス:「Active Record
に組み込まれているStoreを使うとNoSQLっぽくアクセスできると書かれていますね」「ActiveRecord::Store
はJSONシリアライズしたものを1個のカラムに保存できたりしますね: かなり昔からあって存在も何となく知っていたけど、普段は使わないかな」「たしかにメインで使うものではないかも」
# 同記事より
class Item < ApplicationRecord
store_accessor :user_attributes, :color
store_accessor :user_attributes, :name, prefix: true
store_accessor :user_attributes, :location, prefix: 'primary'
end
# 同記事より
=>item = Item.create!(color: 'red', user_attributes_name: 'Jonathan', primary_location: 'New Zealand')
>#<Item:0x000055d63f4f0360
id: 4,
user_attributes: {"color"=>"red", "name"=>"Jonathan", "location"=>"New Zealand"}>
=>item.color
>"red"
=> item.user_attributes_name
>"Jonathan"
=> item.name
>NoMethodError: undefined method `name'...
=> item.primary_location
>"New Zealand"
参考: Rails API ActiveRecord::Store
「こういうのを使いたくなる場合があるとすれば、スキーマを決めるほどでもないような情報を保存するときでしょうね: たとえばログインユーザーが使えるテーマのリストとか、ウィンドウの表示ペインを縦に並べるか横に並べるかみたいなコンフィグ情報をJSONで保存したいときとか」「なるほど」
「そういう情報をJSONシリアライズして1カラムに保存しておくとインポートやエクスポートがしやすくなるというのはありますね: たとえばJetBrains IDEのXMLコンフィグファイルをエクスポートして他の環境に持っていけたりしますけど、そういうのをやるには便利」「言われてみれば、UIで使えるフォントのリストみたいに環境によって変わりそうなものはスキーマを作りたくないですね」
「ActiveRecord::Store
は、スキーマを決めるほど厳密でもないけどモデルでアクセサを使いたいような、ある意味中途半端なデータを扱うときに便利でしょうね」「なるほど」「もちろん重要なデータについてはスキーマを定義しますし、そういうものにActiveRecord::Store
を使うべきではありませんが、そうでないデータもたまにあるんですよ」「そうなってから利用を検討すればいいということですね」
前編は以上です。
バックナンバー(2022年度第2四半期)
週刊Railsウォッチ: attr_accessorが通常のメソッドより速い理由、ES2022の新機能ほか(20220628後編)
- 20220627前編 Kaigi on Rails 2022がCFP公開、Discordの公式Railsサーバーが一般公開ほか
- 20220621後編 VSCodeでRubyコード実行結果を表示、rubygems.orgがgem作者に多要素認証を呼びかけほか
- 20220620前編 GitLabがRailsにこだわる理由、Rails7アップグレードのハマりどころほか
- 20220614後編 Rubyの実行モデル解説記事、shale gem、HTTP/3がRFC 9114にほか
- 20220613前編 Hotwireをアプリ構築で学ぶ、Active RecordのDurationとPostgreSQL intervalデータ型ほか
- 20220607後編 Shopifyのlanguage server ruby-lsp、PostgreSQL 15 Beta 1リリースほか
- 20220606前編 BasecampのHotwireページネーション、Query Object、Lograge gemほか
- 20220531 Railsコミュニティアンケート結果発表、書籍『Sustainable Web Development with Ruby on Rails』ほか
- 20220524後編 Railsコアチームとコミッターに新メンバー、ruby-buildでのRust YJITサポートほか
- 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ウォッチタグ)