FormObject
FormObjectについて学んだのでまとめてみます。
FormObjectとは
form_withのmodelにActiveRecord以外のオブジェクトを渡すデザインパターン(設計手法)です。
メリットとしては
・DBを使わないフォームでも、ActiveRecordを利用した場合と同じお作法を利用できるので可読性が増す
・他の箇所に分散されがちなロジックをFormObject内に集めることができる
などがあるようです。
RailsでDBのレコードにアクセスする際、生のSQLではなくActiveRecordが提供するクエリメソッドを使います。
User.all
とかUser.find(1)
とかですね。
これはモデルクラスがApplicationRecordを、ApplicationRecordがActiveRecord::Baseを継承しているから使えるものです。
ではモデルに紐づかないクラス、つまりActiveRecordを継承しないクラスを作りたい時はどうするか。
DBに保存する必要はないがActiveRecordの機能は使いたい、という場合にどうするか、記事の検索フォームを例に見てみます。
検索フォーム
# view = form_with model: @search_articles_form, scope: :q, url: admin_articles_path, method: :get, html: { class: 'form-inline' } do |f| => f.select :author_id, Author.pluck(:name, :id) , { include_blank: '著者' }, class: 'form-control' .input-group = f.search_field :title, class: 'form-control', placeholder: 'タイトル'
# controller def index authorize(Article) @search_articles_form = SearchArticlesForm.new(search_params) @articles = @search_articles_form.search.order(id: :desc).page(params[:page]).per(25) end private def search_params params[:q]&.permit(:title, :author_id) end
検索のロジックを書くapp/forms/search_article_form.rbをいうファイルを作成し、その中でActiveModel::Modelをincludeします。
コントローラーから切り出すことでFatControllerを防ぎます。
class SearchArticlesForm include ActiveModel::Model # これ include ActiveModel::Attributes attribute :author_id, :integer attribute :title, :string def search relation = Article.distinct relation = relation.by_author(author_id) if author_id.present? title_words.each do |word| relation = relation.title_contain(word) end relation end private def title_words title.present? ? title.split(nil) : [] end end
# model class Article < ApplicationRecord # 略 scope :by_author, ->(author_id) { where(author_id: author_id) } # 略
ActiveModel
はActiveRecordの中でデータベースに関連する部分以外の機能を切り出したモジュールです。
これをincludeすることでActiveRecordを継承していないクラスでもActiveRecordの便利な機能が使えるようになります。
また、include ActiveModel::Attributes
とありますがこれはActiveRecordにあるAttribute APIという機能を使うためのものです。
Attribute APIはActiveRecordを操作する際にクラス属性の型を意識しなくても、指定の型へ変換してくれています。
このようにid、created_atを文字列で渡しましたが型が変換されています。
このActiveModel::Attributesをincludeしてattribute :属性, 型
とすることで属性、型を定義できます。
最後に
FormObjectをうまく使うとコントローラの肥大化を防げそうです。
何でもかんでもコントローラに詰め込まず、上手に切り出して可読性の高いコードを書けるようになりたいです。
参考サイト
form objectを使ってみよう - メドピア開発者ブログ
【Rails】FormObject で Controller を綺麗に - stmn tech blog
ActiveModel とActiveModel::Attributes について - WeBlog