ActiveModelとFormObject
[ActiveModelとFormObject]
ActiveModelは、ActiveRecordを継承しないクラスでもActiveRecordと同じメソッド(validatesなど)が使えるようになるもの(ActiveRecordのDBに関係する部分を除いたライブラリ)
ActiveModelはFormObjectで使われることが多い
FormObjectは、form_withのmodelオプションにActive Record以外のオブジェクトを渡すもの
FormObjectを利用するメリットは、以下の2つです
・DBを使わないフォームでもActive Recordを利用した時の[form_with model: ○○]の形式で記述でき、コントローラの中の記述も同じような記述で記載できるので、可読性を高くできる
・モデルと1対1で紐づかないようなフォームで、他の箇所に分散されるロジックをFormObject内に集めることができる
FormObjectでActiveModelを利用した例を以下に示します
[app/views ファイル] <%= form_with model: @search_articles_form, scope: :q, url: admin_articles_path do |f| %> <%= f.select :category_id, Category.pluck(:name, :id) , { include_blank: "カテゴリ" }, class: 'form-control' %> <%= f.select :author_id, Author.pluck(:name, :id), { include_blank: "著者" }, class: 'form-control' %> <%= f.select :tag_id, Tag.pluck(:name, :id), { include_blank: "タグ" }, class: 'form-control' %> <%= f.search_field :body, class: 'form-control', placeholder: '記事内容' %> <%= f.search_field :title, class: 'form-control', placeholder: 'タイトル' %> <%= f.submit '検索', class: "btn btn-default btn-flat" %>
[app/controllers ファイル] def index @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(:category_id, :author_id, :tag_id, :body, :title) end
[app/forms ファイル] include ActiveModel::Model ④ include ActiveModel::Attributes ⑤ attribute :category_id, :integer ⑥ attribute :author_id, :integer attribute :tag_id, :integer attribute :body, :string attribute :title, :string def search ③ relation = Article.distinct title_words.each do |word| relation = relation.title_contain(word) end relation = relation.by_category(category_id) if category_id.present? relation = relation.by_author(author_id) if author_id.present? relation = relation.by_tag(tag_id) if tag_id.present? body_words.each do |word| relation = relation.body_contain(word) end relation end private def title_words title.present? ? title.split(nil) : [] end def body_words body.present? ? body.split(nil) : [] end
[app/models/article.rb] scope :by_category, ->(category_id) { where(category_id: category_id) } scope :title_contain, ->(word) { where('title LIKE ?', "%#{word}%") } scope :by_author, ->(author_id) { where(author_id: author_id) } scope :by_tag, ->(tag_id) { joins(:article_tags).where(article_tags: { tag_id: tag_id }) } scope :body_contain, ->(body) { joins(:sentences).merge(where('sentences.body LIKE ?', "%#{body}%")) }
①でActiveModelのインスタンスを作成(検索条件のインスタンス)
②でActiveModelのインスタンスメソッドを使っている(searchメソッド)
③のsearchメソッドでは、受け取った検索でArticleから検索を行なっている
④は、ActiveModelを使う時に記述する
⑤は、ActiveModelを利用した際のデータの型変換を定義する際に記述(型変換は、⑥で記述している)
⑥は、attributeに属性名と型を渡すことにより、属性が使えるようになる
参考記事:
form objectを使ってみよう - メドピア開発者ブログ
ActiveModel::Attributes が最高すぎるんだよな。 - Qiita
【Rails】 便利なpluckメソッドをマスターしよう! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト
【Ruby on Rails】Active Recordの絞り込みメソッドまとめ | プログラミングマガジン
Formオブジェクト~1つのフォームで複数モデルとやりとりをする画期的なヤツ~ - Qiita
ActiveModel::Attributesを使う - Qiita
【Rails】「ActiveModel::Attributes」が便利という話 - 日々の学びのアウトプットするブログ
【Ruby】配列の中のハッシュの中から、キーワード検索する - Qiita
【Rails】 joinsメソッドのテーブル結合からネストまでの解説書 | Pikawaka - ピカ1わかりやすいプログラミング用語サイト