はじめに
この記事の筆者はプログラミングを学習し始めたばかりの初心者です。間違いがあれば指摘していただけると幸いです。
概要
この記事はRuby on Rails6 実践ガイドを読んで学んだことを自分用のメモとして記録したものです。抜粋してピックアップするので読みづらいと思われます。すいません。
この本には、続編の機能拡張編もあり、記事を書いている段階で二冊とも学習を終えています。復習もかねて記事を書いていくつもりです。
18のチャプターに分かれているので、見出しごとに区切っていきます。
前の記事
Ruby on Rails6 実践ガイド cp4~cp6 【メモ】
Ruby on Rails6 実践ガイド cp7~cp9 【メモ】
Ruby on Rails6 実践ガイド cp10~cp12 【メモ】
Ruby on Rails6 実践ガイド cp13~cp15 【メモ】
Chapter 16 単一テーブル継承
単一テーブル継承とは、オブジェクト指向プログラミングの継承概念をリレーショナルデータベースで疑似的に実現する方法です。
単一テーブル継承を用いると複数の種類の類似したオブジェクトを1つのテーブルにまとめて記録することができます。
文字列型のtypeカラムとpostal_codeカラムを持つaddressというテーブルが存在する場合、次のように定義すると単一テーブル継承を表現できます。
class Address < ApplicationRecord
end
class HomeAddress < Address
end
class WorkAddress < Address
end
HomeAddressクラスとWorkAddressはAddressクラスを継承しています。
このとき、次のようにHomeAddressオブジェクトとWorkAddressオブジェクトをデータベースに保存できます。
HomeAddress.create(postal_code: "1000001")
WorkAddress.create(postal_code: "1000002")
これらの情報はAddressテーブルに下の表のように記録されます。
id | type | postal_code |
---|---|---|
1 | HomeAddress | 1000001 |
2 | WorkAddress | 1000002 |
typeカラムの値はRailsが自動的にセットします。
また、HomeAddressオブジェクトやWorkAddressオブジェクトをデータベースから取得するコードは次のようになります。
a1 = HomeAddress.find(1)
a2 = WorkAddress.find(2)
idの値は、typeカラムのクラス名と対応している必要があります。
fields_for
= form_with model: @customer_form do |f|
= f.fields_for :customer, f.object.customer do |ff|
-#省略
フォームビルダーのインスタンスメソッドfields_forを用いると、フォームの対象となるオブジェクトを切り替えることができます。同一のフォームで複数のモデルオブジェクトのフォームを生成することができます。
第一引数にレコード名、第二引数が対象となるオブジェクトです。
関連づけられたオブジェクトの保存のタイミング
Cusotmerオブジェクトに、HomeAddressオブジェクトとWorkAddressオブジェクトが結び付けられている場合、
customer.save
customer.home_address.save
customer.work_address.save
Cusotmerオブジェクトが保存されても、自動的にHomeAddressオブジェクトが保存されるわけではありません。
ただし、Cusomerオブジェクトがデータベースに未保存の場合は自動的にHomeAddressオブジェクトとWorkAddressオブジェクトも保存されます。
transaction
ActiveRecord::Base.transaction do
customer.save!
customer.home_address.save!
customer.work_address.save!
end
ActiveRecord::Base.transactionブロックで囲むと、この範囲のデータべース処理をトランザクションとして実行します。
Chapter 17 Capybara
let!
let(:staff_member) { create(:staff_member) }
let!(:staff_member) { create(:staff_member) }
letメソッドは、1回目の呼び出し時にオブジェクトを作って記憶し、二回目は同じ結果をそのまま返します。
let!メソッドは定義時にオブジェクトを作成する点が違います。
inclusion
validates :gender, inclusion: { in: %w(male female), allow_blank: true }
inclusionは値が特定のリストの中にあることを確かめます。
同時に複数のオブジェクトのバリデーションを行う
def valid?
customer.valid? && customer.home_address.valid?
&& customer.work_address.valid?
end
1つのフォームで複数のオブジェクトを扱うとき、上のようにバリデーションのチェックを行うと問題が生じます。customerオブジェクトがバリデーションに失敗した場合、その時点でfalseを返してしまうので、home_addressとwork_addressのバリデーションがチェックされません。その結果エラーをフォームに表示することができなくなってしまいます。
def valid?
[ customer, customer.home_address, customer.work_address ]
.map(&:valid?).all?
end
オブジェクトを配列にして、mapを使ってそれぞれにvalid?を呼び出すと結果が配列になって返ってきます。
all?メソッドは配列の要素がすべてtrueかどうかを検証するメソッドです。
これで、すべてのオブジェクトのバリデーションチェックができるようになりました。
autosave
class Customer < ApplicationRecord
has_one :home_address, dependent: :destroy, autosave: true
has_one :work_address, dependent: :destroy, autosave: true
end
autosaveオプションにtrueを指定すると、関連付けられたオブジェクトも自動的に保存されるようになります。
正規化とバリデーションを共有する
ActiveSupport::Concernを用いるとコードを共有できます。
ActiveSupport::Concernについては、前の章の記事にも書いています。
class EmailHolder
extend ActiveSupport::Concern
included do
include StringNormalizer
before_validation do
self.email = normalize_as_email(email)
end
validates :email, presence: true, "valid_email_2/email": true,
uniqueness: { case_sensitive: true }
end
end
StringNormalizerは前の章で作成した正規化のためのモジュールです。valid_email_2/emailはメールアドレスのバリデーションを追加するGemです。
#使用するモデルでincludeする
include EmailHolder
Chapter 18 フォームオブジェクト
mark_for_destruction
customer.home_address.mark_for_destruction
関連付けられたモデルオブジェクトに対して、mark_for_destructionメソッドを呼び出すと、削除対象という印がつけられます。削除対象は親がデータベースに保存される際に削除されます。上の例では、削除対象がhome_addressで親がcustomerです。
この仕組みがうまく作用するためには、関連付けのautosaveオプションにtrueを指定する必要があります。
関連付けのスコープ
has_many :personal_phones, -> { where(address_id: nil).order(:id) },
class_name: "Phone", autosave: true
has_manyの第二引数にProcオブジェクトを指定すると、関連付けのスコープを示すことができます。
index
= form_with model: @customer_form, scope: "form" do |f|
= customer.perdonal_phones.each_with_index do |phone, index|
= f.fields_for :phones, phone, index: index do |ff|
= f.text_field :number
複数個の同一オブジェクトのフォームを作成する際、indexオプションを付けるとそれらを識別することができます。
indexオプションを付けることによって、input要素のname属性が変化します。上の例だと、form[customer][phones][0][number]のようになります。
続き
続きの記事のURLも順次追加していきます。
Ruby on Rails6 実践ガイド[機能拡張編] cp3~cp6 【メモ】
Ruby on Rails6 実践ガイド[機能拡張編] cp7~cp9 【メモ】
Ruby on Rails6 実践ガイド [機能拡張編] cp10~cp12 【メモ】
引用元
※マークダウンの引用を用いている部分は以下の書籍から引用しています。
Ruby on Rails6 実践ガイド