Swaggerを使って RubyonRails ✖︎ WebAPI 作成してみた

# はじめに
現在友人依頼の作付計画アプリを作成中です 🌱
作物マスターのAPIあったら楽だなーと思いつつ(あるっぽい👀)
根幹機能なのでやめときました💦

seedファイル使って手動でぽちぽちマスター登録をしているので、せっかくならWebAPIに挑戦してみようと思います！

初心者ですので、間違い等ありましたらご指摘いただけますと幸いです！

# この記事の目標
- RubyonRailsのseed機能を使って作成した作物マスターをWebAPIで公開する

# この記事でやらないこと
- 前提知識や細かい設定の説明

前提知識などは、下記の記事を参考にしています！

https://qiita.com/KNR109/items/d3b6aa8803c62238d990

https://qiita.com/KNR109/items/7e094dba6bcf37ed73cf#commponents

# Swagger
SwaggerとはOpenAPIを利用しREST APIを設計するために使用するツールセットのことです

公式ドキュメント

https://swagger.io/docs/specification/about/

GitHub

https://github.com/swagger-api/swagger-ui?tab=readme-ov-file

# 完成物

![スクリーンショット 2024-09-20 18.14.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/0feb49ef-6fe2-1ba3-c0d5-8e4485ff657c.png)

![スクリーンショット 2024-09-22 16.40.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/8dfd674c-ad93-44c3-d33d-9a3b824a241e.png)

# WebAPI作成

### Cropの作成
マスターは登録済みのものを使用しますが、この段階でない場合は作成する必要があります

```
$ rails g model Crop
```
今回データベース設計等は割愛しています。
seedファイルで一気に作成します。
```seeds.rb
  Crop.find_or_create_by!(name: "ナス") do |crop|
    crop.user_id = admin.id
    crop.plant_family = "ナス科"
    crop.rotation_year = "4年以上"
    crop.companion = "ビガンバナ科、マメ科、シソ科"
  end

  Crop.find_or_create_by!(name: "ピーマン") do |crop|
    crop.user_id = admin.id
    crop.plant_family = "ナス科"
    crop.rotation_year = "4年以上"
    crop.companion = "ビガンバナ科、マメ科、シソ科"
  end
  :
```
user_idをadmin.idにしているのは、マスター管理はadmin(管理者)が行い
MyCropPlanのユーザーはマスターとは別に個人で作物登録をできるようにしているためです。APIで公開するデータからuser_idは排除する予定です。

### ルーティング
`/api/v1/crops`と`/api/v1/crops/{id}`に対応するルーティングを設定します。

```routes.rb
Rails.application.routes.draw do
:
  namespace :api do
    namespace :v1 do
      resources :crops, only: [:index, :show]
    end
  end
:
end
```

### コントローラ、アクション作成
先ほど作成したルーティングに沿ってコントローラとアクションを設定します

```app/controllers/api/vi/crops_controller.rb
module Api
  module V1
    class CropsController < ApplicationController

      def index
        admin_user_ids = Admin.pluck(:id)
        @crops = Crop.where(user_id: admin_user_ids)

        render json: @crops.select(:id, :name, :plant_family, :rotation_year, :companion)
      end

      def show
        @crop = Crop.find(params[:id])
        render json: {
          id: @crop.id,
          name: @crop.name,
          plant_family: @crop.plant_family,
          rotation_year: @crop.rotation_year,
          companion: @crop.companion
        }
      end

    end
  end
end
```
今回は作物マスターの公開のためログイン不要としていますが、設計によってはログイン必須(`before_action :authenticate_user!`を追記)にすることも検討しましょう。
また、ここで公開するカラムなどを設定するため`user_id`など不要と判断するものは除外します！

これでURIを叩けばjson形式で情報を取得できるようになっているはずです。
ただ、APIをより使いやすいものにするためにドキュメントを作成してみようと思います。

![スクリーンショット 2024-09-20 18.14.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/0feb49ef-6fe2-1ba3-c0d5-8e4485ff657c.png)

### OpenAPIファイルを作成(YAML形式)
今から作るのはAPIの説明書のようなものです。
今回はGETメソッドで作物一覧取得と作物取得のみです。
POSTやDELETEメソッドはありませんのでご了承ください🙇

```public/swagger.yaml
openapi: 3.0.3

info:
  title: MyCropPlan API
  description: MyCropPlanの作物マスターAPIです
  version: 1.0.0

servers:
- url: https://-数字-.vfs.cloud9.ap-northeast-1.amazonaws.com/
  description: ローカル環境
- url: https://my-crop-plan.com/
  description: 本番環境

tags:
  - name: crops
    description: 作物の操作

paths:
  /api/v1/crops:
    get:
      summary: 作物一覧取得
      tags: [crops]
      responses:
        '200':
          description: A list of crops
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Crop'

  /api/v1/crops/{id}:
    get:
      summary: 作物の取得
      tags: [crops]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: A crop object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Crop'

        '404':
          description: Crop not found

components:
  schemas:
    Crop:
      type: object
      properties:
        id:
          type: integer
          example: 1
        name:
          type: string
          example: トマト
        plant_family:
          type: string
          example: ナス科
        rotation_year:
          type: string
          example: 4年以上
        companion:
          type: string
          example: ネギ、バジル、ニラ、落花生、ビガンバナ科、マメ科、シソ科
```

上記コードはローカル環境を入れて動作確認できるようにしていますが、最終的には削除しています。(ローカル環境に不正なアクセスをされることを防止するため)

### Swagger UIのダウンロード
下記URLにアクセスして、ソースコードをダウンロードします

https://github.com/swagger-api/swagger-ui/releases

ダウンロードしたファイルの中からdistというファイルをpublicの中にコピーし、名前をswaggerに変更します。

![スクリーンショット 2024-09-20 18.50.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/a8ccd9bc-c69c-19e7-77aa-a5c5899976bb.png)

```
public/
└── swagger/
    ├── index.html
    ├── swagger-ui.css
    ├── swagger-ui-bundle.js
    └── ...（その他の静的ファイル）
```

index.htmlの`body`要素の中に、`<script>`を追加します

```index.html
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Swagger UI</title>
    <link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
    <link rel="stylesheet" type="text/css" href="index.css" />
    <link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
    <link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
  </head>

  <body>
    <div id="swagger-ui"></div>
    <script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
    <script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
    <script src="./swagger-initializer.js" charset="UTF-8"> </script>

    <!-- ここから追加 -->
    <script>
      window.onload = function() {
        const ui = SwaggerUIBundle({
          url: "/swagger.yaml",
          dom_id: '#swagger-ui',
          deepLinking: true,
          presets: [
            SwaggerUIBundle.presets.apis,
            SwaggerUIStandalonePreset
          ],
          layout: "StandaloneLayout"
        });
      };
    </script>
    <!-- ここまで追加 -->
  </body>
</html>
```

ここで`/swagger.yaml`を読み込みことにより先ほど作成した"説明書"をドキュメントとして表示させることができます。

![スクリーンショット 2024-09-22 16.40.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782860/8dfd674c-ad93-44c3-d33d-9a3b824a241e.png)

# URLにアクセス
今の私が出来得る範囲でAPIを作成してみました。

https://my-crop-plan.com/api/v1/crops

https://my-crop-plan.com/api/v1/crops/1

上記URLにアクセスすることでJSON形式のデータが表示されます。

https://my-crop-plan.com/swagger/index.html

上記にアクセスすることでAPIの取扱説明書であるswaggerUIが表示されます。

# さいごに
知識不足を実感したので、現在オライリージャパンの「WebAPI The Good Parts」を読んでいます。理解を深めていきたいと思います！

間違えているところや、改善点ございましたらご指摘いただけますと幸いです！