openapi_blocks 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +153 -85
- data/README.pt-BR.md +230 -342
- data/app/controllers/openapi_blocks/spec_controller.rb +48 -13
- data/config/routes.rb +2 -1
- data/lib/openapi_blocks/base.rb +3 -0
- data/lib/openapi_blocks/builder.rb +3 -1
- data/lib/openapi_blocks/controller.rb +44 -0
- data/lib/openapi_blocks/resource.rb +20 -0
- data/lib/openapi_blocks/routing/extractor.rb +16 -1
- data/lib/openapi_blocks/serializer.rb +193 -0
- data/lib/openapi_blocks/spec/components.rb +14 -6
- data/lib/openapi_blocks/version.rb +1 -1
- data/lib/openapi_blocks.rb +4 -0
- metadata +18 -1
data/README.pt-BR.md
CHANGED
|
@@ -4,11 +4,35 @@ OpenapiBlocks é uma gem Rails que gera automaticamente documentação OpenAPI 3
|
|
|
4
4
|
|
|
5
5
|
Sem anotações manuais. Sem ruído de DSL nos controllers. Basta declarar o que deve ser exposto e o spec é gerado automaticamente.
|
|
6
6
|
|
|
7
|
+
## Principais mudanças (recentes)
|
|
8
|
+
- Versão padrão do OpenAPI: `3.1.0` (suportado: `3.1.0`, `3.0.3`).
|
|
9
|
+
- A Swagger UI é servida no caminho onde a engine foi montada e usa endpoints do mesmo origin (same-origin) para evitar CORS — a UI mostra uma lista de servidores, mas buscará o spec a partir da URL montada.
|
|
10
|
+
- A saída YAML é normalizada para chaves em string (`deep_stringify_keys`) para que o campo `openapi` seja reconhecido pelo Swagger UI.
|
|
11
|
+
- O DSL `association` usa `read_only: true` para marcar associações como somente resposta e excluí-las dos schemas `*Input`; associações/atributos `read_only` continuam presentes nas respostas.
|
|
12
|
+
- O `tags` é gerado no nível do documento a partir dos paths e pode ser customizado via `tags` nas classes e operações.
|
|
13
|
+
- Referências de schema aceitam `Symbol` (ex.: `schema: :user`) e arrays com items como símbolos (ex.: `items: :user`).
|
|
14
|
+
# OpenapiBlocks
|
|
15
|
+
|
|
16
|
+
OpenapiBlocks é uma gem Rails que gera automaticamente documentação OpenAPI 3.0/3.1 a partir dos seus modelos ActiveRecord, validações do ActiveModel e rotas do Rails — inspirada em ActiveModel::Serializer.
|
|
17
|
+
|
|
18
|
+
Sem anotações manuais. Sem ruído de DSL nos controllers. Basta declarar o que deve ser exposto e o spec é gerado automaticamente. Inclui um serializer interno de alto desempenho — aproximadamente 3.6× mais rápido que `as_json` com escalabilidade linear consistente.
|
|
19
|
+
|
|
20
|
+
## Principais mudanças (recentes)
|
|
21
|
+
- `OpenapiBlocks::Resource` e `OpenapiBlocks::Controller` foram introduzidos para separar responsabilidades de serialização e documentação.
|
|
22
|
+
- Versão padrão do OpenAPI: `3.1.0` (suportado: `3.1.0`, `3.0.3`).
|
|
23
|
+
- Scalar UI agora é servido em `/docs/scalar` ao lado da Swagger UI em `/docs`.
|
|
24
|
+
- A Swagger UI usa endpoints same-origin para evitar problemas de CORS ao usar "Try it out"; a UI mostra servidores configurados, mas busca o spec a partir da URL montada da engine.
|
|
25
|
+
- A saída YAML é normalizada para chaves em string (`deep_stringify_keys`) para que o campo `openapi` seja reconhecido pelo Swagger UI.
|
|
26
|
+
- O DSL `association` utiliza `read_only: true` para marcar associações como somente-resposta e excluí-las dos schemas `*Input`; atributos/associações `read_only` continuam presentes em respostas.
|
|
27
|
+
- `tags` são gerados no nível do documento a partir dos paths e podem ser customizados via `tags` nas classes e operações.
|
|
28
|
+
- Referências de schema aceitam `Symbol` (ex.: `schema: :user`) e arrays com `items` como símbolos (ex.: `items: :user`).
|
|
29
|
+
- O serializer compila um método extrator monolítico por classe em tempo de boot usando `class_eval`, eliminando ramificações por objeto e chamadas lambda em tempo de execução.
|
|
30
|
+
|
|
7
31
|
---
|
|
8
32
|
|
|
9
33
|
## Instalação
|
|
10
34
|
|
|
11
|
-
Adicione ao seu
|
|
35
|
+
Adicione ao seu Gemfile:
|
|
12
36
|
|
|
13
37
|
```ruby
|
|
14
38
|
gem "openapi_blocks"
|
|
@@ -28,468 +52,332 @@ bundle install
|
|
|
28
52
|
|
|
29
53
|
```ruby
|
|
30
54
|
# config/routes.rb
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
55
|
Rails.application.routes.draw do
|
|
35
56
|
mount OpenapiBlocks::Engine => "/docs"
|
|
57
|
+
|
|
36
58
|
resources :users
|
|
37
59
|
end
|
|
38
60
|
```
|
|
39
61
|
|
|
40
62
|
Isso expõe:
|
|
41
63
|
|
|
42
|
-
|
|
43
|
-
GET /docs
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
GET /docs/openapi.yaml
|
|
64
|
+
```
|
|
65
|
+
GET /docs -> Scalar UI
|
|
66
|
+
GET /docs/swagger -> Swagger UI
|
|
67
|
+
GET /docs/openapi.json -> OpenAPI spec in JSON
|
|
68
|
+
GET /docs/openapi.yaml -> OpenAPI spec in YAML
|
|
69
|
+
```
|
|
47
70
|
|
|
48
71
|
### 2. Configure o initializer
|
|
49
72
|
|
|
50
73
|
```ruby
|
|
51
74
|
# config/initializers/openapi_blocks.rb
|
|
52
|
-
|
|
53
75
|
OpenapiBlocks.configure do |config|
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
config.openapi_version = "3.1.0" # "3.0.3" ou "3.1.0"
|
|
77
|
+
|
|
56
78
|
config.info do
|
|
57
79
|
title "Minha API"
|
|
58
80
|
version "1.0.0"
|
|
59
81
|
description "Documentação da API gerada automaticamente"
|
|
82
|
+
|
|
60
83
|
contact do
|
|
61
84
|
name "Minha equipe"
|
|
62
85
|
email "api@mycompany.com"
|
|
63
86
|
url "https://mycompany.com"
|
|
64
87
|
end
|
|
88
|
+
|
|
65
89
|
license do
|
|
66
90
|
name "MIT"
|
|
67
91
|
url "https://opensource.org/licenses/MIT"
|
|
68
92
|
end
|
|
69
93
|
end
|
|
94
|
+
|
|
70
95
|
config.servers do
|
|
71
96
|
server do
|
|
72
97
|
url "https://api.mycompany.com"
|
|
73
98
|
description "Produção"
|
|
74
99
|
end
|
|
100
|
+
|
|
75
101
|
server do
|
|
76
102
|
url "http://localhost:3000"
|
|
77
103
|
description "Desenvolvimento"
|
|
78
104
|
end
|
|
79
105
|
end
|
|
80
|
-
config.watch = :development # recarrega automaticamente em mudanças de arquivo
|
|
81
|
-
end
|
|
82
|
-
```
|
|
83
106
|
|
|
84
|
-
|
|
107
|
+
config.watch = :development # auto-reload em mudanças de arquivo
|
|
85
108
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
109
|
+
# opcional: esquemas de segurança
|
|
110
|
+
config.security do
|
|
111
|
+
bearer_token format: "JWT"
|
|
112
|
+
api_key name: "X-API-Key", in: :header
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
```
|
|
92
116
|
|
|
93
117
|
---
|
|
94
118
|
|
|
95
119
|
## Uso
|
|
96
120
|
|
|
97
|
-
|
|
121
|
+
OpenapiBlocks fornece duas classes base com responsabilidades distintas:
|
|
98
122
|
|
|
99
|
-
|
|
123
|
+
- `OpenapiBlocks::Resource` — define o model, campos, associações e lógica de serialização.
|
|
124
|
+
- `OpenapiBlocks::Controller` — define operações da API, parâmetros e respostas para documentação.
|
|
125
|
+
- `OpenapiBlocks::Base` — classe legada que combina ambas as responsabilidades. Ainda suportada.
|
|
100
126
|
|
|
101
|
-
|
|
127
|
+
### Resource + Controller (recomendado)
|
|
128
|
+
|
|
129
|
+
```
|
|
102
130
|
app/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
openapi/
|
|
132
|
+
user_resource.rb -> serialização + schema
|
|
133
|
+
user_openapi.rb -> documentação da API
|
|
134
|
+
post_resource.rb
|
|
135
|
+
post_openapi.rb
|
|
107
136
|
```
|
|
108
137
|
|
|
109
138
|
```ruby
|
|
110
|
-
# app/openapi/
|
|
111
|
-
|
|
112
|
-
class UserOpenapi < OpenapiBlocks::Base
|
|
139
|
+
# app/openapi/user_resource.rb
|
|
140
|
+
class UserResource < OpenapiBlocks::Resource
|
|
113
141
|
# o model User é inferido automaticamente pelo nome da classe
|
|
114
142
|
|
|
115
|
-
# ignora campos sensíveis ou desnecessários
|
|
116
143
|
ignore :password_digest, :reset_password_token
|
|
117
144
|
|
|
118
|
-
|
|
119
|
-
association :company
|
|
120
|
-
association :posts, type: :array
|
|
121
|
-
|
|
122
|
-
# atributos virtuais (não existem no banco)
|
|
123
|
-
attribute :full_name, type: :string
|
|
124
|
-
attribute :token, type: :string, read_only: true
|
|
125
|
-
end
|
|
126
|
-
```
|
|
145
|
+
association :posts, type: :array, read_only: true
|
|
127
146
|
|
|
128
|
-
|
|
147
|
+
attribute :full_name, type: :string, read_only: true
|
|
148
|
+
attribute :access_token, type: :string, read_only: true
|
|
149
|
+
attribute :nickname, type: :string
|
|
129
150
|
|
|
130
|
-
|
|
151
|
+
# método definido aqui — chamado na instância do recurso
|
|
152
|
+
def full_name
|
|
153
|
+
"#{object.name} (#{object.email})"
|
|
154
|
+
end
|
|
131
155
|
|
|
132
|
-
|
|
133
|
-
class User < ApplicationRecord
|
|
134
|
-
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
|
|
135
|
-
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
136
|
-
validates :age, numericality: { greater_than: 0 }
|
|
137
|
-
validates :role, inclusion: { in: %w[admin user guest] }
|
|
156
|
+
# ou omita o método e ele será delegado ao model automaticamente
|
|
138
157
|
end
|
|
139
158
|
```
|
|
140
159
|
|
|
141
|
-
OpenapiBlocks gera:
|
|
142
|
-
|
|
143
|
-
- `User` schema a partir das colunas e tipos de `db/schema.rb`
|
|
144
|
-
- `UserInput` schema para bodies de request de `POST`, `PUT` e `PATCH` (sem `id`, `created_at`, `updated_at`)
|
|
145
|
-
- `UserInput` schema para bodies de request de `POST`, `PUT` e `PATCH` (sem `id`, `created_at`, `updated_at` e atributos virtuais marcados como `read_only`)
|
|
146
|
-
- campos `required` a partir de validações `presence: true`
|
|
147
|
-
- `minLength` e `maxLength` a partir de validações `length`
|
|
148
|
-
- `minimum` e `maximum` a partir de validações `numericality`
|
|
149
|
-
- `enum` a partir de validações `inclusion`
|
|
150
|
-
- `format: "email"` a partir de validações de formato
|
|
151
|
-
- todos os paths a partir de `config/routes.rb`
|
|
152
|
-
|
|
153
|
-
### Atributos Virtuais
|
|
154
|
-
|
|
155
|
-
Atributos virtuais são campos que existem apenas na resposta da API e não no banco de dados.
|
|
156
|
-
|
|
157
|
-
| Opção | Descrição | Aparece em User | Aparece em UserInput |
|
|
158
|
-
| ---------------- | ------------------------------------------ | :-------------: | :------------------: |
|
|
159
|
-
| read_only: true | Campos calculados ou gerados pelo sistema | SIM | NÃO |
|
|
160
|
-
| read_only: false | Campos que o cliente pode enviar e receber | SIM | SIM |
|
|
161
|
-
|
|
162
160
|
```ruby
|
|
163
|
-
attribute :full_name, type: :string, read_only: true # apenas resposta
|
|
164
|
-
attribute :access_token, type: :string, read_only: true # apenas resposta
|
|
165
|
-
attribute :nickname, type: :string # request e response
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Customizando operações
|
|
169
|
-
|
|
170
|
-
````ruby
|
|
171
161
|
# app/openapi/user_openapi.rb
|
|
172
|
-
class UserOpenapi < OpenapiBlocks::
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
description "Retorna uma lista paginada de usuários ativos"
|
|
176
|
-
parameter :page, in: :query, type: :integer, description: "Número da página"
|
|
177
|
-
parameter :per_page, in: :query, type: :integer, description: "Itens por página"
|
|
178
|
-
response 200, description: "Lista de usuários", schema: { type: :array, items: :User }
|
|
179
|
-
response 401, description: "Não autorizado"
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
operation :show do
|
|
183
|
-
summary "Buscar um usuário"
|
|
184
|
-
response 200, description: "Usuário encontrado", schema: :User
|
|
185
|
-
response 404, description: "Usuário não encontrado"
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
operation :create do
|
|
189
|
-
# OpenapiBlocks
|
|
190
|
-
|
|
191
|
-
OpenapiBlocks é uma gem Rails que gera automaticamente documentação OpenAPI 3.0/3.1 a partir dos seus modelos ActiveRecord, validações do ActiveModel e rotas do Rails, inspirada em ActiveModel::Serializer.
|
|
192
|
-
|
|
193
|
-
Sem anotações manuais. Sem ruído de DSL nos controllers. Basta declarar o que deve ser exposto e o spec é gerado automaticamente.
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## Instalação
|
|
198
|
-
|
|
199
|
-
Adicione ao seu `Gemfile`:
|
|
200
|
-
|
|
201
|
-
```ruby
|
|
202
|
-
gem "openapi_blocks"
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Depois execute:
|
|
206
|
-
|
|
207
|
-
```bash
|
|
208
|
-
bundle install
|
|
209
|
-
```
|
|
162
|
+
class UserOpenapi < OpenapiBlocks::Controller
|
|
163
|
+
resource UserResource
|
|
164
|
+
controller UsersController
|
|
210
165
|
|
|
211
|
-
|
|
166
|
+
tags "Users"
|
|
212
167
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
```ruby
|
|
218
|
-
# config/routes.rb
|
|
219
|
-
Rails.application.routes.draw do
|
|
220
|
-
mount OpenapiBlocks::Engine => "/docs"
|
|
168
|
+
operation :index do
|
|
169
|
+
summary "List all users"
|
|
170
|
+
description "Returns a paginated list of active users"
|
|
221
171
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
Isso expõe:
|
|
227
|
-
|
|
228
|
-
```
|
|
229
|
-
GET /docs -> Swagger UI
|
|
230
|
-
GET /docs/openapi.json -> OpenAPI spec em JSON
|
|
231
|
-
GET /docs/openapi.yaml -> OpenAPI spec em YAML
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### 2. Configure o initializer
|
|
235
|
-
|
|
236
|
-
```ruby
|
|
237
|
-
# config/initializers/openapi_blocks.rb
|
|
238
|
-
OpenapiBlocks.configure do |config|
|
|
239
|
-
# Versões suportadas: "3.1.0" e "3.0.3". O padrão desta gem é "3.1.0".
|
|
240
|
-
config.openapi_version = "3.1.0"
|
|
241
|
-
|
|
242
|
-
config.info do
|
|
243
|
-
title "Minha API"
|
|
244
|
-
version "1.0.0"
|
|
245
|
-
description "Documentação da API gerada automaticamente"
|
|
246
|
-
|
|
247
|
-
contact do
|
|
248
|
-
name "Minha equipe"
|
|
249
|
-
email "api@mycompany.com"
|
|
250
|
-
url "https://mycompany.com"
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
license do
|
|
254
|
-
name "MIT"
|
|
255
|
-
url "https://opensource.org/licenses/MIT"
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
config.servers do
|
|
260
|
-
server do
|
|
261
|
-
url "https://api.mycompany.com"
|
|
262
|
-
description "Produção"
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
server do
|
|
266
|
-
url "http://localhost:3000"
|
|
267
|
-
description "Desenvolvimento"
|
|
268
|
-
end
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
config.watch = :development # recarrega automaticamente em mudanças de arquivo
|
|
272
|
-
|
|
273
|
-
# opcional: esquemas de segurança
|
|
274
|
-
config.security do
|
|
275
|
-
bearer_token format: "JWT"
|
|
276
|
-
api_key name: "X-API-Key", in: :header
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
```
|
|
172
|
+
parameter :page, in: :query, type: :integer, description: "Page number"
|
|
173
|
+
parameter :per_page, in: :query, type: :integer, description: "Items per page"
|
|
280
174
|
|
|
281
|
-
|
|
282
|
-
|
|
175
|
+
response 200, description: "List of users", schema: { type: :array, items: :User }
|
|
176
|
+
response 401, description: "Unauthorized"
|
|
177
|
+
end
|
|
283
178
|
|
|
284
|
-
|
|
179
|
+
operation :show do
|
|
180
|
+
summary "Get a user"
|
|
285
181
|
|
|
286
|
-
|
|
182
|
+
response 200, description: "User found", schema: :User
|
|
183
|
+
response 404, description: "User not found"
|
|
287
184
|
|
|
288
|
-
|
|
185
|
+
no_security!
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
```
|
|
289
189
|
|
|
290
|
-
|
|
190
|
+
```ruby
|
|
191
|
+
# app/controllers/users_controller.rb
|
|
192
|
+
def index
|
|
193
|
+
render json: UserResource.serialize(User.includes(:posts))
|
|
194
|
+
end
|
|
291
195
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
post_openapi.rb -> Post model
|
|
297
|
-
order_openapi.rb -> Order model
|
|
298
|
-
```
|
|
196
|
+
def show
|
|
197
|
+
render json: UserResource.serialize(User.find(params[:id]))
|
|
198
|
+
end
|
|
199
|
+
```
|
|
299
200
|
|
|
300
|
-
|
|
301
|
-
# app/openapi/user_openapi.rb
|
|
302
|
-
class UserOpenapi < OpenapiBlocks::Base
|
|
303
|
-
# model User é inferido automaticamente a partir do nome da classe
|
|
201
|
+
### Base (legado, classe única)
|
|
304
202
|
|
|
305
|
-
|
|
306
|
-
|
|
203
|
+
```ruby
|
|
204
|
+
# app/openapi/user_openapi.rb
|
|
205
|
+
class UserOpenapi < OpenapiBlocks::Base
|
|
206
|
+
tags "Users"
|
|
307
207
|
|
|
308
|
-
|
|
309
|
-
ignore :password_digest, :reset_password_token
|
|
208
|
+
ignore :password_digest
|
|
310
209
|
|
|
311
|
-
|
|
312
|
-
association :company
|
|
313
|
-
association :posts, type: :array, read_only: true # excluído do UserInput
|
|
210
|
+
association :posts, type: :array, read_only: true
|
|
314
211
|
|
|
315
|
-
|
|
316
|
-
# read_only: true -> exposto apenas na resposta (User)
|
|
317
|
-
# read_only: false -> exposto em User e UserInput
|
|
318
|
-
attribute :full_name, type: :string, read_only: true
|
|
319
|
-
attribute :access_token, type: :string, read_only: true
|
|
320
|
-
attribute :nickname, type: :string
|
|
321
|
-
end
|
|
322
|
-
```
|
|
212
|
+
attribute :full_name, type: :string, read_only: true
|
|
323
213
|
|
|
324
|
-
|
|
214
|
+
operation :index do
|
|
215
|
+
summary "List all users"
|
|
216
|
+
response 200, description: "List of users", schema: { type: :array, items: :User }
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
```
|
|
325
220
|
|
|
326
|
-
|
|
221
|
+
```ruby
|
|
222
|
+
# app/controllers/users_controller.rb
|
|
223
|
+
def index
|
|
224
|
+
render json: UserOpenapi.serialize(User.includes(:posts))
|
|
225
|
+
end
|
|
226
|
+
```
|
|
327
227
|
|
|
328
|
-
|
|
329
|
-
class User < ApplicationRecord
|
|
330
|
-
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
|
|
331
|
-
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
332
|
-
validates :age, numericality: { greater_than: 0 }
|
|
333
|
-
validates :role, inclusion: { in: %w[admin user guest] }
|
|
334
|
-
end
|
|
335
|
-
```
|
|
228
|
+
---
|
|
336
229
|
|
|
337
|
-
|
|
230
|
+
## Serializer
|
|
338
231
|
|
|
339
|
-
|
|
340
|
-
- `UserInput` schema para bodies de request de `POST`, `PUT` e `PATCH` (sem `id`, `created_at`, `updated_at` e atributos virtuais marcados como `read_only`)
|
|
341
|
-
- campos `required` a partir de validações `presence: true`
|
|
342
|
-
- `minLength` e `maxLength` a partir de validações `length`
|
|
343
|
-
- `minimum` e `maximum` a partir de validações `numericality`
|
|
344
|
-
- `enum` a partir de validações `inclusion`
|
|
345
|
-
- `format: "email"` a partir de validações de formato
|
|
346
|
-
- todos os paths a partir de `config/routes.rb`
|
|
232
|
+
O serializer interno compila um método extrator monolítico por classe em tempo de boot usando `class_eval`. Não há loops, nem indirection por lambda e nem ramificações em tempo de execução por objeto.
|
|
347
233
|
|
|
348
|
-
|
|
234
|
+
### Performance (200 registros, arm64, Ruby 4.0)
|
|
349
235
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
236
|
+
| Método | i/s | μs/i | vs serialize |
|
|
237
|
+
|---|---:|---:|---:|
|
|
238
|
+
| serialize | 4 239 | 235 | — |
|
|
239
|
+
| to_json | 1 444 | 692 | 2.94× mais lento |
|
|
240
|
+
| as_json | 1 186 | 843 | 3.58× mais lento |
|
|
241
|
+
| oj+as_json | 1 126 | 888 | 3.77× mais lento |
|
|
354
242
|
|
|
355
|
-
|
|
356
|
-
summary "List all users"
|
|
357
|
-
description "Returns a paginated list of active users"
|
|
358
|
-
tags "Users", "Admin" # sobrescreve tags em nível de operação
|
|
243
|
+
Escalamento é linear — a vantagem ~3.6× em relação a `as_json` se mantém de 10 a 5000 registros.
|
|
359
244
|
|
|
360
|
-
|
|
361
|
-
parameter :per_page, in: :query, type: :integer, description: "Items per page"
|
|
245
|
+
### Atributos virtuais e resolução de método
|
|
362
246
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
247
|
+
| Declarado com | Método no resource? | Chamada |
|
|
248
|
+
|---|---:|---|
|
|
249
|
+
| `attribute :full_name` | sim | `resource_instance.full_name` |
|
|
250
|
+
| `attribute :full_name` | não | `object.full_name` (delegado ao model) |
|
|
251
|
+
| coluna no db | — | `object.full_name` (direto) |
|
|
366
252
|
|
|
367
|
-
|
|
368
|
-
summary "Get a user"
|
|
253
|
+
### Resolução de serializer de associação
|
|
369
254
|
|
|
370
|
-
|
|
371
|
-
response 404, description: "User not found"
|
|
372
|
-
end
|
|
255
|
+
Para cada associação, a resolução procura na ordem:
|
|
373
256
|
|
|
374
|
-
|
|
375
|
-
|
|
257
|
+
1. `PostResource` — se existir `serialize`, é usado diretamente.
|
|
258
|
+
2. `PostOpenapi` — se for um `Controller`, delega ao seu `_resource`.
|
|
259
|
+
3. Fallback — chama `as_json` no valor da associação.
|
|
376
260
|
|
|
377
|
-
|
|
378
|
-
response 422, description: "Invalid data"
|
|
379
|
-
end
|
|
261
|
+
---
|
|
380
262
|
|
|
381
|
-
|
|
382
|
-
summary "Update a user"
|
|
263
|
+
## O que é gerado automaticamente
|
|
383
264
|
|
|
384
|
-
|
|
385
|
-
response 404, description: "User not found"
|
|
386
|
-
response 422, description: "Invalid data"
|
|
387
|
-
end
|
|
265
|
+
Dado este model:
|
|
388
266
|
|
|
389
|
-
|
|
390
|
-
|
|
267
|
+
```ruby
|
|
268
|
+
class User < ApplicationRecord
|
|
269
|
+
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
|
|
270
|
+
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
271
|
+
validates :age, numericality: { greater_than: 0 }
|
|
272
|
+
validates :role, inclusion: { in: %w[admin user guest] }
|
|
273
|
+
end
|
|
274
|
+
```
|
|
391
275
|
|
|
392
|
-
|
|
393
|
-
response 404, description: "User not found"
|
|
394
|
-
end
|
|
395
|
-
end
|
|
396
|
-
```
|
|
276
|
+
OpenapiBlocks gera:
|
|
397
277
|
|
|
398
|
-
|
|
278
|
+
- `User` schema a partir de `db/schema.rb` (colunas e tipos)
|
|
279
|
+
- `UserInput` schema para bodies de `POST`, `PUT` e `PATCH` (sem `id`, `created_at`, `updated_at` e campos `read_only`)
|
|
280
|
+
- `required` a partir de validações `presence: true`
|
|
281
|
+
- `minLength` e `maxLength` a partir de validações `length`
|
|
282
|
+
- `minimum` e `maximum` a partir de validações `numericality`
|
|
283
|
+
- `enum` a partir de validações `inclusion`
|
|
284
|
+
- `format: "email"` a partir de validações de formato
|
|
285
|
+
- Todos os paths a partir de `config/routes.rb`
|
|
399
286
|
|
|
400
|
-
|
|
287
|
+
---
|
|
401
288
|
|
|
402
|
-
|
|
289
|
+
## Segurança
|
|
403
290
|
|
|
404
|
-
|
|
405
|
-
config.security do
|
|
406
|
-
bearer_token format: "JWT" # Authorization: Bearer <token>
|
|
407
|
-
api_key name: "X-API-Key", in: :header # X-API-Key: <key>
|
|
408
|
-
end
|
|
409
|
-
```
|
|
291
|
+
Configure esquemas de segurança globais no initializer:
|
|
410
292
|
|
|
411
|
-
|
|
293
|
+
```ruby
|
|
294
|
+
config.security do
|
|
295
|
+
bearer_token format: "JWT" # Authorization: Bearer <token>
|
|
296
|
+
api_key name: "X-API-Key", in: :header # X-API-Key: <key>
|
|
297
|
+
end
|
|
298
|
+
```
|
|
412
299
|
|
|
413
|
-
|
|
414
|
-
operation :index do
|
|
415
|
-
security :bearerAuth # apenas bearer nesta operação
|
|
416
|
-
end
|
|
300
|
+
Substitua a segurança por operação:
|
|
417
301
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
302
|
+
```ruby
|
|
303
|
+
operation :index do
|
|
304
|
+
security :bearerAuth # apenas bearer nesta operação
|
|
305
|
+
end
|
|
422
306
|
|
|
423
|
-
|
|
307
|
+
operation :show do
|
|
308
|
+
no_security! # endpoint público — sem autenticação
|
|
309
|
+
end
|
|
310
|
+
```
|
|
424
311
|
|
|
425
|
-
|
|
312
|
+
---
|
|
426
313
|
|
|
427
|
-
|
|
428
|
-
association :company # belongs_to — $ref para Company schema
|
|
429
|
-
association :posts, type: :array # has_many — array de $ref para Post schema
|
|
430
|
-
association :posts, type: :array, read_only: true # excluído do UserInput (response only)
|
|
431
|
-
```
|
|
314
|
+
## Associações
|
|
432
315
|
|
|
433
|
-
|
|
316
|
+
```ruby
|
|
317
|
+
association :company # belongs_to — $ref para Company schema
|
|
318
|
+
association :posts, type: :array # has_many — array de $ref para Post schema
|
|
319
|
+
association :posts, type: :array, read_only: true # excluído do UserInput (response only)
|
|
320
|
+
```
|
|
434
321
|
|
|
435
|
-
|
|
322
|
+
---
|
|
436
323
|
|
|
437
|
-
|
|
324
|
+
## Atributos Virtuais
|
|
438
325
|
|
|
439
|
-
|
|
440
|
-
| ---------------- | -------------------------------------- | :-------------: | :------------------: |
|
|
441
|
-
| read_only: true | Campos calculados ou gerados pelo sistema | SIM | NÃO |
|
|
442
|
-
| read_only: false | Campos que o cliente pode enviar e receber | SIM | SIM |
|
|
326
|
+
Atributos virtuais são campos que existem apenas na resposta da API e não no banco de dados.
|
|
443
327
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
```
|
|
328
|
+
| Opção | Descrição | Aparece em User | Aparece em UserInput |
|
|
329
|
+
|---|---|:---:|:---:|
|
|
330
|
+
| `read_only: true` | Campos calculados ou gerados pelo sistema | SIM | NÃO |
|
|
331
|
+
| `read_only: false` | Campos que o cliente pode enviar e receber | SIM | SIM |
|
|
449
332
|
|
|
450
|
-
|
|
333
|
+
```ruby
|
|
334
|
+
attribute :full_name, type: :string, read_only: true # response only
|
|
335
|
+
attribute :access_token, type: :string, read_only: true # response only
|
|
336
|
+
attribute :nickname, type: :string # request and response
|
|
337
|
+
```
|
|
451
338
|
|
|
452
|
-
|
|
339
|
+
---
|
|
453
340
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
341
|
+
## Mapeamento de tipos
|
|
342
|
+
|
|
343
|
+
| Tipo do ActiveRecord | Tipo OpenAPI |
|
|
344
|
+
|---|---|
|
|
345
|
+
| integer | integer / int32 |
|
|
346
|
+
| bigint | integer / int64 |
|
|
347
|
+
| float | number / float |
|
|
348
|
+
| decimal | number / double |
|
|
349
|
+
| string | string |
|
|
350
|
+
| text | string |
|
|
351
|
+
| boolean | boolean |
|
|
352
|
+
| date | string / date |
|
|
353
|
+
| datetime | string / date-time |
|
|
354
|
+
| uuid | string / uuid |
|
|
355
|
+
| json / jsonb | object |
|
|
467
356
|
|
|
468
|
-
|
|
357
|
+
---
|
|
469
358
|
|
|
470
|
-
|
|
359
|
+
## Auto-reload em desenvolvimento
|
|
471
360
|
|
|
472
|
-
|
|
361
|
+
OpenapiBlocks observa mudanças em:
|
|
473
362
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
363
|
+
```
|
|
364
|
+
app/openapi/**/*.rb
|
|
365
|
+
app/models/**/*.rb
|
|
366
|
+
config/routes.rb
|
|
367
|
+
db/schema.rb
|
|
368
|
+
```
|
|
480
369
|
|
|
481
|
-
|
|
370
|
+
O spec é regenerado automaticamente na próxima requisição para `/docs/openapi.json` sempre que qualquer um desses arquivos muda. Não é necessário reiniciar o servidor.
|
|
482
371
|
|
|
483
|
-
|
|
372
|
+
---
|
|
484
373
|
|
|
485
|
-
|
|
374
|
+
## Requisitos
|
|
486
375
|
|
|
487
|
-
|
|
488
|
-
|
|
376
|
+
- Ruby >= 3.2
|
|
377
|
+
- Rails >= 7.0
|
|
489
378
|
|
|
490
|
-
|
|
379
|
+
---
|
|
491
380
|
|
|
492
|
-
|
|
381
|
+
## Licença
|
|
493
382
|
|
|
494
|
-
|
|
495
|
-
````
|
|
383
|
+
MIT (LICENSE.txt)
|