onsi 0.8.0 → 1.0.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/.circleci/config.yml +1 -1
- data/.codeclimate.yml +1 -1
- data/.yardopts +4 -0
- data/CHANGELOG.md +11 -0
- data/README.md +3 -3
- data/Rakefile +13 -0
- data/lib/onsi.rb +2 -0
- data/lib/onsi/controller.rb +33 -0
- data/lib/onsi/error_responder.rb +113 -4
- data/lib/onsi/errors.rb +26 -5
- data/lib/onsi/includes.rb +46 -1
- data/lib/onsi/model.rb +204 -73
- data/lib/onsi/params.rb +124 -22
- data/lib/onsi/resource.rb +118 -8
- data/lib/onsi/version.rb +3 -1
- data/onsi.gemspec +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4603a96ecc939b0121b0ba15b31f09fb54f6078c
|
4
|
+
data.tar.gz: 0b12f9fead4d425b6a64555d10214d8ea2f5d7aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 738ea3ac8f9fc4fc118130c9b9760c66a1ca49d3bc5c6efd0870d7f052f6f578383cb357439e1084aa9cbc8675f172f322c291540b58f9b3239a2a367ae4dba1
|
7
|
+
data.tar.gz: fa8db433e03643c00579eb51f0d3d8c0e186ccee4aec830563aa014358d53142b87a03b5ab287e01ea2de310ffd03a8eed5f3b29c5bf8432102937053a664d49
|
data/.circleci/config.yml
CHANGED
@@ -2,7 +2,7 @@ version: 2
|
|
2
2
|
jobs:
|
3
3
|
latest:
|
4
4
|
environment:
|
5
|
-
CC_TEST_REPORTER_ID:
|
5
|
+
CC_TEST_REPORTER_ID: 7e6c6740d17509f50ec9c750311962b3b3fcb2d3a7033c2f664dc3b012bd9439
|
6
6
|
docker:
|
7
7
|
- image: circleci/ruby:latest
|
8
8
|
working_directory: ~/repo
|
data/.codeclimate.yml
CHANGED
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -4,11 +4,11 @@ Used to generate API responses from a Rails App.
|
|
4
4
|
|
5
5
|
***
|
6
6
|
|
7
|
-
[](https://circleci.com/gh/maddiesch/onsi)
|
8
8
|
|
9
|
-
[](https://codeclimate.com/github/maddiesch/onsi/maintainability)
|
10
10
|
|
11
|
-
[](https://codeclimate.com/github/maddiesch/onsi/test_coverage)
|
12
12
|
|
13
13
|
### Install
|
14
14
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core/rake_task'
|
3
|
+
require 'yard'
|
3
4
|
|
4
5
|
RSpec::Core::RakeTask.new(:spec)
|
5
6
|
|
6
7
|
task default: :spec
|
8
|
+
|
9
|
+
namespace :docs do
|
10
|
+
desc 'Generate docs'
|
11
|
+
task :generate do
|
12
|
+
YARD::CLI::Yardoc.run
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Get docs stats'
|
16
|
+
task :stats do
|
17
|
+
YARD::CLI::Stats.run('--list-undoc')
|
18
|
+
end
|
19
|
+
end
|
data/lib/onsi.rb
CHANGED
data/lib/onsi/controller.rb
CHANGED
@@ -1,20 +1,53 @@
|
|
1
1
|
require 'active_support/concern'
|
2
2
|
|
3
3
|
module Onsi
|
4
|
+
##
|
5
|
+
# Helper methods for rendering API responses.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# class PersonController < ActionController::API
|
9
|
+
# include Onsi::Controller
|
10
|
+
#
|
11
|
+
# render_version(:v1)
|
12
|
+
#
|
13
|
+
# def show
|
14
|
+
# person = Person.find(params[:id])
|
15
|
+
# render_resource(person)
|
16
|
+
# end
|
17
|
+
# end
|
4
18
|
module Controller
|
5
19
|
extend ActiveSupport::Concern
|
6
20
|
|
21
|
+
##
|
22
|
+
# Defines class methods available on the class.
|
7
23
|
module ClassMethods
|
24
|
+
##
|
25
|
+
# Set a controller wide default render version.
|
26
|
+
#
|
27
|
+
# @param version [Symbol] The version.
|
8
28
|
def render_version(version = nil)
|
9
29
|
@render_version = version if version
|
10
30
|
@render_version
|
11
31
|
end
|
12
32
|
|
33
|
+
##
|
34
|
+
# Ensures that the render_version is set on a subclass
|
35
|
+
#
|
36
|
+
# @private
|
13
37
|
def inherited(subclass)
|
14
38
|
subclass.render_version(@render_version)
|
15
39
|
end
|
16
40
|
end
|
17
41
|
|
42
|
+
##
|
43
|
+
# Render the JSON response.
|
44
|
+
#
|
45
|
+
# @param resource [Onsi::Resource, Enumerable, Onsi::Model]
|
46
|
+
#
|
47
|
+
# @param opts [Hash] The options hash. If a version is included that will
|
48
|
+
# take presidence over the controller default .render_version
|
49
|
+
#
|
50
|
+
# - The other keys for opts will be passed directly the #render method.
|
18
51
|
def render_resource(resource, opts = {})
|
19
52
|
version = opts.delete(:version) || self.class.render_version || Model::DEFAULT_API_VERSION
|
20
53
|
payload = Resource.render(resource, version)
|
data/lib/onsi/error_responder.rb
CHANGED
@@ -3,6 +3,25 @@ require 'active_support/concern'
|
|
3
3
|
module Onsi
|
4
4
|
##
|
5
5
|
# Handles default errors without StandardError
|
6
|
+
#
|
7
|
+
# Error handled by default:
|
8
|
+
# - ActiveRecord::RecordNotFound
|
9
|
+
# - ActiveRecord::RecordInvalid
|
10
|
+
# - ActionController::ParameterMissing
|
11
|
+
# - {Onsi::Params::MissingReqiredAttribute}
|
12
|
+
# - {Onsi::Params::RelationshipNotFound}
|
13
|
+
# - {Onsi::Errors::UnknownVersionError}
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# class PeopleController < ApplicationController
|
17
|
+
# include Onsi::Controller
|
18
|
+
# include Onsi::ErrorResponderBase
|
19
|
+
#
|
20
|
+
# # ...
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @author Maddie Schipper
|
24
|
+
# @since 1.0.0
|
6
25
|
module ErrorResponderBase
|
7
26
|
extend ActiveSupport::Concern
|
8
27
|
|
@@ -15,16 +34,45 @@ module Onsi
|
|
15
34
|
rescue_from Onsi::Errors::UnknownVersionError, with: :respond_invalid_version_error_400
|
16
35
|
end
|
17
36
|
|
37
|
+
##
|
38
|
+
# Render an API error response.
|
39
|
+
#
|
40
|
+
# @param response [Onsi::ErrorResponse] The response object to render
|
18
41
|
def render_error(response)
|
19
42
|
render(response.renderable)
|
20
43
|
end
|
21
44
|
|
45
|
+
##
|
46
|
+
# Can be overriden to report an un-handled exception to your error service
|
47
|
+
# of choice.
|
48
|
+
#
|
49
|
+
# @param exception [StandardError] The error to report.
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# class ApplicationController < ActionController::API
|
53
|
+
# include Onsi::ErrorResponderBase
|
54
|
+
# include Onsi::Controller
|
55
|
+
#
|
56
|
+
# def notify_unhandled_exception(exception)
|
57
|
+
# Bugsnag.notify(exception)
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# # ...
|
61
|
+
# end
|
62
|
+
def notify_unhandled_exception(exception)
|
63
|
+
Rails.logger.error "Unhandled Exception `#{exception.class.name}: #{exception.message}`"
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# @private
|
22
68
|
def render_error_404(_error)
|
23
69
|
response = ErrorResponse.new(404)
|
24
70
|
response.add(404, 'not_found')
|
25
71
|
render_error(response)
|
26
72
|
end
|
27
73
|
|
74
|
+
##
|
75
|
+
# @private
|
28
76
|
def render_error_422(error)
|
29
77
|
response = ErrorResponse.new(422)
|
30
78
|
error.record.errors.details.each do |name, details|
|
@@ -40,6 +88,8 @@ module Onsi
|
|
40
88
|
render_error(response)
|
41
89
|
end
|
42
90
|
|
91
|
+
##
|
92
|
+
# @private
|
43
93
|
def respond_param_error_400(error)
|
44
94
|
response = ErrorResponse.new(400)
|
45
95
|
response.add(
|
@@ -50,6 +100,8 @@ module Onsi
|
|
50
100
|
render_error(response)
|
51
101
|
end
|
52
102
|
|
103
|
+
##
|
104
|
+
# @private
|
53
105
|
def respond_missing_relationship_error_400(error)
|
54
106
|
response = ErrorResponse.new(400)
|
55
107
|
response.add(
|
@@ -60,6 +112,8 @@ module Onsi
|
|
60
112
|
render_error(response)
|
61
113
|
end
|
62
114
|
|
115
|
+
##
|
116
|
+
# @private
|
63
117
|
def respond_invalid_version_error_400(error)
|
64
118
|
notify_unhandled_exception(error)
|
65
119
|
response = ErrorResponse.new(400)
|
@@ -71,6 +125,8 @@ module Onsi
|
|
71
125
|
render_error(response)
|
72
126
|
end
|
73
127
|
|
128
|
+
##
|
129
|
+
# @private
|
74
130
|
def respond_missing_attr_error_400(error)
|
75
131
|
response = ErrorResponse.new(400)
|
76
132
|
response.add(
|
@@ -83,10 +139,6 @@ module Onsi
|
|
83
139
|
render_error(response)
|
84
140
|
end
|
85
141
|
|
86
|
-
def notify_unhandled_exception(exception)
|
87
|
-
Rails.logger.error "Unhandled Exception `#{exception.class.name}: #{exception.message}`"
|
88
|
-
end
|
89
|
-
|
90
142
|
private
|
91
143
|
|
92
144
|
def error_metadata(error)
|
@@ -103,14 +155,48 @@ module Onsi
|
|
103
155
|
end
|
104
156
|
end
|
105
157
|
|
158
|
+
##
|
159
|
+
# The error response container.
|
160
|
+
#
|
161
|
+
# @author Maddie Schipper
|
162
|
+
# @since 1.0.0
|
163
|
+
#
|
164
|
+
# @example
|
165
|
+
# def handle_error(error)
|
166
|
+
# response = Onsi::ErrorResponse.new(400)
|
167
|
+
# response.add(400, 'bad_request', title: 'The payload was invalid')
|
168
|
+
# render_error(response)
|
169
|
+
# end
|
106
170
|
class ErrorResponse
|
171
|
+
##
|
172
|
+
# The HTTP status for the response.
|
173
|
+
#
|
174
|
+
# @return [Integer]
|
107
175
|
attr_reader :status
|
108
176
|
|
177
|
+
##
|
178
|
+
# Create a new ErrorResponse
|
179
|
+
#
|
180
|
+
# @param status [Integer] The HTTP status for the response.
|
109
181
|
def initialize(status)
|
110
182
|
@status = status
|
111
183
|
@errors = []
|
112
184
|
end
|
113
185
|
|
186
|
+
##
|
187
|
+
# Add a renderable error to the errors
|
188
|
+
#
|
189
|
+
# @param status [#to_s, nil] The status of the error. Usually the same as
|
190
|
+
# the HTTP status passed to #initialize
|
191
|
+
#
|
192
|
+
# @param code [String] The error code for the error. e.g. `bad_request`
|
193
|
+
#
|
194
|
+
# @param title [String, nil] The user displayable title for the error.
|
195
|
+
#
|
196
|
+
# @param details [String, nil] The user displayable details for the error.
|
197
|
+
#
|
198
|
+
# @param meta [Hash, nil] Any additional metadata to associate
|
199
|
+
# with the error.
|
114
200
|
def add(status, code, title: nil, details: nil, meta: nil)
|
115
201
|
@errors << {}.tap do |err|
|
116
202
|
err[:status] = (status || @status).to_s
|
@@ -121,10 +207,18 @@ module Onsi
|
|
121
207
|
end
|
122
208
|
end
|
123
209
|
|
210
|
+
##
|
211
|
+
# Create the error objects.
|
212
|
+
#
|
213
|
+
# @return [Hash] The JSON-API error hash.
|
124
214
|
def as_json
|
125
215
|
{ errors: @errors.as_json }
|
126
216
|
end
|
127
217
|
|
218
|
+
##
|
219
|
+
# Returns a hash that can be passed to #render
|
220
|
+
#
|
221
|
+
# @private
|
128
222
|
def renderable
|
129
223
|
{
|
130
224
|
json: as_json,
|
@@ -137,6 +231,17 @@ end
|
|
137
231
|
module Onsi
|
138
232
|
##
|
139
233
|
# Handles default errors and builds JSON-API responses.
|
234
|
+
#
|
235
|
+
# @note Also includes Onsi::ErrorResponderBase but will add a StandardError
|
236
|
+
# handler.
|
237
|
+
#
|
238
|
+
# @example
|
239
|
+
# class PeopleController < ApplicationController
|
240
|
+
# include Onsi::Controller
|
241
|
+
# include Onsi::ErrorResponder
|
242
|
+
#
|
243
|
+
# # ...
|
244
|
+
# end
|
140
245
|
module ErrorResponder
|
141
246
|
extend ActiveSupport::Concern
|
142
247
|
|
@@ -145,6 +250,10 @@ module Onsi
|
|
145
250
|
include(Onsi::ErrorResponderBase)
|
146
251
|
end
|
147
252
|
|
253
|
+
##
|
254
|
+
# Render a 500 error.
|
255
|
+
#
|
256
|
+
# @private
|
148
257
|
def render_error_500(error)
|
149
258
|
notify_unhandled_exception(error)
|
150
259
|
response = ErrorResponse.new(500)
|
data/lib/onsi/errors.rb
CHANGED
@@ -1,18 +1,39 @@
|
|
1
1
|
module Onsi
|
2
|
+
##
|
3
|
+
# Container module for custom errors
|
2
4
|
module Errors
|
5
|
+
##
|
6
|
+
# Base Error for all Onsi custom errors
|
7
|
+
#
|
8
|
+
# @author Maddie Schipper
|
9
|
+
# @since 1.0.0
|
3
10
|
class BaseError < StandardError; end
|
4
11
|
|
12
|
+
##
|
13
|
+
# An unknown version is requested to be rendered
|
14
|
+
#
|
15
|
+
# @author Maddie Schipper
|
16
|
+
# @since 1.0.0
|
5
17
|
class UnknownVersionError < BaseError
|
6
|
-
|
18
|
+
##
|
19
|
+
# The class that does not support the requested version.
|
20
|
+
attr_reader :klass
|
7
21
|
|
22
|
+
##
|
23
|
+
# The version requested that isn't supported
|
24
|
+
attr_reader :version
|
25
|
+
|
26
|
+
##
|
27
|
+
# Create a new UnknownVersionError
|
28
|
+
#
|
29
|
+
# @param klass (see #klass)
|
30
|
+
#
|
31
|
+
# @param version (see #version)
|
8
32
|
def initialize(klass, version)
|
33
|
+
super("Unsupported version #{version} for #{klass.name}")
|
9
34
|
@klass = klass
|
10
35
|
@version = version
|
11
36
|
end
|
12
|
-
|
13
|
-
def message
|
14
|
-
"Unsupported version #{version} for #{klass.name}"
|
15
|
-
end
|
16
37
|
end
|
17
38
|
end
|
18
39
|
end
|
data/lib/onsi/includes.rb
CHANGED
@@ -1,19 +1,64 @@
|
|
1
1
|
module Onsi
|
2
|
+
##
|
3
|
+
# Used to include other objects in a root Resource objects.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# def index
|
7
|
+
# @person = Person.find(params[:person_id])
|
8
|
+
# @email = @person.emails.find(params[:id])
|
9
|
+
# @includes = Onsi::Includes.new(params[:include])
|
10
|
+
# @includes.fetch_person { @person }
|
11
|
+
# @includes.fetch_messages { @email.messages }
|
12
|
+
# render_resource(Onsi::Resource.new(@email, params[:version].to_sym, includes: @includes))
|
13
|
+
# end
|
2
14
|
class Includes
|
15
|
+
##
|
16
|
+
# The fetch method matcher regex
|
17
|
+
#
|
18
|
+
# @private
|
19
|
+
FETCH_METHOD_REGEXP = Regexp.new('\Afetch_(?:.*)\z').freeze
|
20
|
+
|
21
|
+
##
|
22
|
+
# The includes
|
23
|
+
#
|
24
|
+
# @return [Array<Symbol>]
|
3
25
|
attr_reader :included
|
4
26
|
|
27
|
+
##
|
28
|
+
# Create a new Includes object.
|
29
|
+
#
|
30
|
+
# @param included [String, Enumerable<String, Symbol>, nil] The keys to be
|
31
|
+
# included.
|
32
|
+
#
|
33
|
+
# @return [Onsi::Includes]
|
5
34
|
def initialize(included)
|
6
35
|
@included = parse_included(included)
|
7
36
|
end
|
8
37
|
|
38
|
+
##
|
39
|
+
# @private
|
9
40
|
def method_missing(name, *args, &block)
|
10
|
-
if name =~
|
41
|
+
if name =~ FETCH_METHOD_REGEXP
|
11
42
|
add_fetch_method(name.to_s.gsub(/\Afetch_/, ''), *args, &block)
|
12
43
|
else
|
13
44
|
super
|
14
45
|
end
|
15
46
|
end
|
16
47
|
|
48
|
+
##
|
49
|
+
# @private
|
50
|
+
def respond_to_missing?(name, include_private = false)
|
51
|
+
if name =~ FETCH_METHOD_REGEXP
|
52
|
+
true
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Load all included resources.
|
60
|
+
#
|
61
|
+
# @private
|
17
62
|
def load_included
|
18
63
|
@load_included ||= {}.tap do |root|
|
19
64
|
included.each do |name|
|
data/lib/onsi/model.rb
CHANGED
@@ -1,16 +1,68 @@
|
|
1
1
|
require 'active_support/concern'
|
2
2
|
|
3
3
|
module Onsi
|
4
|
+
##
|
5
|
+
# The Model helper for create a renderable helper.
|
6
|
+
#
|
7
|
+
# @author Maddie Schipper
|
8
|
+
# @since 1.0.0
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# class Person < ApplicationRecord
|
12
|
+
# include Onsi::Model
|
13
|
+
#
|
14
|
+
# api_render(:v1) do
|
15
|
+
# # Passing the name of the attribute only will call that name as a method on
|
16
|
+
# # the instance of the method.
|
17
|
+
# attribute(:first_name)
|
18
|
+
# attribute(:last_name)
|
19
|
+
# # You can give attribute a block and it will be called on the object
|
20
|
+
# # instance. This lets you rename or compute attributes
|
21
|
+
# attribute(:full_name) { "#{first_name} #{last_name}" }
|
22
|
+
#
|
23
|
+
# # Relationship requires a minimum of 2 parameters. The first is the name
|
24
|
+
# # of the relationship in the rendered JSON. The second is the type.
|
25
|
+
# # When fetching the value, Onsi will add `_id` and call that method on the
|
26
|
+
# # object instance. e.g. `team_id` in this case.
|
27
|
+
# relationship(:team, :team)
|
28
|
+
#
|
29
|
+
# # Relationships can take a block that will be called on the object instance
|
30
|
+
# # and the return value will be used as the ID
|
31
|
+
# relationship(:primary_email, :email) { emails.where(primary: true).first.id }
|
32
|
+
# end
|
33
|
+
# end
|
4
34
|
module Model
|
5
|
-
DEFAULT_API_VERSION = :v1
|
6
|
-
|
7
35
|
extend ActiveSupport::Concern
|
8
36
|
|
37
|
+
##
|
38
|
+
# The current default rendered API version.
|
39
|
+
DEFAULT_API_VERSION = :v1
|
40
|
+
|
41
|
+
##
|
42
|
+
# Defines class methods available on the class.
|
9
43
|
module ClassMethods
|
44
|
+
##
|
45
|
+
# Add a version to be rendered.
|
46
|
+
#
|
47
|
+
# @param version [Symbol] The version that will trigger this render block.
|
48
|
+
#
|
49
|
+
# @param block [Block] The block. Called on an instance
|
50
|
+
# of {Onsi::Model::ModelRenderer}
|
10
51
|
def api_render(version, &block)
|
11
52
|
api_renderer(version).instance_exec(&block)
|
12
53
|
end
|
13
54
|
|
55
|
+
##
|
56
|
+
# Fetch the {Onsi::Model::ModelRenderer} for the version.
|
57
|
+
#
|
58
|
+
# @param version [Symbol] The version to fetch the renderer for.
|
59
|
+
#
|
60
|
+
# @param for_render [true, false] Specifies if the version should be
|
61
|
+
# required to exist. Should only ever be true when attempting to render
|
62
|
+
# the resource.
|
63
|
+
#
|
64
|
+
# @raise [Onsi::Errors::UnknownVersionError] If the version isn't defined
|
65
|
+
# and the for_render param is true.
|
14
66
|
def api_renderer(version, for_render: false)
|
15
67
|
@api_renderer ||= {}
|
16
68
|
if for_render
|
@@ -20,95 +72,174 @@ module Onsi
|
|
20
72
|
end
|
21
73
|
@api_renderer[version]
|
22
74
|
end
|
75
|
+
end
|
23
76
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
77
|
+
##
|
78
|
+
# The class that holds attributes and relationships for a model's version.
|
79
|
+
#
|
80
|
+
# @note You shouldn't ever have to directly interact with one of
|
81
|
+
# these classes.
|
82
|
+
#
|
83
|
+
# @author Maddie Schipper
|
84
|
+
# @since 1.0.0
|
85
|
+
class ModelRenderer
|
86
|
+
##
|
87
|
+
# The default date format for a rendered Date. (ISO-8601)
|
88
|
+
DATE_FORMAT = '%Y-%m-%d'.freeze
|
89
|
+
|
90
|
+
##
|
91
|
+
# The default date-time format for a rendered Date and Time. (ISO-8601)
|
92
|
+
DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'.freeze
|
93
|
+
|
94
|
+
##
|
95
|
+
# Create a new ModelRenderer
|
96
|
+
#
|
97
|
+
# @private
|
98
|
+
def initialize
|
99
|
+
@attributes = {}
|
100
|
+
@relationships = {}
|
101
|
+
@metadata = {}
|
102
|
+
end
|
33
103
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
104
|
+
##
|
105
|
+
# The type name.
|
106
|
+
#
|
107
|
+
# @param name [String, nil] The resource object type name.
|
108
|
+
#
|
109
|
+
# @note Not required. If there is no type, the class name will be used
|
110
|
+
# when rendering the object. (Name is underscored)
|
111
|
+
def type(name = nil)
|
112
|
+
@type = name if name
|
113
|
+
@type
|
114
|
+
end
|
38
115
|
|
39
|
-
|
40
|
-
|
41
|
-
|
116
|
+
##
|
117
|
+
# Add an attribute to the rendered attributes.
|
118
|
+
#
|
119
|
+
# @param name [String, Symbol, #to_sym] The name of the attribute.
|
120
|
+
# If no block is passed the name will be called on
|
121
|
+
# the {Onsi::Resource#object}
|
122
|
+
#
|
123
|
+
# @param block [Block] The block used to fetch a dynamic attribute.
|
124
|
+
# It will be executed in the context of the {Onsi::Resource#object}
|
125
|
+
#
|
126
|
+
# @example
|
127
|
+
# api_render(:v1) do
|
128
|
+
# attribute(:first_name)
|
129
|
+
# attribute(:last_name)
|
130
|
+
# attribute(:full_name) { "#{first_name} #{last_name}" }
|
131
|
+
#
|
132
|
+
# # ...
|
133
|
+
#
|
134
|
+
# end
|
135
|
+
def attribute(name, &block)
|
136
|
+
@attributes[name.to_sym] = block || name
|
137
|
+
end
|
42
138
|
|
43
|
-
|
44
|
-
|
45
|
-
|
139
|
+
##
|
140
|
+
# Add a relationship to the rendered relationships.
|
141
|
+
#
|
142
|
+
# @param name [Symbol, #to_sym] The relationship name.
|
143
|
+
#
|
144
|
+
# @param type [String, #to_s] The relationship type.
|
145
|
+
#
|
146
|
+
# @param block [Block] The block used to fetch a dynamic attribute.
|
147
|
+
# It will be executed in the context of the {Onsi::Resource#object}
|
148
|
+
#
|
149
|
+
# @example
|
150
|
+
# api_render(:v1) do
|
151
|
+
# relationship(:team, :team)
|
152
|
+
#
|
153
|
+
# # ...
|
154
|
+
#
|
155
|
+
# end
|
156
|
+
def relationship(name, type, &block)
|
157
|
+
@relationships[name.to_sym] = { type: type, attr: block || name }
|
158
|
+
end
|
46
159
|
|
47
|
-
|
48
|
-
|
49
|
-
|
160
|
+
##
|
161
|
+
# Add a metadata value to the rendered object's meta.
|
162
|
+
#
|
163
|
+
# @param name [#to_sym] The name for the meta value.
|
164
|
+
#
|
165
|
+
# @param block [Block] The block used to fetch the meta value.
|
166
|
+
# It will be executed in the context of the {Onsi::Resource#object}
|
167
|
+
def meta(name, &block)
|
168
|
+
@metadata[name.to_sym] = block
|
169
|
+
end
|
50
170
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
171
|
+
##
|
172
|
+
# Render all attributes
|
173
|
+
#
|
174
|
+
# @private
|
175
|
+
def render_attributes(object)
|
176
|
+
@attributes.each_with_object({}) do |(key, value), attrs|
|
177
|
+
val = value.respond_to?(:call) ? object.instance_exec(&value) : object.send(value)
|
178
|
+
attrs[key.to_s] = format_attribute(val)
|
56
179
|
end
|
180
|
+
end
|
57
181
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
182
|
+
##
|
183
|
+
# Render all relationships
|
184
|
+
#
|
185
|
+
# @private
|
186
|
+
def render_relationships(object)
|
187
|
+
@relationships.each_with_object({}) do |(key, value), rels|
|
188
|
+
render_relationship_entry(object, key, value, rels)
|
62
189
|
end
|
190
|
+
end
|
63
191
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
192
|
+
##
|
193
|
+
# Render all metadata
|
194
|
+
#
|
195
|
+
# @private
|
196
|
+
def render_metadata(object)
|
197
|
+
@metadata.each_with_object({}) do |(key, block), meta|
|
198
|
+
meta[key.to_s] = object.instance_exec(&block)
|
68
199
|
end
|
200
|
+
end
|
69
201
|
|
70
|
-
|
202
|
+
private
|
71
203
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
204
|
+
def render_relationship_entry(object, key, value, rels)
|
205
|
+
attr = value[:attr]
|
206
|
+
relationship = get_relationship_value(attr, object)
|
207
|
+
data = format_relationship(relationship, value)
|
208
|
+
rels[key.to_s] = {
|
209
|
+
'data' => data
|
210
|
+
}
|
211
|
+
end
|
80
212
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
end
|
213
|
+
def get_relationship_value(attr, object)
|
214
|
+
if attr.respond_to?(:call)
|
215
|
+
object.instance_exec(&attr)
|
216
|
+
else
|
217
|
+
object.send("#{attr}_id")
|
87
218
|
end
|
219
|
+
end
|
88
220
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
221
|
+
def format_relationship(relationship, value)
|
222
|
+
case relationship
|
223
|
+
when Array
|
224
|
+
relationship.map { |v| { 'type' => value[:type].to_s, 'id' => v.to_s } }
|
225
|
+
else
|
226
|
+
{
|
227
|
+
'type' => value[:type].to_s,
|
228
|
+
'id' => relationship.to_s
|
229
|
+
}
|
99
230
|
end
|
231
|
+
end
|
100
232
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
233
|
+
def format_attribute(value)
|
234
|
+
case value
|
235
|
+
when Date
|
236
|
+
value.strftime(DATE_FORMAT)
|
237
|
+
when DateTime, Time
|
238
|
+
value.utc.strftime(DATETIME_FORMAT)
|
239
|
+
when String
|
240
|
+
value.presence
|
241
|
+
else
|
242
|
+
value
|
112
243
|
end
|
113
244
|
end
|
114
245
|
end
|
data/lib/onsi/params.rb
CHANGED
@@ -1,36 +1,75 @@
|
|
1
|
+
require_relative 'errors'
|
2
|
+
|
1
3
|
module Onsi
|
2
4
|
##
|
3
5
|
# Used to handle parsing JSON-API formated params
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# class PeopleController < ApplicationController
|
9
|
+
# include Onsi::Controller
|
10
|
+
#
|
11
|
+
# def create
|
12
|
+
# attributes = Onsi::Param.parse(
|
13
|
+
# params,
|
14
|
+
# [:first_name, :last_name],
|
15
|
+
# [:team]
|
16
|
+
# )
|
17
|
+
# render_resource Person.create!(attributes.flatten)
|
18
|
+
# end
|
19
|
+
# end
|
4
20
|
class Params
|
5
21
|
##
|
6
|
-
# Raised when
|
22
|
+
# Raised when a safe_fetch fails.
|
7
23
|
#
|
8
|
-
# The ErrorResponder will rescue from this and return an appropriate
|
9
|
-
#
|
10
|
-
class RelationshipNotFound <
|
24
|
+
# @note The ErrorResponder will rescue from this and return an appropriate
|
25
|
+
# error to the user
|
26
|
+
class RelationshipNotFound < Onsi::Errors::BaseError
|
27
|
+
##
|
28
|
+
# The key that the relationship wasn't found for
|
29
|
+
#
|
30
|
+
# @return [String]
|
11
31
|
attr_reader :key
|
12
32
|
|
33
|
+
##
|
34
|
+
# @private
|
13
35
|
def initialize(message, key)
|
14
36
|
super(message)
|
15
|
-
@key = key
|
37
|
+
@key = key.to_s
|
16
38
|
end
|
17
39
|
end
|
18
40
|
|
19
41
|
##
|
20
42
|
# Raised when a required attribute has a nil value. `Params#require`
|
21
43
|
#
|
22
|
-
# The ErrorResponder will rescue from this and return an appropriate
|
23
|
-
#
|
24
|
-
class MissingReqiredAttribute <
|
44
|
+
# @note The ErrorResponder will rescue from this and return an appropriate
|
45
|
+
# error to the user
|
46
|
+
class MissingReqiredAttribute < Onsi::Errors::BaseError
|
47
|
+
##
|
48
|
+
# The attribute that was missing when required.
|
49
|
+
#
|
50
|
+
# @return [String]
|
25
51
|
attr_reader :attribute
|
26
52
|
|
53
|
+
##
|
54
|
+
# @private
|
27
55
|
def initialize(message, attr)
|
28
56
|
super(message)
|
29
|
-
@attribute = attr
|
57
|
+
@attribute = attr.to_s
|
30
58
|
end
|
31
59
|
end
|
32
60
|
|
33
61
|
class << self
|
62
|
+
##
|
63
|
+
# Parse a JSON-API formatted params object.
|
64
|
+
#
|
65
|
+
# @param params [ActionController::Parameters] The parameters to parse.
|
66
|
+
#
|
67
|
+
# @param attributes [Array<String, Symbol>] The whitelisted attributes.
|
68
|
+
#
|
69
|
+
# @param relationships [Array<String, Symbol>] The whitelisted relationships.
|
70
|
+
# Should be the key for the relationships name.
|
71
|
+
#
|
72
|
+
# @return [Params] The new params object.
|
34
73
|
def parse(params, attributes = [], relationships = [])
|
35
74
|
data = params.require(:data)
|
36
75
|
data.require(:type)
|
@@ -39,6 +78,17 @@ module Onsi
|
|
39
78
|
new(attrs, relas)
|
40
79
|
end
|
41
80
|
|
81
|
+
##
|
82
|
+
# Parse a JSON-API formatted JSON object.
|
83
|
+
#
|
84
|
+
# @param body [String, #read] The parameters to parse.
|
85
|
+
#
|
86
|
+
# @param attributes [Array<String, Symbol>] The whitelisted attributes.
|
87
|
+
#
|
88
|
+
# @param relationships [Array<String, Symbol>] The whitelisted relationships.
|
89
|
+
# Should be the key for the relationships name.
|
90
|
+
#
|
91
|
+
# @return [Onsi::Params] The new params object.
|
42
92
|
def parse_json(body, attributes = [], relationships = [])
|
43
93
|
content = body.respond_to?(:read) ? body.read : body
|
44
94
|
json = JSON.parse(content)
|
@@ -104,8 +154,28 @@ module Onsi
|
|
104
154
|
end
|
105
155
|
end
|
106
156
|
|
107
|
-
|
157
|
+
##
|
158
|
+
# The attributes for the params.
|
159
|
+
#
|
160
|
+
# @return [ActionController::Parameters]
|
161
|
+
attr_reader :attributes
|
162
|
+
|
163
|
+
##
|
164
|
+
# The relationships for the params.
|
165
|
+
#
|
166
|
+
# @return [Hash]
|
167
|
+
attr_reader :relationships
|
108
168
|
|
169
|
+
##
|
170
|
+
# Create a new Params instance.
|
171
|
+
#
|
172
|
+
# @param attributes [ActionController::Parameters] The attributes
|
173
|
+
#
|
174
|
+
# @param relationships [Hash] Flattened relationships hash
|
175
|
+
#
|
176
|
+
# @note Should not be created directly. Use .parse or .parse_json
|
177
|
+
#
|
178
|
+
# @private
|
109
179
|
def initialize(attributes, relationships)
|
110
180
|
@attributes = attributes
|
111
181
|
@relationships = relationships
|
@@ -113,12 +183,20 @@ module Onsi
|
|
113
183
|
|
114
184
|
##
|
115
185
|
# Flatten an merge the attributes & relationships into one hash.
|
186
|
+
#
|
187
|
+
# @return [Hash] The flattened attributes and relationships
|
116
188
|
def flatten
|
117
189
|
attrs_hash.to_h.merge(relationships.to_h).with_indifferent_access
|
118
190
|
end
|
119
191
|
|
120
192
|
##
|
121
193
|
# Fetch a value from the attributes or return the passed default value
|
194
|
+
#
|
195
|
+
# @param key [String, Symbol] The key to fetch.
|
196
|
+
#
|
197
|
+
# @param default [Any] The default value if the attribute doesn't exist.
|
198
|
+
#
|
199
|
+
# @return [Any]
|
122
200
|
def fetch(key, default = nil)
|
123
201
|
attrs_hash[key] || default
|
124
202
|
end
|
@@ -126,7 +204,11 @@ module Onsi
|
|
126
204
|
##
|
127
205
|
# Make an attributes key required.
|
128
206
|
#
|
129
|
-
#
|
207
|
+
# @param key [String, Symbol] The key of the attribute to require.
|
208
|
+
#
|
209
|
+
# @raise [MissingReqiredAttribute] The value you have required isn't present
|
210
|
+
#
|
211
|
+
# @return [Any] The value for the attribute
|
130
212
|
def require(key)
|
131
213
|
value = attrs_hash[key]
|
132
214
|
if value.nil?
|
@@ -137,14 +219,19 @@ module Onsi
|
|
137
219
|
end
|
138
220
|
|
139
221
|
##
|
140
|
-
# Handle finding a relationship's object
|
222
|
+
# Handle finding a relationship's object.
|
223
|
+
#
|
224
|
+
# @param key [String, Symbol] The key for the relationship
|
141
225
|
#
|
142
|
-
#
|
143
|
-
#
|
226
|
+
# @raise [RelationshipNotFound] Thrown instead of an `ActiveRecord::RecordNotFound`
|
227
|
+
# This allows the `Onsi::ErrorResponder` to build an appropriate response.
|
144
228
|
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
229
|
+
# @example
|
230
|
+
# params.safe_fetch(:person) do |id|
|
231
|
+
# Person.find(id)
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# @return [Any]
|
148
235
|
def safe_fetch(key)
|
149
236
|
yield(@relationships[key])
|
150
237
|
rescue ActiveRecord::RecordNotFound
|
@@ -156,21 +243,36 @@ module Onsi
|
|
156
243
|
#
|
157
244
|
# Any getter will run the value through the transform block.
|
158
245
|
#
|
159
|
-
#
|
246
|
+
# @param key [String, Symbol] The key to transform.
|
247
|
+
#
|
248
|
+
# @param block [Block] The block to perform the transform.
|
249
|
+
#
|
250
|
+
# @note The values are memoized
|
160
251
|
#
|
161
|
-
#
|
252
|
+
# @example
|
253
|
+
# params.transform(:date) { |date| Time.parse(date) }
|
254
|
+
#
|
255
|
+
# @return [Any]
|
162
256
|
def transform(key, &block)
|
163
257
|
@attrs_hash = nil
|
164
258
|
transforms[key.to_sym] = block
|
165
259
|
end
|
166
260
|
|
167
261
|
##
|
168
|
-
# Set a default value.
|
262
|
+
# Set a default value for attributes.
|
169
263
|
#
|
170
264
|
# This value will only be used if the key is missing from the passed attributes
|
171
265
|
#
|
172
|
-
#
|
173
|
-
#
|
266
|
+
# @param key [String, Symbol] The key to set a default on.
|
267
|
+
#
|
268
|
+
# @param value [Any, #call] The default value.
|
269
|
+
# If the object responds to call (Lambda) it will be called when
|
270
|
+
# parsing attributes
|
271
|
+
#
|
272
|
+
# @example
|
273
|
+
# params.default(:missing, -> { :foo })
|
274
|
+
# subject.flatten[:missing]
|
275
|
+
# # => :foo
|
174
276
|
def default(key, value)
|
175
277
|
@attrs_hash = nil
|
176
278
|
defaults[key.to_sym] = value
|
data/lib/onsi/resource.rb
CHANGED
@@ -1,18 +1,72 @@
|
|
1
|
+
require_relative 'errors'
|
2
|
+
|
1
3
|
module Onsi
|
4
|
+
##
|
5
|
+
# The wrapper for generating a object
|
6
|
+
#
|
7
|
+
# @author Maddie Schipper
|
8
|
+
# @since 1.0.0
|
2
9
|
class Resource
|
3
|
-
|
10
|
+
##
|
11
|
+
# Root object type key
|
12
|
+
#
|
13
|
+
# @private
|
14
|
+
TYPE_KEY = 'type'.freeze
|
15
|
+
|
16
|
+
##
|
17
|
+
# Root object id key
|
18
|
+
#
|
19
|
+
# @private
|
20
|
+
ID_KEY = 'id'.freeze
|
21
|
+
|
22
|
+
##
|
23
|
+
# Root object attributes key
|
24
|
+
#
|
25
|
+
# @private
|
26
|
+
ATTRIBUTES_KEY = 'attributes'.freeze
|
4
27
|
|
5
|
-
|
6
|
-
|
7
|
-
|
28
|
+
##
|
29
|
+
# Root object relationships key
|
30
|
+
#
|
31
|
+
# @private
|
8
32
|
RELATIONSHIPS_KEY = 'relationships'.freeze
|
9
|
-
META_KEY = 'meta'.freeze
|
10
|
-
DATA_KEY = 'data'.freeze
|
11
|
-
INCLUDED_KEY = 'included'.freeze
|
12
33
|
|
13
|
-
|
34
|
+
##
|
35
|
+
# Root object meta key
|
36
|
+
#
|
37
|
+
# @private
|
38
|
+
META_KEY = 'meta'.freeze
|
39
|
+
|
40
|
+
##
|
41
|
+
# Root object data key
|
42
|
+
#
|
43
|
+
# @private
|
44
|
+
DATA_KEY = 'data'.freeze
|
45
|
+
|
46
|
+
##
|
47
|
+
# Root object included key
|
48
|
+
#
|
49
|
+
# @private
|
50
|
+
INCLUDED_KEY = 'included'.freeze
|
51
|
+
|
52
|
+
##
|
53
|
+
# Raised if the resource or includes are invalid.
|
54
|
+
class InvalidResourceError < Onsi::Errors::BaseError; end
|
14
55
|
|
15
56
|
class << self
|
57
|
+
##
|
58
|
+
# Convert an object into a Onsi::Resource
|
59
|
+
#
|
60
|
+
# @param resource [Onsi::Resource, Enumerable, ActiveRecord::Base] The
|
61
|
+
# object to be converted.
|
62
|
+
# - If a Onsi::Resource is passed it will be directly returned.
|
63
|
+
# - If an Enumerable is passed #map will be called and .as_resource will
|
64
|
+
# be recursivly called for each object.
|
65
|
+
# - If any other object is passed it will be wrapped in a Onsi::Resource
|
66
|
+
#
|
67
|
+
# @param version [Symbol] The version of the resource. `:v1`
|
68
|
+
#
|
69
|
+
# @return [Onsi::Resource, Array<Onsi::Resource>]
|
16
70
|
def as_resource(resource, version)
|
17
71
|
case resource
|
18
72
|
when Onsi::Resource
|
@@ -24,6 +78,15 @@ module Onsi
|
|
24
78
|
end
|
25
79
|
end
|
26
80
|
|
81
|
+
##
|
82
|
+
# Render a resource to JSON
|
83
|
+
#
|
84
|
+
# @param resource (see .as_resource)
|
85
|
+
#
|
86
|
+
# @param version [Symbol] The version to render as. `:v1`
|
87
|
+
#
|
88
|
+
# @return [Hash] The rendered resource as a hash ready to be converted
|
89
|
+
# to JSON.
|
27
90
|
def render(resource, version)
|
28
91
|
resources = as_resource(resource, version)
|
29
92
|
{}.tap do |root|
|
@@ -35,6 +98,8 @@ module Onsi
|
|
35
98
|
end
|
36
99
|
end
|
37
100
|
|
101
|
+
private
|
102
|
+
|
38
103
|
def all_included(resources)
|
39
104
|
Array(resources).map(&:flat_includes).flatten.uniq do |res|
|
40
105
|
"#{res[TYPE_KEY]}-#{res[ID_KEY]}"
|
@@ -42,6 +107,39 @@ module Onsi
|
|
42
107
|
end
|
43
108
|
end
|
44
109
|
|
110
|
+
##
|
111
|
+
# The backing object.
|
112
|
+
#
|
113
|
+
# @note MUST include Onsi::Model
|
114
|
+
#
|
115
|
+
# @return [Any] The object to be rendered by the resource.
|
116
|
+
attr_reader :object
|
117
|
+
|
118
|
+
##
|
119
|
+
# The version to render.
|
120
|
+
#
|
121
|
+
# @return [Symbol]
|
122
|
+
attr_reader :version
|
123
|
+
|
124
|
+
##
|
125
|
+
# The includes for the object.
|
126
|
+
#
|
127
|
+
# @return [Array<Onsi::Includes>]
|
128
|
+
attr_reader :includes
|
129
|
+
|
130
|
+
##
|
131
|
+
# Create a new resouce.
|
132
|
+
#
|
133
|
+
# @param object [Any] The resource backing object.
|
134
|
+
#
|
135
|
+
# @param version [Symbol] The version to render. Can be nil. If nil is
|
136
|
+
# passed the DEFAULT_API_VERSION will be used.
|
137
|
+
#
|
138
|
+
# @note The object MUST be a single object that includes Onsi::Model
|
139
|
+
#
|
140
|
+
# @note The includes MUST be an array of Onsi::Include objects.
|
141
|
+
#
|
142
|
+
# @return [Onsi::Resource] The new resource
|
45
143
|
def initialize(object, version = nil, includes: nil)
|
46
144
|
@object = object
|
47
145
|
@version = version || Model::DEFAULT_API_VERSION
|
@@ -49,6 +147,10 @@ module Onsi
|
|
49
147
|
validate!
|
50
148
|
end
|
51
149
|
|
150
|
+
##
|
151
|
+
# Creates a raw JSON object.
|
152
|
+
#
|
153
|
+
# @return [Hash]
|
52
154
|
def as_json(_opts = {})
|
53
155
|
{}.tap do |root|
|
54
156
|
root[TYPE_KEY] = type
|
@@ -60,10 +162,18 @@ module Onsi
|
|
60
162
|
end
|
61
163
|
end
|
62
164
|
|
165
|
+
##
|
166
|
+
# All rendered includes
|
167
|
+
#
|
168
|
+
# @private
|
63
169
|
def rendered_includes
|
64
170
|
@rendered_includes ||= perform_render_includes
|
65
171
|
end
|
66
172
|
|
173
|
+
##
|
174
|
+
# Flat includes
|
175
|
+
#
|
176
|
+
# @private
|
67
177
|
def flat_includes
|
68
178
|
rendered_includes.values.map { |root| root[DATA_KEY] }.flatten
|
69
179
|
end
|
data/lib/onsi/version.rb
CHANGED
data/onsi.gemspec
CHANGED
@@ -32,4 +32,5 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_development_dependency 'rspec-rails', '~> 3.7.2'
|
33
33
|
spec.add_development_dependency 'simplecov', '~> 0.15'
|
34
34
|
spec.add_development_dependency 'sqlite3', '~> 1.3.10'
|
35
|
+
spec.add_development_dependency 'yard', '~> 0.9.16'
|
35
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: onsi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maddie Schipper
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -142,6 +142,20 @@ dependencies:
|
|
142
142
|
- - "~>"
|
143
143
|
- !ruby/object:Gem::Version
|
144
144
|
version: 1.3.10
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: yard
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: 0.9.16
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: 0.9.16
|
145
159
|
description: Format JSON API responses and parse inbound requests.
|
146
160
|
email:
|
147
161
|
- me@maddiesch.com
|
@@ -154,6 +168,8 @@ files:
|
|
154
168
|
- ".gitignore"
|
155
169
|
- ".rspec"
|
156
170
|
- ".rubocop.yml"
|
171
|
+
- ".yardopts"
|
172
|
+
- CHANGELOG.md
|
157
173
|
- Gemfile
|
158
174
|
- LICENSE.txt
|
159
175
|
- README.md
|