この記事は第231回 Okinawa.rb MeetupでLTした内容を元にしています。発表の機会をいただき、ありがとうございました!
Railsらしさ(Rails way)を学ぶ方法として以下のリポジトリが参考になるよ、という紹介記事です。
きっかけ
Ruby on Railsを久しぶり(Rails 5.2以来)に触ることになり、学び直しとして特に「Railsっぽさ」を知れるものを探していました。
そんな中見つけたのが前述の https://github.com/solid-process/rails-way-app です。
solid-process/rails-way-app とはなにか
READMEにあるように
The project's main objective is to demonstrate different approaches to improving the design of a Rails application without compromising its conventions and structure.
どういったアプローチでコードを構成するかを提示することでRailsの複雑さへの対応方法を紹介しています。
サンプルアプリはユーザ認証のあるTODOリストのHTMLを返す部分とJSONを返す部分の2種類を持つWebアプリケーションです。
ブランチごとにステップがあり、数字が小さい順から大きくなるにつれて作者の提案するRailsらしさを段階的に知ることができます。
ステップを踏むことでコードへのアプローチをより理解しやすくなっていると思いました。
ブランチがどういった状態か、どういったアプローチを取っているのかについては rails-way-app/docs/03_BRANCH_DESCRIPTIONS.md で概要を把握することが可能です。
リポジトリ作者の意見に基づくものなので、必ずしも「このアプローチがRailsらしさである」とは言い切れませんが、考え方としてしっくりくるなと僕は思いました。
個人的に面白かったところ
050-separation-of-entry-points
こちらで紹介されているとおり、「HTMLを返却するコントローラーとJSONを返却するコントローラーを分ける」というアプローチです。
scope module: :web, defaults: { format: "html" }, constraints: { format: "html" } do namespace :user do resource :session, only: [ :destroy ] resource :registration, only: [ :destroy ] resources :sessions, only: [ :new, :create ] resources :passwords, only: [ :new, :create, :edit, :update ] resources :registrations, only: [ :new, :create ] namespace :settings do resource :profile, only: [ :edit, :update ] resource :token, only: [ :edit, :update ] end end namespace :task do resources :lists do resources :items namespace :items do resources :complete, only: [ :update ] resources :incomplete, only: [ :update ] end end end end namespace :api, defaults: { format: "json" }, constraints: { format: "json" } do namespace :v1 do namespace :user do resource :token, only: [ :update ] resource :password, only: [ :update ] resource :registration, only: [ :destroy ] resources :sessions, only: [ :create ] resources :registrations, only: [ :create ] namespace :passwords do resource :resetting, only: [ :create, :update ] end end namespace :task do resources :lists, except: [ :new, :edit ] do resources :items, except: [ :new, :edit ] namespace :items do resources :complete, only: [ :update ] resources :incomplete, only: [ :update ] end end end end end
似たようなresourceの定義を用意し、 defaults: { format: "html" }, constraints: { format: "html" } と defaults: { format: "json" }, constraints: { format: "json" } で出し分ける方法を取っています。
よくあるのはコントローラーの中で respond_toのブロックを使って
respond_to do |format| format.html format.json end
で出し分ける方法ですが、このコントローラーを分けるアプローチは面白いと思いました。
051-separation-of-entry-points_fat-models
上記の場合だと、別々のコントローラーで似たような定義が残ってしまう問題があります。その解消法として Fat Model, つまりモデル側に処理を定義し、コントローラーでそれを呼び出すような形に変更することが対応しましょうとなります。
063-domain-model_user-operations
何百行と続くFat Modelにならないように、POROをつかうことでFat Modelを解消するアプローチの紹介です。ここではユーザの削除処理を例に、 User::AccountDeletion というクラスを定義し、引数として User モデルを受け取ることで処理の責務を分離しています。
このアプローチの紹介文に参考リンクとしてあった
日本語訳
も非常に勉強になりました。
solid-process/rails-way-app 見てみてください
こんな感じで、おもしろいリポジトリだったのでよかったら見てみてください!