module Shamu::JsonApi::Rails::Controller
Add support for writing resources as well-formed JSON API.
Constants
- ID_PATTERN
Pattern to identify request params that hold 'ids'
- JSON_CONTEXT_KEYWORDS
Private Instance Methods
@!visibility public
Annotate an exception that is being rendered to the browser - for example to add current user or security information if available.
# File lib/shamu/json_api/rails/controller.rb, line 200 def annotate_json_error( error, builder ) if ::Rails.env.development? builder.meta :type, error.class.to_s builder.meta :backtrace, error.backtrace end end
# File lib/shamu/json_api/rails/controller.rb, line 342 def build_json_response( context ) Shamu::JsonApi::Response.new( json_context( **context.slice( *JSON_CONTEXT_KEYWORDS ) ) ) end
Builds a well-formed JSON API response for a collection of resources.
@param [Enumerable<Object>] resources to present as a JSON array. @param [Class] presenter {Presenter} class to use when building the
response for each of the resources. If not given, attempts to find a presenter by calling {Context#find_presenter}
@param (see json_context
) @yield (response) write additional top-level links and meta
information.
@yieldparam [JsonApi::Response] response @return [JsonApi::Response] the presented JSON response.
# File lib/shamu/json_api/rails/controller.rb, line 101 def json_collection( resources, presenter = nil, pagination: :auto, **context, &block ) response = build_json_response( context ) response.collection resources, presenter json_paginate_resources response, resources, pagination yield response if block_given? response.as_json end
@!visibility public
Build a {JsonApi::Context} for the current request and controller.
@param [Hash<Symbol,Array>] fields to include in the response. If not
provided looks for a `fields` request argument and parses that. See {JsonApi::Context#initialize}.
@param [Array<String>] namespaces to look for {Presenter presenters}.
If not provided automatically adds the controller name and it's namespace. For example in the `Users::AccountController` it will add the `Users::Accounts` and `Users` namespaces. See {JsonApi::Context#find_presenter}.
@param [Hash<Class,Class>] presenters a hash that maps resource classes
to the presenter class to use when building responses. See {JsonApi::Context#find_presenter}.
@return [JsonApi::Context] the builder context honoring any filter
parameters sent by the client.
# File lib/shamu/json_api/rails/controller.rb, line 230 def json_context( fields: :not_set, namespaces: :not_set, presenters: :not_set ) Shamu::JsonApi::Context.new \ fields: fields == :not_set ? json_context_fields : fields, namespaces: namespaces == :not_set ? json_context_namespaces : namespaces, presenters: presenters == :not_set ? json_context_presenters : presenters end
# File lib/shamu/json_api/rails/controller.rb, line 293 def json_context_fields params[:fields] end
# File lib/shamu/json_api/rails/controller.rb, line 297 def json_context_namespaces name = self.class.name.sub( /Controller$/, "" ) namespaces = [ name.pluralize ] loop do name = name.deconstantize break if name.blank? namespaces << name end namespaces end
# File lib/shamu/json_api/rails/controller.rb, line 310 def json_context_presenters end
@!visibility public
Write an error response. See {Shamu::JsonApi::Response#error} for details.
@param (see Shamu::JsonApi::Response#error
) @yield (builder) @yieldparam [Shamu::JsonApi::ErrorBuilder] builder to customize the
error response.
@return [JsonApi::Response] the presented JSON response.
# File lib/shamu/json_api/rails/controller.rb, line 180 def json_error( error = nil, **context, &block ) response = build_json_response( context ) response.error error do |builder| builder.http_status json_http_status_code_from_error( error ) annotate_json_error( error, builder ) yield builder if block_given? end response.to_json end
# File lib/shamu/json_api/rails/controller.rb, line 320 def json_http_status_code_from_error( error ) case error when ActiveRecord::RecordNotFound then :not_found when ActiveRecord::RecordInvalid then :unprocessable_entity when /AccessDenied/ then :forbidden else if error.is_a?( Exception ) ActionDispatch::ExceptionWrapper.status_code_for_exception( error ) else :bad_request end end end
# File lib/shamu/json_api/rails/controller.rb, line 334 def json_http_status_code_from_request case request.method when "POST" then :created when "HEAD" then :no_content else :ok end end
# File lib/shamu/json_api/rails/controller.rb, line 148 def json_page_parameter( page_param_name, param, value ) params = self.params params = params.to_unsafe_hash if params.respond_to?( :to_unsafe_hash ) page_params = params.reverse_merge page_param_name => {} page_params[page_param_name][param] = value page_params end
@!visibility public
Add page-based pagination links for the resources to the builder.
@param [#current_page,#next_page,#previous_page] resources a collection that responds to `#current_page` @param [JsonApi::BaseBuilder] builder to add links to. @param [String] param the name of the key page parameter to adjust @return [void]
# File lib/shamu/json_api/rails/controller.rb, line 136 def json_paginate( resources, builder, param: :page ) page = resources.current_page if resources.respond_to?( :next_page ) ? resources.next_page : true builder.link :next, url_for( json_page_parameter( param, :number, page + 1 ) ) end if resources.respond_to?( :prev_page ) ? resources.prev_page : page > 1 builder.link :prev, url_for( json_page_parameter( param, :number, page - 1 ) ) end end
# File lib/shamu/json_api/rails/controller.rb, line 313 def json_paginate_resources( response, resources, pagination ) pagination = resources.respond_to?( :current_page ) if pagination == :auto return unless pagination json_paginate resources, response end
@!visibility public
Get the pagination request parameters.
@param [Symbol] param the request parameter to read pagination
options from.
@return [Pagination] the pagination state
# File lib/shamu/json_api/rails/controller.rb, line 165 def json_pagination( param: :page ) page_params = params[ param ] || {} Pagination.new( page_params.merge( param: param ) ) end
@!visibility public
Map a JSON body to a hash. @return [Hash] the parsed JSON payload.
# File lib/shamu/json_api/rails/controller.rb, line 279 def json_request_payload @json_request_payload ||= begin body = request.body.read || "{}" json = JSON.parse( body, symbolize_names: true ) unless json.blank? fail NoJsonBodyError unless json[ :data ] end json ? json[ :data ] : {} end end
@!visibility public
Builds a well-formed JSON API response for a single resource.
@param [Object] resource to present as JSON. @param [Class] presenter {Presenter} class to use when building the
response for the given resource. If not given, attempts to find a presenter by calling {Context#find_presenter}.
@param (see json_context
) @yield (response) write additional top-level links and meta
information.
@yieldparam [JsonApi::Response] response @return [JsonApi::Response] the presented JSON response.
# File lib/shamu/json_api/rails/controller.rb, line 38 def json_resource( resource, presenter = nil, **context, &block ) response = build_json_response( context ) response.resource resource, presenter yield response if block_given? response.as_json end
Write all the validation errors from a record to the response.
@param (see Shamu::JsonApi::Response#validation_errors
) @yield (builder, attr, message) @yieldparam (see Shamu::JsonApi::Response#validation_errors
) @return [JsonApi::Response] the presented JSON response.
# File lib/shamu/json_api/rails/controller.rb, line 121 def json_validation_errors( errors, **context, &block ) response = build_json_response( context ) response.validation_errors errors, &block response.as_json end
# File lib/shamu/json_api/rails/controller.rb, line 252 def map_json_resource_payload( resource ) # rubocop:disable Metrics/AbcSize payload = resource[ :attributes ] ? resource[ :attributes ].dup : {} payload[ :id ] = resource[ :id ] if resource.key?( :id ) if relationships = resource[ :relationships ] relationships.each do |key, value| attr_key = "#{ key.to_s.singularize }_id" if value[ :data ].is_a?( Array ) attr_key += "s" if value[ :data ].is_a?( Array ) payload[ attr_key.to_sym ] = value[ :data ].map { |d| d[ :id ] } payload[ key ] = value[ :data ].map { |d| map_json_resource_payload( d ) } else payload[ attr_key.to_sym ] = value[ :data ][ :id ] payload[ key ] = map_json_resource_payload( value[ :data ] ) end end end payload end
Present the resources as json and render it adding appropriate HTTP response codes and headers.
# File lib/shamu/json_api/rails/controller.rb, line 111 def render_collection( resources, presenter: nil, pagination: :auto, **context, &block ) render json: json_collection( resources, presenter, pagination: pagination, **context, &block ) end
@!visibility public
Present the `resource` as json and render it adding appropriate HTTP response codes and headers for standard JSON API actions.
@param [Symbol,Number] status the HTTP status code. @param (see json_resource
)
# File lib/shamu/json_api/rails/controller.rb, line 52 def render_resource( resource, presenter: nil, status: nil, location: nil, **context, &block ) json = json_resource( resource, presenter, **context, &block ) # Include canonical url to resource if present if data = json[ "data" ] if links = data[ "links" ] location ||= links[ "self" ] if links[ "self" ] end end render json: json, status: status, location: location end
@!visibility public
Renders a {Shamu::Services::Result} presenting either the validation errors or the entity.
@param [Shamu::Services::Result] result of a service call @param (see json_resource
)
# File lib/shamu/json_api/rails/controller.rb, line 72 def render_result( result, presenter: nil, status: nil, **context, &block ) if result.valid? if result.entity status ||= case request.method when "POST" then :created when "DELETE" then :no_content else :ok end render_resource result.entity, presenter: presenter, status: status, **context, &block else head status || :no_content end else render json: json_validation_errors( result.errors, **context ), status: :unprocessable_entity end end
# File lib/shamu/json_api/rails/controller.rb, line 192 def render_unhandled_exception( exception ) render json: json_error( exception ), status: :internal_server_error end
See (Shamu::Rails::Entity#request_params
)
# File lib/shamu/json_api/rails/controller.rb, line 238 def request_params( param_key ) if relationships = json_request_payload[ :relationships ] return map_json_resource_payload( relationships[ param_key ][ :data ] ) if relationships.key?( param_key ) end payload = map_json_resource_payload( json_request_payload ) request.params.each do |key, value| payload[ key.to_sym ] ||= value if ID_PATTERN =~ key end payload end