FormObjectを用いた検索機能
FormObject
form_withのmodelオプションにActiveRecord以外のオブジェクトを渡す時(複数のモデルを扱うときなど)のデザインパターン。ActiveModelをインクルードすることで実現。
- DBを使わないフォームでも、ActiveRecordを利用した場合と同じお作法を利用できるので可読性が増す
- 他の箇所に分散されがちなロジックをform object内に集めることができ、凝集度を高められる
ActiveModel
ActiveRecordからDBに依存する部分を除いた振る舞いを提供しているライブラリ。ActiveRecordを継承しないクラスでもActiveRecordと同じようなメソッドが使えるようになる
include ActiveModel::Attributes attribute :title, :string # attributeメソッド(属性の型変換)が使える # 文字列をモデルの期待しているデータ型に変換するメソッド extend ActiveModel::Callbacks before_update :reset_me # コールバックができる # extendは当該クラスのクラスメソッドとして追加(includeはインスタンスメソッドとして追加) include ActiveModel::Model # これひとつで以下のモジュールが使えるようになる # ActiveModel::Validations バリデーションを可能にする # ActiveModel::Translation 翻訳:human_attribute_nameを使用可能にする # ActiveModel::Naming モデル名の便利メソッド:model_nameを使用可能にする # など
ActiveModelによるFormObjectの実装例(書籍の検索)
FormObjectのクラス SearchBooksFormを定義。
# app/forms/search_books_form.rb class SearchBooksForm include ActiveModel::Model include ActiveModel::Attributes attribute :author_id, :integer attribute :category_id, :integer attribute :title, :string def search books = Book.all books = books.where(article_id: self.article_id) # selfは省略可 books = books.where(category_id: self.category_id) self.title.split(nil).each do |word| books = books.where('title LIKE ?', "%#{word}%") end books end
検索フォームではmodelオプションでSearchBooksFormのインスタンスを渡し、scopeオブションとurlオプションでbooks_controllerにparams[:q]が送られるようにする。
<%= form_with model: @search_articles_form, scope: :q, url:books_path, method: :get do |f| %> <%= f.text_field :title %>
コントローラ
# books_controller.rb def index @search_articles_form = SearchArticlesForm.new(search_params) # 検索ワードのattributesを持ったSearchBooksFormインスタンスを定義 @articles = @search_articles_form.search # 専用のsearchメソッドにより検索 end def search_params params[:q]&.permit(:title, :category_id, :author_id, :body, :tag_id) end
formobjectに属性値として検索ワードを渡し、searchメソッド内でそれらの属性値にアクセスし、レコードを絞り込んでいる。