Rails6+Reactで付箋アプリっぽいページを作ってみた。2 (API作成編)

# 記事について
[前回](https://qiita.com/hanaitama-kaz/items/5b68b9c665f75020bcd4)、モデルの作成まで行ったので、Reactで作ったUIとやり取りするためのAPIを用意してみます。

ここで作成するAPIは以下の二つにします。（作成とか削除とかはまた今度。。）
・ユーザ毎のタスク一覧の取得
・タスクの押し付け・・・もとい、タスク所有ユーザの更新

# 関連する記事
書いているうちに分量がすごくなって記事を分割したので、リンク先をまとめておきます。
[その１(環境構築〜モデル作成編)](https://qiita.com/hanaitama-kaz/items/5b68b9c665f75020bcd4)
[その２(API作成編)](https://qiita.com/hanaitama-kaz/items/b461ff8564a5368523fc)
[その３(UI作成編1)](https://qiita.com/hanaitama-kaz/items/9b680c50566161046690)
[その４(UI作成編2)](https://qiita.com/hanaitama-kaz/items/e82a9ec9538914861746)
[その５(react-contenteditable導入編)](https://qiita.com/hanaitama-kaz/items/17832369f279ba0e331a)
[おまけ(モデルのテスト編)](https://qiita.com/hanaitama-kaz/items/d3b9e54913139aecf530)


# ユーザ毎タスク一覧の取得
RailsでAPIを作成する際に作るのはControllerとViewです。
ユーザの一覧を取得したいので、"api/users/user_task_list"のようにアクセスできればなと。

## コントローラの生成
ということで、コントローラを生成してもらいます。

```bash:shell
# 以下のコマンドで、viewの追加やroute.rbの編集も行ってくれます。
# APIにスタイルはいらないので、--no-stylesheetsをつけてみました。
bundle exec rails g controller API::Users user_task_list --no-stylesheets
```

## コントローラの編集
生成されたコントローラに実際に処理を追加してみます。

```ruby:app/controllers/api/users_controller.rb
class Api::UsersController < ApplicationController
  def user_task_list
    # ユーザ情報を取得してインスタンス変数に格納しておく。
    @users = User.all(); # <= 追加する処理
  end
end
```

## jbuilderファイルの作成
React用のAPIなのでデータのやりとりは、JSONで行うのが楽です。
ということで、ユーザデータをJSONで出力するためのテンプレートファイルを追加してあげます。
名前は、app/views/api/users/list.json.jbuilderとなります。

```ruby:app/views/api/users/user_task_list.json.jbuilder 
# jbuilderでは好きな形でデータが出力できます。
json.users @users do | user |
  json.id(user.id);
  json.name(user.name);
  json.tasks do
    user.tasks.each do | task |
      json.set! task.id do
        json.id(task.id);
        json.title(task.title);
        json.description(task.description);
        json.due_date(task.due_date.strftime("%Y-%m-%d"));
      end
    end
  end
end

# こんなイメージのjsonデータになります。
# あとで、IDをキーにタスクデータを取得したいので、ちょっと変な形になってます。
# {
#   users: [ {
#        id: 1
#        name: "Not Assigned"
#        tasks: { 
#          "1" : { id: 1, title: "test001", description: ...},
#          "3" : { id: 3, title: "test003", description: ...}
#        }
#      }, {
#        id: 2
#        name: "Alice"
#        tasks: { 
#          "2" : { id: 2, title: "test002", description: ...}
#        }
#      }
#   ]
# }
```

## テストしてみます。
これで、ユーザ一覧をJSONデータとして出力できるようになったはず。
と言うことで、テストしてみます。
ここでは、以下のようなテストコードをテストコードに追加してみました。

```ruby:test/controllers/api/users_controller_test.rb(抜粋です。) 
  test "should get user_task_list.json" do
    # jsonフォーマット指定でGETリクエストを投げます。
    get api_users_user_task_list_url(:json)

    # 200OKが返ってくるはず。
    assert_response :success

    # Boby部をJSONデータに変換
    json_data = ActiveSupport::JSON.decode(@response.body);

    # usersの配列の長さは3のはず(fixtureで3人作っていれば)
    assert_equal(3, json_data['users'].length);

  end
```

テスト実行

```bash:shell
# うまくいったら喜ぼう。
bundle exec rails test test/controllers/api/users_controller_test.rb
```

# タスクの押し付け
それでは、タスク所有ユーザ切り替え用のAPIを準備していきます。

## コントローラの生成
ということで、コントローラの作成です。
内部的にはタスクのユーザIDを切り替えるので、'switch_user'の名前でアクションを追加します。

```bash:shell
bundle exec rails g controller API::Tasks switch_user --no-stylesheets

# おや、なんか変だ。。
# route.rbに"get 'tasks/switch_user'"を追加したと言っている。
      create  app/controllers/api/tasks_controller.rb
       route  namespace :api do
  get 'tasks/switch_user'
end
```

## route.rbの修正
先ほどの結果で、switch_userアクションの呼び出しメソッドが"GET"になってしまったので、route.rbを修正します。(更新ならPUTがいいかな？と。)

```ruby:config/route.rb
  # 変更箇所の前後のみ記載しております。
  namespace :api do
    put 'tasks/switch_user'  # <= getからputに変更します。
  end
```

## コントローラの編集
それでは、追加されたtasks_controller.rbを編集して、switch_userアクションを実装します。
なお、ここで大事になるのはリクエストデータのもらい方。
今回は、以下のような形でもらうことを想定した実装としています。

```json:リクエストデータイメージ
{
  "switch_info": { 
    "task_id": 1, 
    "user_id": 2 
  }
}
```

switch_userアクションの内容は、以下のとおり、変更対象のタスクのユーザデータを入れ替えて、更新する処理となります。
後々、エラー処理がしやすいように、エラーメッセージを返すための仕組みも入れてあります。

```ruby:app/controllers/api/tasks_controller.rb(抜粋です。)
  # タスク押し付け処理
  def switch_user
    # エラーメッセージを入れるための配列を用意しておきます。
    @errors = [];

    # 許可済みのパラメータを生成します。(switch_uesr_params()というprivateメソッドを追加しておきました。)
    suparam = switch_user_params();

    begin
      # タスクデータを取得
      @task = Task.find(suparam[:task_id]);

      # ユーザデータを取得
      @user = User.find(suparam[:user_id]);

      # タスク所有者を変更
      @task.user = @user;

      # タスクデータを更新
      if ! @task.save then
        # 失敗したら、show_errorというテンプレートを使ってエラー情報を返すようにしました。
        @errors.push("failed to switch user: task.save() faield");
        render :show_error
      end

    rescue => ex
      # 例外発生時もshow_errorというテンプレートを使って例外メッセージを返すようにしました。
      @errors.push("failed to switch user: an exception occurred.");
      @errors.push(ex.to_s);
      render :show_error
    end

  end

  private
    # task_idとuser_idだけを許可済みパラメータとして返します。
    def switch_user_params()
      return params.require(:switch_info).permit(:task_id, :user_id);
    end

```

## jbuilderファイルの準備
ここでは、処理結果をJSON形式で返すため、以下２つパターンでjbuilderファイルを用意します。
エラーがない場合は、更新後のタスクデータを。 => switch_user.json.jbuilder
エラーがあった場合は、エラーメッセージを。    => show_error.json.jbuilder

まず、更新成功時に返すswitch_user.json.jbuilder

```ruby:app/views/api/tasks/switch_user.json.jbuilder
# task { id: 1, title: "hoge" ...}というような形で返します。
json.task do
  json.id(@task.id);
  json.title(@task.title);
  json.description(@task.description);
  json.due_date(@task.due_date.strftime("%Y-%m-%d %H:%M:%S"));
  json.user_id(@task.user.id);
end
```

次にエラー情報を返すshow_error.json.jbuilder

```ruby:app/views/api/tasks/show_error.json.jbuilder
# @errorsと言う名前の配列にエラー情報を入れてあるので、その中身を出力してあげます。
json.errors @errors do | msg |
  json.message(msg);
end
```

## テストしてみます。
これで、適切なパラメータでリクエストを投げれば、タスクの担当者が切り替わるはずです。
ので、やっぱりテストしてみます。

テスト内容はこちら！
・ユーザの切り替えがうまく行くこと。
・失敗したらエラー情報がもらえること。

```ruby:test/controllers/api/tasks_controller_test.rb(抜粋です。)
  # 正常パターン
  test "should success to switch user" do
    notassigned = users(:notassigned);
    alice = users(:alice);
    task1 = notassigned.tasks[0];

    # aliceのタスクが増えるはず。
    assert_difference 'alice.tasks.count' do
      # リクエスト投げてみる。
      put(api_tasks_switch_user_url(:json), params: { switch_info: { task_id: task1.id, user_id: alice.id } });
 
      # 200OKのはず
      assert_response :success

      # 念のためリロード
      alice.tasks.reload();
      notassigned.tasks.reload();

    end

    # レスポンスから、JSONデータを取得
    json_data = ActiveSupport::JSON.decode(@response.body);

    # タスクデータのユーザIDがaliceのIDになるはず。
    assert_equal(alice.id, json_data['task']['user_id']);

    # notassignedさんのタスクが0個になるはず。
    assert_equal(0, notassigned.tasks.count);

  end

  # 存在しないユーザに切り替えるとエラーになるはず。
  test "should fail to swith user if user is not exist" do
    # 新しいユーザを作る。が、保存しない。
    new_user = User.new(name: "Bab");

    # タスクデータのインスタンスを取得
    task2 = tasks(:task2);

    # 保存してないユーザに割り当ててみよう。
    put(api_tasks_switch_user_url(:json), params: { switch_info: { taks_id: task2.id, user_id: new_user.id } });

    # 一応200OK。
    assert_response :success

    json_data = ActiveSupport::JSON.decode(@response.body);

    # エラー情報が返されるはず。
    assert(json_data['errors'].count > 0);

  end

```

テストOKであれば、一応APIは出来上がりと言うことにしよう！！