Ruby on Rails6 実践ガイド[機能拡張編] cp3~cp6 【メモ】

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

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

前の記事
[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)
[Ruby on Rails6 実践ガイド cp16~cp18 【メモ】](https://qiita.com/nizi24/items/e83749826f8d70223311)


# 機能拡張編　Chapter 3 検索フォーム

### indexの名前

add_indexメソッドはデフォルトで次のように名前を生成します。

* "index_"、テーブル名、"_on_"を連結する。
* 単独のインデックスであればカラム名を追加する。
* 複合インデックスであれば、すべてのカラム名を"_and_"で連結して追加する。

しかし、インデックスの名前には制限があるので、複合インデックスとして組み合わせるカラムの数が多いと制限を超えてしまうことがあります。
このような場合には、nameオプションを用いてインデックス名を指定する必要があります。

```ruby
add_index :customers, [ :birth_year, :family_name_kana, :given_name_kana ],
  name: "index_customers_on_birth_year_and_furigana"
```

---
### 複合インデックス

X、Y、Zというカラムに対して複合インデックスが設定されている場合、カラムX単独の検索、カラムXとYを組み合わせた検索、3つのカラムを組み合わせた検索ではこの複合インデックスが活用されます。
しかし、カラムY単独の検索、カラムZ単独の検索、カラムYとZを組みあわせた検索では、この複合インデックスは利用されません。
なので、すべての組み合わせの検索を最適化するには下のようなインデックスを設定する必要があります。

* カラムYとZの複合インデックス
* カラムXとZの複合インデックス
* カラムZのインデックス

---
### &.(ぼっち演算子)とtryメソッドの違い

どちらもレシーバーがnilの時はnilを返すメソッドですが、レシーバーがnil以外の時は少し挙動が違います。
レシーバーに定義されていないメソッドを&.で呼び出そうとしたときはNoMethodErrorが発生しますが、tryの場合はnilが返ります。

```ruby
user&.name
user.try(:name)
```
上の例だと、userのインスタンスメソッドにnameがない場合は&.を使った場合のみNoMethodErrorが発生します。

---
### Relationオブジェクトを溜める

```ruby
attr_accessor :name, :gender, :birthday

def search
  rel = User

  rel = rel.where(name: name) if name.present?
  rel = rel.where(gender: gender) if gender.present?
  rel = rel.where(birthday: birthday) if birthday.present?
  rel = rel.order(:name)
end
```

上のコードはとあるフォームオブジェクトです。検索結果を返すsearchオブジェクトを定義しています。
whereやorderはRelationオブジェクトを返すので、上のコードのようにさまざまな検索条件をRelationオブジェクトに溜めることができます。

#### joinsでテーブルを結合する

※上のコードの続きです。

```ruby
rel = rel.joins(:articles)
rel = rel.where("articles.title" => title) if title.present?
```

joinsメソッドは<strong>テーブル結合</strong>を行います。テーブル結合によって、他のテーブルのカラムの値に基づいてレコードを絞り込むことができるようになります。joinsメソッドの引数は関連付けの名前です。このメソッドもRelationオブジェクトを返します。
テーブル結合を行うと、二行目のコードのように他のテーブルを対象とする検索ができるようになります。

#### 重複を取り除く

```ruby
rel = rel.distinct
```
distinctメソッドを呼び出すと、検索結果から重複を取り除くことができます。

# 機能拡張編　Chapter 4 次回から自動でログイン

### cookies

```ruby
cookies.signed[:user_id] = user.id
```

signedメソッドを呼び出すとクッキーの値を閲覧不可かつ変更不可にします。

```ruby
cookies.permanent.signed[:user_id] = user.id
```
permanentメソッドを用いると、クッキーの有効期限が20年後に設定されます。

```ruby
cookies.delete(:user_id)
```
上のコードでクッキーに記録されたIDを消すことができます。

---
### クッキーのテスト

```ruby
expect(response.cookies).to have_key("customer_id")
expect(response.cookies["customer_id"]).to match(/[0-9a-f]{40}\z/)
```

一行目では、クッキーが"customer_id"というキーをもっているかどうかを調べています。
二行目では、クッキーの値が閲覧不可になっているかどうかを調べています。
閲覧不可のクッキーは末尾に40桁の16進数を持つ特徴があるので、そのことを正規表現を用いて調べています。

# 機能拡張編　Chapter 5 IPアドレスによるアクセス制限

### 値が数値であることを検証する

```ruby
validates :octet1, numericality: { only_integer: true },
  inclusion: { in: 0..255 }
```
numericalityは、値が数値のみかどうかを検証します。only_integerにtrueを指定すると整数のみにマッチするようになります。
inclusionのみでは"XYZ"のような文字列が整数0に変更されてしまうので、エラーにはなりません。numericalityは、変換前の値に対してバリデーションが行われるのでエラーになります。

[Railsガイド](https://railsguides.jp/active_record_validations.html#numericality)にはさらに詳しく解説されています。



# 機能拡張編　Chapter 6 多対多の関連付け

多対多の関連はリンクテーブルを用いて、2つの1対多の関連付けを組み合わせたものに分けることができます。

---
### 数値に3桁区切りのコンマを追加する

```ruby
number_with_delimiter(100000)
```
100,000が返ってきます。

---
### 多対多の関連の数を数える

顧客(customer)とプログラム(program)が多対多の関連であるとき、

```ruby
program.cusotmers.count
```

のようにプログラムの参加者数を数えることができます。
しかし、JOINを用いた複雑なクエリが発行されてしまうので、パフォーマンスが悪くなります。

リンクテーブル申し込み(entry)を数えても同じことなので下のようにも書くことができます。

```ruby
program.entries.count
```
リンクテーブルの数を数えるようにすると複雑なクエリが発行されなくなり、パフォーマンスが向上します。

### N+1問題を解決する

上記のコードだとプログラムごとに申込者数を数えているので「N+1問題」が存在します。
下のコードによって一回のクエリでそれぞれのプログラムの申込者数を取得できるようになります。

```ruby
Program.joins(:entries)
  .select("programs.*, COUNT(entries.id) AS number_of_applicants")
  .group("programs.id")
```
joinsの引数には関連付けの名前を指定します。テーブル名ではありません。
selectメソッドの引数にはテーブルから値を取得するカラムを指定します。
SQLの関数COUNTは引数に指定したカラムの値がNULLでないレコードの個数を返します。
ASは左辺の値に名前をつけるので、number_of_applicantsというカラムとしてentriesテーブルのレコード数を取得することができます。
groupメソッドは引数に指定したカラムを基準にレコードをグループに分けます。COUNTのような集計関数をselectに指定した場合は原則として呼び出しが必須です。グループに分けたことにより、program_idごとのentriesの数を取得できます。

```ruby
program[:number_of_applicants]
```
上のように参照できます。

### テーブルの左外部結合

普通にjoinsメソッドでテーブルを結合すると、結合される側のテーブルからまったく参照されていないレコードが検索結果から除外されてしまいます。つまり、一件以上申し込みのあったプログラムのみしか検索されません。

```ruby
left_joins(:entries)
```
joinsをleft_joinsで書き換えると参照されていないレコードも残るようになります。



## 続き
続きの記事のURLも順次追加していきます。

[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)