Formオブジェクトを使用したテーブルの編集・更新のやり方

## 概要

この記事で使用している言語のバージョンは以下のとおりです。

* Ruby 2.6.5
* Rails 6.0.3.4

Formオブジェクトで編集・更新を行うにはどうするか調べたのですが、バージョンが古いものが多かったので自分なりに考えて出来たやり方をここで共有します。

もっと良い書き方出来るところが絶対あると思いますので修正すべき点は指摘して頂けると大変うれしいです。

今回作ったのはサンプルのアプリなので簡単な作りとなっています。ご了承をよろしくお願いいたします。

## 作成したコード

```stations_controller.rb
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
```

```station_form.rb

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

```

```new.html.erb
<%= 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 %>

```

```edit.html.erb
<%= 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オブジェクトで一番重要なのは以下です

```station_form.rb
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アクション時です。(下記は他の記述を省略しています)

```station_controller.rb
  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メソッド内で処理をする必要があります。


```station_form.rb
 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*が何かよく分からない方は[参考文献](https://docs.ruby-lang.org/ja/latest/doc/spec=2fcall.html#super "ドキュメント")をどうぞ！簡単に言うと何もないinitializeメソッドを行っている認識でいいかと思います。)

ここでidが渡された場合は保存されたデータを取得する動きになります。
このタイミングで僕がつまいづいたのはただ情報を取得するだけだとeditのフォームに情報が表示されない点です。

原因は簡単で**StationFormインスタンス自体に情報が入っていないから**です。

```staion_form.rb
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アクションが行われる時は引数としてパラメーターを渡しています。

```stations_controller.rb
  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オブジェクトをつかった編集と更新のポイントかなと思います。
ただ、先程も述べたようにもっときれいな書き方が出来るところがあるのと、人によって書き方も違うと思うので教えて頂けると大変嬉しいです。

この記事がすこしでも参考になれば嬉しいです。

最後までご覧頂きありがとうございました！