rails_ninja 0.1.0 → 0.3.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 +4 -4
- data/README.md +27 -6
- data/lib/rails_ninja/api.rb +47 -1
- data/lib/rails_ninja/openapi/generator.rb +36 -10
- data/lib/rails_ninja/schema/parameter_decoder.rb +16 -2
- data/lib/rails_ninja/schema/validator.rb +4 -0
- data/lib/rails_ninja/tasks.rake +3 -2
- data/lib/rails_ninja/types/file.rb +17 -0
- data/lib/rails_ninja/version.rb +1 -1
- data/lib/rails_ninja.rb +4 -2
- data/rails_ninja.gemspec +3 -3
- metadata +8 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c11362d2af26f7b911e310e2c67e603b5dfd63fd62e5758ce8936da06b1bb36
|
|
4
|
+
data.tar.gz: fb2bd66b8af63055d31a8c1cfc5eb2023883bc46f244c6f77fed0e0668dad4a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e541dbca84d3e26df63229509abc3b5d70bae9da3ce6e8aa31da8c3a97a7df5d0f81ffd7e790820d96a019f0c16b984e2b329fc09fef5d0ea5b55c136c5b844c
|
|
7
|
+
data.tar.gz: '0381a91dd66a8c50be828018978d88d48e95b4f607325e94f1e4fb152d6714c79a79b2e6ef98e6c224c2d6a0409b96a061d748890edf188574e269a8a610b7d8'
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Rails Ninja is a small Rails API framework inspired by
|
|
|
5
5
|
request validation and response serialization, and generated OpenAPI
|
|
6
6
|
documentation.
|
|
7
7
|
|
|
8
|
-
Rails Ninja requires Ruby 3 or newer and Rails 7 or newer.
|
|
8
|
+
Rails Ninja requires Ruby 3 or newer and Rails 7.1 or newer.
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
@@ -103,8 +103,8 @@ end
|
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
Fields are required by default. Available scalar types are `String`, `Int`,
|
|
106
|
-
`Float`, and `
|
|
107
|
-
nested schema or a one-element array of a scalar or schema.
|
|
106
|
+
`Float`, `Boolean`, and `File` under `RailsNinja::Types`. A field may also
|
|
107
|
+
contain a nested schema or a one-element array of a scalar or schema.
|
|
108
108
|
|
|
109
109
|
JSON input is strictly type-checked. Canonical path, query, and form values are
|
|
110
110
|
decoded first, so an integer query value such as `"20"` becomes `20`. Invalid
|
|
@@ -114,6 +114,24 @@ into `params` as symbol keys.
|
|
|
114
114
|
For `GET` and `DELETE`, a request schema is read from and documented as query
|
|
115
115
|
parameters. `POST`, `PUT`, and `PATCH` use a request body.
|
|
116
116
|
|
|
117
|
+
### File uploads
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
schema :DocumentIn do
|
|
121
|
+
field :title, RailsNinja::Types::String
|
|
122
|
+
field :attachments, [RailsNinja::Types::File]
|
|
123
|
+
field :metadata, DocumentMetadata, required: false
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A request schema with a `File` field is documented as `multipart/form-data`
|
|
128
|
+
and its values arrive as `ActionDispatch::Http::UploadedFile`. Arrays of files
|
|
129
|
+
accept both `attachments[]` and repeated bare `attachments` parts, which is what
|
|
130
|
+
OpenAPI generated clients send. Nested schema fields may be sent as JSON
|
|
131
|
+
strings in their own part; they are validated with JSON types, not form
|
|
132
|
+
coercion. `File` fields are only supported at the top level of a request
|
|
133
|
+
schema and cannot appear in responses.
|
|
134
|
+
|
|
117
135
|
Schemas may also be standalone:
|
|
118
136
|
|
|
119
137
|
```ruby
|
|
@@ -253,10 +271,13 @@ Generate an OpenAPI 3.2 JSON file for every API:
|
|
|
253
271
|
```sh
|
|
254
272
|
bundle exec rake rails_ninja:openapi:generate
|
|
255
273
|
bundle exec rake rails_ninja:openapi:generate OUTPUT=docs/api
|
|
274
|
+
bundle exec rake rails_ninja:openapi:generate OPENAPI_VERSION=3.1.0
|
|
256
275
|
```
|
|
257
276
|
|
|
258
|
-
The default
|
|
259
|
-
|
|
277
|
+
The default OpenAPI version is 3.2.0. Set `OPENAPI_VERSION` to a 3.0.x, 3.1.x,
|
|
278
|
+
or 3.2.x version when a consumer requires another dialect. The default output
|
|
279
|
+
directory is `public/openapi`. File names come from the API class name, such as
|
|
280
|
+
`PublicApi` to `public_api.json`.
|
|
260
281
|
|
|
261
282
|
## Development
|
|
262
283
|
|
|
@@ -267,7 +288,7 @@ bundle install
|
|
|
267
288
|
bundle exec rake test
|
|
268
289
|
```
|
|
269
290
|
|
|
270
|
-
CI tests every compatible combination of Action Pack and Active Support 7.
|
|
291
|
+
CI tests every compatible combination of Action Pack and Active Support 7.1
|
|
271
292
|
through 8.1 with MRI Ruby 3.0 through 4.0. Each lane resolves the latest patch
|
|
272
293
|
release in its minor series.
|
|
273
294
|
|
data/lib/rails_ninja/api.rb
CHANGED
|
@@ -336,6 +336,8 @@ module RailsNinja
|
|
|
336
336
|
|
|
337
337
|
if errors.empty?
|
|
338
338
|
params.merge!(validated)
|
|
339
|
+
# Mounted groups / included endpoints run on their own instance with its own params
|
|
340
|
+
@_ninja_handler_instance&.params&.merge!(validated)
|
|
339
341
|
return
|
|
340
342
|
end
|
|
341
343
|
|
|
@@ -347,7 +349,9 @@ module RailsNinja
|
|
|
347
349
|
def request_input(schema_class)
|
|
348
350
|
path = decode_parameters(schema_class, request.path_parameters)
|
|
349
351
|
query = decode_parameters(schema_class, request.query_parameters)
|
|
350
|
-
body = if
|
|
352
|
+
body = if request.media_type == "multipart/form-data"
|
|
353
|
+
Schema::ParameterDecoder.new(schema_class, multipart_parameters(schema_class), wrap_arrays: true).call
|
|
354
|
+
elsif FORM_MEDIA_TYPES.include?(request.media_type)
|
|
351
355
|
decode_parameters(schema_class, request.request_parameters)
|
|
352
356
|
else
|
|
353
357
|
request.request_parameters.deep_symbolize_keys
|
|
@@ -356,6 +360,48 @@ module RailsNinja
|
|
|
356
360
|
path.merge(query).merge(body)
|
|
357
361
|
end
|
|
358
362
|
|
|
363
|
+
# Rack keeps only the last part when a multipart name repeats without "[]",
|
|
364
|
+
# but OpenAPI clients send arrays exactly that way ("files", "files").
|
|
365
|
+
# Re-parse the body so repeated bare names accumulate into arrays.
|
|
366
|
+
module RepeatedMultipartNames
|
|
367
|
+
def normalize_params(params, name, value, *rest)
|
|
368
|
+
return super unless params.key?(name) && !name.include?("[")
|
|
369
|
+
|
|
370
|
+
existing = params[name]
|
|
371
|
+
params[name] = existing.is_a?(Array) ? existing << value : [existing, value]
|
|
372
|
+
params
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def multipart_parameters(schema_class)
|
|
377
|
+
rails_params = request.request_parameters
|
|
378
|
+
io = request.env["rack.input"]
|
|
379
|
+
# Rails' parse is already complete unless a list field could have lost repeated parts
|
|
380
|
+
list_fields = schema_class._fields.values.any? { |f| f.type.is_a?(Array) }
|
|
381
|
+
return rails_params if io.nil? || rails_params.empty? || !list_fields
|
|
382
|
+
|
|
383
|
+
parser = Rack::Utils.default_query_parser.dup.extend(RepeatedMultipartNames)
|
|
384
|
+
|
|
385
|
+
raw = if (pairs = request.env["rack.request.form_pairs"])
|
|
386
|
+
params = parser.make_params
|
|
387
|
+
pairs.each { |name, value| parser.normalize_params(params, name, value) }
|
|
388
|
+
params.to_params_hash
|
|
389
|
+
elsif io.respond_to?(:rewind)
|
|
390
|
+
io.rewind
|
|
391
|
+
# parse_multipart replaces the tempfile cleanup list; keep the ones from Rails' parse
|
|
392
|
+
earlier_tempfiles = Array(request.env["rack.tempfiles"])
|
|
393
|
+
parsed = Rack::Multipart.parse_multipart(request.env, parser) || {}
|
|
394
|
+
request.env["rack.tempfiles"] = earlier_tempfiles | Array(request.env["rack.tempfiles"])
|
|
395
|
+
parsed
|
|
396
|
+
else
|
|
397
|
+
raise Error, "multipart bodies need Rack >= 3.1 or a rewindable rack.input " \
|
|
398
|
+
"(wrap the app in Rack::RewindableInput::Middleware)"
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Same step Rails applies to POST params: turns Rack's file hashes into UploadedFile.
|
|
402
|
+
ActionDispatch::Request::Utils.normalize_encode_params(raw)
|
|
403
|
+
end
|
|
404
|
+
|
|
359
405
|
def decode_parameters(schema_class, parameters)
|
|
360
406
|
Schema::ParameterDecoder.new(schema_class, parameters).call
|
|
361
407
|
end
|
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
module RailsNinja
|
|
4
4
|
module OpenAPI
|
|
5
5
|
class Generator
|
|
6
|
-
|
|
6
|
+
DEFAULT_VERSION = "3.2.0"
|
|
7
|
+
SUPPORTED_VERSION_PATTERN = /\A3\.(?:0|1|2)\.\d+\z/
|
|
8
|
+
|
|
9
|
+
def initialize(api_class, openapi_version: DEFAULT_VERSION)
|
|
10
|
+
unless openapi_version.match?(SUPPORTED_VERSION_PATTERN)
|
|
11
|
+
raise ArgumentError, "unsupported OpenAPI version: #{openapi_version.inspect} (expected 3.0.x, 3.1.x, or 3.2.x)"
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
@api_class = api_class
|
|
15
|
+
@openapi_version = openapi_version
|
|
8
16
|
end
|
|
9
17
|
|
|
10
18
|
def to_hash
|
|
@@ -16,7 +24,7 @@ module RailsNinja
|
|
|
16
24
|
end
|
|
17
25
|
|
|
18
26
|
spec = {
|
|
19
|
-
openapi:
|
|
27
|
+
openapi: @openapi_version,
|
|
20
28
|
info: {
|
|
21
29
|
title: @api_class._title || @api_class.name || "API",
|
|
22
30
|
version: @api_class._version || "0.1.0",
|
|
@@ -124,14 +132,32 @@ module RailsNinja
|
|
|
124
132
|
end
|
|
125
133
|
|
|
126
134
|
def build_request_body(schema)
|
|
127
|
-
{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
multipart = schema._fields.values.any? { |f| file_type?(f.type) }
|
|
136
|
+
reject_nested_files!(schema)
|
|
137
|
+
media_type = multipart ? "multipart/form-data" : "application/json"
|
|
138
|
+
|
|
139
|
+
{ required: true, content: { media_type => { schema: schema_ref(schema) } } }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def file_type?(type)
|
|
143
|
+
type = type.first if type.is_a?(Array)
|
|
144
|
+
type.is_a?(Class) && type <= Types::File
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Nested objects travel as JSON strings inside multipart, which cannot carry a file.
|
|
148
|
+
def reject_nested_files!(schema, path = [], seen = Set.new)
|
|
149
|
+
return unless seen.add?(schema)
|
|
150
|
+
|
|
151
|
+
schema._fields.each do |name, field|
|
|
152
|
+
nested = Array(field.type).first
|
|
153
|
+
next unless nested.is_a?(Class) && nested <= Schema::Base
|
|
154
|
+
|
|
155
|
+
if nested._fields.values.any? { |f| file_type?(f.type) }
|
|
156
|
+
raise Error, "File fields are only supported at the top level of a request schema " \
|
|
157
|
+
"(found under #{(path + [name]).join('.')})"
|
|
158
|
+
end
|
|
159
|
+
reject_nested_files!(nested, path + [name], seen)
|
|
160
|
+
end
|
|
135
161
|
end
|
|
136
162
|
|
|
137
163
|
def schema_ref(type)
|
|
@@ -8,9 +8,12 @@ module RailsNinja
|
|
|
8
8
|
|
|
9
9
|
attr_reader :schema_class, :data
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# wrap_arrays: treat a lone value as a one-element list. Only safe for
|
|
12
|
+
# multipart, the one source where repeated names are preserved.
|
|
13
|
+
def initialize(schema_class, data, wrap_arrays: false)
|
|
12
14
|
@schema_class = schema_class
|
|
13
15
|
@data = data || {}
|
|
16
|
+
@wrap_arrays = wrap_arrays
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def call
|
|
@@ -26,9 +29,13 @@ module RailsNinja
|
|
|
26
29
|
|
|
27
30
|
def decode_value(value, type)
|
|
28
31
|
if type.is_a?(Array)
|
|
32
|
+
value = Array.wrap(value) if @wrap_arrays
|
|
29
33
|
value.is_a?(Array) ? value.map { |item| decode_value(item, type.first) } : value
|
|
30
34
|
elsif type.is_a?(Class) && type <= Schema::Base
|
|
31
|
-
|
|
35
|
+
# JSON already carries native types: validate it as-is, no form coercion.
|
|
36
|
+
return decode_json_object(value) if value.is_a?(::String)
|
|
37
|
+
|
|
38
|
+
value.is_a?(Hash) ? self.class.new(type, value, wrap_arrays: @wrap_arrays).call : value
|
|
32
39
|
elsif type.is_a?(Class) && type <= Types::BaseScalar
|
|
33
40
|
decode_scalar(value, type)
|
|
34
41
|
else
|
|
@@ -47,6 +54,13 @@ module RailsNinja
|
|
|
47
54
|
end
|
|
48
55
|
end
|
|
49
56
|
|
|
57
|
+
# OpenAPI clients send nested objects in multipart/form bodies as JSON strings.
|
|
58
|
+
def decode_json_object(value)
|
|
59
|
+
MultiJson.load(value)
|
|
60
|
+
rescue MultiJson::ParseError
|
|
61
|
+
value
|
|
62
|
+
end
|
|
63
|
+
|
|
50
64
|
def decode_integer(value)
|
|
51
65
|
INTEGER_PATTERN.match?(value) ? Kernel.Integer(value, 10) : value
|
|
52
66
|
end
|
|
@@ -11,6 +11,8 @@ module RailsNinja
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def call
|
|
14
|
+
return [{}, ["Expected object, got #{data.class}"]] unless data.respond_to?(:key?)
|
|
15
|
+
|
|
14
16
|
errors = []
|
|
15
17
|
validated = {}
|
|
16
18
|
|
|
@@ -77,6 +79,8 @@ module RailsNinja
|
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
def validate_schema(value, type)
|
|
82
|
+
return [nil, [": Expected object, got #{value.class}"]] unless value.respond_to?(:key?)
|
|
83
|
+
|
|
80
84
|
result, errors = type.validate(value)
|
|
81
85
|
[result, errors.map { |err| ".#{err}" }]
|
|
82
86
|
end
|
data/lib/rails_ninja/tasks.rake
CHANGED
|
@@ -5,8 +5,9 @@ namespace :rails_ninja do
|
|
|
5
5
|
desc "Generate OpenAPI JSON spec files for all registered APIs"
|
|
6
6
|
task generate: :environment do
|
|
7
7
|
output = ENV.fetch("OUTPUT", "public/openapi")
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
openapi_version = ENV.fetch("OPENAPI_VERSION", RailsNinja::OpenAPI::Generator::DEFAULT_VERSION)
|
|
9
|
+
RailsNinja.generate_openapi(output: output, openapi_version: openapi_version)
|
|
10
|
+
puts "Generated OpenAPI #{openapi_version} specs in #{output}/"
|
|
10
11
|
end
|
|
11
12
|
end
|
|
12
13
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNinja
|
|
4
|
+
module Types
|
|
5
|
+
class File < BaseScalar
|
|
6
|
+
class << self
|
|
7
|
+
def ruby_classes
|
|
8
|
+
[ActionDispatch::Http::UploadedFile]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def openapi_schema
|
|
12
|
+
{ type: "string", format: "binary" }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/rails_ninja/version.rb
CHANGED
data/lib/rails_ninja.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "action_controller/metal"
|
|
4
|
+
require "active_support/core_ext/array/wrap"
|
|
4
5
|
require "active_support/core_ext/enumerable"
|
|
5
6
|
require "rack"
|
|
6
7
|
require "multi_json"
|
|
@@ -9,6 +10,7 @@ require_relative "rails_ninja/version"
|
|
|
9
10
|
require_relative "rails_ninja/errors"
|
|
10
11
|
require_relative "rails_ninja/types/base_scalar"
|
|
11
12
|
require_relative "rails_ninja/types/boolean"
|
|
13
|
+
require_relative "rails_ninja/types/file"
|
|
12
14
|
require_relative "rails_ninja/types/float"
|
|
13
15
|
require_relative "rails_ninja/types/int"
|
|
14
16
|
require_relative "rails_ninja/types/string"
|
|
@@ -34,12 +36,12 @@ module RailsNinja
|
|
|
34
36
|
@registered_apis ||= []
|
|
35
37
|
end
|
|
36
38
|
|
|
37
|
-
def self.generate_openapi(output: "public/openapi")
|
|
39
|
+
def self.generate_openapi(output: "public/openapi", openapi_version: OpenAPI::Generator::DEFAULT_VERSION)
|
|
38
40
|
require "fileutils"
|
|
39
41
|
FileUtils.mkdir_p(output)
|
|
40
42
|
|
|
41
43
|
registered_apis.each do |api_class|
|
|
42
|
-
generator = OpenAPI::Generator.new(api_class)
|
|
44
|
+
generator = OpenAPI::Generator.new(api_class, openapi_version: openapi_version)
|
|
43
45
|
|
|
44
46
|
filename = api_class.name.split("::").last
|
|
45
47
|
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
data/rails_ninja.gemspec
CHANGED
|
@@ -26,10 +26,10 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
end
|
|
27
27
|
spec.require_paths = ["lib"]
|
|
28
28
|
|
|
29
|
-
spec.add_dependency "actionpack", ">= 7.
|
|
30
|
-
spec.add_dependency "activesupport", ">= 7.
|
|
29
|
+
spec.add_dependency "actionpack", ">= 7.1"
|
|
30
|
+
spec.add_dependency "activesupport", ">= 7.1"
|
|
31
31
|
spec.add_dependency "multi_json", "~> 1.15"
|
|
32
|
-
spec.add_dependency "rack", ">= 2.
|
|
32
|
+
spec.add_dependency "rack", ">= 2.2.4"
|
|
33
33
|
|
|
34
34
|
spec.add_development_dependency "minitest", ">= 5.0"
|
|
35
35
|
spec.add_development_dependency "rack-test", ">= 2.0"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_ninja
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Benjamin Urrutia
|
|
@@ -15,28 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '7.
|
|
18
|
+
version: '7.1'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '7.
|
|
25
|
+
version: '7.1'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: activesupport
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '7.
|
|
32
|
+
version: '7.1'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '7.
|
|
39
|
+
version: '7.1'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: multi_json
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -57,14 +57,14 @@ dependencies:
|
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version:
|
|
60
|
+
version: 2.2.4
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version:
|
|
67
|
+
version: 2.2.4
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: minitest
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -137,6 +137,7 @@ files:
|
|
|
137
137
|
- lib/rails_ninja/tasks.rake
|
|
138
138
|
- lib/rails_ninja/types/base_scalar.rb
|
|
139
139
|
- lib/rails_ninja/types/boolean.rb
|
|
140
|
+
- lib/rails_ninja/types/file.rb
|
|
140
141
|
- lib/rails_ninja/types/float.rb
|
|
141
142
|
- lib/rails_ninja/types/int.rb
|
|
142
143
|
- lib/rails_ninja/types/string.rb
|