onsi 0.8.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![CircleCI](https://circleci.com/gh/
|
7
|
+
[![CircleCI](https://circleci.com/gh/maddiesch/onsi.svg?style=svg)](https://circleci.com/gh/maddiesch/onsi)
|
8
8
|
|
9
|
-
[![Maintainability](https://api.codeclimate.com/v1/badges/
|
9
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/8d21ec50b172146416c9/maintainability)](https://codeclimate.com/github/maddiesch/onsi/maintainability)
|
10
10
|
|
11
|
-
[![Test Coverage](https://api.codeclimate.com/v1/badges/
|
11
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/8d21ec50b172146416c9/test_coverage)](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
|