- Ruby / Rails関連
週刊Railsウォッチ(20200831前編)GitHubがRuby 2.7にアップグレード、Durationに変換メソッドが追加、hair_triggerでデータベーストリガほか
こんにちは、hachi8833です。RubyKaigi Takeout 2020はもう今週の金曜土曜ですね。YouTubeのRubyKaigiチャンネルでリマインダーを設定できるそうです。
Also you can now set a reminder for #rubykaigi on YouTube. As usual, English interpretation is available for Japanese-spoken talks! https://t.co/DzCv83vJ71
— RubyKaigi (@rubykaigi) August 27, 2020
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
⚓Rails: 先週の改修(Rails公式ニュースより)
以下のコミットリストのChangelogを中心に見繕いました。
- コミットリスト: Comparing master@{2020-08-21}...@{2020-08-27} · rails/rails
- 6.1マイルストーン: 6.1.0 Milestone -- 26件
さっき気づいたけどいつからか知らんがGitHubのコミットの日付表示が昔はauthor dateだったのに今はcommit dateに変わっとるな
— Ryuta Kamizono (@kamipo) August 25, 2020
つっつきボイス:「そうそう、GitのコミットにはAuthorDate
とCommitDate
の2つがありますね」「そういえばあった」「たしか普通にコミットでオプションを付けるとAuthorDate
を変えられるんですけど、CommitDate
は自動的にシステムの日付になるので、CommitDate
を変えようとすると何か一工夫必要だった覚えがあります」「そうそう、--date
オプションはAuthorDate
を変えるとき」「CommitDate
を任意に変えようとすると面倒だった気がする」
参考: gitのコミット時間を変更する - Qiita
参考: Git のコミットのタイムスタンプには author date と committer date の 2 種類があるという話 - ひだまりソケットは壊れない
⚓新機能: ActionView::Helpers::TranslationHelper#translate
がブロックを受け取って訳文と解決済み訳文キーを取れるようになった
このコミットは
ActionView::Helpers#translate
ヘルパーメソッド(ちなみにエイリアスは#t
)がブロックを受け取れるよう拡張する。
このメソッドをブロック付きで呼び出すと、translate
呼び出しが訳文を第1ブロック引数として、解決済み訳文キーを第2ブロック引数としてyield
する。
<%= translate(".key") do |translation, resolved_key| %>
<span data-i18n-key="<%= resolved_key %>"><%= translation %></span>
<% end %>
相対的な訳文キーが完全修飾キーより先行する場合や、呼び出し側が解決済みキーに関心がない場合は、第2ブロック引数を省略可能。
<%= translate("action.template.key") do |translation| %>
<p><%= translation %></p>
<p><%= translation %>, but a second time</p>
<% end %>
訳文を
yield
するメリットは、テンプレートローカルな変数がこれによって再利用できること。RubyのObject#tap
も使える。ただしこのコミットより先んじて、訳文キーの解決が
ActionView
内部で行われているために、呼び出し側から利用できなかった(解決済みキー自体を明示的に決定するつもりがない限り限り)。これをブロックパラメータとして利用できるようにしたことで、生成する要素内で翻訳された値をアノテートできるようになる。
同コミットより大意
参考: translate
-- ActionView::Helpers::TranslationHelper
つっつきボイス:「なるほど、t
ヘルパーに機能が追加された」「translate
にブロックを付けると複雑になるのであんまりやりすぎない方がいいかなとは思いますけど」「できるようになったのはいいこと👍」「想像ですけど、ブロック渡しにするとブロックをラムダとして解釈することになってキャッシュの高速化が効かなくなったりするのかな?それほど影響しないでしょうけど」
「Rubyのコードはこういうふうにブロックを付けるといろいろ融通を利かせられる実装が多いですよね」「デバッグでも便利なことがありますし」
⚓STI以外の型でもdemodulizeされたクラス名の保存をサポート
ポリモーフィックな型でdemodulizeされたクラス名の保存をサポートする。
Rails 6.1より前は、STI型でしかstore_full_sti_class
クラス属性を用いてdemodulizeされたクラス名を保存できなかった。
このプルリクによって、STI型でもポリモーフィック型でもstore_full_class_name
クラス属性を扱えるようになった。
同PRより大意
つっつきボイス:「でもじゅらいず?」「どう日本語にしようか悩んでます」「ポリモーフィックなクラスでも動くようになったということかな」「関連するプルリクを見てみる方がいいかも↓」
- PR: Fix association creation to respect store full sti class by kamipo · Pull Request #29722 · rails/rails
- PR: Fix eager loading to respect `store_full_sti_class` setting by kamipo · Pull Request #29601 · rails/rails
- PR: Association creation and finding should work consistently by kamipo · Pull Request #32048 · rails/rails
「#29601が関連しているあたり、eager loadingがらみなのかも」「この辺はどう動いてるのかよくわからない...」
⚓STIよもやま話
「STIの挙動ってすぐには予測がつきにくいことがありますよね、親クラスに対してeach
したときにそのクラスが返ってくるのか親クラスが返ってくるのかとか: まあinstance_of
でチェックすればいいんですけど」
「この辺の記事↓を見てみると、モデルのインスタンスをnew
するとサブクラスがあればサブクラスとして、それ以外は普通のActiveRecord::Base
としてnew
するのね」「find_sti_class
はどっかで調べたことあるかも」
参考: ActiveRecord では STI をどう実装しているかを調べたメモ - Qiita
参考: find_sti_class
(ActiveRecord::Inheritance::ClassMethods) - APIdock
「STIはたまにうまく設計にはまるときがありますけど、それ以外ではあんまり使わないかな」「自分もそうかも」「継承がとてもうまく作用するケースみたいに、STIがキレイに合うときはたしかにある」「STIのいい点は1個のテーブルに入るところですよね」「横断しているtypeをSQLのクエリで一発で引っ張りたい、しかもインデックスも効かせたいというときは、結果としてSTIがデータ構造的にもうまく当てはまりやすい気がします」「ふむふむ」
参考: STI -- Active Record の関連付け - Railsガイド
「以前よく作ったのが通知機能でのSTIなんですけど、通知にはいろんなタイプがあるけどメッセージはだいたい共通していて、でもクリックしたときのジャンプ先は通知のタイプによって違う、みたいな機能」「ふむふむ」「Notificationみたいな親クラスを作ってそれぞれの通知タイプを作る感じでSTIにしたんですけど、理由は通知が軽く数千万レコードぐらいに増えてしまって毎回複数テーブルから引っ張ってくるのがまったく現実的でなくなってしまうから」「なるほど〜」
⚓新機能: ActiveSupport::Duration
に各種変換メソッドを追加
- 元記事: Add ActiveSupport::Duration conversion methods by jasonyork · Pull Request #38699 · rails/rails
以下が追加されたそうです。
in_seconds
in_minutes
in_hours
in_days
in_weeks
in_months
in_years
つっつきボイス:「Active SupportのDurationは便利ですし、よく作ったと思いますね」「in_*
で変換を表すのか」「たとえば『1日は何分か』を1.day.in_minutes
って書けるのね」「12.hours.in_days
は0.5だからたしかに半日」
# activesupport/lib/active_support/duration.rb#L352
+ alias :in_seconds :to_i
+
+ # Returns the amount of minutes a duration covers as a float
+ #
+ # 1.day.in_minutes # => 1440.0
+ def in_minutes
+ in_seconds / SECONDS_PER_MINUTE.to_f
+ end
+
+ # Returns the amount of hours a duration covers as a float
+ #
+ # 1.day.in_hours # => 24.0
+ def in_hours
+ in_seconds / SECONDS_PER_HOUR.to_f
+ end
+
+ # Returns the amount of days a duration covers as a float
+ #
+ # 12.hours.in_days # => 0.5
+ def in_days
+ in_seconds / SECONDS_PER_DAY.to_f
+ end
+
+ # Returns the amount of weeks a duration covers as a float
+ #
+ # 2.months.in_weeks # => 8.696
+ def in_weeks
+ in_seconds / SECONDS_PER_WEEK.to_f
+ end
+
+ # Returns the amount of months a duration covers as a float
+ #
+ # 9.weeks.in_months # => 2.07
+ def in_months
+ in_seconds / SECONDS_PER_MONTH.to_f
+ end
+
+ # Returns the amount of years a duration covers as a float
+ #
+ # 30.days.in_years # => 0.082
+ def in_years
+ in_seconds / SECONDS_PER_YEAR.to_f
+ end
「カレンダー的な処理では便利そう」「このぐらいささやかだと自分で書いてもいいでしょうけど」「こういうメソッドがあることに気づかずに実装しちゃいそう😆」「そうかも😆」
⚓rails secrets
を非推奨化、5.2〜のrails credentials
を推奨
つっつきボイス:「rails secrets
はちょっと前からrails credentials
への移行が進んでた気がする」「rails secrets
はとりあえずsoft deprecateされたんですね」
Rails::Application
にcredential用のattr_writer
を追加- ドキュメントからsecrets.ymlへの参照を削除
- Railsの
secrets
が非推奨化されたことを示すヘッダーをsecrets
コマンドのUSAGE
に追加
同PRより大意
参考: 【Rails5.2】秘匿情報はsecret.ymlではなくcredentials.yml.encで管理する【初心者】 - Qiita
⚓Railsガイド「Active Recordクエリガイド」のサンプルのモデルをBookstoreに変更
現実に即したサンプルにしたとのことです。
# guides/source/active_record_querying.md#L32
-class Client < ApplicationRecord
- has_one :address
- has_many :orders
- has_and_belongs_to_many :roles
+class Author < ApplicationRecord
+ has_many :books, -> { order(year_published: :desc) }
+end
+class Book < ApplicationRecord
+ belongs_to :supplier
+ belongs_to :author
+ has_many :reviews
+ has_and_belongs_to_many :orders, join_table: 'books_orders'
+
+ scope :in_print, -> { where(out_of_print: false) }
+ scope :out_of_print, -> { where(out_of_print: true) }
+ scope :old, -> { where('year_published < ?', 50.years.ago )}
+ scope :out_of_print_and_expensive, -> { out_of_print.where('price > 500') }
+ scope :costs_more_than, ->(amount) { where('price > ?', amount) }
end
つっつきボイス:「こちらはドキュメントの更新」「Active Recordクエリインターフェイスガイドのサンプルコードで使うモデルを書店ベースのものに書き換えたのね」「Client
やAddress
がAuthor
やCustomer
になったりBook
やSupplier
が追加されたりと前より複雑になった感」「図も更新されてますね」「もっと複雑で現実的な事例をサポートできるようにしたと」「説明により適したサンプルになるならいいと思います👍」
参考: Active Record クエリインターフェイス - Railsガイド
⚓Rails
⚓GitHubがRubyを2.7に上げた
Upgrading GitHub to Ruby 2.7 - The GitHub Blog
環境変数でRuby 2.6とRuby 2.7を切り替えられるデュアルブートにしてアップグレードを進めたそうです。
つっつきボイス:「ついにGitHubがRubyを2.7に!」「これは偉大!🏔」「2.7は昨年クリスマスのリリースだったから、半年以上かかったということか」「やっぱりこのぐらいはかかりますよね」「アップグレードしたことでいろいろ速くなったらしいことが書かれてる↓」
「GitHubがRubyを使い続けていることでRubyがいろいろよくなっているのはいいですよね」「GitHub向けの修正もちょいちょい入ったりしてるようですし」
なお、今度のRubyKaigi Takeout 2020では2.7での最適化の話↓も聞けるそうなので楽しみにしています。
⚓hair_trigger: データベース側でトリガするマイグレーションを生成
hair-trigger: (形)(引き金が軽いことから)すぐカッとなりやすい
つっつきボイス:「ヘアトリガー?」「なるほど、モデルにtrigger.after
とかでトリガーを書いてマイグレーションを生成するとデータベース側のトリガにできるのか↓」「あら、そうみたい」「なかなかアグレッシブなgem」
# 同リポジトリより
class AccountUser < ActiveRecord::Base
trigger.after(:insert) do
"UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;"
end
trigger.after(:update).of(:name) do
"INSERT INTO user_changes(id, name) VALUES(NEW.id, NEW.name);"
end
end
# 同リポジトリより
rake db:generate_trigger_migration
-- 同リポジトリより: MySQLの場合
CREATE TRIGGER account_users_after_insert_row_tr AFTER INSERT ON account_users
FOR EACH ROW
BEGIN
UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;
END;
CREATE TRIGGER account_users_after_update_on_name_row_tr AFTER UPDATE ON account_users
FOR EACH ROW
BEGIN
IF NEW.name <> OLD.name OR (NEW.name IS NULL) <> (OLD.name IS NULL) THEN
INSERT INTO user_changes(id, name) VALUES(NEW.id, NEW.name);
END IF;
END;
「整合性制約をデータベース側に置くというのはデータベース的には本来あるべき姿ではありますね」「たしかに」「基本的にRDBMSは整合性制約を保証する機能があるものですし」
「こういうgemは割と好きかも」「整合性制約をRailsでやりたい人はそうじゃないかもしれませんね」
「ただこのトリガーにはあくまでSQLしか書けないので、Rubyで処理したデータをブロックに渡すみたいな処理はそのままでは書けないことになりますよね」「あ〜そうなっちゃうのか」「なにしろトリガーなので」「どうしてもやりたければストアドプロシージャでRubyでやるのと同じ処理を書くとかになるでしょうし: その意味でこのgemはSQL脳の人じゃないと使いこなすのが大変かも」「Rubyでトリガーのロジックを書きたい人には悩ましいですね...」「最近のRailsエンジニアだとデータベーストリガーを使ったことない人もいるかも」
参考: ストアドプロシージャ - Wikipedia
参考: データベーストリガ - Wikipedia
「限定的な状況では有用だと思いますけど、いつもこれでやれるとは限らないかな: まあ自分は好きですけど❤️」
⚓カーソルベースとオフセットベースのページネーション
つっつきボイス:「たしかにカーソルベースとオフセットベースのページネーションは全然違う」「カーソルでやれるならその方がめちゃくちゃ大きいページのページネーションで明らかに有利ですけど」「ですよね」「そういえばOracleなんかだとセッションの範囲で使えるテンポラリテーブルを作ったりできますね」
参考: ページネーションとは|Web用語(意味・説明) | プロモニスタ
「実際、カーソルにしないと遅くてどうしようもないというケースはたしかにありますし」「使わざるを得ないときはありますね」「カーソルを自力で操作するのはちょっとしんどいですけど」
「このネタはruby-jp Slackでも見かけましたね」「ところでページングと書いてあると違うものを想像しません?」「OSのメモリ管理の方のページング?」「はい😆」「まあSQLの文脈なので意味はわかりますけど」「記事にはページャーという言葉も出てきてますけどless
とかmore
みたいなページャーコマンドを思い出しちゃいます」
参考: ページング方式 - Wikipedia
参考: ページャとは - IT用語辞典 e-Words
追いかけボイス: 「一応MySQLにもCREATE TEMPORARY TABLEというのがあります: PostgreSQLでもできる気がすると思ったら普通にありました↓」「temporary table、WITH句だと書ききれない複雑な処理結果とかをtemporary tableに突っ込んで処理する、みたいな用途に使えるのでたまに使いたいことあるんですけど、CREATE TABLEなpriviledgeが必要なのでそこがやや使いにくいと思うことはありますね」
参考: 第107回 CREATE TEMPORARY TABLEによる一時テーブルの利用:MySQL道普請便り|gihyo.jp … 技術評論社
参考: TEMPORARY TABLE(一時テーブル)を探る - Qiita
⚓その他Rails
つっつきボイス:「ところでWindows + WSL2でやるときって、Windowsのファイルシステム側でソースコードを変更してそれをWSL2側で動かそうとすると面倒なことになりがちですよね」「やろうとしたらできなかったり、できたとしてもトラブルが多くて大変だったな〜という印象あります」「ファイルシステムが両者で違うのでもうしょうがない」「わかってはいるんですけどね...」「8ビットで表現されるパーミッションですら両者で違ってますし」「WindowsとWSL2もいろいろすごく頑張ってるのはわかるんですけど」「ファイルアクセスはもうしょうがない」
I think I'm getting more and more convinced that everyone should be running rack-mini-profiler in production (enabled for "admins"). APMs (New Relic + etc) are just not enough information.
— Nate Berkopec (@nateberkopec) August 25, 2020
つっつきボイス:「なるほど、例のrack-mini-profiler↓をproduction環境で動かすべきと」「APMツールでうんと詳細な情報を取ろうと思ったらrack-mini-profilerを動かしておかないと取れませんし」
「rack-mini-profilerをすべてのインスタンスで動かすと全体のパフォーマンスが落ちてしまうので、全体のうち数パーセントのインスタンスだけでrack-mini-profilerを走らせてそこだけAPMで取るというのもよく行われますね」「なるほど!」「New Relicが高いので💸、5つのインスタンスのうち1つだけで取るなんてことも割とやりますし」
参考: APM(アプリケーション性能管理)ツール7選 | ニーズが高まる理由・重要性を解説 - アプリケーション性能管理 | ボクシルマガジン
前編は以上です。
バックナンバー(2020年度第3四半期)
週刊Railsウォッチ(20200825後編)Rubyクラスライブラリをgem化、Rubyテストフレームワークrr、ChromebookでWindowsが動くほか
- 20200824前編 「Active Jobスタイルガイド」は有用、SiderがGitLabに対応、eager loading時のselectを修正ほか
- 20200818後編 ruby_jardデバッガがスゴい、RubyオンラインマニュアルにEdit機能が追加、Ruby 2.7のBundlerを消す方法ほか
- 20200817前編 お盆も続くRails改修、Rails 6.1にManyモナドが入る?rails-auth gemでクライアント認証ほか
- 20200811山の日短縮版 RSpec Queueでパラレルテスト、カロリーメイトとRubyのコラボ、Rubyのcoercionほか
- 20200804後編 「RubyKaigi Takeout 2020」9月オンライン開催、メールバリデータtruemail、Gitのmasterが変更可能にほか
- 20200803前編 書籍『パーフェクトRuby on Rails』増補改訂版、マルチDBで抽象クラスをscaffold生成、GitLabがPumaに乗り換えほか
- 20200721後編 『パーフェクトRuby on Rails』増補改訂版発売間近、scan_left gemでレイジーなinjectほか
- 20200720前編 10月開催「Kaigi on Rails」CFP募集中、enumにデフォルト値設定機能、RailsでBitemporal Data Modelほか
- 20200714後編 ruby-warning gemでワーニングを手軽に抑制、rubocop -aの振る舞いが変わる、書籍『MySQL徹底入門 第4版』ほか
- 20200713前編 rspec-openapiでスキーマ自動生成、Rails Architect Conf動画、
where()
ハッシュキーに比較演算子条件を書ける機能ほか - 20200707後編 Rubyで無名structリテラル提案、書籍『AWS認定ソリューションアーキテクト』、21世紀のC言語ほか
- 20200706前編 Railsでのマルチテナンシー実装戦略を比較、Railsでサブクエリを使う、URI.parserが非推奨化ほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。