記事概要
Ruby on RailsにFormオブジェクトを実装する方法について、まとめる
前提
- Ruby on Railsでアプリケーションを作成している
サンプルアプリ(GitHub)
Formオブジェクトパターンとは
1つのフォームから送信した情報を複数のテーブルに保存する、Railsを利用する開発における実装パターン
処理イメージ
ActiveModel::Model
クラスにActiveModel::Model
をincludeすると、そのクラスのインスタンスはActiveRecordを継承したクラスのインスタンスと同様にform_with
やrender
などのヘルパーメソッドの引数として扱え、バリデーションの機能を使用できるようになる
Formオブジェクトパターンを実装するためにActiveModel::Model
をincludeしたクラスのことを、「Formオブジェクト」と呼ぶこともある
手順1.新たにmodelsディレクトリ直下にファイルを作成し、クラスを定義する
-
app/models
ディレクトリ配下に、手動でdonation_address.rb
を作成する - 以下のように記述し、クラスを定義する
donation_address.rb
class DonationAddress end
手順2.作成したクラスにform_withメソッドに対応する機能とバリデーションを行う機能を持たせる
-
ActiveModel::Model
をincludeするdonation_address.rbclass DonationAddress # ActiveModel::Modelをinclude include ActiveModel::Model end
手順3.保存したい複数のテーブルのカラム名全てを属性値として扱えるようにする
-
attr_accessor
を使用し、donationsテーブルとaddressesテーブルに保存したいカラム名を、すべて指定するdonation_address.rbclass DonationAddress # ActiveModel::Modelをinclude include ActiveModel::Model # donationsテーブルとaddressesテーブルに保存したいカラム名 attr_accessor :postal_code, :prefecture, :city, :house_number, :building_name, :price, :user_id end
手順4.バリデーションの処理を書く
- donationモデルのバリデーションを削除する
donation.rb
class Donation < ApplicationRecord belongs_to :user has_one :address # 1以上、1000000以下の整数を許可する # validates :price, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 1000000, message: "is invalid"} end
- addressモデルのバリデーションを削除する
address.rb
class Address < ApplicationRecord belongs_to :donation # 数字3桁、ハイフン、数字4桁の並びのみ許可する # validates :postal_code, presence: true, format: {with: /\A[0-9]{3}-[0-9]{4}\z/, message: "is invalid. Include hyphen(-)"} # 0以外の整数を許可する # validates :prefecture, numericality: {other_than: 0, message: "can't be blank"} end
- donationモデルとaddressモデルにあったバリデーションの記述を、Formオブジェクトへ記述する
donation_address.rb
class DonationAddress # ActiveModel::Modelをinclude include ActiveModel::Model # donationsテーブルとaddressesテーブルに保存したいカラム名 attr_accessor :postal_code, :prefecture, :city, :house_number, :building_name, :price, :user_id # donationsテーブルとaddressesテーブルのバリデーション with_options presence: true do validates :price, numericality: {only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 1000000, message: 'is invalid'} validates :user_id # 「belongs_to :user」のアソシエーションによって設定されているバリデーションを新たに追加 validates :postal_code, format: {with: /\A[0-9]{3}-[0-9]{4}\z/, message: "is invalid. Include hyphen(-)"} end validates :prefecture, numericality: {other_than: 0, message: "can't be blank"} end
手順5.データをテーブルに保存する処理を書く
- フォームから送られてきた情報をテーブルに保存する処理を記述する
donation_address.rb
# 中略 # フォームから送られてきた情報をテーブルに保存する処理 def save # 寄付情報を保存し、変数donationに代入する donation = Donation.create(price: price, user_id: user_id) # 住所を保存する # donation_idには、変数donationのidと指定する Address.create(postal_code: postal_code, prefecture: prefecture, city: city, house_number: house_number, building_name: building_name, donation_id: donation.id) end # 中略
手順6.コントローラーでFormオブジェクトのインスタンスを生成する
- Formオブジェクトのインスタンスを生成する
donations_controller.rb
class DonationsController < ApplicationController before_action :authenticate_user!, except: :index def index end def new # newアクションで生成したインスタンスは、form_withのmodelオプションに指定できる @donation_address = DonationAddress.new end def create # エラーハンドリングしていた場合、ストロングパラメーターによって値を取得したインスタンスが、renderで表示されたビューのmodelオプションに指定 @donation_address = DonationAddress.new(donation_address_params) if @donation_address.valid? @donation_address.save redirect_to root_path else render :new, status: :unprocessable_entity end end private # ストロングパラメーターを設定 def donation_address_params # フォームがFormオブジェクトのインスタンスに紐付くことにより、送信されるパラメーターは、donation_addressハッシュを含む二重構造になる params.require(:donation_address).permit(:postal_code, :prefecture, :city, :house_number, :building_name, :price).merge(user_id: current_user.id) end # ストロングパラメーターを削除 # def donation_params # params.permit(:price).merge(user_id: current_user.id) # end # def address_params # params.permit(:postal_code, :prefecture, :city, :house_number, :building_name).merge(donation_id: @donation.id) # end end
手順7.フォームのmodelオプションを設定する
- 生成したインスタンスを
app/views/donations/new.html.erb
で利用する<%= form_with model: @donation_address, url: donations_path, local: true do |f| %> <%# 削除 %> <%#= form_with url: donations_path, local: true do |f| %> <%# 省略 %>
手順8.エラーメッセージを表示する
- メッセージを表示させるための部分テンプレート
_error_messages.html.erb
を、app/views/donations
ディレクトリに手動作成する - 部分テンプレートに下記を記述する
<% if model.errors.any? %> <div class="error-alert"> <ul> <% model.errors.full_messages.each do |message| %> <li class="error-message"> <%= message %> </li> <% end %> </ul> </div> <% end %>
- 部分テンプレートを読み込めるように、
app/views/donations/new.html.erb
に下記を記述する<%= form_with model: @donation_address, url: donations_path, local: true do |f| %> <%# 削除 %> <%#= form_with url: donations_path, local: true do |f| %> <%= render 'error_messages', model: @donation_address %> <%# エラーメッセージの表示 %> <%# 省略 %>
Ruby on Railsまとめ