SQLアンチパターンon Rails Ⅰ部（論理設計）

# はじめに
SQLアンチパターンをRailsの文脈で読み解いていきます。
この記事では、Ⅰ部 データベース論理設計のアンチパターン（1~8章）を取り上げます。

# リンク
- [SQLアンチパターンon Rails Ⅱ部（物理設計）](https://qiita.com/eduidl/items/d535a25cd02c9007da29)
- [SQLアンチパターンon Rails Ⅲ部（クエリ）](https://qiita.com/eduidl/items/f6383517dea9ad13c6ce)
- [SQLアンチパターンon Rails Ⅳ部（アプリケーション開発）](https://qiita.com/eduidl/items/c39e9df63c990196fcdd)

# 本文

## 1. ジェイウォーク（信号無視）
```rb
class CreateProducts < ActiveRecord::Migration[5.1]
  def change
    create_table :products do |t|
      t.string :name, null: false
      t.string :account_ids
    end
  end
end
```

|name|account_ids|
|:-:|:-:|
|"Visual TurboBuilder"|"12,34"|

みたいなことは誰もしませんよね。

```rb
class Product < ApplicationRecord
  has_many :product_accounts
  has_many :accounts, through: :product_accounts
end

class ProductAccount < ApplicationRecord
  belongs_to :product
  belongs_to :account
end
```

交差テーブル作って`has_many`するだけ。

## 2. ナイーブツリー（素朴な木）
隣接リストは遅いという話。
この辺りのgemを使うと良さそう。

- [ancestry](https://github.com/stefankroes/ancestry)（経路列挙モデル）
- [awesome_nested_set](https://github.com/collectiveidea/awesome_nested_set) （入れ子集合モデル）
- [closure_tree](https://github.com/ClosureTree/closure_tree)（閉包テーブルモデル）

## 3. ID リクワイアド（とりあえず ID）
主張は大きく2つに分かれると思います。

- 自然キーや複合キーも使おう
- 代理キー使うにしても、`id`ではなくもう少しわかりやすい名前をつけましょう

```rb
class CreateBugs < ActiveRecord::Migration[5.1]
  def change
    create_table :bugs, id: false do |t|
      ...
    end
  end
end
```

と`id: false`すれば、代理キーを作らないようにしたり、

```rb
class CreateBugs < ActiveRecord::Migration[5.1]
  def change
    create_table :bugs, primary_key: :bug_id do |t|
      ...
    end
  end
end
```

のようにして、代理キーの名前を変えることも可能です。
またその場合、以下のようにして、モデルの方でも使う主キーを明示的に書く必要があります。

```rb
class Bug < ApplicationRecord
  self.primary_key = 'bug_id'
end
```

[composite_primary_keys](https://github.com/composite-primary-keys/composite_primary_keys)というgemを使えば、複合主キーも扱えるようになるようです。

一応このように色々レールを外れる手段は用意されていますが、そこまでしてレールを逸脱するような旨味は感じられませんでした。

- どうせSQLなんてそこまで書かないから`USING`が使えても嬉しくない
- `id`と`bug_id`でそこまでわかりやすさが変わるとも思えない。寧ろそれで主キーと外部キーの区別がついてわかりやすいのでは？
 
私はそれより、とりあえず`timestamp`が気になります。

## 4. キーレスエントリ（外部キー嫌い）
`foreign_key: true`を書きましょう。

```rb
class CreateBugs < ActiveRecord::Migration[5.1]
  def change
    create_table :bugs do |t|
    end
  end
end

class CreateBugStatuses < ActiveRecord::Migration[5.1]
  def change
    create_table :bug_statuses do |t|
      t.references :bug, foreign_key: true, index: { unique: true }
    end
  end
end
```

```rb
class BugStatus < ApplicationRecord
  belongs_to :bug, optional: true
end
```

のように`optional: true`を使えば、`BugStatus`の`bug_id`はnull許容となります。`BugStatus.create`でレコード作成できます。
（`optional: true`を書かないと、`BugStatus.create`はエラー。
ただし、`dependent: nullify`として、`belongs_to`している`bug`を削除すれば、`bug_id`はnullにできる。）
nullも非許容にしたければ、`null: false`としておきましょう。


## 5. EAV（エンティティ・アトリビュート・バリュー）
```rb
class CreateIssueAttributes < ActiveRecord::Migration[5.1]
  def change
    create_table :issue_attributes do |t|
      t.references :issue, foreign_key: true
      t.string :attr_name, null: false
      t.string :attr_value
    end

    add_index :issue_attributes, [:issue_id, :attr_name], unique: true
  end
end
```

こんなこと誰もしませんよね。（2回目）
素直にカラムを追加するなどして対処しましょう。

## 6. ポリモーフィック関連
本文ではアンチパターンを用いても良い場合として、 ORMを用いる場合というのが挙げられています。
少なくとも、
> しかし、フレームワークを用いずに、手書きでポリモーフィック関連を実装するのは「車輪の再発明（Reinvesting the Wheel）」アンチパターン[BMMM98]です。無駄が多く、リスクの高い行為だと言わざるを得ません。　
（本文から引用）

よりは遥かにましなようです。
フレームワークとしてサポートしているので、使えばいいと私は思います。
（SQLを書こうとすると辛みがあるかもしれない？）

また、本書によると参照を逆にするとのことですが、あまりうまく書ける方法が思い浮かびませんでした。
例えば、[Railsガイドのポリモーフィック関連](https://railsguides.jp/association_basics.html#%E3%83%9D%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E9%96%A2%E9%80%A3%E4%BB%98%E3%81%91)の例は以下の通り。

```rb
class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end
 
class Employee < ApplicationRecord
  has_many :pictures, as: :imageable
end
 
class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end
```

それを本のやり方に従い、ポリモーフィック関連を使わないように書き換えてみます。
まず、交差テーブルを使う方法だと、下のようになります。

```rb
class Picture < ApplicationRecord
  has_one :employee_picture
  has_one :employee, through: :employee_picture
  has_one :product_picture
  has_one :product, through: :product_picture

  def imageable
    employee || product
  end

  # employee && product == falseとなることをvalidationで保証する必要があるが割愛
end

class EmployeePicture < ApplicationRecord
  belongs_to :employee
  belongs_to :picture
end
 
class Employee < ApplicationRecord
  has_many :employee_pictures
  has_many :pictures, through: :employee_pictures
end

class ProductPicture < ApplicationRecord
  belongs_to :product
  belongs_to :picture
end
 
class Product < ApplicationRecord
  has_many :product_pictures
  has_many :pictures, through: :product_pictures
end
```

また、共通の親テーブルだと下のようになります。

```rb
class Picture < ApplicationRecord
  belongs_to :imageable
  has_one :employee, through: :imageable
  has_one :product, through: :imageable

  def imageable
    employee || product
  end
end

class Imageable < ApplicationRecord
  has_one :picture
  has_one :employee
  has_one :product

  # employee && product == falseとなることをvalidationで保証する必要があるが割愛
end
 
class Employee < ApplicationRecord
  belongs_to :imageable
end

class Product < ApplicationRecord
  belongs_to :imageable
end
```

この二つなら共通の親テーブルの方が好きです。

## 7. マルチカラムアトリビュート（複数列属性）
タグを最大3つまで持つので、`tag_id1`, `tag_id2`, `tag_id3`とカラムを作るみたいな話。
こんなこと誰もしませんよね。（3回目）
やりたいことの本質は1章のジェイウォークのそれとかわらないので、解決策も同じ。
テーブル増やして、`has_many`

## 8. メタデータトリブル（メタデータ大増殖）
レコードが増えたからといって、`log_2017`, `log_2018`みたいにテーブルを分割してはいけない、という話。
解決策としては、DBの機能であるパーティショニングを使って、水平分割or垂直分割することになる。
DBによって様々だと思われる。
例えば、PostgreSQLだと、[Rails+PostgreSQLのパーティショニングを制覇する（翻訳）](https://techracho.bpsinc.jp/hachi8833/2017_12_05/48155)が参考になるかもしれない。

すみません。まだ大量のレコードを経験したことがないのでわかりません。

# おわり
こういう記事書きたいなと思っていて、いざ書き始めたが、悲しいことに知見が足りませんでした（特に8）。