[あやふや解消シリーズ]ORMについて[vol.1]

# TL;DR
- ORMとは、オブジェクト指向のプログラミング言語で関係データベースを扱えるようにうまく変換してくれるもの。
- ORMを利用する際には「N+1問題」に注意する必要がある。
- Railsでは基本的にはORマッパーにAciruveRecordを使用している

---

# 3行で分かるこの記事を書いた動機
```:
筆者:「Rails独学で勉強してます!」
????:「ORマッパーは何使ってる？」
筆者:「ﾐ°(学びの浅はかさを見破られ恥ずかしさのあまり舌をかみ切って死亡)」
```

# ORMってなんの略？
ORMとは、
__`Object-relational mapping`
(日:オブジェクト関係マッピング)__の略称です。

# ORMって何？
>
- ORMとは、アプリケーションが持つリッチなオブジェクトをリレーショナルデータベース(RDBMS)のテーブルに接続することです。
- オブジェクト関係マッピングは、オブジェクト指向言語からリレーショナルデータベースにアクセスする技術である。
- オブジェクト関係マッピングとは、データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。

>引用:
[Railsガイド/ 1.2 O/Rマッピング](https://railsguides.jp/active_record_basics.html)
[ORMは不快なアンチパターン/ ORMの仕組み](https://www.kaitoy.xyz/2015/09/13/orm-is-offensive-anti-pattern/)
[Wikipedia/ オブジェクト関係マッピング](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E9%96%A2%E4%BF%82%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0)

つまり、O/Rマッピングとは、__`オブジェクト指向プログラミング言語におけるオブジェクトとリレーショナルデータベース（RDB）の間でデータ形式の相互変換を行う機能`__のことを指す。


# ORMのメリット/デメリットは？
## メリット
### SQLを書かなくてもデータベースにアクセスできる
- 例1
  以下のようなデータベースから特定の名前を持つユーザの情報を検索したい場合、

>table name -> users
>>| id | name | sex |
|:--:|:--:|:--:|
| 1 | Alice | female |
| 2 | Bob | man |
| 3 | Chris | man |

```:
//MySQL
SELECT * FROM users WHERE name = 'Alice';

//ORM(例:ActiveRecord)
User.find_by(name: "Alice")
```

### 利用するSQLの種類によって生じる微妙な差異を吸収できる
- 例2
  以下のようなデータベースからpointが60以下のユーザの特定のカラムの数値を更新したい場合、

>table name -> users
>>| id | name | point |promotion| flag |
|:--:|:--:|:--:|:--:|:--:|
| 1 | Alice | 80 |1|0|
| 2 | Bob | 50 |1|0|
| 3 | Chris | 80 |1|0|

```:
//MySQL
UPDATE users SET promotion = 0, flag = 1 WHERE point <= 60;

//PostgreSQL
UPDATE users SET {promotion,flag} = {0,1} WHERE point <= 60;

//ORM(例:ActiveRecord)
User.where("point <= 60").update(promotion:0, flag:1)
```

## デメリット
### N+1問題によるパフォーマンスの悪化

> N+1問題とは、一覧を取得するSQLを発行してから、各要素ごとに個別のSQLを発行してしまうこと。
厳密には`1+N問題`と呼んだほうが、実態をより正確に表現できる
>>引用:
[SlideShare/ O/Rマッパーによるトラブルを未然に防ぐ/ 32](https://www.slideshare.net/kwatch/how-topreventorm-troubles)

N+1問題の怖いところは、問題を放置していても__`動きはする`__ことである。
必要以上にSQLクエリを走らせることになるため、データ量が増えるにつれパフォーマンスを低下させてしまう。

- 具体例

```:前提条件
UserとPostは1:Nの関係
# app/models/user.rb
class User < ActiveRecord::Base
  has_many :posts
end

# app/models/post.rb
class Post < ActiveRecord::Base
  belongs_to :user
end

コントローラではPostモデルのみ取得
# app/contollers/posts_controller.rb
  def index
    @posts = Post.all  # SELECT "posts".* FROM "posts" が発行される。実際にはViewで発行される
  end
```
```:ビューでUserモデルの情報を表示
<h1>Listing posts</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Content</th>
      <th>User</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <!-- @posts.eachで　SELECT "posts".* FROM "posts" クエリが発行される。 -->
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.content %></td>
        <!-- post.user.name で　SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", user_id]] クエリが発行される。 -->
        <td><%= post.user.name %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
```  
```:この時のログ
Processing by PostsController#index as HTML
  Post Load (0.2ms) SELECT "posts".* FROM "posts"
  User Load (0.2ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 1]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 2]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 3]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 4]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 5]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 6]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 7]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 8]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 9]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 10]]
  Rendered posts/index.html.erb within layouts/application (32.9ms)
Completed 200 OK in 147ms (Views: 132.6ms | ActiveRecord: 2.0ms)
```

#### 対策;N+1問題を検出するgemであるbulletを利用する
Gemfileに追加してbundle install、いくつかの設定を調整したのち、問題のUserモデルの情報を表示するとポップアップで
N+1問題を検出し、解決方法を提示してくれる。
下記のように変更し、再び表示させるとポップアップは表示されないようになる。

```:変更点
# app/contollers/posts_controller.rb
  def index
    @posts = Post.all.includs(:user)
    # 下記2つのSQLが発行される
    # SELECT "posts".* FROM "posts"
    # SELECT "users".* FROM "users"  WHERE "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    # この変更によってpost.user.nameの際にいちいち
　　　SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", user_id]]
　　　を発行しなくてすむ
  end
```

>引用
[Rails Webook/ N+1問題/Eager Loadingとは](https://ruby-rails.hatenadiary.com/entry/20141108/1415418367)
[TECHSCORE BLOG/ Railsライブラリ紹介N+1問題を検出するgemであるbulletを利用する](https://www.techscore.com/blog/2012/12/25/rails%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E7%B4%B9%E4%BB%8B-n1%E5%95%8F%E9%A1%8C%E3%82%92%E6%A4%9C%E5%87%BA%E3%81%99%E3%82%8B%E3%80%8Cbullet%E3%80%8D/)

---

# Active Recordについて
Active Recordとは、__`ORMシステムに記述されている「Active Recordパターン」を実装したもの`__
>Active Recordパターンは、データアクセスのロジックを常にオブジェクトに含めておくことで、そのオブジェクトの利用者にデータベースへの読み書き方法を指示できるといったもの

# CoC(設定より規約)
ActiveRecordはこれを採択しているため、Railsで採用されている慣習に従っている限り、設定用のコードを最小限で済ませることが可能になっている。

>引用
[Railsガイド/ ActiveRecordの基礎](https://railsguides.jp/active_record_basics.html)