- Ruby / Rails関連
週刊Railsウォッチ(20201116前編)6.1のActive Storageでimage_processing gemが必須に、Webアプリ設計の変遷とフロントエンド領域の再定義ほか
こんにちは、hachi8833です。
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇
⚓Rails: 先週の改修(Rails公式ニュースより)
以下のコミットリストのChangelogを中心に見繕いました。ドキュメントの更新が増えているようです。
- コミットリスト: Comparing @{2020-11-05}...master@{2020-11-12} · rails/rails
- マイルストーン: 6.1.0 Milestone(83%、22件オープン)
⚓ PredicateBuilder
で委譲をサポート
- PR: Support delegators in query Predicate building by seanpdoyle · Pull Request #40554 · rails/rails
このコミットより前は、
where(id: user)
やwhere(author: user)
オブジェクトを値として指定するクエリがシリアライズに成功するのは、値(この例ではuser
)がActiveRecord::Base
の子孫のインスタンスである場合だけだった。
(SimpleDelegatorやdelegate_missing_to
やその他の方法で)デコレーターを用いるアプリケーションでは、この変換が必須なのが少々つらくなることがある。
このコミットはPredicateBuilder
を変更して、基本となる比較条件を#respond_to?
呼び出しに置き換える。
同PRより大意
つっつきボイス:「where(id: user)
やwhere(author: user)
のようにwhere()
にActive Recordオブジェクトを渡すことができるんですが、デコレーターを通す場合は変換しないといけなかったのか」
「Changelogのコード例↓はいわゆるPORO(Plain Old Ruby Object)で、delegate_missing_to :@author
を書いておけばその後のPost.where(author: AdminAuthor.new(author))
が動くように変更されたのね」
#id
と主キーを委譲するオブジェクトを用いてpredicate条件をビルドする。
Changelogより
# Changelogより
class AdminAuthor
delegate_missing_to :@author
def initialize(author)
@author = author
end
end
Post.where(author: AdminAuthor.new(author))
参考: ruby - How can I determine if an object is a PORO or not? - Stack Overflow
「変更部分を見ると↓、修正前のif value.is_a?(Base)
はActive Recordの場合だけ処理を進めるけど、value.respond_to?(:id)
に変えたことで#id
メソッドを持っているかどうかでチェックするようになった」「なるほど、Active Record以外のオブジェクトでも#id
メソッドがあれば動くんですね」「どのぐらい使われるかはわかりませんが、やりたいことは見えてきました」
# activerecord/lib/active_record/relation/predicate_builder.rb#L61
def build(attribute, value, operator = nil)
- value = value.id if value.is_a?(Base)
+ value = value.id if value.respond_to?(:id)
if operator ||= table.type(attribute.name).force_equality?(value) && :eq
bind = build_bind_attribute(attribute.name, value)
attribute.public_send(operator, bind)
else
handler_for(value).call(attribute, value)
end
end
⚓ has_one :through
でコンストラクタを使えるようになった
これに関する参考情報はガイドやドキュメントに見つからなかったが、
has_one :through
関連付けの関連付けコンストラクタが無効になっているように見える。自分は単にこれをテストで有効にしたところ、何も壊れなかったので、これを無効にすべき理由が何なのかを考えている。そのあたりがわかる情報をもらえると嬉しい。また、無効にすべき十分な理由がある、この変更に重要性がない、または不要なリスクがあるならば、ドキュメントを更新してhas_one :through
ではbuild_association
やcreate_association
を利用できないことを反映すべき(もしそうならこのPRの代わりに自分がドキュメントを変更してもよい)。
同PRより大意
つっつきボイス:「今まではhas_one :through
のときにbuild_association
やcreate_association
などのコンストラクタが使えなかったのか」「今までできなかった理由が謎」「使ってはいけないという記述がドキュメントに見当たらなかったので実装したそうです」
「テストコードでもコンストラクタのビルドをチェックしている↓」
# activerecord/test/cases/associations/has_one_through_associations_test.rb#L71
+ def test_association_build_constructor_builds_through_record
+ new_member = Member.create(name: "Chris")
+ new_club = new_member.build_club
+ assert new_member.current_membership
+ assert_equal new_club, new_member.club
+ assert_predicate new_club, :new_record?
+ assert_predicate new_member.current_membership, :new_record?
+ assert new_member.save
+ assert_predicate new_club, :persisted?
+ assert_predicate new_member.current_membership, :persisted?
+ end
「なるほど、calculate_constructable
メソッドで:through
を止めてたのね↓」「:through
の場合はコンストラクタブルにならないようにわざわざチェックしてたとは」「ホントだ」「このメソッドがあった理由は謎ですが、消したらできるようになったのでプルリクしたんでしょうね」
# activerecord/lib/active_record/reflection.rb#L693
-
- private
- def calculate_constructable(macro, options)
- !options[:through]
- end
「has_one :through
はあまり書かなさそうだけど、使いたいときに使えるようになったのはいいと思います」
⚓ 開発時のルーティングをリロードフックの後でappend
するようになった
append
したrootルーティングが内部のwelcomeコントローラより優先されるようにする。
自分が解決しようとしているこの特定のユースケースは「rootルーティングがないアプリ」と「rootルーティングをappend
するエンジン」の組み合わせである。ルーティングファイルは初期段階ではここで読み込まれるが、welcomeコントローラはここで追加されているらしい。これはつまり、現在はビルトインのルーティングがadd_builtin_route
で追加されるより「前に」実行されるエンジンイニシャライザを作成しないといけないということになる。このパッチによって、ビルトインのルーティングはルーティングファイルが評価された「後で」アプリケーションに追加されるようになる。
同PRより大意
つっつきボイス:「welcomeコントローラは、Railsのdevelopmentモードでデフォルト表示される例のWelcome画面を出すヤツですね」
「なるほど、developmentモードでビルトインのルーティング処理を移動してroutes_reloader.execute
を追加している↓」「Railsエンジンとの兼ね合いでデフォルトのrootルーティングの読み込み順がうまく設定できなかったのを、ユーザー定義のルーティングを先に設定してからビルトインのルーティングを設定するように修正したらしい」「append
したrootがそれによって内部のwelcomeコントローラより優先されるようになったんですね」
# railties/lib/rails/application/finisher.rb#L198 (移動前との差分を表示しています)
initializer :add_builtin_route do |app|
if Rails.env.development?
app.routes.prepend do
get "/rails/info/properties" => "rails/info#properties", internal: true
get "/rails/info/routes" => "rails/info#routes", internal: true
get "/rails/info" => "rails/info#index", internal: true
end
app.routes.append do
get "/" => "rails/welcome#index", internal: true
end
+ routes_reloader.execute
end
end
「たしかRailsのルーティングは先に読み込まれたものが有効になったと思うので、それも関連していたのかもしれませんね」
Railsのルーティングは、ルーティングファイルの「上からの記載順に」マッチします。このため、たとえば
resources :photos
というルーティングがget 'photos/poll'
よりも前の行にあれば、resources
行のshow
アクションがget
行の記述よりも優先されますので、get
行のルーティングは有効になりません。これを修正するには、get
行をresources
行よりも上の行に移動してください。これにより、get
行がマッチするようになります。
Railsガイドより
⚓ アップグレードガイドにimage_processing
セクションを追加
Active Storageで
image_processing
gemが必須になったActive Storageでvariantsを処理するときに、
mini_magick
を直接用いるのではなくimage_processing gemをバンドルすることが必須になりました。image_processingはデフォルトでmini_magick
を背後で用いるよう構成されるので、アップグレードは(訳注: Gemfileの)mini_magick
gemを単にimage_processing
gemに置き換えて、既に不要になったcombine_options
が明示的に使われているのを削除しておくのが最も簡単です。
ただし、素のresize
呼び出しをimage_processing
のマクロに変更することが推奨されます(リサイズ後のサムネイル画像もシャープになるので)。
video.preview(resize: "100x100")
video.preview(resize: "100x100>")
video.preview(resize: "100x100^")
たとえば上をそれぞれ以下のように置き換えます。
video.preview(resize_to_fit: [100, 100])
video.preview(resize_to_limit: [100, 100])
video.preview(resize_to_fill: [100, 100])
同PRより大意
つっつきボイス:「アップグレードガイドに追加された注意書きです」「以前Railsに導入されたimage_processing gemが6.1からは必須になったのか(ウォッチ20180511)」「これまではmini_magickかimage_processingのどちらかを使えばいいという感じだった気がします」「今後はmini_magickを使いたければimage_processing経由で使ってくれということか」「これまでmini_magickを素で使ってた人は修正が必要になりますね」
⚓Rails
⚓ onkさんの「Smart UIパターン」記事
設計ナイトで高ぶった結果1時間コースの発表資料が完成したので供養場所を探しています。聞いてくれ!!!
— Takafumi ONAKA (@onk) November 1, 2020
つっつきボイス:「はてブで大バズリしていたonkさんの記事がRailsにも言及していたのでピックアップしました」「そういえば最近のTwitterでテーブルデータゲートウェイの話題を見かけた気がするけど、もしかするとこの記事がきっかけだったのかも🤔」
参考: テーブルデータゲートウェイ
「記事にも登場している『エンタープライズアプリケーションアーキテクチャパターン』(通称PoEAA)↓はやっぱり名著」「記事でもPoEAAに登場したトランザクションスクリプトやアクティブレコードやデータマッパーなどのいろんなパターンをおさらいしていますね」
「そしてRailsの密結合設計の話と@_yasaichiさんの名スライド『Ruby on Railsの正体と向き合い方も扱われていますね(ウォッチ20190401)』」「Railsは最短期間で開発できる代わりにその構造のまま大きくなるとすごく大変になる、はそのとおりだと思います」「Railsほどのラピッド開発を他のフレームワークでやるのは大変そう」
「記事にある『本当に複雑なものと、複雑ではあるが工夫で対処できるもの』という見出しはわかりやすい切り口ですね」「おぉ」「本質的に複雑なものもあるけど、複雑なりに対処できるものもあるという話」「おや、TechRacho記事も参照されている」「はい、それでこの記事に気づきました」
本当に複雑なものと、複雑ではあるが工夫で対処できるもの
同記事見出しより
「そして複雑さに対処する方法がいくつかリストアップされていますね: TrailblazerのOperationや、HanamiのInteractorなど」「rectifyは見たことなかった」
「その次の『同機能の CUI コマンドを作るときに重複が無い』のパラグラフ↓もいいですね: 言い換えるとコントローラがコントローラの役割だけを担うようになっている」「なるほど」
Web アプリケーションとしてのインタフェース (View 以外) が Controller の役目で、「同機能の CUI コマンドを作るときに重複が無い」という基準で考えると良い。
同記事より
「『Controller or Action と 1:1 対応する新しい層』という考え方も流行りましたね: この場合ファイル数がものすごく増えてしまうんですが、そこはもう仕方がない」
「System on Record(SoR)とSystem of Engagement(SoE)という考えも見たことある↓」
「『SoEの戦場がクライアント側に移っている』もわかる: ユーザーとのインタラクション処理をフロントエンド側に移して、そこでGraphQLのインターフェイスが使えればいいという感じでやることが増えているように思います: その分SoRの複雑さについては引き続きサーバーサイドエンジニアが面倒を見る必要はあるでしょうね」
「この記事も含めて、最近の設計関連記事の多くが似たような結論に達しているのが面白いですね: たとえばActive Recordはあまりにもよくできていて、小さい問題を解決するときには非常によい、とか」
「その一方でフロントエンドでGraphQLを使うと割と手軽に同じようなこともできるようになりつつありますね: もちろんActive Recordのようにサーバーサイドでないと解決できないことは依然としてありますが」「従来だとサーバーサイドでAPIを作らないといけなかったのが、GraphQLによってフロントエンジニアがサーバーサイドエンジニアを煩わせずにやれる部分が増えてきたように思います」「それが主戦場がクライアント側に移ったということですね」
「逆にサーバーサイドでないとできないことは何かというと、データの永続化や、複雑なドメインロジックの取り扱いのような、ブラックボックスの中をいじる部分」「たしかに」
「フロントエンド側でやれることが増えてきた分、サーバーサイドエンジニアは今後そうしたブラックボックスの中というか下のレイヤに注力する方が今後生き残りやすいのかもしれないとちょっと思いました」「考えさせらる...」
「onkさんの記事、設計方法の変遷を見渡すことができて素晴らしい👍」「ありがたいです🙏」
⚓ スライド『「フロントエンド領域」を再定義する』もチェックしておきたい
「そういえばつい最近mizchiさんもフロントエンドエンジニアの職務範囲についてスライドを発表していましたね」「あ、これですか↓」「そうそう、ここで説明されているフロントエンドの変遷もいろいろ納得がいきました👍」
「従来のモノリシックなフレームワークでは↓、サーバーサイドエンジニアがJSやHTMLまでカバーしていて、マークアップエンジニアがいなければ上から下まで全部やっていた」「とても見やすい図ですね」「Railsの推奨構成は基本的にこれ」
「やがてフロントエンドフレームワークが使われるようになると↓、サーバーは永続化とAPIに集中するようになってくるけど、SSR(Server Side Rendering)をやろうとするとサーバー側かフロントエンド側のどちらかが相手側に越境しないといけなくなってつらくなってきた」「ふむふむ」
参考: 【動画付き】Next.js の Server Side Rendering (SSR) を理解する。create-react-app と比べてみた。 - Qiita
「スライドではそこから更に進んで以下のようにフロントエンドフレームワークがSSRを扱う(つまりサーバー側のレンダリングをフロントエンドで行う)ようになっている↓: そうなるとサーバー側はますますバックエンドAPIに徹するようになってきて、基本的な画面もサーバー側では扱わなくなってきたりする」「最近のフル装備フロントエンドはこんな構成なのか」「フロントエンドの職務範囲が広がってきているんですね」
「先のことはわかりませんが、将来こんなふうにフロントエンドがフルスタックになったらどうなるんだろう↓」「ここまでくると何だか逆方向に振り切って最初のレガシー図に近い形に戻ってませんか?」「結局そういうことですよね😆」「Twitterでも指摘してた人を見ました」「歴史は繰り返しつつあるのかも」
「上のようにフルスタックのフロントエンド構成になったら、APIのインターフェイス部分についてもフロントエンドが書くことになりますが、使うのはフロントエンド側なのでそこは自分たちが使いやすいように書けばいいと思います」「ただ、そのAPIの内部で使うドメインロジックについてはサーバーサイドのブラックボックスの中にしか書けないものもまだまだあるので、フロントエンドと通信するAPIのさらに裏にそのための「内部API」的なものが引き続き必要でしょうね」「APIのAPIという感じですね」「その部分がおそらくonkさんの記事で言う『本当に複雑なもの』に相当するかもしれないと最近思っています」
「少なくともサーバーサイドの仕事がいきなりなくなることはないんじゃないかなと想像してます」「現実にはサーバーサイドエンジニアも多少なりともフロントエンドを触ることになるでしょうね」「ただRailsの位置づけがバックエンドAPI中心になることが増えているのは感じています」「少々極端な想像ですが、たとえば今後Railsサーバーがフロントエンドと従来のような直接のWeb API通信をしなくなって、gRPCのようなものを介して通信する一種のドメインロジック用内部サーバーのようなものになる、という形も一応考えられますね(なお今のRailsはそういうものをまったく目指していないと思います)」
参考: サービス間通信のための新技術「gRPC」入門 | さくらのナレッジ
「Railsフレームワークの立ち位置は、ある意味でCSSフレームワークにおけるBootstrapと少し似ているかもしれませんね: プロダクトが成功してエンジニアリソースを多く割り当てられるようになり、継続開発期間が長くなるに連れて、だんだんBootstrapを必要としなくなったり好みが分かれたりすることもあるけど、プロトタイプを最初に作るときはやっぱりBootstrapが早いし便利なので今も使われ続けている、それと似たような立ち位置」「なるほど」
つっつき後に、「週刊気になったITニュース」↓でも「フロントエンド領域を再定義する」スライドが取り上げられていたことに気づきました。
参考: 週刊気になったITニュース(2020/11/15号) - masa寿司の日記
⚓ Railsビューのパーシャルがパフォーマンスに与える影響(Ruby Weeklyより)
つっつきボイス:「ビューのパーシャルがパフォーマンスに与える影響の記事は周期的に見かけますね: パーシャルをループで回したりするよりコレクションを使ったりインライン展開したりする方が速いとか↓」「定番のお題ですね」
「Railsのビュー周りは折に触れてパフォーマンスが改善されているので昔と違う部分があるかも」「定番の話ではありますがパフォーマンスへの影響が大きい部分なので、最新のRailsの実装で測定結果がどう変わっているかを知るうえでこういう記事を定期的にチェックしておくとよいと思います」「たとえばERBの速度が大きく向上したら、従来だと10倍の差だったのが20倍になったりするようなことがあるかもしれませんよね」
⚓ Railsから切り出されたgem完全ガイド
切り出されたgemがshimと呼ばれているとは知りませんでした。
つっつきボイス:「Railsからいろんな機能がgemに切り出されている」「こういう歴史的な記事はありがたい」「Rails 4.2で切り出されたgemが割と多いのか」「そうしておかないとRailsがいくらでも大きくなってしまいますね」
「shimといえばrbenvの設定にshimsディレクトリがありますね」「関連記事にもrbenvのことが書かれてる↓」「ホントだ」
参考: What is a shim?. A shim is a small library that… | by Ujjawal Dixit | Medium
⚓ RailsのトップFAQ 12件(Hacklinesより)
つっつきボイス:「質問の内容はとても基本的なんですが、頻度の高い質問に答えている記事です」「お〜、何て丁寧な回答!」「マイグレーションのロールバックとか、RubyやRailsのnil
とempty
とblank
の違いなどにみっちり答えているのがスゴい👍」「Railsにまだ慣れてない人にとって便利そう」
「と思ったら質問は12件なのに回答は5件しかない...?」「あれれ、ホントだ」「今後追加するのかな?」
後で気が付きましたが、元記事の末尾からリンクされていた以下の記事に12件のQ&Aがすべて載っていました。少々わかりにくいですね。
前編は以上です。
バックナンバー(2020年度第4四半期)
週刊Railsウォッチ(20201111後編)RubyConf 2020が11/17〜19オンライン開催、GitHub Container Registryベータ開始、スマートロックほか
- 20201110前編 Rails 6.1 RC1がリリース、Railsアプリに最適なEC2インスタンスタイプ、n_plus_one_control gemほか
- 20201028後編 RuboCop 1.0.0 stable版がリリース、Ruby DSLのGUIフレームワークGlimmer、Keycloakほか
- 20201026前編 Shopifyのerb-lint gem、Form Objectを使いやすくするyaaf gem、railsrcの機能追加ほか
- 20201021後編 webpack 5リリースでWebpacker対応開始、AWS Lambda Extensions発表、Pythonにマクロ構文追加提案ほか
- 20201020前編 Percona Toolkitは優秀、Active Admin非公式ガイド、Railsをリアクティブにするガイドほか
- 20201013後編 ruby-type-profilerがtypeprofにリネーム、AWS API Gatewayの実行ログは便利、M5Stackほか
- 20201012前編 Railsの隠し機能routing visualizer、action_args gem、N+1用goldiloader gemほか
- 20201006後編 Rubyの
defined?
キーワード、Ractorベースのジョブスケジューラ、Caddy Webサーバーほか - 20201005前編 Ruby 2.7.2がリリース、Shopifyのモジュラー化gem「packwerk」、stimulus_reflexほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。