shamu 0.0.7 → 0.0.8
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/lib/generators/shamu/api_controller/templates/api_controller.rb +2 -2
- data/lib/generators/shamu/api_controller/templates/api_responder.rb +1 -1
- data/lib/generators/shamu/application_presenter/templates/application_presenter.rb +1 -1
- data/lib/shamu/json_api/rails/controller.rb +220 -0
- data/lib/shamu/json_api/rails/pagination.rb +54 -0
- data/lib/shamu/json_api/rails/responder.rb +55 -0
- data/lib/shamu/json_api/rails.rb +9 -0
- data/lib/shamu/rails.rb +1 -2
- data/lib/shamu/version.rb +1 -1
- data/spec/lib/shamu/{rails/json_api_spec.rb → json_api/rails/controller_spec.rb} +12 -3
- data/spec/lib/shamu/json_api/rails/pagination_spec.rb +11 -0
- data/spec/lib/shamu/{rails/json_api_responder_spec.rb → json_api/rails/responder_spec.rb} +3 -3
- metadata +11 -7
- data/lib/shamu/rails/json_api.rb +0 -192
- data/lib/shamu/rails/json_api_responder.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afa18618f6ec75e4163bbc06c2ac89d60b5729d6
|
4
|
+
data.tar.gz: 0d3fc88e85d907868e82a98d8b877c326eacb4b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 405509ea9020934875f4d74015c62b4733511b948c81caa617e67c94ffb3512dffd2264a8f8dcfcccd8277a2835eb142c6a84fb6edf090f6ed8dfdbd9aa8becf
|
7
|
+
data.tar.gz: af3f05ab6ac199ae75d9716d430ff3270eab6fc156f74a709ad87be0d70d7042fbcc8865cb5f660cdc60d1494657854b76a8c7e6fc10a2c0039ab98402cd6f35
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Base {Shamu::JsonApi::Presenter} that all other presenters should
|
2
2
|
# inherit from.
|
3
|
-
class ApplicationPresenter <
|
3
|
+
class ApplicationPresenter < Shamu::JsonApi::Presenter
|
4
4
|
include ::Rails.application.routes.url_helpers
|
5
5
|
|
6
6
|
# Override default_url_options in config/environments files.
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require "rack"
|
2
|
+
|
3
|
+
module Shamu
|
4
|
+
module JsonApi
|
5
|
+
module Rails
|
6
|
+
|
7
|
+
# Add support for writing resources as well-formed JSON API.
|
8
|
+
module Controller
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
before_action do
|
13
|
+
render json: json_error( "The 'include' parameter is not supported" ), status: :bad_request if params[:include] # rubocop:disable Metrics/LineLength
|
14
|
+
request.formats = [ :json_api, :json ]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# def process_action( * )
|
19
|
+
# # If no format has been specfied, default to json_api
|
20
|
+
# request.parameters[:format] ||= "json_api"
|
21
|
+
# super
|
22
|
+
# end
|
23
|
+
|
24
|
+
# Builds a well-formed JSON API response for a single resource.
|
25
|
+
#
|
26
|
+
# @param [Object] resource to present as JSON.
|
27
|
+
# @param [Class] presenter {Presenter} class to use when building the
|
28
|
+
# response for the given resource. If not given, attempts to find a
|
29
|
+
# presenter by calling {Context#find_presenter}.
|
30
|
+
# @param (see #json_context)
|
31
|
+
# @yield (response) write additional top-level links and meta
|
32
|
+
# information.
|
33
|
+
# @yieldparam [JsonApi::Response] response
|
34
|
+
# @return [JsonApi::Response] the presented JSON response.
|
35
|
+
def json_resource( resource, presenter = nil, **context, &block )
|
36
|
+
response = build_json_response( context )
|
37
|
+
response.resource resource, presenter
|
38
|
+
yield response if block_given?
|
39
|
+
response.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
# Builds a well-formed JSON API response for a collection of resources.
|
43
|
+
#
|
44
|
+
# @param [Enumerable<Object>] resources to present as a JSON array.
|
45
|
+
# @param [Class] presenter {Presenter} class to use when building the
|
46
|
+
# response for each of the resources. If not given, attempts to find
|
47
|
+
# a presenter by calling {Context#find_presenter}
|
48
|
+
# @param (see #json_context)
|
49
|
+
# @yield (response) write additional top-level links and meta
|
50
|
+
# information.
|
51
|
+
# @yieldparam [JsonApi::Response] response
|
52
|
+
# @return [JsonApi::Response] the presented JSON response.
|
53
|
+
def json_collection( resources, presenter = nil, pagination: :auto, **context, &block )
|
54
|
+
response = build_json_response( context )
|
55
|
+
response.collection resources, presenter
|
56
|
+
json_paginate_resources response, resources, pagination
|
57
|
+
yield response if block_given?
|
58
|
+
response.to_json
|
59
|
+
end
|
60
|
+
|
61
|
+
# Write all the validation errors from a record to the response.
|
62
|
+
#
|
63
|
+
# @param (see Shamu::JsonApi::Response#validation_errors)
|
64
|
+
# @yield (builder, attr, message)
|
65
|
+
# @yieldparam (see Shamu::JsonApi::Response#validation_errors)
|
66
|
+
# @return [JsonApi::Response] the presented JSON response.
|
67
|
+
def json_validation_errors( errors, **context, &block )
|
68
|
+
response = build_json_response( context )
|
69
|
+
response.validation_errors errors, &block
|
70
|
+
|
71
|
+
response.to_json
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @!visibility public
|
77
|
+
#
|
78
|
+
# Add page-based pagination links for the resources to the builder.
|
79
|
+
#
|
80
|
+
# @param [#current_page,#next_page,#previous_page] resources a collection that responds to `#current_page`
|
81
|
+
# @param [JsonApi::BaseBuilder] builder to add links to.
|
82
|
+
# @param [String] param the name of the key page parameter to adjust
|
83
|
+
# @return [void]
|
84
|
+
def json_paginate( resources, builder, param: :page )
|
85
|
+
page = resources.current_page
|
86
|
+
|
87
|
+
if resources.respond_to?( :next_page ) ? resources.next_page : true
|
88
|
+
builder.link :next, url_for( json_page_parameter( param, :number, page + 1 ) )
|
89
|
+
end
|
90
|
+
|
91
|
+
if resources.respond_to?( :prev_page ) ? resources.prev_page : page > 1
|
92
|
+
builder.link :prev, url_for( json_page_parameter( param, :number, page - 1 ) )
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def json_page_parameter( page_param_name, param, value )
|
97
|
+
page_params = params.reverse_merge page_param_name => {}
|
98
|
+
page_params[page_param_name][param] = value
|
99
|
+
|
100
|
+
page_params
|
101
|
+
end
|
102
|
+
|
103
|
+
# @!visibility public
|
104
|
+
#
|
105
|
+
# Get the pagination request parameters.
|
106
|
+
#
|
107
|
+
# @param [Symbol] param the request parameter to read pagination
|
108
|
+
# options from.
|
109
|
+
# @return [Pagination] the pagination state
|
110
|
+
def json_pagination( param: :page )
|
111
|
+
page_params = params[ param ] || {}
|
112
|
+
|
113
|
+
Pagination.new( page_params.merge( param: param ) )
|
114
|
+
end
|
115
|
+
|
116
|
+
# @!visibility public
|
117
|
+
#
|
118
|
+
# Write an error response. See {Shamu::JsonApi::Response#error} for details.
|
119
|
+
#
|
120
|
+
# @param (see Shamu::JsonApi::Response#error)
|
121
|
+
# @yield (builder)
|
122
|
+
# @yieldparam [Shamu::JsonApi::ErrorBuilder] builder to customize the
|
123
|
+
# error response.
|
124
|
+
# @return [JsonApi::Response] the presented JSON response.
|
125
|
+
def json_error( error = nil, **context, &block )
|
126
|
+
response = build_json_response( context )
|
127
|
+
|
128
|
+
response.error error do |builder|
|
129
|
+
builder.http_status json_http_status_code_from_error( error )
|
130
|
+
yield builder if block_given?
|
131
|
+
end
|
132
|
+
|
133
|
+
response.to_json
|
134
|
+
end
|
135
|
+
|
136
|
+
JSON_CONTEXT_KEYWORDS = [ :fields, :namespaces, :presenters ].freeze
|
137
|
+
|
138
|
+
# @!visibility public
|
139
|
+
#
|
140
|
+
# Build a {JsonApi::Context} for the current request and controller.
|
141
|
+
#
|
142
|
+
# @param [Hash<Symbol,Array>] fields to include in the response. If not
|
143
|
+
# provided looks for a `fields` request argument and parses that.
|
144
|
+
# See {JsonApi::Context#initialize}.
|
145
|
+
# @param [Array<String>] namespaces to look for {Presenter presenters}.
|
146
|
+
# If not provided automatically adds the controller name and it's
|
147
|
+
# namespace.
|
148
|
+
#
|
149
|
+
# For example in the `Users::AccountController` it will add the
|
150
|
+
# `Users::Accounts` and `Users` namespaces.
|
151
|
+
#
|
152
|
+
# See {JsonApi::Context#find_presenter}.
|
153
|
+
# @param [Hash<Class,Class>] presenters a hash that maps resource classes
|
154
|
+
# to the presenter class to use when building responses. See
|
155
|
+
# {JsonApi::Context#find_presenter}.
|
156
|
+
# @return [JsonApi::Context] the builder context honoring any filter
|
157
|
+
# parameters sent by the client.
|
158
|
+
def json_context( fields: :not_set, namespaces: :not_set, presenters: :not_set )
|
159
|
+
Shamu::JsonApi::Context.new fields: fields == :not_set ? json_context_fields : fields,
|
160
|
+
namespaces: namespaces == :not_set ? json_context_namespaces : namespaces,
|
161
|
+
presenters: presenters == :not_set ? json_context_presenters : presenters
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
def json_context_fields
|
166
|
+
params[:fields]
|
167
|
+
end
|
168
|
+
|
169
|
+
def json_context_namespaces
|
170
|
+
name = self.class.name.sub /Controller$/, ""
|
171
|
+
namespaces = [ name.pluralize ]
|
172
|
+
loop do
|
173
|
+
name = name.deconstantize
|
174
|
+
break if name.blank?
|
175
|
+
|
176
|
+
namespaces << name
|
177
|
+
end
|
178
|
+
|
179
|
+
namespaces
|
180
|
+
end
|
181
|
+
|
182
|
+
def json_context_presenters
|
183
|
+
end
|
184
|
+
|
185
|
+
def json_paginate_resources( response, resources, pagination )
|
186
|
+
pagination = resources.respond_to?( :current_page ) if pagination == :auto
|
187
|
+
return unless pagination
|
188
|
+
|
189
|
+
json_paginate resources, response
|
190
|
+
end
|
191
|
+
|
192
|
+
def json_http_status_code_from_error( error )
|
193
|
+
case error
|
194
|
+
when ActiveRecord::RecordNotFound then :not_found
|
195
|
+
when ActiveRecord::RecordInvalid then :unprocessable_entity
|
196
|
+
when /AccessDenied/ then :forbidden
|
197
|
+
else
|
198
|
+
if error.is_a?( Exception )
|
199
|
+
ActionDispatch::ExceptionWrapper.status_code_for_exception( error )
|
200
|
+
else
|
201
|
+
:bad_request
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def json_http_status_code_from_request
|
207
|
+
case request.method
|
208
|
+
when "POST" then :created
|
209
|
+
when "HEAD" then :no_content
|
210
|
+
else :ok
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def build_json_response( context )
|
215
|
+
Shamu::JsonApi::Response.new( json_context( **context.slice( *JSON_CONTEXT_KEYWORDS ) ) )
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Shamu
|
2
|
+
module JsonApi
|
3
|
+
module Rails
|
4
|
+
|
5
|
+
# Pagination information gathered from the request.
|
6
|
+
class Pagination
|
7
|
+
include Attributes
|
8
|
+
include Attributes::Assignment
|
9
|
+
include Attributes::Validation
|
10
|
+
|
11
|
+
# ============================================================================
|
12
|
+
# @!group Attributes
|
13
|
+
#
|
14
|
+
|
15
|
+
# @!attribute
|
16
|
+
# @return [Symbol] the request parameter the pagination was read from. Default `:page`.
|
17
|
+
attribute :param, default: :page
|
18
|
+
|
19
|
+
# @!attribute
|
20
|
+
# @return [Integer] the page number.
|
21
|
+
attribute :number, coerce: :to_i
|
22
|
+
|
23
|
+
# @!attribute
|
24
|
+
# @return [Integer] the size of each page.
|
25
|
+
attribute :size, coerce: :to_i
|
26
|
+
|
27
|
+
# @!attribute
|
28
|
+
# @return [Integer] offset into the list.
|
29
|
+
attribute :offset, coerce: :to_i
|
30
|
+
|
31
|
+
# @!attribute
|
32
|
+
# @return [Integer] limit the total number of results.
|
33
|
+
attribute :limit, coerce: :to_i
|
34
|
+
|
35
|
+
# @!attribute
|
36
|
+
# @return [String] opaque cursor value
|
37
|
+
attribute :cursor, coerce: :to_i
|
38
|
+
|
39
|
+
#
|
40
|
+
# @!endgroup Attributes
|
41
|
+
|
42
|
+
validate :only_one_kind_of_paging
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def only_one_kind_of_paging
|
47
|
+
kinds = [ number, offset, cursor ].compact
|
48
|
+
errors.add :base, :only_one_kind_of_paging if kinds.count > 2 || ( size && limit )
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Shamu
|
2
|
+
module JsonApi
|
3
|
+
module Rails
|
4
|
+
|
5
|
+
# Support JSON API responses with the standard rails `#respond_with` method.
|
6
|
+
module Responder
|
7
|
+
|
8
|
+
# Render the response as JSON
|
9
|
+
# @return [String]
|
10
|
+
def to_json
|
11
|
+
if has_errors?
|
12
|
+
display_errors
|
13
|
+
elsif get?
|
14
|
+
display resource
|
15
|
+
elsif put? || patch?
|
16
|
+
display resource, :location => api_location
|
17
|
+
elsif post?
|
18
|
+
display resource, :status => :created, :location => api_location
|
19
|
+
else
|
20
|
+
head :no_content
|
21
|
+
end
|
22
|
+
end
|
23
|
+
alias_method :to_json_api, :to_json
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
# @visibility private
|
28
|
+
def display( resource, given_options = {} )
|
29
|
+
given_options.merge!( options )
|
30
|
+
|
31
|
+
json =
|
32
|
+
if resource.is_a?( Enumerable )
|
33
|
+
controller.json_collection resource, **given_options
|
34
|
+
else
|
35
|
+
controller.json_resource resource, **given_options
|
36
|
+
end
|
37
|
+
|
38
|
+
super json, given_options
|
39
|
+
end
|
40
|
+
|
41
|
+
# @visibility private
|
42
|
+
def display_errors
|
43
|
+
controller.render format => controller.json_validation_errors( resource_errors ), :status => :unprocessable_entity # rubocop:disable Metrics/LineLength
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def validation_resource?( resource )
|
49
|
+
resource.respond_to?( :valid? ) && resource.respond_to?( :errors )
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/shamu/rails.rb
CHANGED
@@ -5,8 +5,7 @@ module Shamu
|
|
5
5
|
require "shamu/rails/entity"
|
6
6
|
require "shamu/rails/controller"
|
7
7
|
require "shamu/rails/features"
|
8
|
-
require "shamu/rails/json_api"
|
9
|
-
require "shamu/rails/json_api_responder"
|
10
8
|
require "shamu/rails/railtie"
|
9
|
+
require "shamu/json_api/rails"
|
11
10
|
end
|
12
11
|
end
|
data/lib/shamu/version.rb
CHANGED
@@ -8,7 +8,7 @@ module JsonApiControllerSpec
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class ResourcesController < ActionController::Base
|
11
|
-
include Shamu::Rails::
|
11
|
+
include Shamu::JsonApi::Rails::Controller
|
12
12
|
end
|
13
13
|
|
14
14
|
module Resources
|
@@ -78,12 +78,21 @@ describe JsonApiControllerSpec::ResourcesController, type: :controller do
|
|
78
78
|
end
|
79
79
|
|
80
80
|
subject do
|
81
|
-
get :index
|
81
|
+
get :index
|
82
82
|
JSON.parse( response.body )
|
83
83
|
end
|
84
84
|
|
85
85
|
it { is_expected.to include "data" => kind_of( Array ) }
|
86
|
-
it { is_expected.to include "links" =>
|
86
|
+
it { is_expected.to include "links" => include( "next" => match( /page.*number/ ) ) }
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#json_pagination" do
|
90
|
+
it "parses pagination parameters" do
|
91
|
+
controller.params[:page] = { number: 3 }
|
92
|
+
pagination = controller.send :json_pagination
|
93
|
+
|
94
|
+
expect( pagination.number ).to eq 3
|
95
|
+
end
|
87
96
|
end
|
88
97
|
|
89
98
|
it "writes an error" do
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "rails_helper"
|
2
|
+
|
3
|
+
describe Shamu::JsonApi::Rails::Pagination do
|
4
|
+
it "retains nil value if not set" do
|
5
|
+
expect( Shamu::JsonApi::Rails::Pagination.new.number ).to be_nil
|
6
|
+
end
|
7
|
+
|
8
|
+
it "only allows one kind of paging" do
|
9
|
+
expect( Shamu::JsonApi::Rails::Pagination.new( size: 1, limit: 1 ) ).not_to be_valid
|
10
|
+
end
|
11
|
+
end
|
@@ -8,13 +8,13 @@ module JsonApiResponderSpec
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class Responder < ActionController::Responder
|
11
|
-
include Shamu::Rails::
|
11
|
+
include Shamu::JsonApi::Rails::Responder
|
12
12
|
end
|
13
13
|
|
14
14
|
class ResourcesController < ActionController::Base
|
15
|
-
include Shamu::Rails::
|
15
|
+
include Shamu::JsonApi::Rails::Controller
|
16
16
|
|
17
|
-
respond_to :
|
17
|
+
respond_to :json_api, :json
|
18
18
|
self.responder = Responder
|
19
19
|
|
20
20
|
def json_api_responder_spec_resource_url( * )
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shamu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Alexander
|
@@ -286,6 +286,10 @@ files:
|
|
286
286
|
- lib/shamu/json_api/error.rb
|
287
287
|
- lib/shamu/json_api/error_builder.rb
|
288
288
|
- lib/shamu/json_api/presenter.rb
|
289
|
+
- lib/shamu/json_api/rails.rb
|
290
|
+
- lib/shamu/json_api/rails/controller.rb
|
291
|
+
- lib/shamu/json_api/rails/pagination.rb
|
292
|
+
- lib/shamu/json_api/rails/responder.rb
|
289
293
|
- lib/shamu/json_api/relationship_builder.rb
|
290
294
|
- lib/shamu/json_api/resource_builder.rb
|
291
295
|
- lib/shamu/json_api/response.rb
|
@@ -301,8 +305,6 @@ files:
|
|
301
305
|
- lib/shamu/rails/controller.rb
|
302
306
|
- lib/shamu/rails/entity.rb
|
303
307
|
- lib/shamu/rails/features.rb
|
304
|
-
- lib/shamu/rails/json_api.rb
|
305
|
-
- lib/shamu/rails/json_api_responder.rb
|
306
308
|
- lib/shamu/rails/railtie.rb
|
307
309
|
- lib/shamu/rspec.rb
|
308
310
|
- lib/shamu/rspec/matchers.rb
|
@@ -389,6 +391,9 @@ files:
|
|
389
391
|
- spec/lib/shamu/json_api/common_builder_spec.rb
|
390
392
|
- spec/lib/shamu/json_api/context_spec.rb
|
391
393
|
- spec/lib/shamu/json_api/error_builder_spec.rb
|
394
|
+
- spec/lib/shamu/json_api/rails/controller_spec.rb
|
395
|
+
- spec/lib/shamu/json_api/rails/pagination_spec.rb
|
396
|
+
- spec/lib/shamu/json_api/rails/responder_spec.rb
|
392
397
|
- spec/lib/shamu/json_api/relationship_builder_spec.rb
|
393
398
|
- spec/lib/shamu/json_api/resource_builder_spec.rb
|
394
399
|
- spec/lib/shamu/json_api/response_spec.rb
|
@@ -400,8 +405,6 @@ files:
|
|
400
405
|
- spec/lib/shamu/rails/entity_spec.rb
|
401
406
|
- spec/lib/shamu/rails/features.yml
|
402
407
|
- spec/lib/shamu/rails/features_spec.rb
|
403
|
-
- spec/lib/shamu/rails/json_api_responder_spec.rb
|
404
|
-
- spec/lib/shamu/rails/json_api_spec.rb
|
405
408
|
- spec/lib/shamu/security/active_record_policy_spec.rb
|
406
409
|
- spec/lib/shamu/security/hashed_value_spec.rb
|
407
410
|
- spec/lib/shamu/security/policy_refinement_spec.rb
|
@@ -501,6 +504,9 @@ test_files:
|
|
501
504
|
- spec/lib/shamu/json_api/common_builder_spec.rb
|
502
505
|
- spec/lib/shamu/json_api/context_spec.rb
|
503
506
|
- spec/lib/shamu/json_api/error_builder_spec.rb
|
507
|
+
- spec/lib/shamu/json_api/rails/controller_spec.rb
|
508
|
+
- spec/lib/shamu/json_api/rails/pagination_spec.rb
|
509
|
+
- spec/lib/shamu/json_api/rails/responder_spec.rb
|
504
510
|
- spec/lib/shamu/json_api/relationship_builder_spec.rb
|
505
511
|
- spec/lib/shamu/json_api/resource_builder_spec.rb
|
506
512
|
- spec/lib/shamu/json_api/response_spec.rb
|
@@ -512,8 +518,6 @@ test_files:
|
|
512
518
|
- spec/lib/shamu/rails/entity_spec.rb
|
513
519
|
- spec/lib/shamu/rails/features.yml
|
514
520
|
- spec/lib/shamu/rails/features_spec.rb
|
515
|
-
- spec/lib/shamu/rails/json_api_responder_spec.rb
|
516
|
-
- spec/lib/shamu/rails/json_api_spec.rb
|
517
521
|
- spec/lib/shamu/security/active_record_policy_spec.rb
|
518
522
|
- spec/lib/shamu/security/hashed_value_spec.rb
|
519
523
|
- spec/lib/shamu/security/policy_refinement_spec.rb
|
data/lib/shamu/rails/json_api.rb
DELETED
@@ -1,192 +0,0 @@
|
|
1
|
-
require "rack"
|
2
|
-
|
3
|
-
module Shamu
|
4
|
-
module Rails
|
5
|
-
|
6
|
-
# Add support for writing resources as well-formed JSON API.
|
7
|
-
module JsonApi
|
8
|
-
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
included do
|
11
|
-
before_action do
|
12
|
-
render json: json_error( "The 'include' parameter is not supported" ), status: :bad_request if params[:include] # rubocop:disable Metrics/LineLength
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def process_action( * )
|
17
|
-
# If no format has been specfied, default to json_api
|
18
|
-
request.parameters[:format] ||= "json_api"
|
19
|
-
super
|
20
|
-
end
|
21
|
-
|
22
|
-
# Builds a well-formed JSON API response for a single resource.
|
23
|
-
#
|
24
|
-
# @param [Object] resource to present as JSON.
|
25
|
-
# @param [Class] presenter {Presenter} class to use when building the
|
26
|
-
# response for the given resource. If not given, attempts to find a
|
27
|
-
# presenter by calling {Context#find_presenter}.
|
28
|
-
# @param (see #json_context)
|
29
|
-
# @yield (response) write additional top-level links and meta
|
30
|
-
# information.
|
31
|
-
# @yieldparam [JsonApi::Response] response
|
32
|
-
# @return [JsonApi::Response] the presented JSON response.
|
33
|
-
def json_resource( resource, presenter = nil, **context, &block )
|
34
|
-
response = build_json_response( context )
|
35
|
-
response.resource resource, presenter
|
36
|
-
yield response if block_given?
|
37
|
-
response.to_json
|
38
|
-
end
|
39
|
-
|
40
|
-
# Builds a well-formed JSON API response for a collection of resources.
|
41
|
-
#
|
42
|
-
# @param [Enumerable<Object>] resources to present as a JSON array.
|
43
|
-
# @param [Class] presenter {Presenter} class to use when building the
|
44
|
-
# response for each of the resources. If not given, attempts to find
|
45
|
-
# a presenter by calling {Context#find_presenter}
|
46
|
-
# @param (see #json_context)
|
47
|
-
# @yield (response) write additional top-level links and meta
|
48
|
-
# information.
|
49
|
-
# @yieldparam [JsonApi::Response] response
|
50
|
-
# @return [JsonApi::Response] the presented JSON response.
|
51
|
-
def json_collection( resources, presenter = nil, pagination: :auto, **context, &block )
|
52
|
-
response = build_json_response( context )
|
53
|
-
response.collection resources, presenter
|
54
|
-
json_paginate_resources response, resources, pagination
|
55
|
-
yield response if block_given?
|
56
|
-
response.to_json
|
57
|
-
end
|
58
|
-
|
59
|
-
# Add page-based pagination links for the resources to the builder.
|
60
|
-
#
|
61
|
-
# @param [#current_page,#next_page,#previous_page] resources a collection that responds to `#current_page`
|
62
|
-
# @param [JsonApi::BaseBuilder] builder to add links to.
|
63
|
-
# @param [String] param the name of the page parameter to adjust for
|
64
|
-
# @return [void]
|
65
|
-
def json_paginate( resources, builder, param: "page[number]" )
|
66
|
-
page = resources.current_page
|
67
|
-
|
68
|
-
if resources.respond_to?( :next_page ) ? resources.next_page : true
|
69
|
-
builder.link :next, url_for( params.reverse_merge( param => resources.current_page + 1 ) )
|
70
|
-
end
|
71
|
-
|
72
|
-
if resources.respond_to?( :prev_page ) ? resources.prev_page : page > 1
|
73
|
-
builder.link :prev, url_for( params.reverse_merge( param => resources.current_page - 1 ) )
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Write an error response. See {Shamu::JsonApi::Response#error} for details.
|
78
|
-
#
|
79
|
-
# @param (see Shamu::JsonApi::Response#error)
|
80
|
-
# @yield (builder)
|
81
|
-
# @yieldparam [Shamu::JsonApi::ErrorBuilder] builder to customize the
|
82
|
-
# error response.
|
83
|
-
# @return [JsonApi::Response] the presented JSON response.
|
84
|
-
def json_error( error = nil, **context, &block )
|
85
|
-
response = build_json_response( context )
|
86
|
-
|
87
|
-
response.error error do |builder|
|
88
|
-
builder.http_status json_http_status_code_from_error( error )
|
89
|
-
yield builder if block_given?
|
90
|
-
end
|
91
|
-
|
92
|
-
response.to_json
|
93
|
-
end
|
94
|
-
|
95
|
-
# Write all the validation errors from a record to the response.
|
96
|
-
#
|
97
|
-
# @param (see Shamu::JsonApi::Response#validation_errors)
|
98
|
-
# @yield (builder, attr, message)
|
99
|
-
# @yieldparam (see Shamu::JsonApi::Response#validation_errors)
|
100
|
-
# @return [JsonApi::Response] the presented JSON response.
|
101
|
-
def json_validation_errors( errors, **context, &block )
|
102
|
-
response = build_json_response( context )
|
103
|
-
response.validation_errors errors, &block
|
104
|
-
|
105
|
-
response.to_json
|
106
|
-
end
|
107
|
-
|
108
|
-
JSON_CONTEXT_KEYWORDS = [ :fields, :namespaces, :presenters ].freeze
|
109
|
-
|
110
|
-
# @!visibility public
|
111
|
-
#
|
112
|
-
# Build a {JsonApi::Context} for the current request and controller.
|
113
|
-
#
|
114
|
-
# @param [Hash<Symbol,Array>] fields to include in the response. If not
|
115
|
-
# provided looks for a `fields` request argument and parses that.
|
116
|
-
# See {JsonApi::Context#initialize}.
|
117
|
-
# @param [Array<String>] namespaces to look for {Presenter presenters}.
|
118
|
-
# If not provided automatically adds the controller name and it's
|
119
|
-
# namespace.
|
120
|
-
#
|
121
|
-
# For example in the `Users::AccountController` it will add the
|
122
|
-
# `Users::Accounts` and `Users` namespaces.
|
123
|
-
#
|
124
|
-
# See {JsonApi::Context#find_presenter}.
|
125
|
-
# @param [Hash<Class,Class>] presenters a hash that maps resource classes
|
126
|
-
# to the presenter class to use when building responses. See
|
127
|
-
# {JsonApi::Context#find_presenter}.
|
128
|
-
# @return [JsonApi::Context] the builder context honoring any filter
|
129
|
-
# parameters sent by the client.
|
130
|
-
def json_context( fields: :not_set, namespaces: :not_set, presenters: :not_set )
|
131
|
-
Shamu::JsonApi::Context.new fields: fields == :not_set ? json_context_fields : fields,
|
132
|
-
namespaces: namespaces == :not_set ? json_context_namespaces : namespaces,
|
133
|
-
presenters: presenters == :not_set ? json_context_presenters : presenters
|
134
|
-
end
|
135
|
-
|
136
|
-
private
|
137
|
-
|
138
|
-
def json_context_fields
|
139
|
-
params[:fields]
|
140
|
-
end
|
141
|
-
|
142
|
-
def json_context_namespaces
|
143
|
-
name = self.class.name.sub /Controller$/, ""
|
144
|
-
namespaces = [ name.pluralize ]
|
145
|
-
loop do
|
146
|
-
name = name.deconstantize
|
147
|
-
break if name.blank?
|
148
|
-
|
149
|
-
namespaces << name
|
150
|
-
end
|
151
|
-
|
152
|
-
namespaces
|
153
|
-
end
|
154
|
-
|
155
|
-
def json_context_presenters
|
156
|
-
end
|
157
|
-
|
158
|
-
def json_paginate_resources( response, resources, pagination )
|
159
|
-
pagination = resources.respond_to?( :current_page ) if pagination == :auto
|
160
|
-
return unless pagination
|
161
|
-
|
162
|
-
json_paginate resources, response
|
163
|
-
end
|
164
|
-
|
165
|
-
def json_http_status_code_from_error( error )
|
166
|
-
case error
|
167
|
-
when ActiveRecord::RecordNotFound then :not_found
|
168
|
-
when ActiveRecord::RecordInvalid then :unprocessable_entity
|
169
|
-
when /AccessDenied/ then :forbidden
|
170
|
-
else
|
171
|
-
if error.is_a?( Exception )
|
172
|
-
ActionDispatch::ExceptionWrapper.status_code_for_exception( error )
|
173
|
-
else
|
174
|
-
:bad_request
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def json_http_status_code_from_request
|
180
|
-
case request.method
|
181
|
-
when "POST" then :created
|
182
|
-
when "HEAD" then :no_content
|
183
|
-
else :ok
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
def build_json_response( context )
|
188
|
-
Shamu::JsonApi::Response.new( json_context( **context.slice( *JSON_CONTEXT_KEYWORDS ) ) )
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
module Shamu
|
2
|
-
module Rails
|
3
|
-
|
4
|
-
# Support JSON API responses with the standard rails `#respond_with` method.
|
5
|
-
module JsonApiResponder
|
6
|
-
|
7
|
-
# Render the response as JSON
|
8
|
-
# @return [String]
|
9
|
-
def to_json
|
10
|
-
if has_errors?
|
11
|
-
display_errors
|
12
|
-
elsif get?
|
13
|
-
display resource
|
14
|
-
elsif put? || patch?
|
15
|
-
display resource, :location => api_location
|
16
|
-
elsif post?
|
17
|
-
display resource, :status => :created, :location => api_location
|
18
|
-
else
|
19
|
-
head :no_content
|
20
|
-
end
|
21
|
-
end
|
22
|
-
alias_method :to_json_api, :to_json
|
23
|
-
|
24
|
-
protected
|
25
|
-
|
26
|
-
# @visibility private
|
27
|
-
def display( resource, given_options = {} )
|
28
|
-
given_options.merge!( options )
|
29
|
-
|
30
|
-
json =
|
31
|
-
if resource.is_a?( Enumerable )
|
32
|
-
controller.json_collection resource, **given_options
|
33
|
-
else
|
34
|
-
controller.json_resource resource, **given_options
|
35
|
-
end
|
36
|
-
|
37
|
-
super json, given_options
|
38
|
-
end
|
39
|
-
|
40
|
-
# @visibility private
|
41
|
-
def display_errors
|
42
|
-
controller.render format => controller.json_validation_errors( resource_errors ), :status => :unprocessable_entity # rubocop:disable Metrics/LineLength
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
def validation_resource?( resource )
|
48
|
-
resource.respond_to?( :valid? ) && resource.respond_to?( :errors )
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|