##環境
Ruby 3.0.2
Rails 6.1.4.1
##状況
userとemployeeテーブルの作成と更新を一つの画面で行いたいとき、
accepts_nested_attributes_forは非推奨なのでFormObjectを使ってリファクタリングしてみた。
app/models/employee.rb
class Employee < ApplicationRecord
...
accepts_nested_attributes_for :user
...
end
##メリット
コントローラーがすっきりする
##EmployeeForm
app/forms/employee_form.rb
class EmployeeForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :firstname, :string
attribute :lastname, :string
attribute :number, :integer
attribute :email, :string
attribute :role, :string
attr_reader :employee
with_options presence: true do
validates :firstname
validates :lastname
end
delegate :persisted?, to: :employee
def initialize(attributes = nil, employee: Employee.new)
@employee = employee
attributes ||= default_attributes
super(attributes)
end
def save
return false if invalid?
ActiveRecord::Base.transaction do
employee.update!(attributes.except('role'))
employee.user.update!(role: role) unless employee.user.nil?
employee
end
rescue => e
Rails.logger.error e.message
false
end
def to_model
employee
end
private
def default_attributes
{
firstname: employee.firstname,
lastname: employee.lastname,
number: employee.number,
email: employee.email,
role: employee.user.try(:role)
}
end
end
-
include ActiveModel::Model
→モデルと同じようにバリデーションを書ける -
include ActiveModel::Attributes
→クラスメソッドattributeに属性名と型を渡すと、attr_accessorと同じように属性が使えるようになる -
a ||= xxx
: aが偽か未定義ならaにxxxを代入する -
#initialize
ではFormオブジェクトの値を初期化している。#super
はActiveModel::Model
の#initialize
を呼び出しており、書き込みメソッド(#firstname=など)を用いて値を代入している。(super は現在のメソッドがオーバーライドしているメソッドを呼び出す) -
#persisted?
と#to_model
はビューの表示(#form_with)に必要なメソッド。#persisted?
は作成・更新に応じてフォームのアクションをPOST・PATCHに切り替えてくれる。 また#to_model
はアクションのURLを適切な場所に切り替えてくれる。 -
employee.update!(attributes.except('role'))
で属性を一つ一つ指定しているやり方が多かったが、できるだけ少ないコードで書きたかったため無理やりexceptしている。
##Controller
app/controllers/manage/employees_controller.rb
class Manage::EmployeesController < ApplicationController
before_action :set_employee, only: [:edit, :update, :destroy]
def index
@employees = Employee.all
end
def new
@employee_form = EmployeeForm.new
end
def create
@employee_form = EmployeeForm.new(employee_form_params)
if @employee_form.save
redirect_to new_manage_employee_path, notice: '設定の追加が完了しました'
else
redirect_to new_manage_employee_path, alert: '設定の追加に失敗しました'
end
end
def edit
@employee_form = EmployeeForm.new(employee: @employee)
end
def update
@employee_form = EmployeeForm.new(employee_form_params, employee: @employee)
if @employee_form.save
redirect_to edit_manage_employee_path(@employee), notice: '設定の更新が完了しました'
else
redirect_to edit_manage_employee_path(@employee), alert: '設定の更新に失敗しました'
end
end
def destroy
end
private
def set_employee
@employee = Employee.find(params[:id])
end
def employee_form_params
params.require(:employee).permit(
:firstname,
:lastname,
:number,
:email,
:role
)
end
end
##View
new.html.slim
= form_with model: @employee_form, url: manage_preferences_employee_path, local: true do |f|
= render 'form', form: @employee_form, f: f
- new.html.slimとedit.html.slimから
@employee_form
をformとして渡す想定
##参考