こんにちは!私、キラメックスのブートキャンプ事業部で開発をしております鈴木です! 社内では、Railsで新規機能の開発、保守を主にやっていたり、Railsのバージョンアップなども担当しております!
今回は、複雑な条件文を撃退するViewModelを紹介します!
TechAcademyを支えるアプリケーションの特徴
TechAcademyのアプリケーションは下記の特徴があります。
- 非SPAのため、レンダリングは全てサーバー側でRailsが担当(Slimで記述)
- サービスとしてToC向けのTechAcademy、ToB向けの研修用のTechAcademy、転職のTechAcacademyキャリアが1つのレポジトリで管理されているモノリシックなアプリケーション
- ロールが6つほど管理されている
- ToC向けに25コース以上を提供している
TechAcademyの受講システムのホーム画面
TechAcademyの例でいうと、受講システムのホーム画面が特に表示条件が複雑化しています。 具体的にいうと、ToC向けのTechAcademy、ToB向けの研修用のTechAcademyで同じテンプレートを使用しており、各サービスごとで表示するコンテンツが異なるため、表示条件が複雑になっています。 また、Railsでレンダリングされている以上、表示条件をmodel内に実装してしまうケースがふえ、Fat Modelになる傾向になります。 SPAにして、Vueなどのコンポーネント単位でテストが行えれば良いのですが、Slimを採用しているため、 各コンポーネントごとでのテストが行いにくい状態になっています。
ここでViewModelを採用しました
実際のコードは以下です。
- コントローラ(app/controllers/learnings/dashboard_controller.rb)
Class Learnings::DashboardController < ApplicationController def index @progress_view_model = Dashboards::ProgressViewModel.new(course) end end
- ビューモデル(app/view_models/learnings/dashboard_view_model.rb)
Class Dashboards::ProgressViewModel def initialize(course) @course = course end # 研修の場合 def is_training? end # ToCの場合 def is_to_c? end # デザイン系のコースの場合 def is_design? course.is_design? end private attr_reader :course end
- テンプレート(app/views/learnings/dashboards/index.html.slim)
= render partial: 'progress', locals: { progress_view_model: @progress_view_model }
app/views/learnings/dashboards/_progress.html.slim
- if progress_dash_board.is_training? - if progress_dash_board.is_to_c? - if progress_dash_board.is_design?
ポイントとしては、ViewModelの命名はコントローラ + コンポーネント名にすることですかね。
メリットとデメリット
メリットは下記ですね!
- POROのViewModelに全ての条件を閉じ込めることによって、テストが書きやすくなります。
- コンポーネント単位でViewModelを定義することにより、ソースが追いやすい。
- インスタンス変数もViewModelに閉じ込めることができ、管理が煩雑にならない
- 変化が激しいView周りでPOROを採用することにより、Controller内を編集するよりもViewModelを捨てるだけでよいため、変更に追従しやすい
またデメリットはこちら!
- 他のコンポーネントでも同様の条件文があった際に、DRYに書けない可能性がある
- インスタンス変数が各コンポーネントごとに定義すると、無駄なクエリを発行してしまう可能性がある(クエリキャッシュがあれば、特に意識しなくても良いですが)
いかがでしたでしょうか? SPAに移行するには、フロントエンドの学習コストと採用コストがかさむので、まずはミニマムで試したい方は是非!
一緒に教育サービスを提供していきたい方がいれば、是非こちらから!