背景
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>
まとめ
モデルから責務を分離できるのは便利ですね。
大規模なフォームなどで積極的に活用していきたい。