Ruby on Rails6 実践ガイド cp16~cp18 【メモ】

## はじめに
この記事の筆者はプログラミングを学習し始めたばかりの初心者です。間違いがあれば指摘していただけると幸いです。

## 概要
この記事は[Ruby on Rails6 実践ガイド](https://book.impress.co.jp/books/1118101134)を読んで学んだことを自分用のメモとして記録したものです。抜粋してピックアップするので読みづらいと思われます。すいません。
この本には、続編の[機能拡張編](https://book.impress.co.jp/books/1118101135)もあり、記事を書いている段階で二冊とも学習を終えています。復習もかねて記事を書いていくつもりです。
18のチャプターに分かれているので、見出しごとに区切っていきます。

前の記事
[Ruby on Rails6 実践ガイド cp4~cp6 【メモ】](https://qiita.com/nizi24/items/aca9a745ccda08081eeb)
[Ruby on Rails6 実践ガイド cp7~cp9 【メモ】](https://qiita.com/nizi24/items/60e7fb846a25830478b9)
[Ruby on Rails6 実践ガイド cp10~cp12 【メモ】](https://qiita.com/nizi24/items/e430cadb57a26a7cff76)
[Ruby on Rails6 実践ガイド cp13~cp15 【メモ】](https://qiita.com/nizi24/items/241e766e7c2afc98292b)

# Chapter 16 単一テーブル継承

><strong>単一テーブル継承</strong>とは、オブジェクト指向プログラミングの継承概念をリレーショナルデータベースで疑似的に実現する方法です。

単一テーブル継承を用いると複数の種類の類似したオブジェクトを1つのテーブルにまとめて記録することができます。

文字列型のtypeカラムとpostal_codeカラムを持つaddressというテーブルが存在する場合、次のように定義すると単一テーブル継承を表現できます。

```ruby
class Address < ApplicationRecord
end

class HomeAddress < Address
end

class WorkAddress < Address
end
```

HomeAddressクラスとWorkAddressはAddressクラスを継承しています。
このとき、次のようにHomeAddressオブジェクトとWorkAddressオブジェクトをデータベースに保存できます。

```ruby
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オブジェクトをデータベースから取得するコードは次のようになります。

```ruby
a1 = HomeAddress.find(1)
a2 = WorkAddress.find(2)
```

idの値は、typeカラムのクラス名と対応している必要があります。

---
### fields_for

```haml
= form_with model: @customer_form do |f|
  = f.fields_for :customer, f.object.customer do |ff|
  -#省略
```

フォームビルダーのインスタンスメソッドfields_forを用いると、フォームの対象となるオブジェクトを切り替えることができます。同一のフォームで複数のモデルオブジェクトのフォームを生成することができます。
第一引数にレコード名、第二引数が対象となるオブジェクトです。

---
### 関連づけられたオブジェクトの保存のタイミング

Cusotmerオブジェクトに、HomeAddressオブジェクトとWorkAddressオブジェクトが結び付けられている場合、

```ruby
customer.save
customer.home_address.save
customer.work_address.save
```

Cusotmerオブジェクトが保存されても、自動的にHomeAddressオブジェクトが保存されるわけではありません。

ただし、Cusomerオブジェクトがデータベースに未保存の場合は自動的にHomeAddressオブジェクトとWorkAddressオブジェクトも保存されます。

### transaction

```ruby
ActiveRecord::Base.transaction do
  customer.save!
  customer.home_address.save!
  customer.work_address.save!
end
```

ActiveRecord::Base.transactionブロックで囲むと、この範囲のデータべース処理をトランザクションとして実行します。

# Chapter 17 Capybara

### let!

```ruby
let(:staff_member) { create(:staff_member) }
let!(:staff_member) { create(:staff_member) }
```

letメソッドは、1回目の呼び出し時にオブジェクトを作って記憶し、二回目は同じ結果をそのまま返します。
let!メソッドは定義時にオブジェクトを作成する点が違います。

---
### inclusion

```ruby
validates :gender, inclusion: { in: %w(male female), allow_blank: true }
```

inclusionは値が特定のリストの中にあることを確かめます。

---
### 同時に複数のオブジェクトのバリデーションを行う 

```ruby
def valid?
  customer.valid? && customer.home_address.valid?
    && customer.work_address.valid?
end
```

1つのフォームで複数のオブジェクトを扱うとき、上のようにバリデーションのチェックを行うと問題が生じます。customerオブジェクトがバリデーションに失敗した場合、その時点でfalseを返してしまうので、home_addressとwork_addressのバリデーションがチェックされません。その結果エラーをフォームに表示することができなくなってしまいます。

```ruby
def valid?
  [ customer, customer.home_address, customer.work_address ]
    .map(&:valid?).all?
end
```

オブジェクトを配列にして、mapを使ってそれぞれにvalid?を呼び出すと結果が配列になって返ってきます。
all?メソッドは配列の要素がすべてtrueかどうかを検証するメソッドです。
これで、すべてのオブジェクトのバリデーションチェックができるようになりました。

---
### autosave

```ruby
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については、[前の章の記事](https://qiita.com/nizi24/items/aca9a745ccda08081eeb)にも書いています。

```ruby:app/models/concerns/email_holder.rb
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です。

```ruby
#使用するモデルでincludeする
include EmailHolder
```

# Chapter 18 フォームオブジェクト

### mark_for_destruction

```ruby
customer.home_address.mark_for_destruction
```

関連付けられたモデルオブジェクトに対して、mark_for_destructionメソッドを呼び出すと、削除対象という印がつけられます。削除対象は親がデータベースに保存される際に削除されます。上の例では、削除対象がhome_addressで親がcustomerです。
この仕組みがうまく作用するためには、関連付けのautosaveオプションにtrueを指定する必要があります。

---
### 関連付けのスコープ

```ruby
has_many :personal_phones, -> { where(address_id: nil).order(:id) },
  class_name: "Phone", autosave: true
```

has_manyの第二引数にProcオブジェクトを指定すると、関連付けのスコープを示すことができます。

---
### index

```ruby
= 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 【メモ】](https://qiita.com/nizi24/items/36da7b89d561c19af30f)
[Ruby on Rails6 実践ガイド[機能拡張編] cp7~cp9 【メモ】](https://qiita.com/nizi24/items/232e4f05f9d416e0a08c)
[Ruby on Rails6 実践ガイド [機能拡張編] cp10~cp12 【メモ】](https://qiita.com/nizi24/items/2ec4ed513fce22a89222)

## 引用元
※マークダウンの引用を用いている部分は以下の書籍から引用しています。
[Ruby on Rails6 実践ガイド](https://book.impress.co.jp/books/1118101134)