【 Ruby on Rails 】ER図からアソシエーションまでの流れを視覚的にアプローチする工夫
序章
こんにちは、Ruby on Railsを中心に学習しているでぃんぐーと申します。
Webアプリケーションでの設計から実装を始めた頃、ER図からアソシエーションの構築までの過程でかなり混乱した経験があります。そこで、私が考案した視覚的な工夫をわかりやすく説明します。
この記事を通じて、ER図の作成からRailsのアソシエーション構築までの流れを、視覚的かつ段階的に理解できるようになることを目指します。
この記事で学べること
- ER図作成までの準備
- マイグレーションファイルの作成を考えずに行う工夫 & 理解
- モデルファイルでのアソシエーション設定を考えずに行う工夫
リレーションとカーディナリティに関しては前提知識として扱います。サクッと確認する程度にとどめておきます。
- リレーション:データベース内のテーブル間の関係性のこと。例えば、ユーザーと投稿の関係など。
- カーディナリティ:テーブル間の関係の数量的な表現。「一対一」「一対多」「多対多」などがある。
以下の記事が非常に参考になります:
本記事では、クローの足記法(烏足記法)を用いたER図を使用します。この記法は、テーブル間の関係を直感的に表現できる特徴があります。
1. ER図を書くまでの準備
このセクションでは、ER図作成の準備として、テーブル間のリレーションを段階的に整理する方法を学びます。
今回はイベントスケジュール調整のアプリケーションを例に考えます。
- イベントページごとに日付が複数が存在する
- イベントごとに複数のユーザーが存在する
- ユーザーは複数の日付を登録できる
- 日付は複数のユーザーを登録できる
- ユーザーごとに複数のコメントが存在する
ここで、現段階で必要そうなテーブルを書いてみましょう:
- Event(イベント)
- User(ユーザー)
- Comment(コメント)
- Date(日付)
中間テーブルがないじゃあないか!!! と思うかもしれません。正解です。
しかし、今回のフローでは中間テーブル作成の見落としがある場合でも途中で気づくことができるため、一旦このまま進めまてみます。
ステップ1: リレーションを日本語で考える
ER図を作成する前の準備として、まずはテーブル間のリレーションを日本語で考えます。
例:
▼ 1. User と Comment
・ User は Comment を多数所持できる
・ Comment は User に紐づく
▼ 2. Event と User
・ Event は User を多数所持できる
・ User は Event に紐づく
▼ 3. Event と Date
・ Event は Date を多数所持できる
・ Date は Event に紐づく
▼ 4. User と Date
・ User は Date を多数所持できる
・ Date は User を多数所持できる
このように、各テーブルを主語にして二通りの文を書きます。ここで注意すべき点は:
- ⭐ 両方が「多数所持できる」場合は「多対多」となります
- ⭐ それ以外の場合は「一対多」または「一対一」となります
💡 「多対多」の関係では、中間テーブル/モデルが必要になります。ここで中間テーブルが不足していることに気づけます。中間テーブル UserDate を用意し、User と Date との関係は以下のように書き直します:
▼ 4. User と Date
・ User は UserDate を通して Date を多数所持できる
・ Date は UserDate を通して Users を多数所持できる
中間テーブル/モデル を主語にした関係も書きます。
▼ 5. UserDate と User, Date
・ UserDate は User に紐づく
・ UserDate は Date に紐づく
❗️ これにより、中間テーブルを含めたテーブル数は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 と Date
・ Event ++--③--o∈ Date
・ Date ∋o--③--++ Event
▼ 4. User と Date
・ User ++--④--o∈ UserDate ∋o--⑤--++ Date
・ Date ++--⑤--o∈ UserDate ∋o--④--++ User
▼ 5. UserDate と User, Date
・ UserDate ∋o--④--++ User
・ UserDate ∋o--⑤--++ Date
ステップ3: ER図を作成する
ここまでの準備ができたら、必要なカラムもつけ、実際にER図を作成します。
ビジュアル解説:
- 矢印の先は「1」を表し、矢印の元は「多」を表します。
- 例えば、User と Comment の関係では、1人のユーザーが多数のコメントを持つことを示しています。
- UserDate は中間テーブルで、User と Date の多対多の関係を仲介しています。
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∈ UserDate ∋o--⑤--++ Date
Userモデル が主語となっているものを書き出しました。
ここで、主語のモデルの直後に ∋
の向きの記号がついているリレーションを探します。
今回は User ∋o--②--++ Event
が該当します。そのため events
を references
に指定してコマンドを実行しました。
これを踏まえて、もう一度見くらべます。
User ∋o--②--++ Event
rails g model User name:string events:references
コマンド実行後、作成されるマイグレーションファイルは以下のようになります:
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
: 外部キー制約を設定します。これにより、関連するテーブルのデータの整合性が保たれます。
中間テーブル(例:UserDates)の作成は以下のように行います:
rails g model UserDates user:references date:references status:integer
必要に応じて、ユニークインデックスを追加することもできます:
add_index :user_dates, [:user_id, :date_id], unique: true
(ユニークインデックスを追加した場合、以下のようになります)
class CreateUserDates < ActiveRecord::Migration[7.1]
def change
create_table :user_dates do |t|
t.references :user, null: false, foreign_key: true
t.references :date, null: false, foreign_key: true
t.integer :status
t.timestamps
end
add_index :user_dates, [:user_id, :date_id], unique: true
end
end
実践的ヒント:
- マイグレーションファイルを作成したら、必ず内容を確認し、必要に応じて修正しましょう。
-
rails db:migrate
コマンドを実行する前に、rails db:migrate:status
で現在のマイグレーションの状態を確認するのが良いでしょう。
すべてのマイグレーションファイルを作成したら、マイグレーションを実行してテーブルを作成します。
3. モデルファイルにアソシエーションを書く
最後に、モデルファイルにアソシエーションを記述します。この過程が最も難しく感じられますが、ここまでの準備があれば比較的簡単に行えます。
例えば、Userモデルで考えてみましょう。
まずは Userモデル が主語となっているものを書き出します。
User ++--①--o∈ Comment
User ∋o--②--++ Event
User ++--④--o∈ UserDate ∋o--⑤--++ Date
このとき、User モデルのアソシエーションは以下のように記述できます:
class User < ApplicationRecord
# User ++--①--o∈ Comment
has_many :comments, dependent: :destroy
# User ∋o--②--++ Event
belongs_to :event
# User ++--④--o∈ UserDate ∋o--⑤--++ Date
has_many :user_dates, dependent: :destroy
has_many :dates, through: :user_dates
end
ここでのポイント:
- リレーション番号の個数とアソシエーションの個数は一致します。
- 記号表現を参考に、アソシエーションの種類(has_many, belongs_to, has_many :through)を決定します。
- 主語のモデル直後に
∋
記号がある場合はbelongs_to
を使用します。 - それ以外の場合は
has_many
またはhas_one
を使用します。 -
dependent: :destroy
は∈
記号があるモデルのアソシエーションに対して使用できます。 - 中間モデルを介すアソシエーションは
through: :user_dates
などのように書きます。
注意点: ∋
、∈
は向きが大事なので間違えないようにしましょう。
コード解説:
-
has_many :comments, dependent: :destroy
: ユーザーが多数のコメントを持ち、ユーザーが削除されたときにそのコメントも削除されることを示します。 -
belongs_to :event
: ユーザーが1つのイベントに紐づくことを示します。 -
has_many :user_dates, dependent: :destroy
: ユーザーが多数のユーザー日付を持つことを示します。 -
has_many :dates, through: :user_dates
: ユーザー日付を介して多数の日付とつながることを示します。
ポイントを踏まえ、中間モデルのアソシエーションも確認してみましょう。
UserDate モデルが主語となっているリレーションを書き出します。
UserDate ∋o--④--++ User
UserDate ∋o--⑤--++ Date
class UserDate < ApplicationRecord
# 中間モデル
# UserDate ∋+--④--++ User
# ∋+--⑤--++ Date
belongs_to :user
belongs_to :date
# ユニークバリデーション
validates :user_id, uniqueness: { scope: :date_id }
end
コード解説:
-
belongs_to :user
とbelongs_to :date
: UserDateが1つのユーザーと1つの日付に紐づくことを示します。 -
validates :user_id, uniqueness: { scope: :date_id }
: 同じユーザーと同じ日付の組み合わせが重複して登録されないようにします。
他のモデルも同様に記述していきます。
まとめ
本記事では、ER図からアソシエーションの構築までの流れを、視覚的なアプローチを用いて説明しました。
- わかりにくい箇所、覚えにくい箇所を、視覚的な表現で工夫しました。
- 文字や記号を使用することで、シンプルかつ効果的な表現を目指しました。
これらの手法を活用することで、複雑に見えるデータベース設計とモデル間の関係をより直感的に理解し、実装することができると考えております。
プログラミング学習の中で躓きやすいこの部分が、少しでも理解に繋がれば幸いです。
Discussion