OasRails is a Rails engine for generating automatic interactive documentation for your Rails APIs. It generates an OAS 3.1 document and displays it using RapiDoc.
🎬 A Demo Video Here: https://vimeo.com/1013687332 🎬
- ApiPie: Doesn't support OAS 3.1, requires learning a DSL, lacks a nice UI
- swagger_yard-rails: Seems abandoned, but serves as inspiration
- Rswag: Not automatic, depends on RSpec; Many developers now use Minitest as it's the default test framework
- grape-swagger: Requires Grape
- rspec_api_documentation: Requires RSpec and a command to generate the docs
- Dynamic: No command required to generate docs
- Simple: Complement default documentation with a few comments; no need to learn a complex DSL
- Pure Ruby on Rails APIs: No additional frameworks needed (e.g., Grape, RSpec)
After experiencing the interactive documentation in Python's fast-api framework, I sought similar functionality in Ruby on Rails. Unable to find a suitable solution, I asked on Stack Overflow years ago. Now, with some free time while freelancing as an API developer, I decided to build my own tool.
Note: This is not yet a production-ready solution. The code may be rough and behave unexpectedly, but I am actively working on improving it. If you like the idea, please consider contributing to its development.
The goal is to minimize the effort required to create comprehensive documentation. By following REST principles in Rails, we believe this is achievable. You can enhance the documentation using Yard tags.
- Installation
- Configuration
- Usage
- Securing the OasRails Engine
- Customizing the View
- Contributing
- License
-
Add this line to your Rails application's Gemfile:
gem "oas_rails"`
-
Execute:
bundle
-
Mount the engine in your config/routes.rb file
mount OasRails::Engine => '/docs'
You'll now have basic documentation based on your routes and automatically gathered information at localhost:3000/docs
. To enhance it, create an initializer file and add Yard tags to your controller methods.
To configure OasRails, you MUST create an initializer file including all your settings. The first step is to create your initializer file, which you can easily do with:
rails generate oas_rails:config
Then fill it with your data. Below are the available configuration options:
config.info.title
: The title of your API documentation.config.info.summary
: A brief summary of your API.config.info.description
: A detailed description of your API. This can include markdown formatting and will be displayed prominently in your documentation.config.info.contact.name
: The name of the contact person or organization.config.info.contact.email
: The contact email address.config.info.contact.url
: The URL for more information or support.
config.servers
: An array of server objects, each containingurl
anddescription
keys. For more details, refer to the OpenAPI Specification.
config.tags
: An array of tag objects, each containingname
anddescription
keys. For more details, refer to the OpenAPI Specification.
config.default_tags_from
: Determines the source of default tags for operations. Can be set to:namespace
or:controller
.config.autodiscover_request_body
: Automatically detects request bodies for create/update methods. Default istrue
.config.autodiscover_responses
: Automatically detects responses from controller renders. Default istrue
.config.api_path
: Sets the API path if your API is under a different namespace.config.ignored_actions
: Sets an array with the controller or controller#action. No necessary to add api_path before.
config.authenticate_all_routes_by_default
: Determines whether to authenticate all routes by default. Default istrue
.config.security_schema
: The default security schema used for authentication. Choose a predefined security schema from[:api_key_cookie, :api_key_header, :api_key_query, :basic, :bearer, :bearer_jwt, :mutual_tls]
.config.security_schemas
: Custom security schemas. Follow the OpenAPI Specification for defining these schemas.
-
config.set_default_responses
: Determines whether to add default errors responses to endpoint. Default istrue
. -
config.possible_default_responses
: Array with possible default errors.(Some will be added depending on the endpoint, example: not_found only works with show/update/delete). Default: [:not_found, :unauthorized, :forbidden]. It should be HTTP status code symbols from the list:[:not_found, :unauthorized, :forbidden, :internal_server_error, :unprocessable_entity]
-
config.response_body_of_default
: body for use in default responses. It must be a String hash like the used in request body tags. Default: "{ message: String }"
In addition to the information provided in the initializer file and the data that can be extracted from the routes and methods automatically, it is essential to document your API in the following way. The documentation is created with the help of YARD, so the methods are documented with comment tags.
Almost every description in an OAS file supports simple markdown. The following tags are available for documenting your endpoints:
@summary
Structure: @summary text
Used to add a summary to the endpoint. It replaces the default summary/title of the endpoint.
Example:
# @summary This endpoint creates a User
@parameter
Structure: @parameter name(position) [type] text
Represents a parameter for the endpoint. The position can be: header
, path
, cookie
, or query
. The type should be a valid Ruby class: String
, Integer
, Array<String>
, etc. Add a !
before the class to indicate a required parameter.
Examples:
# @parameter page(query) [Integer] The page number.
# @parameter slug(path) [!String] Slug of the Project.
@request_body
Structure: @request_body text [type<structure>]
Documents the request body needed by the endpoint. The structure is optional if you provide a valid Active Record class. Use !
to indicate a required request body.
Example:
# @request_body The user to be created [!Hash{user: Hash{name: String, age: Integer, password: String}}]
# @request_body The user to be created [!User]
# @request_body The user to be created [User]
# @request_body The user to be created [!Hash{user: Hash{name: String, age: Integer, password: String, surnames: Array<String>, coords: Hash{lat: String, lng: String}}}]
@request_body_example
Structure: @request_body_example text [type] structure
Adds examples to the provided request body.
Example:
# @request_body_example A complete User. [Hash] {user: {name: 'Luis', age: 30, password: 'MyWeakPassword123'}}
@response
Structure: @response text(code) [type<structure>]
Documents the responses of the endpoint and overrides the default responses found by the engine.
Example:
# @response User not found by the provided Id(404) [Hash{success: Boolean, message: String}]
# @response Validation errors(422) [Hash{success: Boolean, errors: Array<Hash{field: String, type: String, detail: Array<String>}>}]
@response_example
Structure: @response_example text(code) [String Hash]
Documents response examples of the endpoint associated to a response code.
Example:
# @response_example Invalida Email(422) [{success: "false", errors: [{field: "email", type: "email", detail: ["Invalid email"]}] }]
# @response_example Id not exists (404) [{success: "false", message: "Nothing found with the provided ID." }]
@tag
Structure: @tag text
Tags your endpoints. You can complete the tag documentation in the initializer file by defining these tags beforehand. It's not necessary for the tag to exist in your initializer file before use.
Example:
# @tag Users
@no_auth
Structure: @no_auth
This tag will remove any security requirement from the endpoint. Useful when most of your endpoints require authentication and only a few do not.(Ex: Login, Registration...)
Example:
# @no_auth
@auth
Structure: @auth [types]
This tag will set which security mechanisms can be used for the endpoint. The security mechanisms MUST be defined previously in the initializer file.
Example:
# @auth [bearer, basic]
You can use these tags in your controller methods to enhance the automatically generated documentation. Remember to use markdown formatting in your descriptions for better readability in the generated OAS document.
class UsersController < ApplicationController
before_action :set_user, only: %i[show update destroy]
# @summary Returns a list of Users.
#
# @parameter offset(query) [Integer] Used for pagination of response data (default: 25 items per response). Specifies the offset of the next block of data to receive.
# @parameter status(query) [Array<String>] Filter by status. (e.g. status[]=inactive&status[]=deleted).
# @parameter X-front(header) [String] Header for identify the front.
def index
@users = User.all
end
# @summary Get a user by id.
# @auth [bearer]
#
# This method show a User by ID. The id must exist of other way it will be returning a **`404`**.
#
# @parameter id(path) [Integer] Used for identify the user.
# @response Requested User(200) [Hash{user: {name: String, email: String, created_at: DateTime }}]
# @response User not found by the provided Id(404) [Hash{success: Boolean, message: String}]
# @response You don't have the right permission for access to this resource(403) [Hash{success: Boolean, message: String}]
def show
render json: @user
end
# @summary Create a User
# @no_auth
#
# @request_body The user to be created. At least include an `email`. [!User]
# @request_body_example basic user [Hash] {user: {name: "Luis", email: "[email protected]"}}
def create
@user = User.new(user_params)
if @user.save
render json: @user, status: :created
else
render json: { success: false, errors: @user.errors }, status: :unprocessable_entity
end
end
# A `user` can be updated with this method
# - There is no option
# - It must work
# @tags users, update
# @request_body User to be created [!Hash{user: { name: String, email: !String, age: Integer, available_dates: Array<Date>}}]
# @request_body_example Update user [Hash] {user: {name: "Luis", email: "[email protected]"}}
# @request_body_example Complete User [Hash] {user: {name: "Luis", email: "[email protected]", age: 21}}
def update
if @user.update(user_params)
render json: @user
else
render json: @user.errors, status: :unprocessable_entity
end
end
# @summary Delete a User
# Delete a user and his associated data.
def destroy
@user.destroy!
redirect_to users_url, notice: 'User was successfully destroyed.', status: :see_other
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
@user = User.find(params[:id])
end
# Only allow a list of trusted parameters through.
def user_params
params.require(:user).permit(:name, :email)
end
end
To secure the OasRails engine, which exposes an endpoint for showing the OAS definition, you can configure authentication to ensure that only authorized users have access. Here are a few methods to achieve this:
Use basic authentication to protect the OasRails endpoint. You can set this up in an initializer:
# config/initializers/oas_rails.rb
OasRails::Engine.middleware.use(Rack::Auth::Basic) do |username, password|
ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.oas_rails_username, username) &
ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.oas_rails_password, password)
end
You can use Devise's authenticate
helper to restrict access to the OasRails endpoint. For example, you can allow only admin users to access the endpoint:
# config/routes.rb
# ...
authenticate :user, ->(user) { user.admin? } do
mount OasRails::Engine, at: '/docs'
end
To support custom authentication, you can extend the OasRails' ApplicationController using a hook. This allows you to add custom before actions to check for specific user permissions:
# config/initializers/oas_rails.rb
ActiveSupport.on_load(:oas_rails_application_controller) do
# context here is OasRails::ApplicationController
before_action do
raise ActionController::RoutingError.new('Not Found') unless current_user&.admin?
end
def current_user
# Load the current user
User.find(session[:user_id]) # Adjust according to your authentication logic
end
end
The OasRails engine provides an easy way to display your OpenAPI Specification (OAS) within your Rails application. By default, it includes an index
view in the OasRailsController
that displays RapiDoc through a CDN with default configurations. You can easily override this view to replace RapiDoc entirely or configure it differently.
To override the index
view provided by the OasRails engine, follow these steps:
-
Create the Override View File: In your host application, create a new file at the path
app/views/oas_rails/oas_rails/index.html.erb
. If the directories do not exist, you will need to create them. -
Customize the View: Open the newly created
index.html.erb
file and add your custom HTML and ERB code to display the OAS as desired. You can refer to the source code of this project for guidance.
Once the custom view file is in place, Rails will automatically use it instead of the view provided by the OasRails engine. This allows you to fully customize the presentation of the OAS without modifying the engine's code.
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star⭐! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
- Clean, document and structure the code
- Support documentation of authentication methods
- Define Global Tags/Configuration (e.g., common responses like 404 errors)
- Post-process the JSON and replace common objects with references to components
- Create a temporary file with the JSON in production mode to avoid rebuilding it on every request
- Create tags for popular gems used in APIs (e.g., a
@pagy
tag for common pagination parameters) - Add basic authentication to OAS and UI for security reasons (Solution documented, not need to be managed by the engine)
- Implement ability to define OAS by namespaces (e.g., generate OAS for specific routes like
/api
or separate versions V1 and V2)
The gem is available as open source under the terms of the GPL-3.0.