rails-autodoc 0.1.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.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +63 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +81 -0
  6. data/.yardopts +3 -0
  7. data/Appraisals +26 -0
  8. data/CHANGELOG.md +20 -0
  9. data/CONTRIBUTING.md +54 -0
  10. data/Gemfile +10 -0
  11. data/Gemfile.lock +298 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +111 -0
  14. data/Rakefile +26 -0
  15. data/app/controllers/rails_autodoc/spec_controller.rb +52 -0
  16. data/config/routes.rb +6 -0
  17. data/docs/annotation-dsl.md +56 -0
  18. data/docs/architecture.md +37 -0
  19. data/docs/ci-integration.md +32 -0
  20. data/docs/configuration.md +48 -0
  21. data/docs/faq.md +29 -0
  22. data/docs/getting-started.md +54 -0
  23. data/docs/index.md +21 -0
  24. data/docs/inference-rules.md +74 -0
  25. data/docs/limitations.md +34 -0
  26. data/docs/migration-from-rswag.md +42 -0
  27. data/docs/serializer-support.md +25 -0
  28. data/lib/generators/rails_autodoc/install_generator.rb +34 -0
  29. data/lib/generators/rails_autodoc/templates/autodoc-verify.yml +19 -0
  30. data/lib/generators/rails_autodoc/templates/initializer.rb +20 -0
  31. data/lib/rails_autodoc/ast_traversal.rb +74 -0
  32. data/lib/rails_autodoc/configuration.rb +45 -0
  33. data/lib/rails_autodoc/dsl/controller_extensions.rb +65 -0
  34. data/lib/rails_autodoc/engine.rb +13 -0
  35. data/lib/rails_autodoc/generator.rb +54 -0
  36. data/lib/rails_autodoc/openapi_spec_builder.rb +334 -0
  37. data/lib/rails_autodoc/railtie.rb +25 -0
  38. data/lib/rails_autodoc/registry.rb +71 -0
  39. data/lib/rails_autodoc/response_inferencer.rb +158 -0
  40. data/lib/rails_autodoc/route_inspector.rb +139 -0
  41. data/lib/rails_autodoc/schema_mapper.rb +142 -0
  42. data/lib/rails_autodoc/serializers/active_model_serializer.rb +27 -0
  43. data/lib/rails_autodoc/serializers/alba.rb +39 -0
  44. data/lib/rails_autodoc/serializers/base.rb +19 -0
  45. data/lib/rails_autodoc/serializers/blueprinter.rb +27 -0
  46. data/lib/rails_autodoc/serializers/registry.rb +29 -0
  47. data/lib/rails_autodoc/strong_params_parser.rb +188 -0
  48. data/lib/rails_autodoc/tasks/autodoc.rake +26 -0
  49. data/lib/rails_autodoc/version.rb +5 -0
  50. data/lib/rails_autodoc.rb +47 -0
  51. data/mkdocs.yml +16 -0
  52. data/rails-autodoc.gemspec +61 -0
  53. data/spec/combustion/config.ru +4 -0
  54. data/spec/dummy/app/assets/config/manifest.js +1 -0
  55. data/spec/dummy/app/controllers/api/v1/users_controller.rb +45 -0
  56. data/spec/dummy/app/models/user.rb +5 -0
  57. data/spec/dummy/config/application.rb +14 -0
  58. data/spec/dummy/config/boot.rb +5 -0
  59. data/spec/dummy/config/database.yml +3 -0
  60. data/spec/dummy/config/environment.rb +5 -0
  61. data/spec/dummy/config/environments/test.rb +12 -0
  62. data/spec/dummy/config/initializers/rails_autodoc.rb +8 -0
  63. data/spec/dummy/config/initializers/sqlite3_boolean.rb +8 -0
  64. data/spec/dummy/config/routes.rb +11 -0
  65. data/spec/dummy/db/migrate/001_create_users.rb +14 -0
  66. data/spec/dummy/db/schema.rb +12 -0
  67. data/spec/rails_autodoc/configuration_spec.rb +34 -0
  68. data/spec/rails_autodoc/dsl_integration_spec.rb +77 -0
  69. data/spec/rails_autodoc/engine_spec.rb +26 -0
  70. data/spec/rails_autodoc/gem_spec.rb +27 -0
  71. data/spec/rails_autodoc/generator_spec.rb +39 -0
  72. data/spec/rails_autodoc/golden_spec.rb +67 -0
  73. data/spec/rails_autodoc/integration_spec.rb +114 -0
  74. data/spec/rails_autodoc/registry_spec.rb +26 -0
  75. data/spec/rails_autodoc/response_inferencer_spec.rb +26 -0
  76. data/spec/rails_autodoc/route_inspector_spec.rb +56 -0
  77. data/spec/rails_autodoc/schema_mapper_spec.rb +42 -0
  78. data/spec/rails_autodoc/serializers/registry_spec.rb +33 -0
  79. data/spec/rails_autodoc/strong_params_parser_spec.rb +41 -0
  80. data/spec/spec_helper.rb +43 -0
  81. metadata +320 -0
@@ -0,0 +1,19 @@
1
+ name: Autodoc Verify
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ verify:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: "3.3"
17
+ bundler-cache: true
18
+ - name: Verify OpenAPI spec
19
+ run: bundle exec rake autodoc:verify
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RailsAutodoc.configure do |config|
4
+ config.title = "<%= Rails.application.class.module_parent_name %> API"
5
+ config.version = "1.0.0"
6
+ config.description = "Auto-generated API documentation"
7
+ config.mount_path = "/api-docs"
8
+ config.output_path = Rails.root.join("openapi/openapi.yaml")
9
+ config.exclude_paths = [%r{^/rails/}, %r{^/api-docs}]
10
+ config.cache_spec_in_dev = true
11
+
12
+ # config.security_schemes = {
13
+ # "bearer_auth" => {
14
+ # "type" => "http",
15
+ # "scheme" => "bearer",
16
+ # "bearerFormat" => "JWT"
17
+ # }
18
+ # }
19
+ # config.default_security = :bearer_auth
20
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ module AstTraversal
5
+ private
6
+
7
+ def walk_nodes(node, &block)
8
+ yield node
9
+
10
+ node.children.compact.each do |child|
11
+ walk_nodes(child, &block) if child.is_a?(Parser::AST::Node)
12
+ end
13
+ end
14
+
15
+ def each_method_definition(class_node, &block)
16
+ return unless class_node
17
+
18
+ walk_nodes(class_node) do |node|
19
+ yield node if method_definition?(node)
20
+ end
21
+ end
22
+
23
+ def method_definition?(node)
24
+ %i[def defs].include?(node.type)
25
+ end
26
+
27
+ def method_name_for(node)
28
+ case node.type
29
+ when :def then node.children[0].to_s
30
+ when :defs then node.children[1].to_s
31
+ end
32
+ end
33
+
34
+ def find_class_node(node, class_name: nil)
35
+ return nil unless node
36
+
37
+ if node.type == :class && (class_name.nil? || class_name_matches?(node, class_name))
38
+ node
39
+ else
40
+ node.children.compact.each do |child|
41
+ next unless child.is_a?(Parser::AST::Node)
42
+
43
+ found = find_class_node(child, class_name: class_name)
44
+ return found if found
45
+ end
46
+ nil
47
+ end
48
+ end
49
+
50
+ def class_name_matches?(node, class_name)
51
+ const_node = node.children[0]
52
+ return false unless const_node
53
+
54
+ full_name = const_path(const_node)
55
+ simple_name = class_name.split("::").last
56
+
57
+ full_name == class_name ||
58
+ full_name == simple_name ||
59
+ class_name.end_with?("::#{full_name}") ||
60
+ full_name.end_with?("::#{simple_name}")
61
+ end
62
+
63
+ def const_path(node)
64
+ case node.type
65
+ when :const
66
+ parent = node.children[0]
67
+ name = node.children[1].to_s
68
+ parent ? "#{const_path(parent)}::#{name}" : name
69
+ else
70
+ ""
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ class Configuration
5
+ attr_accessor :title,
6
+ :version,
7
+ :description,
8
+ :mount_path,
9
+ :output_path,
10
+ :exclude_paths,
11
+ :include_engines,
12
+ :default_security,
13
+ :cache_spec_in_dev,
14
+ :servers,
15
+ :security_schemes
16
+
17
+ def initialize
18
+ @title = "Rails API"
19
+ @version = "1.0.0"
20
+ @description = "Auto-generated API documentation"
21
+ @mount_path = "/api-docs"
22
+ @output_path = nil
23
+ @exclude_paths = [%r{^/rails/}, %r{^/api-docs}]
24
+ @include_engines = []
25
+ @default_security = nil
26
+ @cache_spec_in_dev = true
27
+ @servers = []
28
+ @security_schemes = {}
29
+ end
30
+
31
+ def excluded_path?(path)
32
+ exclude_paths.any? { |pattern| pattern.match?(path) }
33
+ end
34
+
35
+ def resolved_output_path
36
+ return output_path if output_path
37
+
38
+ if defined?(Rails) && Rails.respond_to?(:root)
39
+ Rails.root.join("openapi/openapi.yaml")
40
+ else
41
+ Pathname.new("openapi/openapi.yaml")
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ module DSL
5
+ module ControllerExtensions
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ def swagger_doc(action:, &block)
10
+ RailsAutodoc.registry.register(self, action, &block)
11
+ end
12
+ end
13
+
14
+ class AnnotationBuilder
15
+ def initialize(annotation)
16
+ @annotation = annotation
17
+ end
18
+
19
+ def summary(text)
20
+ @annotation.summary = text
21
+ end
22
+
23
+ def description(text)
24
+ @annotation.description = text
25
+ end
26
+
27
+ def tag(*tags)
28
+ @annotation.tags.concat(tags.map(&:to_s))
29
+ end
30
+
31
+ def deprecated(value = true)
32
+ @annotation.deprecated = value
33
+ end
34
+
35
+ def exclude(value = true)
36
+ @annotation.exclude = value
37
+ end
38
+
39
+ def body_param(name, type, options = {})
40
+ @annotation.body_params << { name: name.to_s, type: type.to_s }.merge(options)
41
+ end
42
+
43
+ def query_param(name, type, options = {})
44
+ @annotation.query_params << {
45
+ name: name.to_s,
46
+ type: type.to_s,
47
+ required: options.fetch(:required, false)
48
+ }.merge(options)
49
+ end
50
+
51
+ def response(status, options = {})
52
+ @annotation.responses[status.to_s] = options
53
+ end
54
+
55
+ def security(scheme)
56
+ @annotation.security = scheme
57
+ end
58
+
59
+ def request_body(schema)
60
+ @annotation.request_body_schema = schema
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../rails_autodoc" unless defined?(RailsAutodoc) && RailsAutodoc.respond_to?(:configure)
4
+
5
+ module RailsAutodoc
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace RailsAutodoc
8
+
9
+ config.generators do |g|
10
+ g.test_framework :rspec
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "json"
5
+
6
+ module RailsAutodoc
7
+ class Generator
8
+ def initialize(config: RailsAutodoc.config)
9
+ @config = config
10
+ end
11
+
12
+ def generate
13
+ operations = RouteInspector.new(config: @config).operations
14
+ OpenapiSpecBuilder.new(operations: operations, config: @config).build
15
+ end
16
+
17
+ def generate!
18
+ spec = generate
19
+ write_spec(spec)
20
+ spec
21
+ end
22
+
23
+ def write_spec(spec)
24
+ output_path = @config.resolved_output_path
25
+ output_path.dirname.mkpath
26
+ output_path.write(YAML.dump(spec))
27
+ spec
28
+ end
29
+
30
+ def to_json(*_args)
31
+ JSON.pretty_generate(generate)
32
+ end
33
+
34
+ def verify!
35
+ output_path = @config.resolved_output_path
36
+ current = output_path.exist? ? YAML.safe_load(output_path.read, permitted_classes: [Date, Time]) : {}
37
+ fresh = generate
38
+
39
+ unless normalize_spec(current) == normalize_spec(fresh)
40
+ raise SpecDriftError, "OpenAPI spec drift detected at #{output_path}. Run `rake autodoc:generate`."
41
+ end
42
+
43
+ true
44
+ end
45
+
46
+ private
47
+
48
+ def normalize_spec(spec)
49
+ JSON.pretty_generate(spec)
50
+ end
51
+ end
52
+
53
+ class SpecDriftError < StandardError; end
54
+ end
@@ -0,0 +1,334 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ class OpenapiSpecBuilder
5
+ def initialize(
6
+ operations:,
7
+ config: RailsAutodoc.config,
8
+ registry: RailsAutodoc.registry,
9
+ schema_mapper: SchemaMapper.new
10
+ )
11
+ @operations = operations
12
+ @config = config
13
+ @registry = registry
14
+ @schema_mapper = schema_mapper
15
+ end
16
+
17
+ def build
18
+ {
19
+ "openapi" => "3.0.3",
20
+ "info" => info_block,
21
+ "servers" => servers_block,
22
+ "tags" => tags_block,
23
+ "paths" => paths_block,
24
+ "components" => components_block
25
+ }.compact
26
+ end
27
+
28
+ private
29
+
30
+ def info_block
31
+ {
32
+ "title" => @config.title,
33
+ "version" => @config.version,
34
+ "description" => @config.description
35
+ }.compact
36
+ end
37
+
38
+ def servers_block
39
+ return @config.servers if @config.servers.any?
40
+
41
+ if defined?(Rails) && Rails.application.routes.default_url_options[:host]
42
+ host = Rails.application.routes.default_url_options[:host]
43
+ [{ "url" => "https://#{host}" }]
44
+ else
45
+ [{ "url" => "http://localhost:3000" }]
46
+ end
47
+ end
48
+
49
+ def tags_block
50
+ @operations.flat_map(&:tags).uniq.sort.map { |tag| { "name" => tag } }
51
+ end
52
+
53
+ def paths_block
54
+ paths = {}
55
+
56
+ @operations.each do |operation|
57
+ annotation = @registry.find(operation.controller_class, operation.action)
58
+ next if annotation&.exclude
59
+
60
+ path_key = operation.openapi_path
61
+ paths[path_key] ||= {}
62
+ paths[path_key][operation.verb.downcase] = build_operation(operation, annotation)
63
+ end
64
+
65
+ paths
66
+ end
67
+
68
+ def build_operation(operation, annotation)
69
+ model_name = @schema_mapper.infer_model_from_controller(operation.controller_class)
70
+ request_body = build_request_body(operation, annotation, model_name)
71
+ responses = build_responses(operation, annotation, model_name)
72
+
73
+ operation_hash = {
74
+ "operationId" => annotation&.operation_id || operation.operation_id,
75
+ "tags" => merged_tags(operation, annotation),
76
+ "summary" => annotation&.summary || "#{operation.verb} #{operation.openapi_path}",
77
+ "description" => annotation&.description,
78
+ "deprecated" => annotation&.deprecated || false,
79
+ "parameters" => build_parameters(operation, annotation),
80
+ "responses" => responses
81
+ }
82
+
83
+ operation_hash["requestBody"] = request_body if request_body
84
+ operation_hash["security"] = build_security(annotation) if annotation&.security || @config.default_security
85
+ operation_hash.compact
86
+ end
87
+
88
+ def merged_tags(operation, annotation)
89
+ tags = operation.tags.dup
90
+ tags.concat(annotation.tags) if annotation&.tags&.any?
91
+ tags.uniq
92
+ end
93
+
94
+ def build_parameters(operation, annotation)
95
+ params = operation.path_params.map do |name|
96
+ {
97
+ "name" => name,
98
+ "in" => "path",
99
+ "required" => true,
100
+ "schema" => { "type" => "string" }
101
+ }
102
+ end
103
+
104
+ annotation&.query_params&.each do |query_param|
105
+ params << {
106
+ "name" => query_param[:name],
107
+ "in" => "query",
108
+ "required" => query_param.fetch(:required, false),
109
+ "schema" => query_schema(query_param)
110
+ }
111
+ end
112
+
113
+ inferred_query_params(operation).each do |query_param|
114
+ next if params.any? { |entry| entry["name"] == query_param[:name] }
115
+
116
+ params << {
117
+ "name" => query_param[:name],
118
+ "in" => "query",
119
+ "required" => query_param.fetch(:required, false),
120
+ "schema" => query_schema(query_param)
121
+ }
122
+ end
123
+
124
+ params
125
+ end
126
+
127
+ def inferred_query_params(operation)
128
+ source_path = controller_source_path(operation.controller_class)
129
+ return [] unless source_path&.exist?
130
+
131
+ StrongParamsParser.new(
132
+ source_path: source_path,
133
+ class_name: operation.controller_class.name
134
+ ).query_params_for_action(operation.action)
135
+ rescue StandardError
136
+ []
137
+ end
138
+
139
+ def query_schema(query_param)
140
+ schema = { "type" => query_param[:type] || "string" }
141
+ schema["enum"] = query_param[:enum] if query_param[:enum]
142
+ schema
143
+ end
144
+
145
+ def build_request_body(operation, annotation, model_name)
146
+ if annotation&.request_body_schema
147
+ return {
148
+ "required" => true,
149
+ "content" => {
150
+ "application/json" => {
151
+ "schema" => annotation.request_body_schema
152
+ }
153
+ }
154
+ }
155
+ end
156
+
157
+ schema = infer_request_schema(operation, model_name)
158
+
159
+ schema = { type: "object", properties: {}, required: [] } if schema.nil? && annotation&.body_params&.any?
160
+
161
+ return nil unless schema
162
+
163
+ if annotation&.body_params&.any?
164
+ schema[:properties] ||= {}
165
+ schema[:required] ||= []
166
+
167
+ annotation.body_params.each do |param|
168
+ schema[:properties][param[:name]] = {
169
+ "type" => param[:type]
170
+ }.tap do |entry|
171
+ entry["enum"] = param[:enum] if param[:enum]
172
+ end
173
+ schema[:required] << param[:name] unless schema[:required].include?(param[:name])
174
+ end
175
+ end
176
+
177
+ {
178
+ "required" => true,
179
+ "content" => {
180
+ "application/json" => {
181
+ "schema" => deep_stringify(schema)
182
+ }
183
+ }
184
+ }
185
+ end
186
+
187
+ def infer_request_schema(operation, model_name)
188
+ source_path = controller_source_path(operation.controller_class)
189
+ return nil unless source_path&.exist?
190
+
191
+ parser = StrongParamsParser.new(
192
+ source_path: source_path,
193
+ class_name: operation.controller_class.name
194
+ )
195
+ params_result = parser.params_for_action(operation.action)
196
+ return nil unless params_result
197
+
198
+ schema = params_result.schema.dup
199
+ @schema_mapper.apply_types!(schema, model_name: model_name)
200
+
201
+ if params_result.root_key
202
+ {
203
+ type: "object",
204
+ properties: {
205
+ params_result.root_key.to_s => deep_stringify(schema)
206
+ },
207
+ required: [params_result.root_key.to_s]
208
+ }
209
+ else
210
+ schema
211
+ end
212
+ rescue StandardError
213
+ nil
214
+ end
215
+
216
+ def build_responses(operation, annotation, model_name)
217
+ if annotation&.responses&.any?
218
+ return annotation.responses.transform_keys(&:to_s).transform_values do |response|
219
+ build_response_entry(response)
220
+ end
221
+ end
222
+
223
+ source_path = controller_source_path(operation.controller_class)
224
+ hints = if source_path&.exist?
225
+ ResponseInferencer.new(
226
+ source_path: source_path,
227
+ class_name: operation.controller_class.name
228
+ ).responses_for_action(operation.action, verb: operation.verb)
229
+ else
230
+ []
231
+ end
232
+
233
+ if hints.empty?
234
+ hints = [ResponseInferencer::ResponseHint.new(status: "200", schema: { type: "object" },
235
+ description: "Successful response")]
236
+ end
237
+
238
+ hints.each_with_object({}) do |hint, hash|
239
+ entry = { "description" => hint.description || "Response" }
240
+ if hint.schema
241
+ entry["content"] = {
242
+ "application/json" => {
243
+ "schema" => deep_stringify(hint.schema)
244
+ }
245
+ }
246
+ elsif hint.schema_ref
247
+ entry["content"] = {
248
+ "application/json" => {
249
+ "schema" => { "$ref" => "#/components/schemas/#{hint.schema_ref}" }
250
+ }
251
+ }
252
+ elsif model_name && hint.status != "204"
253
+ entry["content"] = {
254
+ "application/json" => {
255
+ "schema" => { "$ref" => "#/components/schemas/#{model_name}" }
256
+ }
257
+ }
258
+ end
259
+ hash[hint.status] = entry
260
+ end
261
+ end
262
+
263
+ def build_response_entry(response)
264
+ entry = { "description" => response[:description] || "Response" }
265
+ if response[:ref]
266
+ entry["content"] = {
267
+ "application/json" => {
268
+ "schema" => { "$ref" => "#/components/schemas/#{response[:ref]}" }
269
+ }
270
+ }
271
+ elsif response[:schema]
272
+ entry["content"] = {
273
+ "application/json" => {
274
+ "schema" => deep_stringify(response[:schema])
275
+ }
276
+ }
277
+ end
278
+ entry
279
+ end
280
+
281
+ def build_security(annotation)
282
+ scheme = annotation&.security || @config.default_security
283
+ [{ scheme.to_s => [] }]
284
+ end
285
+
286
+ def components_block
287
+ schemas = @schema_mapper.all_model_schemas
288
+ components = {}
289
+ components["schemas"] = schemas.transform_values { |schema| deep_stringify(schema) } if schemas.any?
290
+ components["securitySchemes"] = @config.security_schemes if @config.security_schemes.any?
291
+ components.presence
292
+ end
293
+
294
+ def controller_source_path(controller_class)
295
+ conventional_controller_path(controller_class) || existing_source_location_path(controller_class)
296
+ end
297
+
298
+ def existing_source_location_path(controller_class)
299
+ path = source_location_path(controller_class)
300
+ path if path&.exist?
301
+ end
302
+
303
+ def source_location_path(controller_class)
304
+ return nil unless controller_class.respond_to?(:instance_method)
305
+
306
+ Pathname.new(controller_class.instance_method(:initialize).source_location.first)
307
+ rescue StandardError
308
+ nil
309
+ end
310
+
311
+ def conventional_controller_path(controller_class)
312
+ return nil unless defined?(Rails) && Rails.respond_to?(:root)
313
+
314
+ relative = "#{controller_class.name.underscore}.rb"
315
+ path = Rails.root.join("app/controllers", relative)
316
+ path.exist? ? path : nil
317
+ end
318
+
319
+ def deep_stringify(value)
320
+ case value
321
+ when Hash
322
+ value.each_with_object({}) do |(key, val), result|
323
+ result[key.to_s] = deep_stringify(val)
324
+ end
325
+ when Array
326
+ value.map { |item| deep_stringify(item) }
327
+ when Symbol
328
+ value.to_s
329
+ else
330
+ value
331
+ end
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ load "rails_autodoc/tasks/autodoc.rake"
7
+ end
8
+
9
+ initializer "rails_autodoc.configure" do
10
+ if Rails.root.join("config/initializers/rails_autodoc.rb").exist?
11
+ require Rails.root.join("config/initializers/rails_autodoc.rb")
12
+ end
13
+ end
14
+
15
+ initializer "rails_autodoc.dsl" do
16
+ ActiveSupport.on_load(:action_controller) do
17
+ include RailsAutodoc::DSL::ControllerExtensions
18
+ end
19
+ end
20
+
21
+ config.to_prepare do
22
+ RailsAutodoc.registry.clear! if Rails.env.development?
23
+ end
24
+ end
25
+ end