openapi_blocks 0.4.1 → 0.6.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eafa1f0685d4c659609b5116dfac6e7204cee4bd5c60ea882417aebbe4cd67dd
4
- data.tar.gz: db613ff23df3f705b70f126f02a0d03334fffe8a8fcbfd3dbe563343fce69ef8
3
+ metadata.gz: 61f36b05bd60171d8f0a821a4775d65af84b9180e4221b095a8eb49632af0a41
4
+ data.tar.gz: 604229f12f550977abad1b52859582490b4bb66cb1bce7694bb24a9b7c942324
5
5
  SHA512:
6
- metadata.gz: f88f54dcc99be63820da03cedaa9062d5e1c3e185c1cbf0d6aa3ac2c00f066b9ea75002dd1e8d4061f51c0f4bb71d606dc5b632de4f80870f3c598bd47312ef7
7
- data.tar.gz: 75027794cbdebde235211302cc5da8495f2dba128441cdd8d94bfea4d7808acd1a66ef40569f768698c5c2b32fba205f99c71305673105b0229276d4c9c9e505
6
+ metadata.gz: 470ab2f51a7f5a6434c2e3361e0f040cd81e9cd811324f114e14a56909d821b0a5e4fdb4a858b657929303c7f4f99b7ace5a424a76ff2d9b6bb994ea9c308c00
7
+ data.tar.gz: '0900adcc92411360fe6eebb836cf8c424ee45bd435a169f135a572f5d0efe3d55bfede29e6e618f5ae8ad3e48f060e4204161f8afdd3597f822e15b9bb59e1af'
data/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2026-06-03
11
+
12
+ - `OpenapiBlocks::Serializer` optmize serialization using frozen string constants as hash keys to reduce object allocations
13
+
14
+ ## [0.5.0] - 2026-06-02
15
+
16
+ - Added generators
17
+
10
18
  ## [0.4.1] - 2026-06-02
11
19
 
12
20
  - Change parallel version
@@ -146,5 +154,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
146
154
  - `SpecController#ui` action serving Swagger UI with JSON/YAML spec switcher
147
155
 
148
156
  [Unreleased]: https://github.com/evotechbuilder/openapi_blocks/compare/v0.2.0...HEAD
157
+ [0.6.0]: https://github.com/evotechbuilder/openapi_blocks/compare/v0.5.0...v0.6.0
158
+ [0.5.0]: https://github.com/evotechbuilder/openapi_blocks/compare/v0.4.1...v0.5.0
159
+ [0.4.1]: https://github.com/evotechbuilder/openapi_blocks/compare/v0.4.0...v0.4.1
160
+ [0.4.0]: https://github.com/evotechbuilder/openapi_blocks/compare/v0.3.1...v0.4.0
161
+ [0.3.1]: https://github.com/evotechbuilder/openapi_blocks/compare/v0.3.0...v0.3.1
162
+ [0.3.0]: https://github.com/evotechbuilder/openapi_blocks/compare/v0.2.0...v0.3.0
149
163
  [0.2.0]: https://github.com/evotechbuilder/openapi_blocks/compare/v0.1.0...v0.2.0
150
164
  [0.1.0]: https://github.com/evotechbuilder/openapi_blocks/releases/tag/v0.1.0
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # OpenapiBlocks
2
2
 
3
- OpenapiBlocks is a Rails gem that automatically generates OpenAPI 3.0/3.1 documentation from your ActiveRecord models, ActiveModel validations, and Rails routes — inspired by [ActiveModel::Serializer](https://github.com/rails-api/active_model_serializers).
3
+ OpenapiBlocks is a Rails gem that automatically generates OpenAPI 3.0/3.1 documentation from your ActiveRecord models, ActiveModel validations, and Rails routes — inspired by ActiveModel::Serializer (https://github.com/rails-api/active_model_serializers).
4
4
 
5
- Versão em português brasileiro: [README.pt-BR.md](README.pt-BR.md)
5
+ Versão em português brasileiro: README.pt-BR.md
6
6
 
7
- No manual annotation. No DSL noise in your controllers. Just declare what to expose and the spec is generated automatically. Includes a high-performance built-in serializer — ~3.6× faster than `as_json` with consistent linear scaling from 10 to 5000 records.
7
+ No manual annotation. No DSL noise in your controllers. Just declare what to expose and the spec is generated automatically. Includes a high-performance built-in serializer — ~3.6× faster than as_json with consistent linear scaling from 10 to 5000 records.
8
8
 
9
9
  ---
10
10
 
11
11
  ## Installation
12
12
 
13
- Add to your `Gemfile`:
13
+ Add to your Gemfile:
14
14
 
15
15
  ```ruby
16
16
  gem "openapi_blocks"
@@ -24,6 +24,70 @@ bundle install
24
24
 
25
25
  ---
26
26
 
27
+ ## Generators
28
+
29
+ OpenapiBlocks provides three generators to get you started quickly.
30
+
31
+ ### Install
32
+
33
+ ```bash
34
+ rails generate openapi_blocks:install
35
+ ```
36
+
37
+ Creates `config/initializers/openapi_blocks.rb` with all available options commented out, and mounts the engine in `config/routes.rb`:
38
+
39
+ ```ruby
40
+ mount OpenapiBlocks::Engine => "/docs"
41
+ ```
42
+
43
+ ### Openapi
44
+
45
+ ```bash
46
+ rails generate openapi_blocks:openapi User
47
+ ```
48
+
49
+ Creates `app/openapi/user_openapi.rb` with all available DSL options commented out:
50
+
51
+ ```ruby
52
+ # app/openapi/user_openapi.rb
53
+ class UserOpenapi < OpenapiBlocks::Controller
54
+ # resource UserSerializer
55
+ # controller UsersController
56
+
57
+ # tags "Users"
58
+
59
+ # operation :index do
60
+ # summary "List all users"
61
+ # response 200, description: "List of users", schema: { type: :array, items: :User }
62
+ # end
63
+ end
64
+ ```
65
+
66
+ ### Serializer
67
+
68
+ ```bash
69
+ rails generate openapi_blocks:serializer User
70
+ ```
71
+
72
+ Creates `app/serializers/user_serializer.rb` with all available DSL options commented out:
73
+
74
+ ```ruby
75
+ # app/serializers/user_serializer.rb
76
+ class UserSerializer < OpenapiBlocks::Serializer
77
+ # ignore :password_digest, :reset_password_token
78
+
79
+ # association :posts, type: :array, read_only: true
80
+ # association :company
81
+
82
+ # attribute :full_name, type: :string, read_only: true
83
+ # def full_name
84
+ # "#{object.first_name} #{object.last_name}"
85
+ # end
86
+ end
87
+ ```
88
+
89
+ ---
90
+
27
91
  ## Setup
28
92
 
29
93
  ### 1. Mount the Engine
@@ -48,7 +112,7 @@ GET /docs/openapi.yaml -> OpenAPI spec in YAML
48
112
 
49
113
  ### 2. Configure the initializer
50
114
 
51
- `OpenapiBlocks.configure` is required. The gem raises `OpenapiBlocks::Error` on the first request if it was never called or if `info.title` / `info.version` are blank.
115
+ OpenapiBlocks.configure is required. The gem raises OpenapiBlocks::Error on the first request if it was never called or if info.title / info.version are blank.
52
116
 
53
117
  ```ruby
54
118
  # config/initializers/openapi_blocks.rb
@@ -101,9 +165,9 @@ end
101
165
 
102
166
  OpenapiBlocks provides two base classes with distinct responsibilities:
103
167
 
104
- - `OpenapiBlocks::Serializer` — defines the model, fields, associations, and serialization logic. Lives in `app/serializers/`.
105
- - `OpenapiBlocks::Controller` — defines API operations, parameters, and responses for documentation. Lives in `app/openapi/`.
106
- - `OpenapiBlocks::Base` — legacy base class that combines both concerns. Still supported.
168
+ - OpenapiBlocks::Serializer — defines the model, fields, associations, and serialization logic. Lives in app/serializers/.
169
+ - OpenapiBlocks::Controller — defines API operations, parameters, and responses for documentation. Lives in app/openapi/.
170
+ - OpenapiBlocks::Base — legacy base class that combines both concerns. Still supported.
107
171
 
108
172
  ### Recommended: Serializer + Controller
109
173
 
@@ -142,7 +206,7 @@ end
142
206
  ```ruby
143
207
  # app/openapi/user_openapi.rb
144
208
  class UserOpenapi < OpenapiBlocks::Controller
145
- resource UserSerializer
209
+ resource UserSerializer # links to the serializer — schema is derived from it
146
210
  controller UsersController
147
211
 
148
212
  tags "Users"
@@ -169,6 +233,19 @@ class UserOpenapi < OpenapiBlocks::Controller
169
233
  end
170
234
  ```
171
235
 
236
+ #### How the OpenAPI schema is generated
237
+
238
+ When `resource UserSerializer` is declared in a `Controller`, OpenapiBlocks derives the OpenAPI schema directly from the serializer — not from the model. This guarantees that what is documented is exactly what the API returns.
239
+
240
+ The schema is built from three sources on the serializer:
241
+
242
+ - ActiveRecord columns — read from `db/schema.rb` via the inferred model. Column types are mapped to OpenAPI types automatically.
243
+ - `attribute` declarations — virtual fields not present in the database. Fields declared with `read_only: true` appear in the `User` response schema but are excluded from the `UserInput` request schema.
244
+ - `association` declarations — resolved as `$ref` to the associated schema. Associations declared with `read_only: true` appear in the response but are excluded from `UserInput`.
245
+ - `ignore` declarations — columns excluded from both schemas.
246
+
247
+ The `UserInput` schema (used in POST, PUT and PATCH request bodies) is derived automatically from the `User` schema by removing `id`, `created_at`, `updated_at`, and any field marked `read_only: true`.
248
+
172
249
  ```ruby
173
250
  # app/controllers/users_controller.rb
174
251
  def index
@@ -229,7 +306,7 @@ def show
229
306
  end
230
307
  ```
231
308
 
232
- Serializer registration is automatic by convention (`UserSerializer` -> `User`). For explicit registration:
309
+ Serializer registration is automatic by convention (UserSerializer -> User). For explicit registration:
233
310
 
234
311
  ```ruby
235
312
  class AdminUserSerializer < OpenapiBlocks::Serializer
@@ -243,34 +320,41 @@ If no serializer is found, OpenapiBlocks falls back to default Rails rendering a
243
320
 
244
321
  ## Serializer
245
322
 
246
- The built-in serializer compiles a monolithic extractor method per class at boot time using `class_eval`. There are no loops, no lambda indirection, and no runtime branching per object.
323
+ The built-in serializer compiles a monolithic extractor method per class at boot time using class_eval. There are no loops, no lambda indirection, and no runtime branching per object.
247
324
 
248
325
  ### Performance (200 records, arm64, Ruby 4.0)
249
326
 
250
327
  | Method | i/s | μs/i | vs serialize |
251
- | ---------- | ----- | ---- | ------------ |
252
- | serialize | 4 239 | 235 | — |
253
- | to_json | 1 444 | 692 | 2.94× slower |
254
- | as_json | 1 186 | 843 | 3.58× slower |
255
- | oj+as_json | 1 126 | 888 | 3.77× slower |
328
+ |------------|-------|------|--------------|
329
+ | serialize | 4 504 | 198 | — |
330
+ | to_json | 1 444 | 692 | 2.89x slower |
331
+ | as_json | 1 179 | 453 | 2.81x slower |
332
+ | oj+as_json | 1 126 | 572 | 2.89x slower |
333
+ | AMS | 559 | 178 | 9.02x slower |
334
+
335
+ Scaling is linear — the 2.81x advantage over as_json holds from 10 to 5000 records.
336
+
337
+ ### Memory Allocation
256
338
 
257
- Scaling is linearthe 3.6× advantage over `as_json` holds from 10 to 5000 records.
339
+ OpenapiBlocks: 20MB / 225k objects fastest and lowest memory
340
+ as_json: 116MB / 1.2M objects — 2.81x slower, 5.6x more memory
341
+ AMS: 260MB / 2.7M objects — 9x slower, 13x more memory
258
342
 
259
343
  ### Virtual attributes and method resolution
260
344
 
261
- | Declared with | Method in serializer? | Calls |
262
- | ---------------------- | --------------------- | --------------------------------------- |
263
- | `attribute :full_name` | yes | `serializer_instance.full_name` |
264
- | `attribute :full_name` | no | `object.full_name` (delegated to model) |
265
- | column in db | — | `object.attribute` (direct) |
345
+ | Declared with | Method in serializer? | Calls |
346
+ | -------------------- | --------------------- | ------------------------------------- |
347
+ | attribute :full_name | yes | serializer_instance.full_name |
348
+ | attribute :full_name | no | object.full_name (delegated to model) |
349
+ | column in db | — | object.attribute (direct) |
266
350
 
267
351
  ### Association serializer resolution
268
352
 
269
353
  For each association, the serializer resolves the serializer class in this order:
270
354
 
271
- 1. `PostSerializer` — has `serialize`, used directly.
272
- 2. `PostOpenapi` — is a `Controller`, delegates to its `resource`.
273
- 3. Fallback — calls `as_json` on the association value.
355
+ 1. PostSerializer — has serialize, used directly.
356
+ 2. PostOpenapi — is a Controller, delegates to its resource.
357
+ 3. Fallback — calls as_json on the association value.
274
358
 
275
359
  ---
276
360
 
@@ -289,14 +373,14 @@ end
289
373
 
290
374
  OpenapiBlocks generates:
291
375
 
292
- - `User` schema from `db/schema.rb` columns and types
293
- - `UserInput` schema for `POST`, `PUT` and `PATCH` request bodies (without `id`, `created_at`, `updated_at` and `read_only` fields)
294
- - `required` fields from `presence: true` validations
295
- - `minLength`, `maxLength` from `length` validations
296
- - `minimum`, `maximum` from `numericality` validations
297
- - `enum` from `inclusion` validations
298
- - `format: "email"` from format validations
299
- - All paths from `config/routes.rb`
376
+ - User schema from db/schema.rb columns and types
377
+ - UserInput schema for POST, PUT and PATCH request bodies (without id, created_at, updated_at and read_only fields)
378
+ - required fields from presence: true validations
379
+ - minLength, maxLength from length validations
380
+ - minimum, maximum from numericality validations
381
+ - enum from inclusion validations
382
+ - format: "email" from format validations
383
+ - All paths from config/routes.rb
300
384
 
301
385
  ---
302
386
 
@@ -339,10 +423,10 @@ association :posts, type: :array, read_only: true # excluded from UserInput (re
339
423
 
340
424
  Virtual attributes are fields that exist in the API response but not in the database.
341
425
 
342
- | Option | Description | Appears in User | Appears in UserInput |
343
- | ------------------ | -------------------------------------- | :-------------: | :------------------: |
344
- | `read_only: true` | Calculated or system-generated fields | YES | NO |
345
- | `read_only: false` | Fields the client can send and receive | YES | YES |
426
+ | Option | Description | Appears in User | Appears in UserInput |
427
+ | ---------------- | -------------------------------------- | :-------------: | :------------------: |
428
+ | read_only: true | Calculated or system-generated fields | YES | NO |
429
+ | read_only: false | Fields the client can send and receive | YES | YES |
346
430
 
347
431
  ```ruby
348
432
  attribute :full_name, type: :string, read_only: true # response only
@@ -354,19 +438,19 @@ attribute :nickname, type: :string # request and response
354
438
 
355
439
  ## Type Mapping
356
440
 
357
- | ActiveRecord type | OpenAPI type |
358
- | ----------------- | ---------------------- |
359
- | `integer` | `integer` / `int32` |
360
- | `bigint` | `integer` / `int64` |
361
- | `float` | `number` / `float` |
362
- | `decimal` | `number` / `double` |
363
- | `string` | `string` |
364
- | `text` | `string` |
365
- | `boolean` | `boolean` |
366
- | `date` | `string` / `date` |
367
- | `datetime` | `string` / `date-time` |
368
- | `uuid` | `string` / `uuid` |
369
- | `json` / `jsonb` | `object` |
441
+ | ActiveRecord type | OpenAPI type |
442
+ | ----------------- | ------------------ |
443
+ | integer | integer / int32 |
444
+ | bigint | integer / int64 |
445
+ | float | number / float |
446
+ | decimal | number / double |
447
+ | string | string |
448
+ | text | string |
449
+ | boolean | boolean |
450
+ | date | string / date |
451
+ | datetime | string / date-time |
452
+ | uuid | string / uuid |
453
+ | json / jsonb | object |
370
454
 
371
455
  ---
372
456
 
@@ -382,7 +466,7 @@ config/routes.rb
382
466
  db/schema.rb
383
467
  ```
384
468
 
385
- The spec is automatically regenerated on the next request to `/docs/openapi.json` whenever any of these files change. No server restart needed.
469
+ The spec is automatically regenerated on the next request to /docs/openapi.json whenever any of these files change. No server restart needed.
386
470
 
387
471
  ---
388
472
 
@@ -395,4 +479,4 @@ The spec is automatically regenerated on the next request to `/docs/openapi.json
395
479
 
396
480
  ## License
397
481
 
398
- [MIT](LICENSE.txt)
482
+ MIT (LICENSE.txt)
data/README.pt-BR.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # OpenapiBlocks
2
2
 
3
- OpenapiBlocks é uma gem Rails que gera automaticamente documentação OpenAPI 3.0/3.1 a partir dos seus models ActiveRecord, validações ActiveModel e rotas do Rails — inspirada no [ActiveModel::Serializer](https://github.com/rails-api/active_model_serializers).
3
+ OpenapiBlocks é uma gem Rails que gera automaticamente documentação OpenAPI 3.0/3.1 a partir dos seus models ActiveRecord, validações ActiveModel e rotas do Rails — inspirada no ActiveModel::Serializer (https://github.com/rails-api/active_model_serializers).
4
4
 
5
- English version: [README.md](README.md)
5
+ English version: README.md
6
6
 
7
- Sem anotações manuais. Sem DSL nos controllers. Basta declarar o que expor e a spec é gerada automaticamente. Inclui um serializer de alta performance — ~3.6× mais rápido que `as_json` com escalabilidade linear de 10 a 5000 registros.
7
+ Sem anotações manuais. Sem DSL nos controllers. Basta declarar o que expor e a spec é gerada automaticamente. Inclui um serializer de alta performance — ~3.6× mais rápido que as_json com escalabilidade linear de 10 a 5000 registros.
8
8
 
9
9
  ---
10
10
 
11
11
  ## Instalação
12
12
 
13
- Adicione ao seu `Gemfile`:
13
+ Adicione ao seu Gemfile:
14
14
 
15
15
  ```ruby
16
16
  gem "openapi_blocks"
@@ -24,6 +24,70 @@ bundle install
24
24
 
25
25
  ---
26
26
 
27
+ ## Generators
28
+
29
+ O OpenapiBlocks oferece três generators para começar rapidamente.
30
+
31
+ ### Install
32
+
33
+ ```bash
34
+ rails generate openapi_blocks:install
35
+ ```
36
+
37
+ Cria `config/initializers/openapi_blocks.rb` com todas as opções disponíveis comentadas, e monta o engine no `config/routes.rb`:
38
+
39
+ ```ruby
40
+ mount OpenapiBlocks::Engine => "/docs"
41
+ ```
42
+
43
+ ### Openapi
44
+
45
+ ```bash
46
+ rails generate openapi_blocks:openapi User
47
+ ```
48
+
49
+ Cria `app/openapi/user_openapi.rb` com todas as opções de DSL disponíveis comentadas:
50
+
51
+ ```ruby
52
+ # app/openapi/user_openapi.rb
53
+ class UserOpenapi < OpenapiBlocks::Controller
54
+ # resource UserSerializer
55
+ # controller UsersController
56
+
57
+ # tags "Usuários"
58
+
59
+ # operation :index do
60
+ # summary "Lista todos os usuários"
61
+ # response 200, description: "Lista de usuários", schema: { type: :array, items: :User }
62
+ # end
63
+ end
64
+ ```
65
+
66
+ ### Serializer
67
+
68
+ ```bash
69
+ rails generate openapi_blocks:serializer User
70
+ ```
71
+
72
+ Cria `app/serializers/user_serializer.rb` com todas as opções de DSL disponíveis comentadas:
73
+
74
+ ```ruby
75
+ # app/serializers/user_serializer.rb
76
+ class UserSerializer < OpenapiBlocks::Serializer
77
+ # ignore :password_digest, :reset_password_token
78
+
79
+ # association :posts, type: :array, read_only: true
80
+ # association :company
81
+
82
+ # attribute :full_name, type: :string, read_only: true
83
+ # def full_name
84
+ # "#{object.first_name} #{object.last_name}"
85
+ # end
86
+ end
87
+ ```
88
+
89
+ ---
90
+
27
91
  ## Configuração
28
92
 
29
93
  ### 1. Monte o Engine
@@ -48,7 +112,7 @@ GET /docs/openapi.yaml -> Spec OpenAPI em YAML
48
112
 
49
113
  ### 2. Configure o initializer
50
114
 
51
- `OpenapiBlocks.configure` é obrigatório. A gem lança `OpenapiBlocks::Error` na primeira requisição se nunca foi chamado ou se `info.title` / `info.version` estiverem em branco.
115
+ OpenapiBlocks.configure é obrigatório. A gem lança OpenapiBlocks::Error na primeira requisição se nunca foi chamado ou se info.title / info.version estiverem em branco.
52
116
 
53
117
  ```ruby
54
118
  # config/initializers/openapi_blocks.rb
@@ -99,11 +163,11 @@ end
99
163
 
100
164
  ## Uso
101
165
 
102
- OpenapiBlocks oferece duas classes base com responsabilidades distintas:
166
+ O OpenapiBlocks oferece duas classes base com responsabilidades distintas:
103
167
 
104
- - `OpenapiBlocks::Serializer` — define o model, campos, associações e lógica de serialização. Fica em `app/serializers/`.
105
- - `OpenapiBlocks::Controller` — define operações, parâmetros e respostas para documentação. Fica em `app/openapi/`.
106
- - `OpenapiBlocks::Base` — classe base legada que combina ambas as responsabilidades. Ainda suportada.
168
+ - OpenapiBlocks::Serializer — define o model, campos, associações e lógica de serialização. Fica em app/serializers/.
169
+ - OpenapiBlocks::Controller — define operações, parâmetros e respostas para documentação. Fica em app/openapi/.
170
+ - OpenapiBlocks::Base — classe base legada que combina ambas as responsabilidades. Ainda suportada.
107
171
 
108
172
  ### Recomendado: Serializer + Controller
109
173
 
@@ -142,7 +206,7 @@ end
142
206
  ```ruby
143
207
  # app/openapi/user_openapi.rb
144
208
  class UserOpenapi < OpenapiBlocks::Controller
145
- resource UserSerializer
209
+ resource UserSerializer # vincula ao serializer — o schema é derivado dele
146
210
  controller UsersController
147
211
 
148
212
  tags "Usuários"
@@ -169,6 +233,19 @@ class UserOpenapi < OpenapiBlocks::Controller
169
233
  end
170
234
  ```
171
235
 
236
+ #### Como o schema OpenAPI é gerado
237
+
238
+ Quando `resource UserSerializer` é declarado em um `Controller`, o OpenapiBlocks deriva o schema OpenAPI diretamente do serializer — não do model. Isso garante que o que está documentado é exatamente o que a API retorna.
239
+
240
+ O schema é construído a partir de três fontes no serializer:
241
+
242
+ - Colunas do ActiveRecord — lidas do `db/schema.rb` via o model inferido. Os tipos das colunas são mapeados para tipos OpenAPI automaticamente.
243
+ - Declarações `attribute` — campos virtuais que não existem no banco. Campos declarados com `read_only: true` aparecem no schema de resposta `User` mas são excluídos do schema de requisição `UserInput`.
244
+ - Declarações `association` — resolvidas como `$ref` para o schema associado. Associações com `read_only: true` aparecem na resposta mas são excluídas do `UserInput`.
245
+ - Declarações `ignore` — colunas excluídas de ambos os schemas.
246
+
247
+ O schema `UserInput` (usado nos request bodies de POST, PUT e PATCH) é derivado automaticamente do schema `User` removendo `id`, `created_at`, `updated_at` e qualquer campo marcado com `read_only: true`.
248
+
172
249
  ```ruby
173
250
  # app/controllers/users_controller.rb
174
251
  def index
@@ -229,7 +306,7 @@ def show
229
306
  end
230
307
  ```
231
308
 
232
- O registro do serializer é automático por convenção (`UserSerializer` -> `User`). Para registro explícito:
309
+ O registro do serializer é automático por convenção (UserSerializer -> User). Para registro explícito:
233
310
 
234
311
  ```ruby
235
312
  class AdminUserSerializer < OpenapiBlocks::Serializer
@@ -243,34 +320,41 @@ Se nenhum serializer for encontrado, o OpenapiBlocks usa o comportamento padrão
243
320
 
244
321
  ## Serializer
245
322
 
246
- O serializer compila um método extrator monolítico por classe no boot usando `class_eval`. Sem loops, sem indireção via lambda e sem branching por objeto em tempo de execução.
323
+ O serializer compila um método extrator monolítico por classe no boot usando class_eval. Sem loops, sem indireção via lambda e sem branching por objeto em tempo de execução.
247
324
 
248
325
  ### Performance (200 registros, arm64, Ruby 4.0)
249
326
 
250
- | Método | i/s | μs/i | vs serialize |
327
+ | Method | i/s | μs/i | vs serialize |
251
328
  |------------|-------|------|--------------|
252
- | serialize | 4 239 | 235 | — |
253
- | to_json | 1 444 | 692 | 2.94× mais lento |
254
- | as_json | 1 186 | 843 | 3.58× mais lento |
255
- | oj+as_json | 1 126 | 888 | 3.77× mais lento |
329
+ | serialize | 4 504 | 198 | — |
330
+ | to_json | 1 444 | 692 | 2.89x mais lento |
331
+ | as_json | 1 179 | 453 | 2.81x mais lento |
332
+ | oj+as_json | 1 126 | 572 | 2.89x mais lento |
333
+ | AMS | 559 | 178 | 9.02x mais lento |
334
+
335
+ A escalabilidade é linear — a vantagem de 2.81× sobre o as_json se mantém de 10 a 5000 registros.
336
+
337
+ ### Memória alocada
256
338
 
257
- A escalabilidade é lineara vantagem de 3.6× sobre o `as_json` se mantém de 10 a 5000 registros.
339
+ OpenapiBlocks: 20MB / 225k objetos mais rápido e com menor consumo de memória
340
+ as_json: 116MB / 1,2M objetos — 2,81x mais lento, 5,6x mais memória
341
+ AMS: 260MB / 2,7M objetos — 9x mais lento, 13x mais memória
258
342
 
259
343
  ### Atributos virtuais e resolução de métodos
260
344
 
261
- | Declarado com | Método no serializer? | Chama |
262
- |------------------------|-----------------------|-----------------------------------------|
263
- | `attribute :full_name` | sim | `serializer_instance.full_name` |
264
- | `attribute :full_name` | não | `object.full_name` (delegado ao model) |
265
- | coluna no banco | — | `object.attribute` (direto) |
345
+ | Declarado com | Método no serializer? | Chama |
346
+ | -------------------- | --------------------- | ------------------------------------ |
347
+ | attribute :full_name | sim | serializer_instance.full_name |
348
+ | attribute :full_name | não | object.full_name (delegado ao model) |
349
+ | coluna no banco | — | object.attribute (direto) |
266
350
 
267
351
  ### Resolução do serializer de associações
268
352
 
269
353
  Para cada associação, o serializer resolve a classe na seguinte ordem:
270
354
 
271
- 1. `PostSerializer` — tem `serialize`, usado diretamente.
272
- 2. `PostOpenapi` — é um `Controller`, delega para o `_resource`.
273
- 3. Fallback — chama `as_json` no valor da associação.
355
+ 1. PostSerializer — tem serialize, usado diretamente.
356
+ 2. PostOpenapi — é um Controller, delega para o \_resource.
357
+ 3. Fallback — chama as_json no valor da associação.
274
358
 
275
359
  ---
276
360
 
@@ -289,14 +373,14 @@ end
289
373
 
290
374
  O OpenapiBlocks gera:
291
375
 
292
- - Schema `User` a partir das colunas e tipos do `db/schema.rb`
293
- - Schema `UserInput` para os request bodies de `POST`, `PUT` e `PATCH` (sem `id`, `created_at`, `updated_at` e campos `read_only`)
294
- - Campos `required` a partir das validações `presence: true`
295
- - `minLength`, `maxLength` a partir das validações `length`
296
- - `minimum`, `maximum` a partir das validações `numericality`
297
- - `enum` a partir das validações `inclusion`
298
- - `format: "email"` a partir das validações de formato
299
- - Todos os paths a partir do `config/routes.rb`
376
+ - Schema User a partir das colunas e tipos do db/schema.rb
377
+ - Schema UserInput para os request bodies de POST, PUT e PATCH (sem id, created_at, updated_at e campos read_only)
378
+ - Campos required a partir das validações presence: true
379
+ - minLength, maxLength a partir das validações length
380
+ - minimum, maximum a partir das validações numericality
381
+ - enum a partir das validações inclusion
382
+ - format: "email" a partir das validações de formato
383
+ - Todos os paths a partir do config/routes.rb
300
384
 
301
385
  ---
302
386
 
@@ -339,10 +423,10 @@ association :posts, type: :array, read_only: true # excluído do UserInput (som
339
423
 
340
424
  Atributos virtuais são campos que existem na resposta da API mas não no banco de dados.
341
425
 
342
- | Opção | Descrição | Aparece em User | Aparece em UserInput |
343
- |--------------------|----------------------------------------|:---------------:|:--------------------:|
344
- | `read_only: true` | Campos calculados ou gerados pelo sistema | SIM | NÃO |
345
- | `read_only: false` | Campos que o cliente pode enviar e receber | SIM | SIM |
426
+ | Opção | Descrição | Aparece em User | Aparece em UserInput |
427
+ | ---------------- | ------------------------------------------ | :-------------: | :------------------: |
428
+ | read_only: true | Campos calculados ou gerados pelo sistema | SIM | NÃO |
429
+ | read_only: false | Campos que o cliente pode enviar e receber | SIM | SIM |
346
430
 
347
431
  ```ruby
348
432
  attribute :full_name, type: :string, read_only: true # somente resposta
@@ -354,19 +438,19 @@ attribute :nickname, type: :string # requisição e respos
354
438
 
355
439
  ## Mapeamento de Tipos
356
440
 
357
- | Tipo ActiveRecord | Tipo OpenAPI |
358
- |-------------------|------------------------|
359
- | `integer` | `integer` / `int32` |
360
- | `bigint` | `integer` / `int64` |
361
- | `float` | `number` / `float` |
362
- | `decimal` | `number` / `double` |
363
- | `string` | `string` |
364
- | `text` | `string` |
365
- | `boolean` | `boolean` |
366
- | `date` | `string` / `date` |
367
- | `datetime` | `string` / `date-time` |
368
- | `uuid` | `string` / `uuid` |
369
- | `json` / `jsonb` | `object` |
441
+ | Tipo ActiveRecord | Tipo OpenAPI |
442
+ | ----------------- | ------------------ |
443
+ | integer | integer / int32 |
444
+ | bigint | integer / int64 |
445
+ | float | number / float |
446
+ | decimal | number / double |
447
+ | string | string |
448
+ | text | string |
449
+ | boolean | boolean |
450
+ | date | string / date |
451
+ | datetime | string / date-time |
452
+ | uuid | string / uuid |
453
+ | json / jsonb | object |
370
454
 
371
455
  ---
372
456
 
@@ -382,7 +466,7 @@ config/routes.rb
382
466
  db/schema.rb
383
467
  ```
384
468
 
385
- A spec é regenerada automaticamente na próxima requisição a `/docs/openapi.json` sempre que algum desses arquivos for alterado. Sem precisar reiniciar o servidor.
469
+ A spec é regenerada automaticamente na próxima requisição a /docs/openapi.json sempre que algum desses arquivos for alterado. Sem precisar reiniciar o servidor.
386
470
 
387
471
  ---
388
472
 
@@ -395,4 +479,4 @@ A spec é regenerada automaticamente na próxima requisição a `/docs/openapi.j
395
479
 
396
480
  ## Licença
397
481
 
398
- [MIT](LICENSE.txt)
482
+ MIT (LICENSE.txt)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiBlocks
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base # rubocop:disable Style/Documentation
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Creates an OpenapiBlocks initializer and mounts the engine in routes.rb"
9
+
10
+ def create_initializer
11
+ template "initializer.rb.tt", "config/initializers/openapi_blocks.rb"
12
+ end
13
+
14
+ def mount_engine
15
+ route 'mount OpenapiBlocks::Engine => "/docs"'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ OpenapiBlocks.configure do |config|
4
+ # config.openapi_version = "3.1.0" # required — "3.0.3" or "3.1.0"
5
+
6
+ # config.auto_serialize = true # default is false
7
+
8
+ config.info do
9
+ title "My API" # required
10
+ version "1.0.0" # required
11
+ description "API documentation generated automatically"
12
+
13
+ # contact do
14
+ # name "My Team"
15
+ # email "api@mycompany.com"
16
+ # url "https://mycompany.com"
17
+ # end
18
+
19
+ # license do
20
+ # name "MIT"
21
+ # url "https://opensource.org/licenses/MIT"
22
+ # end
23
+ end
24
+
25
+ # config.servers do
26
+ # server do
27
+ # url "http://localhost:3000"
28
+ # description "Development"
29
+ # end
30
+ # end
31
+
32
+ # config.security do
33
+ # bearer_token format: "JWT" # Authorization: Bearer <token>
34
+ # api_key name: "X-API-Key", in: :header # X-API-Key: <key>
35
+ # end
36
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiBlocks
4
+ module Generators
5
+ class OpenapiGenerator < Rails::Generators::NamedBase # rubocop:disable Style/Documentation
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Creates an OpenapiBlocks Controller class in app/openapi/"
9
+
10
+ def create_openapi_file
11
+ template "openapi.rb.tt", "app/openapi/#{file_name}_openapi.rb"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %>Openapi < OpenapiBlocks::Controller
4
+ # resource <%= class_name %>Serializer
5
+ # controller <%= class_name %>Controller
6
+
7
+ # tags "<%= class_name.pluralize %>"
8
+
9
+ # operation :index do
10
+ # summary "List all <%= class_name.pluralize.downcase %>"
11
+ # description "Returns a paginated list of <%= class_name.pluralize.downcase %>"
12
+ #
13
+ # parameter :page, in: :query, type: :integer, description: "Page number"
14
+ # parameter :per_page, in: :query, type: :integer, description: "Items per page"
15
+ #
16
+ # response 200, description: "List of <%= class_name.pluralize.downcase %>", schema: { type: :array, items: :<%= class_name %> }
17
+ # response 401, description: "Unauthorized"
18
+ # end
19
+
20
+ # operation :show do
21
+ # summary "Get a <%= class_name.downcase %>"
22
+ #
23
+ # response 200, description: "<%= class_name %> found", schema: :<%= class_name %>
24
+ # response 404, description: "<%= class_name %> not found"
25
+ # end
26
+
27
+ # operation :create do
28
+ # summary "Create a <%= class_name.downcase %>"
29
+ #
30
+ # response 201, description: "<%= class_name %> created", schema: :<%= class_name %>
31
+ # response 422, description: "Invalid data"
32
+ # end
33
+
34
+ # operation :update do
35
+ # summary "Update a <%= class_name.downcase %>"
36
+ #
37
+ # response 200, description: "<%= class_name %> updated", schema: :<%= class_name %>
38
+ # response 404, description: "<%= class_name %> not found"
39
+ # response 422, description: "Invalid data"
40
+ # end
41
+
42
+ # operation :destroy do
43
+ # summary "Delete a <%= class_name.downcase %>"
44
+ #
45
+ # response 200, description: "<%= class_name %> deleted"
46
+ # response 404, description: "<%= class_name %> not found"
47
+ # end
48
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiBlocks
4
+ module Generators
5
+ class SerializerGenerator < Rails::Generators::NamedBase # rubocop:disable Style/Documentation
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Creates an OpenapiBlocks Serializer class in app/serializers/"
9
+
10
+ def create_serializer_file
11
+ template "serializer.rb.tt", "app/serializers/#{file_name}_serializer.rb"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %>Serializer < OpenapiBlocks::Serializer
4
+ # model <%= class_name %> is inferred automatically from the class name
5
+
6
+ # ignore :password_digest, :reset_password_token
7
+
8
+ # association :posts, type: :array, read_only: true
9
+ # association :company
10
+
11
+ # attribute :full_name, type: :string, read_only: true
12
+ # def full_name
13
+ # "#{object.first_name} #{object.last_name}"
14
+ # end
15
+ end
@@ -4,6 +4,12 @@ require "rails"
4
4
 
5
5
  module OpenapiBlocks
6
6
  class Railtie < Rails::Railtie # rubocop:disable Style/Documentation
7
+ generators do
8
+ require "generators/openapi_blocks/install/install_generator"
9
+ require "generators/openapi_blocks/openapi/openapi_generator"
10
+ require "generators/openapi_blocks/serializer/serializer_generator"
11
+ end
12
+
7
13
  initializer "openapi_blocks.autoload", before: :set_autoload_paths do |app|
8
14
  app.config.eager_load_paths << app.root.join("app/openapi")
9
15
  app.config.eager_load_paths << app.root.join("app/serializers")
@@ -30,17 +30,28 @@ module OpenapiBlocks
30
30
 
31
31
  private
32
32
 
33
- def build_compiled_extractor # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
33
+ def build_compiled_extractor # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
34
34
  classified = classify_fields
35
+ classified[:association].each { |field| build_assoc_method(field) }
35
36
 
36
- model_lines = classified[:model].map { |f| %("#{f}" => object.public_send("#{f}")) }
37
- virtual_lines = classified[:virtual].map { |f| %("#{f}" => inst.public_send("#{f}")) }
38
- delegated_lines = classified[:delegated].map { |f| %("#{f}" => object.public_send("#{f}")) }
39
- assoc_lines = classified[:association].map do |f|
40
- %("#{f}" => _serialize_assoc_#{f}(object))
37
+ all_fields = classified.values.flatten
38
+ all_fields.each do |f|
39
+ const_name = "KEY_#{f.upcase}"
40
+ const_set(const_name, f.freeze) unless const_defined?(const_name)
41
41
  end
42
42
 
43
- classified[:association].each { |field| build_assoc_method(field) }
43
+ model_lines = classified[:model].map do |f|
44
+ %(#{name}::KEY_#{f.upcase} => object.#{f})
45
+ end
46
+ delegated_lines = classified[:delegated].map do |f|
47
+ %(#{name}::KEY_#{f.upcase} => object.#{f})
48
+ end
49
+ virtual_lines = classified[:virtual].map do |f|
50
+ %(#{name}::KEY_#{f.upcase} => inst.#{f})
51
+ end
52
+ assoc_lines = classified[:association].map do |f|
53
+ %(#{name}::KEY_#{f.upcase} => _serialize_assoc_#{f}(object))
54
+ end
44
55
 
45
56
  all_lines = (model_lines + delegated_lines + virtual_lines + assoc_lines).join(",\n ")
46
57
 
@@ -86,7 +97,7 @@ module OpenapiBlocks
86
97
  def self._serialize_assoc_#{field}(object)
87
98
  val = object.public_send(:#{assoc_name})
88
99
  return nil if val.nil?
89
- val.map { |v| #{serializer}.serialize(v) }
100
+ #{serializer}.serialize(val)
90
101
  end
91
102
  RUBY
92
103
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiBlocks
4
- VERSION = "0.4.1"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi_blocks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Caio Santos
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-06-03 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: oj
@@ -139,6 +140,12 @@ files:
139
140
  - Rakefile
140
141
  - app/controllers/openapi_blocks/spec_controller.rb
141
142
  - config/routes.rb
143
+ - lib/generators/openapi_blocks/install/install_generator.rb
144
+ - lib/generators/openapi_blocks/install/templates/initializer.rb.tt
145
+ - lib/generators/openapi_blocks/openapi/openapi_generator.rb
146
+ - lib/generators/openapi_blocks/openapi/templates/openapi.rb.tt
147
+ - lib/generators/openapi_blocks/serializer/serializer_generator.rb
148
+ - lib/generators/openapi_blocks/serializer/templates/serializer.rb.tt
142
149
  - lib/openapi_blocks.rb
143
150
  - lib/openapi_blocks/auto_serialize.rb
144
151
  - lib/openapi_blocks/base.rb
@@ -180,6 +187,7 @@ metadata:
180
187
  source_code_uri: https://github.com/evotechbuilder/openapi_blocks
181
188
  changelog_uri: https://github.com/evotechbuilder/openapi_blocks/blob/main/CHANGELOG.md
182
189
  rubygems_mfa_required: 'true'
190
+ post_install_message:
183
191
  rdoc_options: []
184
192
  require_paths:
185
193
  - lib
@@ -194,7 +202,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
202
  - !ruby/object:Gem::Version
195
203
  version: '0'
196
204
  requirements: []
197
- rubygems_version: 4.0.3
205
+ rubygems_version: 3.4.1
206
+ signing_key:
198
207
  specification_version: 4
199
208
  summary: OpenAPI 3.0/3.1 documentation and high-performance serializer for Rails
200
209
  test_files: []