rails_ninja 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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +276 -0
- data/lib/rails_ninja/api.rb +388 -0
- data/lib/rails_ninja/endpoint.rb +6 -0
- data/lib/rails_ninja/endpoint_group.rb +219 -0
- data/lib/rails_ninja/errors.rb +20 -0
- data/lib/rails_ninja/openapi/generator.rb +290 -0
- data/lib/rails_ninja/openapi/schema_ref.rb +43 -0
- data/lib/rails_ninja/operation.rb +72 -0
- data/lib/rails_ninja/railtie.rb +17 -0
- data/lib/rails_ninja/response.rb +19 -0
- data/lib/rails_ninja/schema/field.rb +11 -0
- data/lib/rails_ninja/schema/one_of.rb +14 -0
- data/lib/rails_ninja/schema/parameter_decoder.rb +66 -0
- data/lib/rails_ninja/schema/serializer.rb +76 -0
- data/lib/rails_ninja/schema/validator.rb +104 -0
- data/lib/rails_ninja/schema.rb +61 -0
- data/lib/rails_ninja/swagger/ui.rb +37 -0
- data/lib/rails_ninja/tasks.rake +12 -0
- data/lib/rails_ninja/types/base_scalar.rb +25 -0
- data/lib/rails_ninja/types/boolean.rb +17 -0
- data/lib/rails_ninja/types/float.rb +17 -0
- data/lib/rails_ninja/types/int.rb +21 -0
- data/lib/rails_ninja/types/string.rb +17 -0
- data/lib/rails_ninja/version.rb +5 -0
- data/lib/rails_ninja.rb +52 -0
- data/rails_ninja.gemspec +37 -0
- metadata +172 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNinja
|
|
4
|
+
module Schema
|
|
5
|
+
class OneOf
|
|
6
|
+
attr_reader :variants, :discriminator
|
|
7
|
+
|
|
8
|
+
def initialize(variants, discriminator: nil)
|
|
9
|
+
@variants = variants
|
|
10
|
+
@discriminator = discriminator
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNinja
|
|
4
|
+
module Schema
|
|
5
|
+
class ParameterDecoder
|
|
6
|
+
INTEGER_PATTERN = /\A-?(?:0|[1-9]\d*)\z/
|
|
7
|
+
NUMBER_PATTERN = /\A-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?\z/
|
|
8
|
+
|
|
9
|
+
attr_reader :schema_class, :data
|
|
10
|
+
|
|
11
|
+
def initialize(schema_class, data)
|
|
12
|
+
@schema_class = schema_class
|
|
13
|
+
@data = data || {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
schema_class._fields.each_with_object({}) do |(name, field), decoded|
|
|
18
|
+
next unless data.key?(name) || data.key?(name.to_s)
|
|
19
|
+
|
|
20
|
+
value = data.key?(name) ? data[name] : data[name.to_s]
|
|
21
|
+
decoded[name] = decode_value(value, field.type)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def decode_value(value, type)
|
|
28
|
+
if type.is_a?(Array)
|
|
29
|
+
value.is_a?(Array) ? value.map { |item| decode_value(item, type.first) } : value
|
|
30
|
+
elsif type.is_a?(Class) && type <= Schema::Base
|
|
31
|
+
value.is_a?(Hash) ? self.class.new(type, value).call : value
|
|
32
|
+
elsif type.is_a?(Class) && type <= Types::BaseScalar
|
|
33
|
+
decode_scalar(value, type)
|
|
34
|
+
else
|
|
35
|
+
value
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def decode_scalar(value, type)
|
|
40
|
+
return value unless value.is_a?(::String)
|
|
41
|
+
|
|
42
|
+
case type.openapi_schema[:type]
|
|
43
|
+
when "integer" then decode_integer(value)
|
|
44
|
+
when "number" then decode_number(value)
|
|
45
|
+
when "boolean" then decode_boolean(value)
|
|
46
|
+
else value
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def decode_integer(value)
|
|
51
|
+
INTEGER_PATTERN.match?(value) ? Kernel.Integer(value, 10) : value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def decode_number(value)
|
|
55
|
+
NUMBER_PATTERN.match?(value) ? Kernel.Float(value) : value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def decode_boolean(value)
|
|
59
|
+
return true if value == "true"
|
|
60
|
+
return false if value == "false"
|
|
61
|
+
|
|
62
|
+
value
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNinja
|
|
4
|
+
module Schema
|
|
5
|
+
class Serializer
|
|
6
|
+
attr_reader :schema_class, :object
|
|
7
|
+
|
|
8
|
+
def initialize(schema_class, object)
|
|
9
|
+
@schema_class = schema_class
|
|
10
|
+
@object = object
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
result = {}
|
|
15
|
+
|
|
16
|
+
schema_class._fields.each do |name, field|
|
|
17
|
+
value = read_attribute(object, name)
|
|
18
|
+
result[name] = serialize_value(value, field.type)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
result
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def serialize_value(value, type)
|
|
27
|
+
return nil if value.nil?
|
|
28
|
+
|
|
29
|
+
if type.is_a?(OneOf)
|
|
30
|
+
variant = pick_variant(type, value)
|
|
31
|
+
variant ? Serializer.new(variant, value).call : value
|
|
32
|
+
elsif array_type?(type)
|
|
33
|
+
value.map { |item| serialize_value(item, type.first) }
|
|
34
|
+
elsif schema_type?(type)
|
|
35
|
+
Serializer.new(type, value).call
|
|
36
|
+
elsif scalar_type?(type)
|
|
37
|
+
value
|
|
38
|
+
else
|
|
39
|
+
raise Error, "Unsupported schema type: #{type.inspect}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def pick_variant(one_of, value)
|
|
44
|
+
return nil unless value.is_a?(Hash)
|
|
45
|
+
|
|
46
|
+
one_of.variants.find do |variant|
|
|
47
|
+
variant._fields.each_value.all? do |field|
|
|
48
|
+
!field.required || value.key?(field.name) || value.key?(field.name.to_s)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def read_attribute(obj, name)
|
|
54
|
+
if obj.is_a?(Hash)
|
|
55
|
+
obj.key?(name) ? obj[name] : obj[name.to_s]
|
|
56
|
+
elsif obj.respond_to?(name)
|
|
57
|
+
obj.public_send(name)
|
|
58
|
+
elsif obj.respond_to?(:[])
|
|
59
|
+
obj[name]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def array_type?(type)
|
|
64
|
+
type.is_a?(Array)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def schema_type?(type)
|
|
68
|
+
type.is_a?(Class) && type <= Schema::Base
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def scalar_type?(type)
|
|
72
|
+
type.is_a?(Class) && type <= Types::BaseScalar
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNinja
|
|
4
|
+
module Schema
|
|
5
|
+
class Validator
|
|
6
|
+
attr_reader :schema_class, :data
|
|
7
|
+
|
|
8
|
+
def initialize(schema_class, data)
|
|
9
|
+
@schema_class = schema_class
|
|
10
|
+
@data = data || {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
errors = []
|
|
15
|
+
validated = {}
|
|
16
|
+
|
|
17
|
+
schema_class._fields.each do |name, field|
|
|
18
|
+
value = data.key?(name) ? data[name] : data[name.to_s]
|
|
19
|
+
|
|
20
|
+
if value.nil?
|
|
21
|
+
if field.required && field.default.nil?
|
|
22
|
+
errors << "#{name} is required"
|
|
23
|
+
next
|
|
24
|
+
end
|
|
25
|
+
value = field.default
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
next if value.nil?
|
|
29
|
+
|
|
30
|
+
result, validation_errors = validate_value(value, field.type)
|
|
31
|
+
if validation_errors.any?
|
|
32
|
+
errors.concat(validation_errors.map { |e| "#{name}#{e}" })
|
|
33
|
+
else
|
|
34
|
+
validated[name] = result
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
[validated, errors]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def validate_value(value, type)
|
|
44
|
+
if array_type?(type)
|
|
45
|
+
validate_array(value, type)
|
|
46
|
+
elsif schema_type?(type)
|
|
47
|
+
validate_schema(value, type)
|
|
48
|
+
elsif scalar_type?(type)
|
|
49
|
+
validate_scalar(value, type)
|
|
50
|
+
else
|
|
51
|
+
raise Error, "Unsupported schema type: #{type.inspect}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate_array(value, type)
|
|
56
|
+
return [nil, [": Expected #{format_type(type)}, got #{value.class}"]] unless value.is_a?(Array)
|
|
57
|
+
|
|
58
|
+
validated = []
|
|
59
|
+
errors = []
|
|
60
|
+
|
|
61
|
+
value.each_with_index do |item, index|
|
|
62
|
+
result, item_errors = validate_value(item, type.first)
|
|
63
|
+
if item_errors.any?
|
|
64
|
+
errors.concat(item_errors.map { |e| "[#{index}]#{e}" })
|
|
65
|
+
else
|
|
66
|
+
validated << result
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
[validated, errors]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_scalar(value, type)
|
|
74
|
+
return [value, []] if type.valid?(value)
|
|
75
|
+
|
|
76
|
+
[nil, [": Expected #{type.type_name}, got #{value.class}"]]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def validate_schema(value, type)
|
|
80
|
+
result, errors = type.validate(value)
|
|
81
|
+
[result, errors.map { |err| ".#{err}" }]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def array_type?(type)
|
|
85
|
+
type.is_a?(Array)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def schema_type?(type)
|
|
89
|
+
type.is_a?(Class) && type <= Schema::Base
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def scalar_type?(type)
|
|
93
|
+
type.is_a?(Class) && type <= Types::BaseScalar
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def format_type(type)
|
|
97
|
+
return "[#{format_type(type.first)}]" if array_type?(type)
|
|
98
|
+
return type.type_name if scalar_type?(type)
|
|
99
|
+
|
|
100
|
+
type.name || type.to_s
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNinja
|
|
4
|
+
module Schema
|
|
5
|
+
class Base
|
|
6
|
+
class << self
|
|
7
|
+
def field(name, type, required: true, default: nil, enum: nil)
|
|
8
|
+
validate_type!(type)
|
|
9
|
+
_fields[name.to_sym] = Field.new(
|
|
10
|
+
name: name.to_sym,
|
|
11
|
+
type: type,
|
|
12
|
+
required: required,
|
|
13
|
+
default: default,
|
|
14
|
+
enum: enum
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def one_of(*variants, discriminator: nil)
|
|
19
|
+
OneOf.new(variants, discriminator: discriminator)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def _fields
|
|
23
|
+
@_fields ||= {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def inherited(subclass)
|
|
27
|
+
super
|
|
28
|
+
subclass.instance_variable_set(:@_fields, _fields.dup)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def validate(data)
|
|
32
|
+
Validator.new(self, data).call
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def serialize(object)
|
|
36
|
+
Serializer.new(self, object).call
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def serialize_many(collection)
|
|
40
|
+
collection.map { |obj| serialize(obj) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def validate_type!(type)
|
|
46
|
+
if type.is_a?(Array)
|
|
47
|
+
raise Error, "List types must contain exactly one type" unless type.one?
|
|
48
|
+
|
|
49
|
+
validate_type!(type.first)
|
|
50
|
+
elsif type.is_a?(OneOf)
|
|
51
|
+
type.variants.each { |variant| validate_type!(variant) }
|
|
52
|
+
elsif type.is_a?(Class) && (type <= Base || type <= Types::BaseScalar)
|
|
53
|
+
return
|
|
54
|
+
else
|
|
55
|
+
raise Error, "Unsupported schema type: #{type.inspect}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNinja
|
|
4
|
+
module Swagger
|
|
5
|
+
module UI
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def html(spec_url: "./openapi.json")
|
|
9
|
+
<<~HTML
|
|
10
|
+
<!DOCTYPE html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8">
|
|
14
|
+
<title>API Documentation</title>
|
|
15
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
|
16
|
+
<style>
|
|
17
|
+
body { margin: 0; padding: 0; }
|
|
18
|
+
</style>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<div id="swagger-ui"></div>
|
|
22
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
23
|
+
<script>
|
|
24
|
+
SwaggerUIBundle({
|
|
25
|
+
url: "#{spec_url}",
|
|
26
|
+
dom_id: '#swagger-ui',
|
|
27
|
+
presets: [SwaggerUIBundle.presets.apis],
|
|
28
|
+
layout: "BaseLayout"
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
33
|
+
HTML
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :rails_ninja do
|
|
4
|
+
namespace :openapi do
|
|
5
|
+
desc "Generate OpenAPI JSON spec files for all registered APIs"
|
|
6
|
+
task generate: :environment do
|
|
7
|
+
output = ENV.fetch("OUTPUT", "public/openapi")
|
|
8
|
+
RailsNinja.generate_openapi(output: output)
|
|
9
|
+
puts "Generated OpenAPI specs in #{output}/"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNinja
|
|
4
|
+
module Types
|
|
5
|
+
class BaseScalar
|
|
6
|
+
class << self
|
|
7
|
+
def valid?(value)
|
|
8
|
+
ruby_classes.any? { |ruby_class| value.is_a?(ruby_class) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def openapi_schema
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def type_name
|
|
16
|
+
name.split("::").last
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ruby_classes
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNinja
|
|
4
|
+
module Types
|
|
5
|
+
class Int < BaseScalar
|
|
6
|
+
class << self
|
|
7
|
+
def ruby_classes
|
|
8
|
+
[::Integer]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def openapi_schema
|
|
12
|
+
{ type: "integer" }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def type_name
|
|
16
|
+
"Integer"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/rails_ninja.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "action_controller/metal"
|
|
4
|
+
require "active_support/core_ext/enumerable"
|
|
5
|
+
require "rack"
|
|
6
|
+
require "multi_json"
|
|
7
|
+
|
|
8
|
+
require_relative "rails_ninja/version"
|
|
9
|
+
require_relative "rails_ninja/errors"
|
|
10
|
+
require_relative "rails_ninja/types/base_scalar"
|
|
11
|
+
require_relative "rails_ninja/types/boolean"
|
|
12
|
+
require_relative "rails_ninja/types/float"
|
|
13
|
+
require_relative "rails_ninja/types/int"
|
|
14
|
+
require_relative "rails_ninja/types/string"
|
|
15
|
+
require_relative "rails_ninja/schema/field"
|
|
16
|
+
require_relative "rails_ninja/schema/one_of"
|
|
17
|
+
require_relative "rails_ninja/schema/parameter_decoder"
|
|
18
|
+
require_relative "rails_ninja/schema/validator"
|
|
19
|
+
require_relative "rails_ninja/schema/serializer"
|
|
20
|
+
require_relative "rails_ninja/schema"
|
|
21
|
+
require_relative "rails_ninja/operation"
|
|
22
|
+
require_relative "rails_ninja/response"
|
|
23
|
+
require_relative "rails_ninja/openapi/schema_ref"
|
|
24
|
+
require_relative "rails_ninja/openapi/generator"
|
|
25
|
+
require_relative "rails_ninja/swagger/ui"
|
|
26
|
+
require_relative "rails_ninja/endpoint_group"
|
|
27
|
+
require_relative "rails_ninja/api"
|
|
28
|
+
require_relative "rails_ninja/endpoint"
|
|
29
|
+
|
|
30
|
+
require_relative "rails_ninja/railtie" if defined?(Rails::Railtie)
|
|
31
|
+
|
|
32
|
+
module RailsNinja
|
|
33
|
+
def self.registered_apis
|
|
34
|
+
@registered_apis ||= []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.generate_openapi(output: "public/openapi")
|
|
38
|
+
require "fileutils"
|
|
39
|
+
FileUtils.mkdir_p(output)
|
|
40
|
+
|
|
41
|
+
registered_apis.each do |api_class|
|
|
42
|
+
generator = OpenAPI::Generator.new(api_class)
|
|
43
|
+
|
|
44
|
+
filename = api_class.name.split("::").last
|
|
45
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
46
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
47
|
+
.downcase + ".json"
|
|
48
|
+
|
|
49
|
+
File.write(File.join(output, filename), generator.to_json)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/rails_ninja.gemspec
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/rails_ninja/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "rails_ninja"
|
|
7
|
+
spec.version = RailsNinja::VERSION
|
|
8
|
+
spec.authors = ["Benjamin Urrutia"]
|
|
9
|
+
spec.email = ["benja@fintual.com"]
|
|
10
|
+
spec.summary = "Django Ninja-inspired API framework for Ruby/Rails"
|
|
11
|
+
spec.description = "Define API endpoints with a decorator-like DSL, schema validation, and automatic OpenAPI spec generation."
|
|
12
|
+
spec.homepage = "https://github.com/fintual-oss/rails-ninja"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
spec.required_ruby_version = ">= 3.0"
|
|
15
|
+
|
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
|
|
20
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/releases"
|
|
21
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
22
|
+
|
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
|
24
|
+
Dir["lib/**/*"].select { |path| File.file?(path) } +
|
|
25
|
+
%w[LICENSE README.md rails_ninja.gemspec]
|
|
26
|
+
end
|
|
27
|
+
spec.require_paths = ["lib"]
|
|
28
|
+
|
|
29
|
+
spec.add_dependency "actionpack", ">= 7.0"
|
|
30
|
+
spec.add_dependency "activesupport", ">= 7.0"
|
|
31
|
+
spec.add_dependency "multi_json", "~> 1.15"
|
|
32
|
+
spec.add_dependency "rack", ">= 2.0"
|
|
33
|
+
|
|
34
|
+
spec.add_development_dependency "minitest", ">= 5.0"
|
|
35
|
+
spec.add_development_dependency "rack-test", ">= 2.0"
|
|
36
|
+
spec.add_development_dependency "rake", ">= 13.0"
|
|
37
|
+
end
|