Rails Composite Primary Keysの table migration

# Rails Composite Primary Keysの table migration

## 備忘録

あんまり個人的には利用していないComposite Keyのサンプルにdatabaseの記載がすくなかったため、備忘録として記載。複合Keyあんまり使うケースがないんだよなと思いつつ。

## 0. 環境

| name | version |
| :-- | :-- |
| rails | 7.2.1.1 |
| mysql | 8.4 |

## 1. Model構成

[Rails Guide](https://guides.rubyonrails.org/active_record_composite_primary_keys.html#associations-between-models-with-composite-primary-keys)をベース作成します。
Bookじゃなくて、OrderItemにしましたが。

```mermaid
classDiagram
class Order{
    +Int shop_id
    +Int id
    +String status
}

class OrderItem {
    +String title
    +Int order_id
    +Int order_shop_id
}
Order "1" --> "*" OrderItem
```

```terminal
$ rails g model Order shop_id:integer id:integer status:string --skip-migration
$ rails g model OrderItem title:string order_id:integer order_shop_id:integer --skip-migration
Loading development environment (Rails 7.2.1.1)
```

```Order.rb
class Order < ApplicationRecord
end
```

```order_item.rb
class OrderItem < ApplicationRecord
end
```

## 2. Migrationの生成

[Rails Guide](https://guides.rubyonrails.org/active_record_composite_primary_keys.html#associations-between-models-with-composite-primary-keys)上の言及にもとづいてModelを生成します。

However, if the above requirements are not met or you would like to use the full composite primary key in associations, you can set the foreign_key: option on the association. This option specifies a composite foreign key on the association; all columns in the foreign key will be used when querying the associated record(s).

[Rails Guide edege](https://edgeguides.rubyonrails.org/active_record_migrations.html#composite-primary-keys)の内容をもとに記載します。

```mermaid
erDiagram
    orders {
        int shop_id PK
        int id PK
        status string
        created_at datetime
        updated_at datetime
    }
    order_items {
        int id PK
        varchar(255) title
        int order_id FK
        int order_shop_id FK
        created_at datetime
        updated_at datetime
    }
    orders |o--o{ order_items : has_many
```

### 2-1. orders tableの生成

```terminal
$ rails generate migration CreateOrder shop_id:integer id:integer status:string
```

```XXX_create_order.rb
class CreateOrder < ActiveRecord::Migration[7.2]
  def change
    create_table :orders do |t|
      t.integer :shop_id
      t.integer :id
      t.string :status

      t.timestamps
    end
  end
end
```

上記の内容を[Rails Guide edege](https://edgeguides.rubyonrails.org/active_record_migrations.html#composite-primary-keys)に基づいて修正します。

```XXX_create_order.rb
class CreateOrder < ActiveRecord::Migration[7.2]
  def change
    create_table :orders, primary_key: [:id, :shop_id] do |t|
      t.integer :shop_id
      t.integer :id
      t.string :status

      t.timestamps
    end
  end
end
```

## 2-2. order_items tableの生成

```terminal
$ rails g migration CreateOrderItem title:string order_id:integer order_shop_id:integer orders:references
```

```XXX_create_order_item.rb
class CreateOrderItem < ActiveRecord::Migration[7.2]
  def change
    create_table :order_items do |t|
      t.string :title
      t.integer :order_id
      t.integer :order_shop_id
      t.references :orders, null: false, foreign_key: true

      t.timestamps
    end
  end
end
```


```XXX_create_order_item.rb
class CreateOrderItem < ActiveRecord::Migration[7.2]
  def change
    create_table :order_items do |t|
      t.string :title
      t.integer :order_id, null: false
      t.integer :order_shop_id, null: false
      t.foreign_key :orders, primary_key: [:id, :shop_id]

      t.timestamps
    end
  end
end
```

## 3. 確認

[Guide](https://guides.rubyonrails.org/active_record_composite_primary_keys.html#associations-between-models-with-composite-primary-keys)に記載の通り、少し癖があるのですが、rails consoleを利用して、確認していきます。

```terminal
% rails c
api(dev)> Order.create(id:[ 1, 1], status: "a")
api(dev)> o = Order.first
api(dev)> o.order_items.create(title: "title", order_id: o.id, order_shop_id: o.shop_id)
```