概要
この記事で使用している言語のバージョンは以下のとおりです。
- Ruby 2.6.5
- Rails 6.0.3.4
Formオブジェクトで編集・更新を行うにはどうするか調べたのですが、バージョンが古いものが多かったので自分なりに考えて出来たやり方をここで共有します。
もっと良い書き方出来るところが絶対あると思いますので修正すべき点は指摘して頂けると大変うれしいです。
今回作ったのはサンプルのアプリなので簡単な作りとなっています。ご了承をよろしくお願いいたします。
作成したコード
class StationsController < ApplicationController
def index
@stations = Station.all
end
def new
@station = StationForm.new
end
def create
@station = StationForm.new(station_params)
if @station.valid?
@station.save
redirect_to root_path
else
render :new
end
end
def edit
@station = StationForm.new(id: params[:id])
end
def update
@station = StationForm.new(station_params.merge(id: params[:id]))
if @station.valid?
@station.update
redirect_to root_path
else
render :edit
end
end
private
def station_params
params.require(:station_form).permit(:name, :address_url)
end
end
class StationForm
include ActiveModel::Model
attr_accessor :name, :address_url
with_options presence: true do
validates :name
validates :address_url
end
def initialize(attribute = {})
if !(attribute[:id] == nil)
@station = Station.find(attribute[:id])
@address = @station.address
if !(self.name = attribute[:name])
self.name = @station.name
else
self.name = attribute[:name]
end
if !(self.address_url = attribute[:address_url])
self.address_url = @address.address_url
else
self.address_url = attribute[:address_url]
end
else
super(attribute)
end
end
def persisted?
if @station.nil?
return false
else
return @station.persisted?
end
end
def save
station = Station.create(name: name)
Address.create(address_url: address_url, station_id: station.id)
end
def update
@station.update(name: name)
@address.update(address_url: address_url)
end
end
<%= form_with model:@station, url:stations_path, local: true do |f|%>
<%= render 'error_messages', model: @station %>
<div class="field">
<%= f.label :name, "駅名" %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :address_url, "駅住所" %>
<%= f.text_field :address_url %>
</div>
<div class="actions">
<%= f.submit "登録する" %>
</div>
<% end %>
<%= form_with model:@station, url:station_path, local: true do |f|%>
<%= render 'error_messages', model: @station %>
<div class="field">
<%= f.label :name, "駅名" %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :address_url, "駅住所" %>
<%= f.text_field :address_url %>
</div>
<div class="actions">
<%= f.submit "登録する" %>
</div>
<% end %>
コード解説
おそらくFormオブジェクトで一番重要なのは以下です
def initialize(attribute = {})
if !(attribute[:id] == nil)
@station = Station.find(attribute[:id])
@address = @station.address
if !(self.name = attribute[:name])
self.name = @station.name
else
self.name = attribute[:name]
end
if !(self.address_url = attribute[:address_url])
self.address_url = @address.address_url
else
self.address_url = attribute[:address_url]
end
else
super(attribute)
end
end
改善の余地だらけだとは思いますが、ここではStationFormインスタンスの初期化を行っています。
引数として設定している *attibute = {}*はインスタンスが存在するかどうか判断するためにつかいます。
Formオブジェクトのインスタンスが生成される時はnew&createアクション時とedit&updateアクション時です。(下記は他の記述を省略しています)
def new
@station = StationForm.new
end
def create
@station = StationForm.new(station_params)
if @station.valid?
@station.save
redirect_to root_path
else
render :new
end
end
def edit
@station = StationForm.new(id: params[:id])
end
def update
@station = StationForm.new(station_params.merge(id: params[:id]))
if @station.valid?
@station.update
redirect_to root_path
else
render :edit
end
end
新規投稿と編集のインスタンス生成時の違いは保存されたデータが既に存在しているかどうかなのでedit&updateを行う時は編集を行う対象データのidを引数として送りinitializeメソッド内で処理をする必要があります。
if !(attribute[:id] == nil)
@station = Station.find(attribute[:id])
@address = @station.address
if !(self.name = attribute[:name])
self.name = @station.name
else
self.name = attribute[:name]
end
if !(self.address_url = attribute[:address_url])
self.address_url = @address.address_url
else
self.address_url = attribute[:address_url]
end
else
super(attribute)
end
引数のidが存在しない場合はsuper(attribute)としてインスタンスを生成します。
(このsuperが何かよく分からない方は参考文献をどうぞ!簡単に言うと何もないinitializeメソッドを行っている認識でいいかと思います。)
ここでidが渡された場合は保存されたデータを取得する動きになります。
このタイミングで僕がつまいづいたのはただ情報を取得するだけだとeditのフォームに情報が表示されない点です。
原因は簡単でStationFormインスタンス自体に情報が入っていないからです。
if !(self.name = attribute[:name])
self.name = @station.name
else
self.name = attribute[:name]
end
if !(self.address_url = attribute[:address_url])
self.address_url = @address.address_url
else
self.address_url = attribute[:address_url]
end
ここでStationFromインスタンス自体に初期値を設定しています。
selfがインスタンスを表していて、formで表示しておきたい値を渡している形になります。
条件分岐をしているのはupdateアクションの時用です。
updateアクションが行われる時は引数としてパラメーターを渡しています。
def update
@station = StationForm.new(station_params.merge(id: params[:id]))
if @station.valid?
@station.update
redirect_to root_path
else
render :edit
end
end
上記のように記述することでid,name,address_urlが渡されるのでattributeの中に3つの値が入ります。
update時は保存する値をStationFormインスタンスに代入する必要があるので代入をします。
ここまでの動きをinitializeメソッドで行っています。
この初期値の設定さえうまく行けばFormオブジェクトをつかった編集と更新はうまくいくはずです。
まとめ
以上が個人的にFormオブジェクトをつかった編集と更新のポイントかなと思います。
ただ、先程も述べたようにもっときれいな書き方が出来るところがあるのと、人によって書き方も違うと思うので教えて頂けると大変嬉しいです。
この記事がすこしでも参考になれば嬉しいです。
最後までご覧頂きありがとうございました!