3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RailsのJSONカラムをちゃんとバリデーションする

Posted at

目的

RailsでJSONカラムを利用するとき、その大抵はRDSの正規化を正しくやろうとすると煩雑になりすぎるためにとった手段であることが多い(少なくとも私は)。
そういう場合対外においてJSONの形式は煩雑になるのでまともにバリデーションができていなかったり、ドキュメンテーションが難しかったりした。

なので本番のJSONカラムに対してJSON Schemaを利用して「バリデーション」「ドキュメンテーション」の両方を実現していこうという話。

※ 基本的にRDSにおいてJSONカラムを利用するのは私は悪手だと考えています。その中でどうしても現状完全に正規化し切るのは難しいという前提でJSONカラムを選択せざるを得なかった時の対応だと思って読んでいただけるといいと思います。

この記事のゴール

  • RailsのJSONカラムに対してバリデーションが入っている
  • JSONカラムに対してドキュメンテーションがされている(自動でドキュメントが更新される)
  • 上記二つが連動することによって陳腐化しないドキュメンテーションになっている

JSON Schemaについて

今回JSONに対してバリデーション・ドキュメンテーションを行うために JSON Schema を使っていく。
書き口がOpenAPIとほぼ同じなのでOpenAPIを書いたことがある人であればほぼ迷わず書けると思う(多分)

今回は該当のリポジトリに doc/jsonschema/ というディレクトリを作成し、この中にJSONファイルを入れるようにしました。
JSONファイルはJSONカラムの形式ごとに分けて運用しています。

JSONカラムにバリデーションをかける

今回はこちらのgemを利用させてもらいました。

Gemfileに json-schema を追加。 & bundle install

# Gemfile
gem 'json-schema'

カスタムバリデーターの追加
(エラーメッセージの構築は今回そこまでできてないです。。とはいえ管理画面からでほぼ自分でセットアップするので一旦これでよしとしてます)

app/validators/json_validator.rb
class JsonValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    JSON::Validator.validate!(options[:schema], value, strict: true)
  rescue JSON::Schema::ValidationError => e
    error_message = options[:message] || e.message
    record.errors.add(attribute, error_message)
  end
end

AutoLoaderのパスが入ってなければ追加(ディレクトリは各プロダクトごとにどうするかは整理してください)

config/application.rb
module Hoge
  class Application < Rails::Application
    ...
    config.autoload_paths << Rails.root.join("app", "validators")
  end
end

Railsのモデルでバリデーションを追加
定数としてJSON Schemaを読み込んでvalidatesを追加。

class Hoge < ApplicationRecord
  JSON_SCHEMA = File.open("doc/jsonschema/hoge.json") do |f|
    JSON.load(f)
  end

  validates :hoge, json: { schema: JSON_SCHEMA }
end

JSON Schemaのドキュメンテーション

方法はいろいろありますが以下の観点でサクッとできそうなものを選定してます(必要になったら載せ替えをすればいいと思っているので若干雑に選定してます)

  • HTML形式で簡単にhostingできてドキュメントが読める(サーバー立てるほどでもなと思ってます)
  • 入れ子構造などを含むJSON Schemaを表現できている

今回はこちらのライブラリを利用させていただきました。

方法としてはGithub Actionからドキュメントを生成してホスティング用のS3にアップロードするようにしています。

ビルド環境の用意

実行にはPythonが必要になります。
今回は当時最新だった 3.10.5 のバージョンを入れてます。(最終的にgithub actionでビルドするのでローカルで作らなくてもまぁ良さそう)

私がpyenv使っているので

doc/jsonschema/.python-version
3.10.5
doc/jsonschema/requirements.txt
json-schema-for-humans
アップロード用のS3バケットの用意

public readでS3を用意(必要最低限の設定にしているので必要に応じて設定は足してください)

resource "aws_s3_bucket" "json_schema_doc_hosting" {
  bucket = "{{bucket name}}"
  acl = "public-read"

  policy = <<POLICY
{
  "Id": "PolicyForApiDocBudget",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::{{bucket name}}/*"
    }
  ]
}
  POLICY

  website {
    index_document = "index.html"
  }
}
github actionからS3にアプロードするためのroleを用意
data "aws_iam_policy_document" "github_actions_deployment_json_schema_assume_role_policy" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = ["arn:aws:iam::{{AWS account_id}}:oidc-provider/token.actions.githubusercontent.com"]
    }

    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"

      values = [
        "repo:{{your repo}}:*"
      ]
    }
  }
}

resource "aws_iam_role" "github_actions_deployment_for_json_schema" {
  name = "gha-iam-role-gha-deployment-json-schema"

  assume_role_policy = data.aws_iam_policy_document.github_actions_deployment_json_schema_assume_role_policy.json
}

resource "aws_iam_role_policy_attachment" "github_actions_deployment_for_json_schema" {
  role       = aws_iam_role.github_actions_deployment_for_json_schema.name
  policy_arn = aws_iam_policy.github_actions_deployment_for_json_schema.arn
}

data "aws_iam_policy_document" "github_actions_deployment_for_json_schema_policy" {
  version = "2012-10-17"

  statement {
    actions = [
      "s3:PutObject"
    ]

    resources = [
      aws_s3_bucket.json_schema_doc_hosting.arn,
      "${aws_s3_bucket.json_schema_doc_hosting.arn}/*"
    ]
  }
}

resource "aws_iam_policy" "github_actions_deployment_for_json_schema" {
  name = "gha-deployment-json-schema-policy"
  policy = data.aws_iam_policy_document.github_actions_deployment_for_json_schema_policy.json
}

resource "aws_iam_openid_connect_provider" "github_actions" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = ["sts.amazonaws.com"]

  thumbprint_list = [data.tls_certificate.github_actions.certificates.0.sha1_fingerprint]
}

data "tls_certificate" "github_actions" {
  url = "https://token.actions.githubusercontent.com/.well-known/openid-configuration"
}
github action で main にマージした時に S3 にアップロード

json-schema-for-humans は Python が必要になるのでインストールして実行

name: Deploy JsonSchema

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write

jobs:
  deploy-json-schema:
    runs-on: ubuntu-18.04
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: 3.10.5
      - name: Install Dependencies
        working-directory: doc/jsonschema
        run: pip install -r requirements.txt
      - name: generate Document
        working-directory: doc/jsonschema
        run: |
          mkdir result
          generate-schema-doc json_schema.json result/
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-region: ap-northeast-1
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/gha-iam-role-gha-deployment-json-schema
          role-session-name: GitHubActions-${{ github.run_id }}
          role-duration-seconds: 900
      - name: upload
        working-directory: doc/jsonschema
        run: aws s3 cp ./result/ s3://{{your S3 domain}}/result/ --recursive
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?