Jbuilder::Schema

Generate JSON Schema compatible with OpenAPI 3 specs from Jbuilder files

Installation

In your Gemfile, put gem "jbuilder-schema" after Jbuilder:

gem "jbuilder"
gem "jbuilder-schema"

And run:

$ bundle

Or install it yourself as:

$ gem install jbuilder-schema

Usage

Wherever you want to generate schemas, call Jbuilder::Schema.yaml or Jbuilder::Schema.json:

Jbuilder::Schema.yaml(@article, title: 'Article', description: 'Article in the blog', locals: { current_user: @user })

Under the hood Jbuilder::Schema.yaml/json will use Action View's render method and support the same arguments.

So in the above example, the @article's to_partial_path path is used to find and render a articles/_article.json.jbuilder template, and article is available in the partial.

Additionally, we can pass any needed locals:.

The title and description set the title and description of the schema — though they can also come from locale files (see Titles & Descriptions);

Use with a directory within app/views

If you have a directory within app/views where your Jbuilder templates are, you can use renderer to capture that along with any locals: common to the templates you'll render:

jbuilder = Jbuilder::Schema.renderer('app/views/api/v1', locals: { current_user: @user })
jbuilder.yaml @article, title: 'Article', description: 'Article in the blog'

This means you don't have to write out the partial path, which gets tedious with multiple schema renders:

Jbuilder::Schema.yaml(partial: 'api/v1/articles/article', locals: { article: @article, current_user: @user }, title: 'Article', description: 'Article in the blog')

Rendering a template

If you're rendering a template like app/views/articles/index.jbuilder:

json.articles @articles, :id, :title

You'll need to pass the relative template path in template: and any needed instance variables in assigns: like so:

Jbuilder::Schema.yaml(template: "articles/index", assigns: { articles: Article.first(3) })

Output

Jbuilder::Schema automatically sets description, type, and required options in JSON-Schema.

For example, if we have a _article.json.jbuilder file:

json.extract! article, :id, :title, :body, :created_at

This will produce the following:

type: object
title: Article
description: Article in the blog
required:
  - id
  - title
  - body
properties:
  id:
    description: ID of an article
    type: integer
  title:
    description: Title of an article
    type: string
  body:
    description: Contents of an article
    type: string
  created_at:
    description: Timestamp when article was created
    type: string
    format: date-time

Customization

Simple

To set your own data in the generated JSON-Schema pass a schema: hash:

json.id article.id, schema: { type: :number, description: "Custom ID description" }
json.title article.title, schema: { minLength: 5, maxLength: 20 }
json.body article.body, schema: { type: :text, maxLength: 500 }
json.created_at article.created_at.strftime('%d/%m/%Y'), schema: { format: :date, pattern: /^(3[01]|[12][0-9]|0[1-9])\/(1[0-2]|0[1-9])\/[0-9]{4}$/ }

This will produce the following:

...
  properties:
    id:
      description: Custom ID description
      type: number
    title:
      description: Title of an article
      type: string
      minLength: 5
      maxLength: 20
    body:
      description: Contents of an article
      type: string
      maxLength: 500
    created_at:
      description: Timestamp when article was created
      type: string
      format: date
      pattern: "^(3[01]|[12][0-9]|0[1-9])\/(1[0-2]|0[1-9])\/[0-9]{4}$"

Bulk

You can customize output for multiple fields at once:

json.extract! user, :id, :name, :email, schema: {id: {type: :string}, email: {type: :email, pattern: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/}}

Nested objects

When you have nested objects in your Jbuilder template, you have to pass it to schema: {object: <nested_object>} when the block starts:

json.extract! article
json.author schema: {object: article.user, object_title: "Author", object_description: "Authors are users who write articles"} do
  json.extract! article.user
end

This will help Jbuilder::Schema to process those fields right.

Collections

If an object or an array of objects is generated in template, either in root or in some field through Jbuilder partials, JSON-Schema $ref is generated pointing to object with the same name as partial. By default those schemas should appear in "#/components/schemas/".

For example, if we have:

json.user do
  json.partial! 'api/v1/users/user', user: user
end

json.articles do
  json.array! user.articles, partial: "api/v1/articles/article", as: :article
end

The result would be:

user:
  type: object
  $ref: #/components/schemas/user
articles:
  type: array
  items:
    $ref: #/components/schemas/article

The path to component schemas can be configured with components_path variable, which defaults to components/schemas. See Configuration for more info.

Titles & Descriptions

Custom titles and descriptions for objects can be specified when calling jbuilder-schema helper (see Usage), for fields and nested objects within schema attributes (see Customization and Nested objects). If not set, they will be searched in locale files.

Titles and descriptions for the models are supposed to be found in locale files under <underscored_plural_model_name>.<title_name> and <underscored_plural_model_name>.<description_name>, for example:

en:
  articles:
    title: Article
    description: The main object on the blog

Descriptions for the fields are supposed to be found in locale files under <underscored_plural_model_name>.fields.<field_name>.<description_name>, for example:

en:
  articles:
    fields:
      title:
        description: The title of an article

<title_name> and <description_name> can be configured (see Configuration), it defaults to title and description.

Configuration

You can configure some variables that Jbuilder::Schema uses (for example, in config/initializers/jbuilder_schema.rb):

Jbuilder::Schema.configure do |config|
  config.components_path = "components/schemas"   # could be "definitions/schemas"
  config.title_name = "title"                     # could be "label"
  config.description_name = "description"         # could be "heading"
end

RSwag

You can use the yaml/json methods in your swagger_helper.rb like this:

RSpec.configure do |config|
  config.swagger_docs = {
    components: {
      schemas: {
        article: Jbuilder::Schema.yaml(FactoryBot.build(:article, id: 1),
          title: 'Article',
          description: 'Article in the blog',
          locals: {
            current_user: FactoryBot.build(:user, admin: true)
          })
      }
    }
  }
end

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/bullet-train-co/jbuilder-schema.

License

The gem is available as open source under the terms of the MIT License.

Open-source development sponsored by: