🍰

【 Ruby on Rails 】ER図からアソシエーションまでの流れを視覚的にアプローチする工夫

2024/08/22に公開

序章

こんにちは、プログラミングスクールでRuby on Railsを学習しているでぃんぐーと申します。
プログラミング学習を始めた頃、ER図からアソシエーションの構築までの過程でかなり混乱した経験があります。そこで、私が考案した視覚的な工夫をわかりやすく説明します。

この記事を通じて、ER図の作成からRailsのアソシエーション構築までの流れを、視覚的かつ段階的に理解できるようになることを目指します。

この記事で学べること

  1. リレーションとカーディナリティの基礎
  2. ER図作成までの準備
  3. マイグレーションファイルの作成を考えずに行う方法 & 理解
  4. モデルファイルでのアソシエーション設定を考えずに行う方法

0. リレーション、カーディナリティーについて

まずは基礎知識として、リレーションとER図について理解しましょう。

  • リレーション:データベース内のテーブル間の関係性のこと。例えば、ユーザーと投稿の関係など。
  • カーディナリティ:テーブル間の関係の数量的な表現。「一対一」「一対多」「多対多」などがある。

以下の記事が非常に参考になります:

本記事では、クローの足記法(烏足記法)を用いたER図を使用します。この記法は、テーブル間の関係を直感的に表現できる特徴があります。

1. ER図を書くまでの準備

このセクションでは、ER図作成の準備として、テーブル間のリレーションを段階的に整理する方法を学びます。

今回用意するテーブルは以下の4つです:

  • Event(イベント)
  • User(ユーザー)
  • Comment(コメント)
  • Schedule(スケジュール)

ステップ1: リレーションを日本語で考える

ER図を作成する前の準備として、まずはテーブル間のリレーションを日本語で考えます。

例:

▼ 1. User と Comment

 ・ User は Comment を多数所持できる
 ・ Comment は User に紐づく

▼ 2. Event と User

 ・ Event は User を多数所持できる
 ・ User は Event に紐づく

▼ 3. Event と Schedule

 ・ Event は Schedule を多数所持できる
 ・ Schedule は Event に紐づく

▼ 4. User と Schedule

 ・ User は Schedule を多数所持できる
 ・ Schedule は User を多数所持できる

このように、各テーブルを主語にして二通りの文を書きます。ここで注意すべき点は:

  • ⭐ 両方が「多数所持できる」場合は「多対多」となります
  • ⭐ それ以外の場合は「一対多」または「一対一」となります

💡 「多対多」の関係では、中間テーブル/モデルが必要になります。例えば、User と Schedule との関係は以下のように書き直します:

▼ 4. User と Schedule

 ・ User は UserSchedule を通して Schedule を多数所持できる
 ・ Schedule は UserSchedule を通して Users を多数所持できる

中間テーブル/モデル を主語にした関係も書きます。

▼ 5. UserSchedule と User, Schedule

 ・ UserSchedule は User に紐づく
 ・ UserSchedule は Schedule に紐づく

❗️ これにより、中間テーブルを含めたテーブル数は5つになりました。

ステップ2: リレーションを記号で表す

次に、日本語で表したリレーションを記号で表現します。使用する記号は、+-o の5種類です。
これにより、文字列でクローの足記法(烏足記法)を表現します。

例えば、以下の場合

User は Post を複数所持している
Post は User に紐づく

↓↓↓↓

User ++----o∈ Post
Post ∋o----++ User
  • 「複数所持している」の場合、 +----∈の向き
  • 「紐づく」の場合、「複数所持している」の向きと逆に書けばよい(∋----+)
  • 主語のモデルは左に配置

このように変換します。因みに、これは一対多の関係です。

📎ごちゃごちゃと説明しましたが、「一対多」、「一対一」の場合は難しくは考えずに
一方を正しく用意できたらもう一方は鏡のように反転させれば大丈夫です。
User ++----o∈ Post | Post ∋o----++ User

この記号表現を用いて、先ほどのリレーションを書き直します。また、後でER図と見比べやすいように番号を振っておきます。このとき、1つのリレーションに対して主語を入れ替え、二通りで書いてみます。

▼ 1. User と Comment

 ・ User ++--①--o∈ Comment
 ・ Comment ∋o--①--++ User

▼ 2. Event と User

 ・ Event ++--②--o∈ User
 ・ User ∋o--②--++ Event

▼ 3. Event と Schedule

 ・ Event ++--③--o∈ Schedule
 ・ Schedule ∋o--③--++ Event

▼ 4. User と Schedule

 ・ User ++--④--o∈ UsersSchedule ∋o--⑤--++ Schedule 
 ・ Schedule ++--⑤--o∈ UsersSchedule ∋o--④--++ User

▼ 5. UserSchedule と User, Schedule

 ・ UserSchedule ∋o--④--++ User
 ・ UserSchedule ∋o--⑤--++ Schedule

ステップ3: ER図を作成する

ここまでの準備ができたら、実際にER図を作成します。

ER図

ビジュアル解説:

  • 矢印の先は「1」を表し、矢印の元は「多」を表します。
  • 例えば、User と Comment の関係では、1人のユーザーが多数のコメントを持つことを示しています。
  • UserSchedule は中間テーブルで、User と Schedule の多対多の関係を仲介しています。

2. マイグレーションファイルを作成する

このセクションでは、ER図をもとにRailsのマイグレーションファイルを作成する方法を学びます。

例として、User モデルと User テーブルを作成するコマンドを見てみましょう:

rails g model User name:string events:references

または

rails g migration CreateUsers name:string events:references

注意点:

  • references は、関連先のモデルがある際に使用します。これにより、外部キーが自動的に設定されます。
  • g model コマンドではモデルファイルも作成されますが、テーブルだけを作成したい場合は g migration コマンドを使用します。

→ 関連先のモデルの確認方法は、そのモデルが主語になっているリレーション(先ほど作成したもの)で確認できます。そのモデルが主語となっているものを書き出します。

User ++--①--o∈ Comment
User ∋o--②--++ Event
User ++--④--o∈ UserSchedule ∋o--⑤--++ Schedule

Userモデル が主語となっているものを書き出しました。
ここで、主語のモデルの直後に 記号がついているリレーションを探します。今回は User ∋o--②--++ Eventが該当します。そのため eventsreferences に指定してコマンドを実行しました。

作成されるマイグレーションファイルは以下のようになります:

class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.references :events, null: false, foreign_key: true
      t.string :name, null: false
      t.timestamps
    end
  end
end

ここで使用されているオプションの意味を簡単に説明します:

  • null: false: そのカラムがNULL値を許容しないことを示します。つまり、必ず値を入れなければならないフィールドです。
  • foreign_key: true: 外部キー制約を設定します。これにより、関連するテーブルのデータの整合性が保たれます。

中間テーブル(例:UserSchedules)の作成は以下のように行います:

rails g model UserSchedules user:references schedule:references status:integer

必要に応じて、ユニークインデックスを追加することもできます:

add_index :user_schedules, [:user_id, :schedule_id], unique: true

(ユニークインデックスを追加した場合、以下のようになります)

class CreateUserSchedules < ActiveRecord::Migration[7.1]
  def change
    create_table :user_schedules do |t|
      t.references :user, null: false, foreign_key: true
      t.references :schedule, null: false, foreign_key: true
      t.integer :status
      t.timestamps
    end
    add_index :user_schedules, [:user_id, :schedule_id], unique: true
  end
end

実践的ヒント:

  • マイグレーションファイルを作成したら、必ず内容を確認し、必要に応じて修正しましょう。
  • rails db:migrate コマンドを実行する前に、rails db:migrate:status で現在のマイグレーションの状態を確認するのが良いでしょう。

すべてのマイグレーションファイルを作成したら、マイグレーションを実行してテーブルを作成します。

3. モデルファイルにアソシエーションを書く

最後に、モデルファイルにアソシエーションを記述します。この過程が最も難しく感じられますが、ここまでの準備があれば比較的簡単に行えます。

例えば、Userモデルで考えてみましょう。
まずは Userモデル が主語となっているものを書き出します。

User ++--①--o∈ Comment
User ∋o--②--++ Event
User ++--④--o∈ UserSchedule ∋o--⑤--++ Schedule

このとき、User モデルのアソシエーションは以下のように記述できます:

class User < ApplicationRecord
  # User ++--①--o∈ Comment
  has_many :comments, dependent: :destroy
  
  # User ∋o--②--++ Event
  belongs_to :event

  # User ++--④--o∈ UserSchedule ∋o--⑤--++ Schedule
  has_many :user_schedules, dependent: :destroy
  has_many :schedules, through: :user_schedules
end

ここでのポイント:

  • リレーション番号の個数とアソシエーションの個数は一致します。
  • 記号表現を参考に、アソシエーションの種類(has_many, belongs_to, has_many :through)を決定します。
  • 主語のモデル直後に 記号がある場合は belongs_to を使用します。
  • それ以外の場合は has_many または has_one を使用します。
  • dependent: :destroy 記号があるモデルのアソシエーションに対して使用できます。
  • 中間モデルを介すアソシエーションは through: :user_schedulesなどのように書きます。

注意点: は向きが大事なので間違えないようにしましょう。

コード解説:

  • has_many :comments, dependent: :destroy: ユーザーが多数のコメントを持ち、ユーザーが削除されたときにそのコメントも削除されることを示します。
  • belongs_to :event: ユーザーが1つのイベントに紐づくことを示します。
  • has_many :user_schedules, dependent: :destroy: ユーザーが多数のユーザースケジュールを持つことを示します。
  • has_many :schedules, through: :user_schedules: ユーザースケジュールを介して多数のスケジュールとつながることを示します。

ポイントを踏まえ、中間モデルのアソシエーションも確認してみましょう。

UserSchedule モデルが主語となっているリレーションを書き出します。

UserSchedule ∋o--④--++ User
UserSchedule ∋o--⑤--++ Schedule
class UserSchedule < ApplicationRecord
  # 中間モデル

  # UserSchedule ∋+--④--++ User
  #              ∋+--⑤--++ Schedule
  belongs_to :user
  belongs_to :schedule

  # ユニークバリデーション
  validates :user_id, uniqueness: { scope: :schedule_id }
end

コード解説:

  • belongs_to :userbelongs_to :schedule: UserScheduleが1つのユーザーと1つのスケジュールに紐づくことを示します。
  • validates :user_id, uniqueness: { scope: :schedule_id }: 同じユーザーと同じスケジュールの組み合わせが重複して登録されないようにします。

他のモデルも同様に記述していきます。

まとめ

本記事では、ER図からアソシエーションの構築までの流れを、視覚的なアプローチを用いて説明しました。

  • わかりにくい箇所、覚えにくい箇所を、視覚的な表現で工夫しました。
  • 文字や記号を使用することで、シンプルかつ効果的な表現を目指しました。

これらの手法を活用することで、複雑に見えるデータベース設計とモデル間の関係をより直感的に理解し、実装することができると考えております。

プログラミング学習の中で躓きやすいこの部分が、少しでも理解に繋がれば幸いです。

Q&A (AIより)

❓Q1: 中間テーブルが必要な理由は?
💡A1: 中間テーブルは「多対多」の関係を表現するために必要。例えば、1人のユーザーが複数のスケジュールを持ち、1つのスケジュールに複数のユーザーが参加できる場合、中間テーブルを使用してこの関係を表現する。

❓Q2: dependent: :destroy って何?
💡A2: これは親レコードが削除されたとき、関連する子レコードも一緒に削除する設定のこと。例えば、ユーザーを削除したら、そのユーザーのコメントも全て削除されるようにできる。

❓Q3: validates :user_id, uniqueness: { scope: :schedule_id } の意味は?
💡A3: これは、ユーザーIDとスケジュールIDの組み合わせが重複しないようにしています。すなわち、同じユーザーが同じスケジュールに二回以上登録できないようにしている。scope: :schedule_id は、この一意性チェックをschedule_idの範囲内で行うという意味。

❓Q5: アソシエーション設定でよくやらかすミスは?
💡A5: よくあるミスは....:

  • 関連名の単数形・複数形の間違い。例えば、has_many :users が正しいが、 has_many :user としてしまうなど。
  • 中間テーブルを使用する際の has_many :through の設定忘れ。
  • 外部キーの指定ミス。例えば、belongs_to :user の場合は user_id カラムが必要。
  • dependent オプションの適切な使用。使用すべき場合と使用しない方が良い場合の使い分け。

❓Q6: この記事の内容の次はのステップは?
💡A6: 次のステップとしては...:

  1. ポリモーフィック関連などの複雑な関連。
  2. データベースのインデックスやパフォーマンスチューニングについて。
  3. Active Recordのコールバックやバリデーションについて。

Discussion