Railsらしさを学ぶ「solid-process/rails-way-app」の紹介

この記事は第231回 Okinawa.rb MeetupでLTした内容を元にしています。発表の機会をいただき、ありがとうございました!

Railsらしさ(Rails way)を学ぶ方法として以下のリポジトリが参考になるよ、という紹介記事です。

github.com

きっかけ

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

リンク

前のステップとのDiff

こちらで紹介されているとおり、「HTMLを返却するコントローラーとJSONを返却するコントローラーを分ける」というアプローチです。

routes.rbをみる

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

リンク

前のステップとのDiff

上記の場合だと、別々のコントローラーで似たような定義が残ってしまう問題があります。その解消法として Fat Model, つまりモデル側に処理を定義し、コントローラーでそれを呼び出すような形に変更することが対応しましょうとなります。

063-domain-model_user-operations

リンク

前のステップとのDiff

何百行と続くFat Modelにならないように、POROをつかうことでFat Modelを解消するアプローチの紹介です。ここではユーザの削除処理を例に、 User::AccountDeletion というクラスを定義し、引数として User モデルを受け取ることで処理の責務を分離しています。

このアプローチの紹介文に参考リンクとしてあった

dev.37signals.com

日本語訳

techracho.bpsinc.jp

も非常に勉強になりました。

solid-process/rails-way-app 見てみてください

こんな感じで、おもしろいリポジトリだったのでよかったら見てみてください!