- Ruby / Rails関連
週刊Railsウォッチ(20200302前編)RubyKaigi 2020は9月に延期、Railsのセキュリティパッチバージョニングが変更、dry-monadsほか
こんにちは、hachi8833です。テックカンファレンスの開催状況をまとめてくれた方がいました。ありがとうございます。
つっつきボイス:「こんなの見つけました」「お〜すげ〜😳、半分以上まっかっかじゃないですか🟥」「RubyKaigiは(つっつき時点では)まだ開催予定ですけど予断を許さない感じ...(臨時ニュース参照↓)」
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください
⚓臨時ニュース: RubyKaigi 2020は9月3日〜5日に延期
土曜日に延期のアナウンスがありました。詳しくはアナウンスをどうぞ。宿や交通の取り直しをお忘れなく。
同じ場所で9月に開催できるとこまで段取っての発表すごい。スタッフのみなさん対応ありがとうございます。 https://t.co/9I5HjnoRJQ
— igaiga (@igaiga555) February 29, 2020
⚓Rails: 先週の改修(Rails公式ニュースより)
この週はコミットリストから見繕いました。
⚓strict_loading
を追加
関連付けからのlazy loadingを防ぐ
#strict_loading
を任意のレコードに追加した。
親レコードがstrict_loading
とマークされている状態で関連付けをlazy loadしようとするとエラーを発生する。余分なクエリを出さないよう関連付けをpreload
する場所を見つけるのに便利。
Changelogより大意
# Changelogより: 使い方
>> Developer.strict_loading.first
>> dev.audit_logs.to_a
#=> ActiveRecord::StrictLoadingViolationError: Developer is marked as strict_loading and AuditLog cannot be lazily loaded.
つっつきボイス:「has_manyの方向にアクセスしたときの振る舞いなのかな🤔」「どうなんでしょう😅」「使い方のコード例だとfirst
取った時点でeager loadingされてそうな気がしないでもないけど...ちょい走り書き感😆」「preload
すべき場所を見つけたいときに便利ということみたいです」「このプルリク、久しぶりに👍とか❤️がびっしり押されてるのでかなり喜ばれてる感じ😋」
⚓SchemaCacheでMarshal
オブジェクトからのシリアライズも選べるようになった
# activerecord/lib/active_record/connection_adapters/schema_cache.rb#L139
+ def dump_to(filename)
+ clear!
+ connection.data_sources.each { |table| add(table) }
+ open(filename, "wb") { |f|
+ if filename.end_with?(".dump")
+ f.write(Marshal.dump(self))
+ else
+ f.write(YAML.dump(self))
+ end
+ }
+ end
つっつきボイス:「SchemaCacheってDBのスキーマかな?」「だと思います」「今まではYAMLからdump
していたのがMarshall
からのdump
もできるようになったということか」
このプルリクによって、シリアライズ戦略に
Marshal
とYAML
のどちらかを選べるようになった。スキーマダンプのパスファイル名は、拡張子が.dump
ならMarshal、.yml
ならYAMLで、データベース接続ごとに定義する。デフォルトはYAMLのまま。
同PRより大意
「なるほどこの辺↓で拡張子を見てファイルを読み込んでる」「どうやらMarshalからのダンプは廃止の流れだったのにまたMarshallが入ってきたので、デフォルトはYAMLにしつつMarshalも選べるようにしたようです」「そのためにload_from
とdump_to
を追加したということね😋」
# activerecord/lib/active_record/connection_adapters/schema_cache.rb#L6
+ def self.load_from(filename)
+ return unless File.file?(filename)
+
+ file = File.read(filename)
+ filename.end_with?(".dump") ? Marshal.load(file) : YAML.load(file)
+ end
参考: 【Rails】SwitchPoint利用時にSchemaCacheを設定しSHOW FULL FIELDSを防ぐ - Qiita
SchemaCacheについて
Railsではrake db:schema:cache:dumpを使うことでdb/schema_cache.ymlにテーブルやカラムの情報が書き出されます。
このキャッシュを使うことでActiveRecordが型情報などを特定する手助けになります。
同記事より
⚓ActiveRecord::Base
サブクラスからのconnected_to
呼び出しを禁止
振る舞いは変わっていないが、以前のAPI名だと「そのクラスでだけ接続を切り替えられる」かのようにミスリードする可能性がある。
connected_to
は現在の接続のコンテキスト(ロールなど)を切り替えるものであって、接続そのものを切り替えるのではない。
同PRより大意
# activerecord/lib/active_record/connection_handling.rb#L138
def connected_to(database: nil, role: nil, shard: nil, prevent_writes: false, &blk)
+ raise NotImplementedError, "connected_to can only be called on ActiveRecord::Base" unless self == Base
+
if database
ActiveSupport::Deprecation.warn("The database key in `connected_to` is deprecated. It will be removed in Rails 6.2.0 without replacement.")
end
つっつきボイス:「ActiveRecord::Base
のサブクラスでconnected_to
を呼べなくするそうです」「connected_to
って何するんだっけ?😆」「connects_to
とかconnected_to?
とかよく似た名前のメソッドがあってややこしいです😅」「親でしか許さないとなると継承とは一体何だったのか🤣」「🤣」「このconnected_to
は接続のコンテキストを切り替えるものなのに、接続を切り替えると勘違いされやすかったのね😆」「メソッド名がイマイチなのかも😆」
- API:
connected_to
-- ActiveRecord::ConnectionHandling - API:
connected_to?
-- ActiveRecord::ConnectionHandling - API:
connects_to
-- ActiveRecord::ConnectionHandling
⚓テンプレートレンダリング時のハッシュアロケーションを削減
つっつきボイス:「こちらは引数を修正しつつアロケーションをちょっぴり減らしたそうです」「可読性の向上がメインみたいなのでこれでいいのかも☺️: これでカリカリチューニングで可読性落ちたとかだったら残念ですし」
# activerecord/lib/active_record/railties/collection_cache_association_loading.rb#L4
module Railties # :nodoc:
module CollectionCacheAssociationLoading #:nodoc:
def setup(context, options, as, block)
- @relation = relation_from_options(**options)
+ @relation = nil
+
+ return super unless options[:cached]
+
+ @relation = relation_from_options(options[:partial], options[:collection])
super
end
- def relation_from_options(cached: nil, partial: nil, collection: nil, **_)
- return unless cached
-
+ def relation_from_options(partial, collection)
relation = partial if partial.is_a?(ActiveRecord::Relation)
relation ||= collection if collection.is_a?(ActiveRecord::Relation)
if relation && !relation.loaded?
relation.skip_preloading!
end
end
「ほほぅ、relation_from_options(**options)
をいきなり呼ぶのをやめてるので、**options
の展開のタイミングを変えてますね」「たしかに」「修正後はrelation_from_options(partial, collection)
となってて、前のような雑な**
じゃなくてオプションをきちんと展開するようになってるので、やはり可読性向上だと思います😋」「コメコメを追放して読みやすくしてくれたんですね🎉」
⚓スキーマキャッシュ関連修正
- PR: Use tempfile when writing schema cache by kytrinyx · Pull Request #38565 · rails/rails
- PR: Normalize resetting schema cache version for YAML and Marshal by kytrinyx · Pull Request #38564 · rails/rails
つっつきボイス:「2つともスキーマキャッシュの話なので冒頭の#38432と関連してるっぽい」「このatomic_write
↓っていうファイル関連メソッド初めて知りました」「Active Supportの機能でしたか」
# activerecord/lib/active_record/connection_adapters/schema_cache.rb#L143
def dump_to(filename)
clear!
connection.data_sources.each { |table| add(table) }
- open(filename, "wb") { |f|
+ File.atomic_write(filename) { |f|
if filename.end_with?(".dump")
f.write(Marshal.dump(self))
else
f.write(YAML.dump(self))
end
}
end
「ちなみに自分はRubyのopen
メソッドって雑なのでキライ😆」「😆」「うろ覚えですけどopen
使って外部APIのURLを叩くこともできた気がする」「マジで😅」「open
っていろんな意味になったりするのが怖いので、atomic_write
みたいな書き方にするのはいいことだと思います😋」
Ruby 2.7で調べると、Kernel#open
でURLを開くにはrequire 'open-url'
する必要があるようです。昔は違ったのかも?🤔
require 'open-url'
uri = 'https://ドメイン名/'
uri = URI.parse(uri)
open(uri).read
上でURLにアクセスできましたが、以下のwarningが表示されました。URI.open
かURI#open
だとwarningは出なくなりました。
warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open
⚓新しい属性に対応
以下のHTML属性が追加された:
*allowpaymentrequest
*nomodule
*playsinline
# actionview/lib/action_view/helpers/tag_helper.rb#L15
- BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus autoplay checked
- compact controls declare default defaultchecked
- defaultmuted defaultselected defer disabled
- enabled formnovalidate hidden indeterminate inert
- ismap itemscope loop multiple muted nohref
- noresize noshade novalidate nowrap open
- pauseonexit readonly required reversed scoped
- seamless selected sortable truespeed typemustmatch
- visible).to_set
+ BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
+ autoplay checked compact controls declare default
+ defaultchecked defaultmuted defaultselected defer
+ disabled enabled formnovalidate hidden indeterminate
+ inert ismap itemscope loop multiple muted nohref
+ nomodule noresize noshade novalidate nowrap open
+ pauseonexit playsinline readonly required reversed
+ scoped seamless selected sortable truespeed
+ typemustmatch visible).to_set
つっつきボイス:「また新しい属性が増えたようです」「改修はビューヘルパーだけど、HTMLの属性が増えたって言ってる?えぐい😆」「allowpaymentrequest
ってペイメント系のリクエストに関連してる感じ💰」「BOOLEAN_ATTRIBUTES
には他にもどっちゃりboolean属性入ってるな〜😅」「フレームワークはこういうのに対応しないといけないから大変そう...」
参考: HTML Standard -- whatwg.org
allowpaymentrequest
iframe
要素の中でPaymentRequest
インターフェイスを用いてpaymentリクエストを許可するかどうかを指定nomodule
- module scriptをサポートするuser agentの実行を
script
要素で禁止するかどうかを設定 playsinline
video
要素の動画コンテンツをplaybackエリアで再生するようuser agentにすすめる
⚓Railsのセキュリティアップデートポリシーが変更
# guides/source/maintenance_policy.md#L57
-the x-y-stable branch. For example, a theoretical 1.2.3 security release would
+the x-y-stable branch. For example, a theoretical 1.2.2.1 security release would
つっつきボイス:「Railsのセキュリティメンテナンスポリシーが変わってセキュリティパッチのバージョン番号が4桁構成になったそうです」「えっとメジャーバージョンとマイナーバージョンと、3つ目は何だっけ😆」「えっと、パッチバージョン」
参考: セマンティック バージョニング 2.0.0 | Semantic Versioning
「6.2.1を6.2.2とかにするのはちょいコワイけど、6.2.1.1とかにするならセキュリティだから当てなきゃという感じが伝わってきますね❤️」「たしかに〜」「Railsのバージョンを上げたいわけじゃないけどセキュリティパッチは当てないといけないということはよくありますので☺️」
⚓Rails
⚓Dry-rbのモナドでServiceを改善する
# 同記事より
class Reservation::Create
include Dry::Monads[:result]
def initialize(user:, room:, start_date:, end_date:, notes: nil)
@user = user
@room = room
@start_date = start_date
@end_date = end_date
@notes = notes
end
def call
check_if_room_available
.bind { create_reservation }
.bind(method(:send_notification))
end
private
attr_reader :user, :room, :start_date, :end_date, :notes
def check_if_room_available
Try(ActiveRecord::ActiveRecordError) { existing_reservations.exists? }.to_result.bind do |result|
if result
Failure('The room is not available in requested time range')
else
Success(nil)
end
end
end
def create_reservation
reservation = Reservation.new(
user: user, room: room, start_date: start_date, end_date: end_date, notes: notes
)
if reservation.save
Success(reservation: reservation)
else
Failure('The reservation could not be created')
end
end
def send_notification(reservation:)
NotificationMailer
.notify_room_booked(user: user, reservation: reservation)
.deliver_later
Success(reservation)
end
def existing_reservations
Reservation.where(room: room).in_time_range(start_date: start_date, end_date: end_date)
end
end
つっつきボイス:「kazzさんが最近Railsでパイプラインの実装で悩んでるので、Dry-rbのこの辺の記事が参考になるかと思って今ローカルで雑に翻訳してみました(許可取れたら公開します)」「自分がモナドやるときが来ようとは: 今この記事に沿ってちょっとやってみてるんですけど、Do
でやるのとbindでやるところがやっと見えてきたのでモナドはこれから😆」
「今ボク関数脳🧠」「この記事で使ってるDry-monadsはDry-transactionの後継ということを翻訳してて知りました」「モナドむずい😭」「大学で数学専攻しててもむずいということは相当手ごわいんですね😳」
「そういえばdry-rbのリポジトリにdry-railsというのが作り中なのを見つけました」「dry-rbでRails的なものを作ってみる企画的な感じ☺️」「思い切りWIP😆」
「この記事では『Railsway Oriented Programming』という関数型由来の概念を援用していて、以下のスライドは割と前のですがその解説です」「どれどれ👀: successとfailという2本の線路で考えるところを絵で説明してるのがとてもいいですね👍」「😋」「エラー処理はraise
しちゃえばいいという考え方もあるんですけど、こういう世界だと単純にraise
するわけにいかなくて、かといって普通にやるとif
の嵐になっちゃいますし😢」
参考: Railway Oriented Programming | F# for fun and profit
参考: notes/Railway oriented programming.md at master · yukitos/notes -- 上の日本語版
⚓コント・モナドロジー
「なははは😆、スライドのくまの会話、今の自分に染みるわ〜」「Maybeモナドと『たぶん』が入り混じったりしてて英語圏らしい笑い😆」「モナド簡単でしょ?とかひどい🤣」「『完全に理解した』出た😆」「endfunctorって何関手だったっけ😅」
後で雑に訳を付けてみました。どことなくいしいひさいちを連想するセンスです。
くま: 関数をいくつかチェインしたいんだけど、エラーもキャプチャしたいんだくま。
ブレインズくん: そんなの簡単。モナドでやれます。
くま: モナド何だか難しそうなんだくま。
ブレインズくん: モナドは単に自己関手の圏におけるモノイドですよ。
くま: ⊂( ・∀・)ワケ ( ・∀・)つワカ ⊂( ・∀・)つラン♪
ブレインズくん: 何か問題でも?
くま: 自己関手がわかんないくま。
ブレインズくん: 簡単です。関手というのは圏と圏の準同型のことで、自己関手というのは単に圏自身に写像する関手のことです。
ブレインズくん: ほらこんなにシンプル!
くま: よし完全に理解したくま。
くま: で、ぼくはどうしたらいいくま?
ブレインズくん: モナドを全部理解する必要がないなら、Maybeを使えばいいのでは?
くま: たぶん(maybe)何をくま?
ブレインズくん: Maybeっていうモナド。
くま: だからたぶん(maybe)何て言うモナドくま?
ブレインズくん: だから、"Maybe"っていう、名前の、モナドなの。
くま: 「たぶんそのモナドの名前は...」じゃなかったのかくま?。
ブレインズくん: Maybeのモナドが名前は...ってヨーダみたいなしゃべり方しないでくれる?
くま: ヨーダみたいにしゃべってるのは君の方だくま。
ブレインズくん: とにかく話を戻すと、君に必要なのはきっとMaybe(definitely Maybe)のはず。
くま: つまりDefinitely Maybeなのかくま?
ブレインズくん: ちなみにぼくは『Definitely Maybe』より『<What's the Story> Morning Glory?』の方が好きだけど。
ブレインズくん: 君にはEitherの方が合ってるかも。
くま: 何と何の『どちらか(either)』なのかくま?
ブレインズくん: Eitherはモナド。
くま: モナドと何の『どちらか(either)』なのかくま?
ブレインズくん: だからEitherだけ(just Either)。
くま: つまりJust Eitherって言うのかくま?
※ 『Definitely Maybe』と『<What's the Story> Morning Glory?)』はどちらもオアシスのアルバムタイトル。
ブレインズくん: ああそうじゃなくって!JustはMaybeの一部なの。
くま: じゃJust Maybeって言うくまか?
ブレインズくん: 違う違う、そこはJustって言わないとだめ。JustかNothingだけ(just Nothing)。
くま: Just Nothingくま?でもさっきDefinitely Maybeって言ってなかったくまか?
ブレインズくん: いや今はEitherの話をしてるんですけど。
くま: Just NothingなのかDefinitely Maybeなのかはっきりして欲しいくま。
ブレインズくん: どっちも違います!いいからEither使えっつーの。
くま: (゜∀。)ワヒャヒャヒャヒャヒャヒャ
「あとRedditに『Dry-rbの問題はドキュメントだ』という書き込みを見つけました」「それほんに: APIの仕様もコード付きのユースケースも見当たらなくて、こんなことできるよってふわっと書いてあるぐらい😢」「さっきのブレインズくんの説明と大差なさそう😆」「今欲しいのは業務に近いユースケース😢」「『これらのpredicateはルールによってカプセル化され、述語論理を用いて互いにコンポジションできる。つまり共通の論理演算子を利用してバリデーションスキーマを構築できる』とか書かれても使い方がわからないって書き込みにありますね😅」「その意味までならわかるんですけど😆」
Dry::Structはたまに使う。Dry::StructはVirtusを置き換えるものだと思っていたが、VirtusはミュータブルなのでForm Objectで便利なのにDry::Structはそうではないのが残念。結局Dry::Structはイミュータブルなstructに使っていて、ミュータブルなstructは手作りと(Trailbrazerの)Reformの合わせ技になっている。
Dry::TypesはDry::Structや手作りForm Objectと相性がいいのでしょっちゅう使っている。しかしDry::Typesはバリデーションのライブラリなのか型強制(coercion)ライブラリなのかアサーションのライブラリなのかはっきりしない。型に対する変更の順序が微妙に重要だったりするのもあって、めちゃくちゃ好きというほどではない。Rubyに型システムを追加しようと一人相撲している感もある。
Redditより大意
dry-structのissue #48を見ると「イミュータブルは仕様」とありました。
⚓Railsリンク集
つっつきボイス:「TechRacho記事もいくつか引用されていたQiita記事を見つけました: Railsガイドより先の設計とかの記事リンクだそうです」「ほほぅ〜頑張っていろいろ集めてる😋」
「これにjoker1007さんの例の名記事↓も含めたいですね」「Service ObjectはRailsの標準ではないというのはまあ置いとくとして😆」「あとyasaichiさんの『Railsの正体』スライドも冒頭に置きたいです」「うん、あれは本当にいいスライドですよ❤️」
⚓WebカメラをActive Storageに直結する(Ruby Weeklyより)
# 同記事より
class VideoAttachmentService
class << self
def attach(model, video_path)
model.picture.attach(
io: File.open(video_path),
filename: "player_video_#{unique_string}.webm"
)
end
private def unique_string
SecureRandom.urlsafe_base64(10)
end
end
end
つっつきボイス:「直結だそうです」「直結でがんがんストリーム配信しまくる?すげ〜😆」「記事では最初に画像のアタッチ、次に動画のアタッチやってますね」「既存の何かのプロトコルの焼き直しに見えなくもないですけど😆」「どうなんでしょう😆」
⚓その他Rails
つっつきボイス:「お馴染みt-wadaさんのプレゼンです」「先週の『開発速度と品質』の話にも通じてそう(ウォッチ20200225)」
「中身見る前に言うと、自分の感覚では品質を下げたからといって開発速度が上がるとは限らないですね: カリッカリのところだと確かにトレードオフになるんですけど、特に開発の初期段階は質と開発速度はむしろ比例すると思ってます🧐」「おぉ」「結局、書くのが早い人は品質もいいし、書くのが遅い人に時間あげてもたいてい品質はよくならない😆」「😆」「言い換えると『開発がスタートする前に設計をどこまでしっかり固めてますか』ということ: このスライドは主に開発が進んでからの話みたいですけど☺️」
↓後でスライド見つけました。「システムを設計するための判断力をつける一番の方法は、自分で設計したシステムを長い間メンテすることだ(p59)」で考えさせられました。
参考: デブサミ2020、講演関連資料まとめ:CodeZine(コードジン)
今回は以上です。
バックナンバー(2020年度第1四半期)
週刊Railsウォッチ(20200226後編)dry-rbを使うべき理由、最近のRubyオンライン教材、AWSから乗り換えた話ほか
- 20200217前編 Railsのオプション引数退治、HSTSのデフォルトmax-ageが1年から2年に変更、semantic_logger gemほか
- 20200212後編 Rubyistが解説するUnicodeとUTF-8、Sorbetが速い理由、CSSの歴史、2019年の脆弱性まとめほか
- 20200210前編 Railsのベンチマークジェネレータ、長いバックグラウンドジョブと戦う、Timestamp切り詰めの謎、Open APIツールほか
- 20200204後編 Ruby3.0の他のbreaking change、Rubyのシリアライザ、GitHubのcode ownersほか
- 20200203前編 Railsの各種高速化コミット、OpenAPIの使い所、パンくずリストgem loaf、Railsビュー最適化ほか
- 20200128後編 もう一つのgemマネージャgel、”Did you mean”の仕組みを追う、DXOpalでブラウザゲームほか
- 20200127前編 Railsでキーワード引数warning退治始まる、ライブラリとフレームワークの違い、ShopifyのRails高速化記事ほか
- 20200121後編 RubyKaigi 2020受付開始、RubyGemsとBundlerの今後、ファイル同期ツールMutagenほか
- 20200120前編 福岡でも公開つっつき会、Railsのconnection_specification_nameでprimaryという名前が非推奨に、structure.sqlとschema.rbほか
- 20200115後編 Ruby 2.7関連情報、Bootstrap 5は今年前半リリースか、PostgreSQLでやってはいけないリストほか
- 20200114前編 config_forのbreaking change、Active Storage variantをDBでトラッキング、SprocketsとWebpackの違いほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp Slackなど)です。