【Rails】単一テーブル継承(STI)について

# 単一テーブル継承(STI)とは
STI(Single Table Inheritance)

同じカラム設計のテーブルを、一つのテーブルにまとめて、継承することで余計なテーブルを増やさず、DRYなテーブル設計にするというもの。
(テーブルが多いですねw)

考え方はクラスの**継承**と同じ!!

# 例

## STI不使用(通常テーブル設計)

![STI不使用.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562358/1dffb357-df02-d042-05a7-42a3e86f6549.png)

`Authors、categorys、tags`3つとも同じカラム設計なのにテーブルをそれぞれ作成しているのは可読性が下がるし、無駄。


## STI使用

![STI使用.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562358/d72b0d01-d52a-74df-1580-696ac244f53d.png)


図にも書いてある通り、authors、category、tagsは**擬似テーブル**であり、DBには実在しないテーブルになります。
実在しないので、データは全て**taxonomies**に保管されます。

この図はDBレベルでの相関図です。
Modelレベルでは、Articles.rbなどの記述を見るとわかるがAuthor、Category、Tag(Article tagsを介して)と直接アソシエーションが組まれています。
ArticleとTaxonomyは各Modelファイルを見ると<font color="Red">**アソシエーションは組まれていない**</font>ことがわかります。

もう一度言いますがこれは<font color="Red">**DBレベルの相関図**</font>です。
DBとModelは切り分けて考えないと必ず混乱します。

# Model

```ruby:article.rb
class Article < ApplicationRecord

  belongs_to :category
  belongs_to :author

  has_many :article_tags, dependent: :destroy
  has_many :tags, through: :article_tags
end
```

```ruby:taxonomies.rb
class Taxonomy < ApplicationRecord

end
```

```ruby:author.rb
class Author < Taxonomy
  has_many :articles
end
```

```ruby:category.rb
class Category < Taxonomy
  has_many :articles
end
```

```ruby:tag.rb
class Tag < Taxonomy
  has_many :article_tags
  has_many :articles, through: :article_tags
end
```

擬似テーブル3つは継承元がApplicationRecordを継承した**Taxonomy**になっている点に注意！！

# Active Record

```
[1] pry(main)> Article.joins(:tags).where(tags: { id: 1 }).first
  Article Load (3.5ms)  SELECT  `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxo
nomies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1 ORDER BY `articles`.`id` ASC LIMIT 1
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'tags.id' in 'where clause': SELECT  `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_ta
gs`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxonomies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1 ORDER
 BY `articles`.`id` ASC LIMIT 1
```

```
[3] pry(main)> Article.joins(:tags).where(tags: { id: 1 })
  Article Load (2.0ms)  SELECT `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxon
omies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1
  Article Load (0.6ms)  SELECT  `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxo
nomies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1 LIMIT 11
=> #<Article::ActiveRecord_Relation:0x3fe90b9879dc>
```

ここでのポイントは**エラーが出力されているか**ということ。

なぜかというと、`where`は条件通りに絞り込んではくれても使用されるまでは実行されないため、エラーが出力されない。

エラー文に**WHERE 'tags'**の文字がある。
これはtagsテーブルからという条件のもと検索をかけているのだが、単一テーブルを継承したテーブルは擬似テーブルであり、実在しない。
実在しないテーブルから検索するのは無理なのでエラーが出ている。

```
[6] pry(main)> Article.joins(:tags).where(taxonomies: { id: 1 })
  Article Load (9.0ms)  SELECT `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxon
omies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `taxonomies`.`id` = 1
=> []
```
これでok


ちなみにどういうSQLが走るかだけ見たい時は`to.sql`を使う。

```
[7] pry(main)> Article.joins(:tags).where(tags: { id: 1 }).to_sql
=> "SELECT `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxonomies`.`id` = `artic
le_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1"
```

テーブル周り弱すぎなのでSQLに励みます。