Rails でDBのユーザー定義関数を使う

当記事では、みんな大好きユーザー定義関数を Rails で使ってみて、理解を深めてみます
API  ファースト開発を使った開発を想定して、DBのユーザー定義関数を Rails プロジェクトに組み込んでみましょう

## DBのユーザー定義関数とは

- RDBMS にだいたいついてる、関数を作る機能です
- 当記事では PostgreSQL を使います

## API ファースト開発とは

1. アプリケーション側からは RDB へ直接クエリーを発行しません
2. 代わりに機能ごとに用意されたユーザー定義関数を実行して、返ってきた結果を使います（副作用を持つ関数もあります
3. 開発の初期段階ではテーブル設計を行わず、必要な機能のインプットとアウトプットが定義されたユーザー定義関数のモックを使ってアプリケーションを開発します
4. ある程度アプリケーションができてきたら、モックになっているユーザー定義関数から逆算してテーブル設計を行い DDL を実行、ユーザー定義関数の実装もモックから本番用に書き換えます
5. テーブル設計の手戻りなくアプリケーションの開発が完了してハッピー

- 詳しくは、[APIファースト開発勉強会に参加してきた](http://qiita.com/sawadybomb/items/e99dc84b9fa0d2d59f2f)に、よくまとまっています

## やってみる

### プロジェクト作成

```
rails new stored_sample --database=postgresql
```

### ユーザー機能作成

```
rails g scaffold User name:text
```

### テーブルを作らない

- まず、テーブルは作りません
- 代わりに、まずは User を全件取得するユーザー定義関数（のモック）を登録する migration にします
- モックなので、ユーザー定義関数の返り値に好きな値をガリガリ書いてしまいます

```rb
class CreateUsers < ActiveRecord::Migration[5.0]
  def up
    execute <<~SQL
      CREATE FUNCTION users() RETURNS TABLE(id INTEGER, name TEXT) AS $$
        VALUES(1, 'taro'), (2, 'jiro'), (3, 'saburo')
      $$ LANGUAGE SQL;
    SQL
  end

  def down
    execute <<~SQL
      DROP FUNCTION users()
    SQL
  end
end
```

### ActiveRecord をやめる

- 継承を外す
- せっかくなので（？）Module function 縛りで頑張りましょう

```rb
module User
  def self.all
    exec(<<~SQL)
      SELECT * FROM users()
    SQL
  end
  
  def self.exec(sql, *args)
    sql = ActiveRecord::Base.send(:sanitize_sql_array, [sql, *args])
    ActiveRecord::Base.connection.execute(sql).to_a
  end
end
```

### find を作る

- find 用のユーザー定義関数を登録する migration

```rb
class AddFindUser < ActiveRecord::Migration[5.0]
  def up
    execute <<~SQL
      CREATE FUNCTION find_user(IN INTEGER, OUT id INTEGER, OUT name TEXT) AS $$
        VALUES (1, 'taro')
      $$ LANGUAGE SQL
    SQL
  end

  def down
    execute <<~SQL
      DROP FUNCTION find_user(INTEGER)
    SQL
  end
end
```

- find メソッドを作成

```rb
module User
  def self.all
    exec(<<~SQL)
      SELECT * FROM users()
    SQL
  end

  def self.find(id)
    exec(<<~SQL, [id]).first
      SELECT * FROM find_user(?)
    SQL
  end

  def self.exec(sql, *args)
    sql = ActiveRecord::Base.send(:sanitize_sql_array, [sql, *args])
    ActiveRecord::Base.connection.execute(sql).to_a
  end
end
```

### VIEW を

- `views/users/index.html.erb` をハッシュ向けに改変する

```html+erb
<p id="notice"><%= notice %></p>

<h1>Users</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @users.each do |user| %>
      <tr>
        <td><%= link_to user["name"], "users/#{user['id']}" %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New User', new_user_path %>
```

- `views/users/show.html.erb` も

```html+erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user['name'] %>
</p>

<%= link_to 'Back', users_path %>
```

### 動く

- index ページ

<img width="391" alt="StoredSample.png" src="https://qiita-image-store.s3.amazonaws.com/0/9513/90305893-06c0-f665-fd35-edac41b46fe6.png">

- show ページ

<img width="207" alt="StoredSample 2.png" src="https://qiita-image-store.s3.amazonaws.com/0/9513/7f2531ef-4cb0-0bd3-baff-aea14079e5a8.png" border="3">

### 辛い

- すいません。ちょっと、辛くなってきたので、これくらいで終わらせてください

## 感想

- 背徳感がすごい
- やはり、一連の流れを自動的にやってくれる DSL がないと厳しい
- さすがに ActiveModel とかを使ってオブジェクトにマッピングして使わないと厳しい（今後（？）の課題）。もうちょっとやりようがあった気がする
- 何か道具を作ってサクサクやっていけるなら、死ぬほど悪いという程ではない気がします
- 想像するに、逆算してテーブル設計をする時になって初めて仕様の矛盾が出てきたりというのがあると思うんですが、そこはテーブル設計が済んだ後に発覚するよりはいい気もしますし、アプリケーションの開発と並行してテーブル設計を育てていく（通常の Rails の開発）過程でも、別に問題はない気もしますし、どうなんでしょうね
- これを Rails Way に乗せようと思うと、途方も無い労力がかかりそう、という事はわかりました
- もっと割り切って、JSON 型の１つの値しか返さない、引数も JSON 型の１つ、みたいなユーザー定義関数だけにして、本当に API にしてしまえばいいんじゃ無いのという感じすらする（その時は Rails を使う必要がぜんぜん無い）

## おまけ

- あ、すごい偶然なんですけど、今日、わしの誕生日です。毎年、みなさまのおかげで生かされております。[毎度どうもありがとうございます](http://yancya.jp/hoshi_wish)