0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-06-10

はじめに

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

概要

この記事は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については、前の章の記事にも書いています。

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です。

#使用するモデルで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 実践ガイド

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?