背景
Formオブジェクトめちゃめちゃ便利だな〜と思ったので、まとめました。
開発環境は「Rails7」です。
Formオブジェクトとは?
FormとModelの役割を分ける時に使用するオブジェクト
Modelに依存しないので、複数のモデルを更新する専用フォームとかが作れる
Formオブジェクトを使うとき
- 1つのフォームで複数のモデルを更新したいとき
 - 複雑なフォームの場合
 
今回のケース
記事と画像(1:多)を投稿するフォームを作成する。
# post.rb
class Post < ApplicationRecord
    belongs_to :user
    has_many :photos, dependent: :destroy
    validates :caption, presence: true
end
# photo.rb
class Photo < ApplicationRecord
    belongs_to :post
    validates :image, presence: true
    mount_uploader :image, ImageUploader    #アップローダー
end
1. フォームオブジェクトの作成
フォームオブジェクトは、 app/formsに作成することが推奨されている
***_form.rb
作成したフォームオブジェクト
Formオブジェクト内に、保存が必要になる、属性・バリデーションを全て定義する
class PostPhotosForm
    include ActiveModel::Model      # モデルの機能を利用するために記載
    include ActiveModel::Attributes # モデルの機能を利用するために記載
    extend CarrierWave::Mount       # アップローダーの使用
    # 属性の定義
    attribute :caption, :string     # Post
    attribute :user_id, :integer    # Post
    attribute :image, :string       # Image
    # Carrier Wave アップローダー
    mount_uploader :image, ImageUploader
    # バリデーション
    validates :caption, presence: true
    validates :image, presence: true
    validates :user_id, presence: true
    # レコードの保存
    def save
        # bool値を返す
        return false if invalid?
        # トランザクション処理
        ActiveRecord::Base.transaction do
            post = Post.create!(caption: caption, user_id: user_id)
            post.photos.build(image: image).save!
        end
    end
end
2. ビューの作成
Formオブジェクトのインスタンスを指定
 <%= form_with(model: @post_photos, url: posts_path, local: true, data:{turbo: false}) do |f| %>
  <%= f.label :caption %>
  <%= f.text_field :caption %>
  <%= f.file_field :image %>
  <%= f.submit "投稿", class: "btn btn-primary" %>
<% end %> 
3. コントローラー
ストロングパラメータを定義
class PostsController < ApplicationController
    before_action :authenticate_user!
    # 記事の作成画面
    def new
        @post_photos = PostPhotosForm.new
    end
    # 記事作成処理
    def create
        @post_photos = PostPhotosForm.new(post_photos_params)
        if @post_photos.save
            redirect_to root_path, flash: {notice: '投稿が保存されました'}
        else
            flash[:alert] = "投稿に失敗しました"
            render :new, status: :unprocessable_entity
        end
    end
    private
    # PostPhotosForm ストロングパラメータ
    def post_photos_params
        params.require(:post_photos_form).permit(:caption, :image).merge(user_id: current_user.id)
    end
end
補足(バリデーションメッセージ)
通常のモデルと同様の記載方法
<ul>
    <% @post_photos.errors.full_messages.each do |error| %>
      <li><%= error %></li>
    <% end %>
</ul> 
まとめ
モデルから責務を分離できるのは便利ですね。
大規模なフォームなどで積極的に活用していきたい。