Formオブジェクトパターン

1つのフォームから送信したデータを複数のテーブルへ分けて保存する方法をFormオブジェクトパターンを使用して実装していきましょう。

## プロジェクト作成

```console:
# 「investment_app」という名前で、Railsバージョン “6.0.0” で作成
% rails _6.0.0_ new investment_app -d mysql

# investment_appディレクトリに移動
% cd investment_app

# investment_appのDBを作成
% rails db:create
```

## ルーティングを設定

```rb:
Rails.application.routes.draw do
  root "investments#index"
  resources :investments, only: [:index, :new, :create]
end
```


## has_one
アソシエーションが１対１の時に使われる、アソシエーションを定義するためのメソッドで,親モデル側に「has_one」を、子モデル側に「belongs_to」を書きます。

# モデルとマイグレーションファイルを作成

## Userモデル作成

```
% rails g model user
```

##  住所情報のAddressモデル作成

```console:
% rails g model address
```

##  投資金額情報のInvestmentモデル作成

```console:
% rails g model investment
```

## with_options
「with_options 〇〇 」で、複数の情報に対して共通したオプションを付けられます。

## formatオプション
モデルのクラスの中でvalidatesメソッドを使う時に利用できるオプションです。
format: {with: 正規表現, message: 正規表現にマッチしなかった場合のエラーメッセージ}
で特定のカラムへ正規表現を用いた入力制限をすることができます。


|正規表現|意味|
|---|---|
|/\A[ぁ-んァ-ン一-龥々]/|全角ひらがな、全角カタカナ、漢字|
|/\A[ァ-ヶー－]+\z/|全角カタカナ|
|/\A[a-zA-Z0-9]+\z/|半角英数|
|/\A\d{3}[-]\d{4}\z/|郵便番号（「-」を含む且つ7桁）|
|greater_than_or_equal_to: 〇〇|〇〇と同じか、それ以上の数値|
|less_than_or_equal_to: □□|□□と同じか、それ以下の数値|


## Userモデルにアソシエーションとバリデーションを設定

```rb:
class User < ApplicationRecord
  has_one :address
  has_one :investment
  with_options presence: true do
    validates :name, format: { with: /\A[ぁ-んァ-ン一-龥々]/, message: "is invalid. Input full-width characters." }
    validates :name_reading, format: { with: /\A[ァ-ヶー－]+\z/, message: "is invalid. Input full-width katakana characters." }
    validates :nickname, format: { with: /\A[a-z0-9]+\z/i, message: "is invalid. Input half-width characters." }
  end
end
```

## Addressモデルのアソシエーション

```rb:
class Address < ApplicationRecord
  belongs_to :user
end
```

## Investmentモデルのアソシエーション

```rb:
class Investment < ApplicationRecord
  belongs_to :user
end
```


## マイグレーションファイルを編集

```rb:User
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :name,           null: false
      t.string :name_reading,   null: false
      t.string :nickname,       null: false
      t.timestamps
    end
  end
end
```

```rb:Address
class CreateAddresses < ActiveRecord::Migration[6.0]
  def change
    create_table :addresses do |t|
      t.string :postal_code,    default: "",  null: false
      t.integer :prefecture,                  null: false
      t.string :city,           default: ""
      t.string :house_number,   default: ""
      t.string :building_name,  default: ""
      t.references :user,                     null: false,  foreign_key: true
      t.timestamps
    end
  end
end
```

```rb:Investment
class CreateInvestments < ActiveRecord::Migration[6.0]
  def change
    create_table :donations do |t|
      t.integer :price,   index: true,  null: false
      t.references :user,               null: false,  foreign_key: true
      t.timestamps
    end
  end
end
```

## ActiveHash」を使って都道府県モデルを作成し実装


```rb:
gem 'active_hash'
```

```console:
% bundle install
```

##　app/modelsに prefecture.rbを作成し以下のように編集

```rb:
class Prefecture < ActiveHash::Base

  self.data = [
               {id: 0, name: '--'}, {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, 
               {id: 3, name: '岩手県'}, {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, 
               {id: 6, name: '山形県'}, {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, 
               {id: 9, name: '栃木県'}, {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, 
               {id: 12, name: '千葉県'}, {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, 
               {id: 15, name: '新潟県'}, {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, 
               {id: 18, name: '福井県'}, {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, 
               {id: 21, name: '岐阜県'}, {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, 
               {id: 24, name: '三重県'}, {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, 
               {id: 27, name: '大阪府'}, {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, 
               {id: 30, name: '和歌山県'}, {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, 
               {id: 33, name: '岡山県'}, {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, 
               {id: 36, name: '徳島県'}, {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, 
               {id: 39, name: '高知県'}, {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, 
               {id: 42, name: '長崎県'}, {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, 
               {id: 45, name: '宮崎県'}, {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
              ]
end
```

## テーブル作成

```
% rails db:migrate
```

## コントローラを作成

```console:
% rails g controller investments 
```

## アクションを定義

```rb:
class InvestmentsController < ApplicationController
  def index
  end

  def new
  end

  def create
    user = User.create(user_params)
    Address.create(address_params(user))
    Investment.create(investment_params(user))
    redirect_to action: :index
  end

  private

  def user_params
    params.permit(:name, :name_reading, :nickname)
  end

  def address_params(user)
    params.permit(:postal_code, :prefecture, :city, :house_number, :building_name).merge(user_id: user.id)
  end

  def investment_params(user)
    params.permit(:price).merge(user_id: user.id)
  end
end
```


newのビューを作成


```erb:
<%= form_with(url: investment_path, local: true) do |form| %>
  <h1>ユーザー名を入力</h1>
  <div class="field">
    <%= form.label :name, "名前（全角）" %>
    <%= form.text_field :name %>
  </div>
  <div class="field">
    <%= form.label :name_reading, "フリガナ（全角カタカナ）" %>
    <%= form.text_field :name_reading %>
  </div>
  <div class="field">
    <%= form.label :nickname, "ニックネーム（半角英数）" %>
    <%= form.text_field :nickname %>
  </div>
  <h1>住所を入力</h1>
  <div class="field">
   <%= form.label :postal_code, "郵便番号（ハイフンを含む）" %>
   <%= form.text_field :postal_code %>
 </div>
 <div class="field">
   <%= form.label :prefecture, "都道府県" %>
   <%= form.collection_select :prefecture, Prefecture.all, :id, :name, {} %>
 </div>
 <div class="field">
   <%= form.label :city, "市町村（任意）" %>
   <%= form.text_field :city %>
 </div>
  <div class="field">
    <%= form.label :house_number, "番地（任意）" %>
    <%= form.text_field :house_number %>
  </div>
  <div class="field">
    <%= form.label :building_name, "建物名（任意）" %>
    <%= form.text_field :building_name %>
  </div>

  <h1>投資金額</h1>
  <div class="field">
    <%= form.label :price, "いくら投資しますか？（1〜1000000円、半角）" %>
    <%= form.text_field :price %>
    <%= "円" %>
  </div>

  <div class="actions">
    <%= form.submit "投資する" %>
  </div>

<% end %>
```


この実装には欠点があります。

ユーザーが必須項目を入力し忘れてた際にバリデーションによってリダイレクトしたり、エラーメッセージが表示できない仕様になっています。また、これらの情報を編集、更新する際の処理が複雑になります。

そこであるデザインパターンを使います。

## Formオブジェクトパターン

1つのフォーム送信で複数のモデルを操作したい場合や、テーブルに情報を保存しない情報に対するバリデーションをかけたい場合に使います。


## ActiveModel::Model
クラスにActiveModel::Modelをincludeすると、そのクラスのインスタンスはActiveRecordを継承したクラスのインスタンスと同様にヘルパーメソッドの引数として扱えたり、バリデーションが使えるようになります。

## app/models/にuser_investment.rbを作成

```rb:
class UserInvestment
  include ActiveModel::Model
end
```

```rb:
class UserInvestment
  include ActiveModel::Model
  attr_accessor :name, :name_reading, :nickname, :postal_code, :prefecture, :city, :house_number, :building_name, :price
end
```

attr_accessorを活用しています。今回のフォームで利用する保存したい各テーブルのカラム名全てについてゲッターとセッターを定義することで、インスタンスを生成した際にform_withの引数として利用できるようになります。

各テーブルへの保存の処理を実行する前に、バリデーションに引っかからないことをまとめてチェックするために
user.rbにあるバリデーションの記述を、Formオブジェクトへ移します。

```rb:
class User < ApplicationRecord
  has_one :address
  has_one :investment

#ここの部分を切り取る
  with_options presence: true do
    validates :name, format: { with: /\A[ぁ-んァ-ン一-龥々]/, message: "is invalid. Input full-width characters." }
    validates :name_reading, format: { with: /\A[ァ-ヶー－]+\z/, message: "is invalid. Input full-width katakana characters." }
    validates :nickname, format: { with: /\A[a-z0-9]+\z/i, message: "is invalid. Input half-width characters." }
  end
#ここまで
end
```

```rb:UserInvestment
class UserInvestment
  include ActiveModel::Model

#ここの部分を貼り付け
  with_options presence: true do
    validates :name, format: { with: /\A[ぁ-んァ-ン一-龥々]/, message: "is invalid. Input full-width characters." }
    validates :name_reading, format: { with: /\A[ァ-ヶー－]+\z/, message: "is invalid. Input full-width katakana characters." }
    validates :nickname, format: { with: /\A[a-z0-9]+\z/i, message: "is invalid. Input half-width characters." }
  end
#ここまで

end
```

続いて、「住所」「投資金」のバリデーションもuser_investment.rbの中に記述します。
そしてデータをテーブルに保存する処理も追加します。

```rb:UserInvestment
class UserInvestment
  include ActiveModel::Model

  with_options presence: true do
    validates :name, format: { with: /\A[ぁ-んァ-ン一-龥々]/, message: "is invalid. Input full-width characters." }
    validates :name_reading, format: { with: /\A[ァ-ヶー－]+\z/, message: "is invalid. Input full-width katakana characters." }
    validates :nickname, format: { with: /\A[a-z0-9]+\z/i, message: "is invalid. Input half-width characters." } 
    validates :postal_code, format: { with: /\A[0-9]{3}-[0-9]{4}\z/, message: "is invalid. Include hyphen(-)" }   
    validates :price, numericality: { only_integer: true, message: "is invalid. Input half-width characters." }
  end
  validates :prefecture, numericality: { other_than: 0, message: "can't be blank" }
  validates :price, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 1000000, message: "is out of setting range" }
end

  def save
    # ユーザーの情報を保存し、「user」という変数に代入
    user = User.create(name: name, name_reading: name_reading, nickname: nickname)
    # 住所の情報を保存
    Address.create(postal_code: postal_code, prefecture: prefecture, city: city, house_number: house_number, building_name: building_name,user_id: user.id)
    # 投資金の情報を保存
    Investment.create(price: price, user_id: user.id)
  end
end

```

## コントローラーのアクションでFormオブジェクトのインスタンスを生成するように変更


```rb:
class  InvestmentsController < ApplicationController
 def index
 end

 def new
   @user_investment = UserInvestment.new   
 end

 def create
   @user_investment = UserDonation.new(donation_params)  
    if @user_investment.valid?
      @user_investment.save
      redirect_to action: :index
    else
      render action: :new
    end
 end

 private
  # 全てのストロングパラメーターを1つに統合
 def investment_params
  params.require(:user_investment).permit(:name, :name_reading, :nickname, :postal_code, :prefecture, :city, :house_number, :building_name, :price)
 end

end

```


## new.html.erbのform_withメソッドに、Formオブジェクトのインスタンスを引数として渡します。

```erb:
<%= form_with(model: @user_investment, url: investment_path, local: true) do |form| %>
  <%= render 'error_messages', model: @user_investment %>
  <h1>ユーザー名を入力</h1>
  <div class="field">
    <%= form.label :name, "名前（全角）" %>
    <%= form.text_field :name %>
  </div>
  <div class="field">
    <%= form.label :name_reading, "フリガナ（全角カタカナ）" %>
    <%= form.text_field :name_reading %>
  </div>
  <div class="field">
    <%= form.label :nickname, "ニックネーム（半角英数）" %>
    <%= form.text_field :nickname %>
  </div>
  <h1>住所を入力</h1>
  <div class="field">
   <%= form.label :postal_code, "郵便番号（ハイフンを含む）" %>
   <%= form.text_field :postal_code %>
 </div>
 <div class="field">
   <%= form.label :prefecture, "都道府県" %>
   <%= form.collection_select :prefecture, Prefecture.all, :id, :name, {} %>
 </div>
 <div class="field">
   <%= form.label :city, "市町村（任意）" %>
   <%= form.text_field :city %>
 </div>
  <div class="field">
    <%= form.label :house_number, "番地（任意）" %>
    <%= form.text_field :house_number %>
  </div>
  <div class="field">
    <%= form.label :building_name, "建物名（任意）" %>
    <%= form.text_field :building_name %>
  </div>

  <h1>投資金額</h1>
  <div class="field">
    <%= form.label :price, "いくら投資しますか？（1〜1000000円、半角）" %>
    <%= form.text_field :price %>
    <%= "円" %>
  </div>

  <div class="actions">
    <%= form.submit "投資する" %>
  </div>

<% end %>
```

以上です。