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

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

## 概要
この記事は[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)
[Ruby on Rails6 実践ガイド[機能拡張編] cp3~cp6 【メモ】](https://qiita.com/nizi24/items/36da7b89d561c19af30f)

# 機能拡張編　Chapter 7 複雑なフォーム

### before_validationにメソッドを指定する

```ruby
before_validation :set_application_start_time

private def set_application_start_time
# ロジックは省略
end
```
before_validationは引数にシンボルを与えると対応するメソッドを実行します。

面白いコードも多くありましたが、汎用的な情報に切り出しにくかったのでこの章は省略します。
また読み直したいと思います。

# 機能拡張編 Chapter 8 トランザクションと排他的ロック

並列で走る複数の処理の結果が、順序やタイミングによって想定外の結果をもたらすことを<strong>レースコンディション</strong>といいます。
データベース処理におけるレースコンディションは、<strong>排他的ロック</strong>をうまく利用することで解決できます。

下のコードはプログラムへの申し込みを行う処理を行っています。

```ruby
def accept(program)
  ActiveRecord::Base.transaction do
    program.lock!
    if max = program.max_number_of_paticipants
      if program.entries.where(canceled: false).count < max
        program.entries.create!(customer: @customer)
        return :accepted
      else
        return :full
      end
    else
      program.entries.create!(customer: @customer)
      return :accepted
    end
  end
end
```
※コントローラーの処理をサービスオブジェクトのクラスのインスタンスメソッドに移したコードです。
※returnでシンボルをコントローラーに返しています。コントローラー側では戻り値によって処理を分岐させています。

上のコードでは、最大参加者数が定義されているか、参加者数が最大に達しているかで処理を分岐させています。
最大参加者数ギリギリの状態で同時に申しこみが行われた場合、参加者数がオーバーしてしまう恐れがあります。
lock!を使うとそのオブジェクトのテーブルレコードに対して排他的ロックを取得します。排他的ロックを取得するにはトランザクションが開始されている必要があります。
transactionブロックの中で例外が発生するとすべての処理がロールバックします。データベースへの保存に失敗した場合、例外が発生するようにsave!やcreate!を使う必要があります。

# 機能拡張編　Chapter 9 フォームの確認画面

### 確認画面の実装
確認画面で更新ボタンをクリックすればデータベースに保存され、訂正ボタンをクリックすると入力フォームに戻るような確認画面を実装します。

### confirmアクション

```ruby
# PATCH
def confirm
  @confirm_form = Customer::AccountForm.new(current_customer)
  @customer_form.assign_attributes(params[:form])
  if @customer_form.valid?
     render action: "confirm"
  else
    flash.now.alert = "入力に誤りがあります。"
    render action: "edit"
  end
end
```
※Customer::AccountFormはフォームオブジェクトのクラスです。
データベースへの保存はせずにバリデーションだけ行っています。

### confirmアクションのビューテンプレート

```haml:_confirm.html.haml
= form_with model: @customer_form, scope: "form",
    url: :customer_account do |f|
  = f.label :family_name
  .field-value.readonly
    = @customer_form.customer.send(family_name)
  = f.hidden_field :family_name
  = f.submit "更新"
  = f.submit "訂正", name: "correct"
    
```
サーバーに送信する情報はhidden_fieldに隠してます。

### updateアクション

```ruby
def update
  @confirm_form = Customer::AccountForm.new(current_customer)
  @customer_form.assign_attributes(params[:form])
  if params[:commit]
    if @customer_form.save
       flash.notice = "アカウント情報を更新しました。"
       redirect_to :customer_account
    else
      flash.now.alert = "入力に誤りがあります。"
      render action: "edit"
    end
  else
    render action: "edit"
  end
end
```
パラメーターにcommitという値がセットされているかどうかで訂正ボタンか更新ボタンのどっちが押されたのか判定しています。

```haml
  = f.submit "更新"
  = f.submit "訂正", name: "correct"
```
submitメソッドにはnameオプションを与えることができます。デフォルトではcommitです。更新ボタンが押されると"commit"というキーのパラメータが、訂正ボタンが押されると"correct"というキーのパラメータがupdateアクションにわたります。

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

[Ruby on Rails6 実践ガイド [機能拡張編] cp10~cp12 【メモ】](https://qiita.com/nizi24/items/2ec4ed513fce22a89222)

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