アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Railsで #update のできるFormObjectを作る

最近FormObjectを作ろうと思って調べてみたところ、ネット上の記事だと #update ができなかったり、Ruby 3.0 だとそのまま動かなかったりしたので、需要があるかと思い作成しました。

まだまだ色々改善点があるので、元のリポジトリの方でもう少し改善していこうと思っています。

また、FormObject自体に関する説明は、こちらのサイト(Railsのデザインパターン: Formオブジェクト)によくまとまっているので、ご参照ください。🙏

モデル

class Post < ApplicationRecord
end

フォーム

class PostForm
  include ActiveModel::Model

  attr_accessor :title, :content

  validates :title, presence: true

  delegate :persisted?, to: :post

  def initialize(post = Post.new, **attributes)
    @post = post
    attributes = { title: post.title, content: post.content } if attributes.empty?
    super(attributes)
  end

  def save
    return if invalid?

    post.update!(title: title, content: content)
  rescue ActiveRecord::RecordInvalid
    false
  end

  def update(params)
    self.attributes = params
    save
  end

  def to_model
    post
  end

  private

  attr_reader :post
end

コントローラー

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def show
    @post = post
  end

  def new
    @post_form = PostForm.new
  end

  def edit
    @post_form = PostForm.new(post)
  end

  def create
    @post_form = PostForm.new(**post_params)

    if @post_form.save
      redirect_to posts_path, notice: 'The post has been created.'
    else
      render :new
    end
  end

  def update
    @post_form = PostForm.new(post, **post_params)

    if @post_form.save
      redirect_to post, notice: 'The post has been updated.'
    else
      render :edit
    end
  end

  def destroy
    post.destroy
    redirect_to posts_url, notice: 'Post was successfully destroyed.'
  end

  private

  def post_params
    params.require(:post).permit(:title, :content)
  end

  def post
    @post ||= Post.find(params[:id])
  end
end

ビュー

<%= form_with(model: post_form) do |f| %>
  <% if post_form.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(post_form.errors.count, "error") %> prohibited this post_form from being saved:</h2>

      <ul>
        <% post_form.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>

  <div class="field">
    <%= f.label :content %>
    <%= f.text_area :content %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

スキーマ

ActiveRecord::Schema.define(version: 20_210_810_101_738) do
  create_table 'posts', force: :cascade do |t|
    t.string 'title'
    t.text 'content'
    t.datetime 'created_at', precision: 6, null: false
    t.datetime 'updated_at', precision: 6, null: false
  end
end

参考

作成するにあたり、参考にさせていただいた記事。