| name | code:ruby-rails |
| description | Build Rails API apps with RSwag documentation. Controllers, service objects, request specs, factories.
<example>
Context: User is building a Rails API
user: "create a users endpoint with RSwag docs"
</example>
<example>
Context: User needs Rails patterns
user: "write a service object for order processing"
</example>
|
Ruby on Rails Development
Rails API with RSwag for OpenAPI documentation. Specs are tests AND docs.
Quick Start
rails new myapi --api -T --database=postgresql
cd myapi
Essential Gems
gem "rswag-api"
gem "rswag-ui"
gem "oj"
gem "kaminari"
group :development, :test do
gem "rspec-rails"
gem "rswag-specs"
gem "factory_bot_rails"
gem "faker"
end
Project Structure
myapi/
├── app/
│ ├── controllers/
│ │ └── api/
│ │ └── v1/
│ │ ├── base_controller.rb
│ │ └── users_controller.rb
│ ├── models/
│ └── services/ # Business logic
├── spec/
│ ├── factories/
│ ├── requests/
│ │ └── api/v1/
│ ├── support/
│ │ ├── api_helpers.rb
│ │ └── factory_bot.rb
│ ├── rails_helper.rb
│ └── swagger_helper.rb
└── swagger/
└── v1/swagger.yaml # Generated
Base Controller
module Api::V1
class BaseController < ApplicationController
rescue_from ActiveRecord::RecordNotFound do |e|
render json: { error: "Not found" }, status: :not_found
end
rescue_from ActionController::ParameterMissing do |e|
render json: { error: e.message }, status: :bad_request
end
private
def authenticate!
token = request.headers["Authorization"]&.split(" ")&.last
@current_user = User.find_by_token(token)
render json: { error: "Unauthorized" }, status: :unauthorized unless @current_user
end
end
end
Resource Controller
module Api::V1
class UsersController < BaseController
before_action :set_user, only: [:show, :update, :destroy]
def index
@users = User.all
render json: @users
end
def show
render json: @user
end
def create
@user = User.new(user_params)
if @user.save
render json: @user, status: :created
else
render json: { errors: @user.errors }, status: :unprocessable_entity
end
end
private
def set_user
@user = User.find(params[:id])
end
def user_params
params.require().permit(, )
RSwag Spec (API Documentation)
require "swagger_helper"
RSpec.describe "Users API", type: :request do
path "/api/v1/users" do
get "List users" do
tags "Users"
produces "application/json"
response "200", "success" do
schema type: :array, items: { "$ref" => "#/components/schemas/User" }
before { create_list(:user, 3) }
run_test!
end
end
post "Create user" do
tags "Users"
consumes "application/json"
produces "application/json"
parameter name: :body, in: :body, schema: { "$ref" => "#/components/schemas/UserInput" }
response "201", "created" do
schema "$ref" => "#/components/schemas/User"
let(:body) { { user: { name: "Alice", email: "alice@example.com" } } }
run_test!
end
response ,
let() { { { } } }
run_test!
path
parameter , ,
get
tags
produces
response ,
schema =>
let() { create().id }
run_test!
response ,
let() { }
run_test!
Swagger Helper
require "rails_helper"
RSpec.configure do |config|
config.openapi_root = Rails.root.join("swagger").to_s
config.openapi_specs = {
"v1/swagger.yaml" => {
openapi: "3.0.1",
info: { title: "API V1", version: "v1" },
paths: {},
components: {
schemas: {
User: {
type: :object,
properties: {
id: { type: :integer },
name: { type: :string },
email: { type: :string, format: :email }
},
required: %w[id name email]
},
Error: {
type: :object,
properties: { error: { type: :string } }
}
},
securitySchemes: {
bearer: { type: :http, scheme: :bearer }
}
}
}
}
config.openapi_format =
Service Objects
class ApplicationService
def self.call(...)
new(...).call
end
private
def success(data = {})
Result.new(success: true, **data)
end
def failure(errors)
Result.new(success: false, errors: Array(errors))
end
Result = Data.define(:success, :errors, :data) do
alias_method :success?, :success
def initialize(success:, errors: [], **data)
super(success:, errors:, data:)
end
end
end
module Orders
class Create < ApplicationService
def initialize(user:, )
= user
= items
failure() .empty?
order = .new( , )
order.save ? success() : failure(order.errors)
Workflow
rails g model User name:string email:string
rails g controller api/v1/users
rails db:migrate
rails rswag:specs:swaggerize
rails s
open http://localhost:3000/api-docs
Summary
| Concern | Solution |
|---|
| Framework | Rails API mode |
| Documentation | RSwag (specs = docs) |
| Authentication | Bearer token |
| Pagination | Kaminari |
| Business logic | Service objects |
| Testing | RSpec request specs |