JSONを返すWeb APIチックなものをRubyで作る

# はじめに

同じような処理をするWebアプリケーションとモバイルアプリケーションを作ることが増えたので、データを扱う処理をなるべく共通化したくて、JSONを返すWebAPIを作ってみたので、作り方をまとめてみました。

なお、作ったものはもう少し複雑ですが、ここで紹介するのは簡易版であることをご理解下さい。

# 開発環境

サーバー：さくらのVPS
Webサーバ：Apache
データベース：SQLite3

まずはgemをインストール。

```sh:
$ sudo gem install sqlite3
```

# データベース生成

とりあえず、サンプルとしてidとnameという列を持つデータベースを利用します。

```sql:sample.sql
CREATE TABLE `sample`(
  id integer primary key,
  name text
);
```

上記ファイルを保存して、Webアプリケーションのルートフォルダに保存します。
その後、以下のコマンドでデータベースを生成します。

```sh:
$ sqlite3 sample.db < sample.sql
```

# ライブラリの読込

CGIを利用するためのライブラリとSQLiteのライブラリを利用します。
ライブラリを読み込んだ後に、それぞれのインスタンスを生成します。

```rb:
require 'cgi'
require 'sqlite3'

$cgi = CGI.new()
$db = SQLite3::Database.new("sample.db")
```

# GETのパラメータを取得

ここではAPIを操作するのにGETメソッドを利用することにして、GETのパラメータを取得します。

```rb:
$_GET = {}
CGI::parse($cgi.query_string).each {|key, val|
 $_GET[key] = val[0].gsub("'","''")
}
```

# 処理の分岐

よくある処理は、追加、編集、削除、一覧かなと思うので、それぞれをmodeというパラメータを参照して処理を分岐します。

```rb:
mode = $_GET["mode"]
case mode
  when "add"
    # 追加用の処理
  when "edit"
    # 編集用の処理
  when "delete"
    # 削除用の処理
  when "list"
    # 一覧用の処理
end
```

# SQL文処理の作成

SQL(SELECT文)で取得したデータを配列に入れ、JSONに変換して出力するとうようなことができるように関数を作ります。

```rb:
# SQL(SELECT文)を実行して戻り値を配列に変換
def sql2array(sql)
  
  begin
    rs = $db.execute2(sql)
  rescue
    sleep 10
    retry
  end
  
  i = 0
  flg = true
  fields = []
  values = []
  
  rs.each do |row|
    if flg then
      fields = row
      flg = false
    else
      j = 0
      val = {}
      row.each do |wk|
        val[fields[j]] = wk
        j += 1
      end
      values[i] = val
      i += 1
    end
  end
  return values
end

# 配列をJSONに変換
def json(vals)
  result = <<EOF
{
	"name": "sample",
	"count": #{vals.length},
	"result":[
EOF
  
  i = 0
  
  vals.each do |row|
    
    if i > 0 then
      result += ","
    end
    
    j = 0
    result += "{"
    
    row.each do |key, val|
      
      if j > 0 then
        result += ","
      end
      
      result += <<EOF
		"#{key}":"#{val}"
EOF
      j += 1
      
    end
    
    result += "}"
    
    i += 1
    
  end
  
  result += <<EOF
	]
}
EOF
  
  return result
  
end
```

# 一覧用の処理

全データを取得してJSONを返す。

```rb:
def list()
  
  sql = "SELECT * FROM sample"
  vals = sql2array(sql)
  return json(vals)
  
end
```

# 追加用の処理

```rb:
def add()
  
  # SQL文の生成
  cols = ["name"]
  
  vals = {}
  sql = "INSERT INTO sample VALUES ((SELECT MAX(id)+1 AS id FROM sample)"
  
  cols.each do |col|
    sql += ",'#{vals[col]}'"
  end
  
  sql += ")"
  
  $db.exec(sql)
  
  return list()
  
end
```

# 編集用の処理

```rb:
def edit()
  
  # SQL文の生成
  cols = ["name"]
  
  vals = {}
  sql = "UPDATE sample SET "
  
  cols.each do |col|
    sql += (col == cols[0]) ? "" : ","
    sql += "#{col} = '#{vals[col]}'"
  end
  
  sql += " WHERE id='#{$_GET["id"]}'"
  
  $db.exec(sql)
  
  return list()
  
end
```

# 削除用の処理

```rb:
def delete()
  
  sql = "DELETE FROM sample WHERE id='#{$_GET["id"]}'"
  $db.exec(sql)
  
  return list()
  
end
```

# 処理の分岐（完成版）

先ほど作ったmodeというパラメータを参照して処理を分岐する部分を修正します。

```rb:
mode = $_GET["mode"]
case mode
  when "add"
    json = add()
  when "edit"
    json = edit()
  when "delete"
    json = delete()
  else
    json = list()
end
```

# JSONの出力

生成したJSONを出力します。

```rb:
puts $cgi.header()
puts json
```

# 全ソースコード

sample.rbという名前で以下のコードを保存します。

```rb:
require 'cgi'
require 'sqlite3'

$cgi = CGI.new()
$db = SQLite3::Database.new("sample.db")

$_GET = {}
CGI::parse($cgi.query_string).each {|key, val|
 $_GET[key] = val[0].gsub("'","''")
}

# SQL(SELECT文)を実行して配列に変換
def sql2array(sql)

  begin
    rs = $db.execute2(sql)
  rescue
    sleep 10
    retry
  end

  i = 0
  flg = true
  fields = []
  values = []

  rs.each do |row|
    if flg then
      fields = row
      flg = false
    else
      j = 0
      val = {}
      row.each do |wk|
        val[fields[j]] = wk
        j += 1
      end
      values[i] = val
      i += 1
    end
  end
  return values
end

# 配列をJSONに変換
def json(vals)
  result = <<EOF
{
    "name": "sample",
    "count": #{vals.length},
    "result":[
EOF

  i = 0

  vals.each do |row|

    if i > 0 then
      result += ","
    end

    j = 0
    result += "{"

    row.each do |key, val|

      if j > 0 then
        result += ","
      end

      result += <<EOF
        "#{key}":"#{val}"
EOF
      j += 1

    end

    result += "}"

    i += 1

  end

  result += <<EOF
    ]
}
EOF

  return result

end

def list()

  sql = "SELECT * FROM sample"
  vals = sql2array(sql)
  return json(vals)

end

def add()

  # SQL文の生成
  cols = ["name"]

  vals = {}
  sql = "INSERT INTO sample VALUES ((SELECT MAX(id)+1 AS id FROM sample)"

  cols.each do |col|
    sql += ",'#{vals[col]}'"
  end

  sql += ")"

  $db.exec(sql)

  return list()

end

def edit()

  # SQL文の生成
  cols = ["name"]

  vals = {}
  sql = "UPDATE sample SET "

  cols.each do |col|
    sql += (col == cols[0]) ? "" : ","
    sql += "#{col} = '#{vals[col]}'"
  end

  sql += " WHERE id='#{$_GET["id"]}'"

  $db.exec(sql)

  return list()

end

def delete()

  sql = "DELETE FROM sample WHERE id='#{$_GET["id"]}'"
  $db.exec(sql)

  return list()

end

mode = $_GET["mode"]
case mode
  when "add"
    json = add()
  when "edit"
    json = edit()
  when "delete"
    json = delete()
  else
    json = list()
end

puts $cgi.header()
puts json
```

# テスト

以下のコードで動作を確認します。

## 一覧

```sh:
$ curl localhost/sample.rb
{"name":"sample","count":0,"result":[]}
```

## 追加

```sh:
$ curl localhost/sample.rb?mode=add&name=テスト
{"name":"sample","count":1,"result":[{"id":"1","name":"テスト"}]}
```
## 編集

```sh:
$ curl localhost/sample.rb?mode=edit&id=1&name=サンプル
{"name":"sample","count":1,"result":[{"id":"1","name":"サンプル"}]}
```

## 削除

```sh:
$ curl localhost/sample.rb?mode=delete&id=1
{"name":"sample","count":0,"result":[]}
```

できた！