Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jsonapi-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
spec.files = Dir['README.md', 'lib/**/*']
spec.require_path = 'lib'

spec.add_dependency 'jsonapi-rb', '~> 0.3.0'
spec.add_dependency 'jsonapi-rb', '~> 0.5.0'
spec.add_dependency 'jsonapi-parser', '~> 0.1.0'

spec.add_development_dependency 'rails', '~> 5.0'
Expand Down
33 changes: 29 additions & 4 deletions lib/generators/jsonapi/initializer/templates/initializer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
JSONAPI::Rails.configure do |config|
# config.register_mime_type = true
# config.register_param_parser = true
# config.register_renderers = true
# config.extend_action_controller = true
# # Set a default serializable class mapping.
# config.jsonapi_class = Hash.new { |h, k|
# names = k.to_s.split('::')
# klass = names.pop
# h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize
# }
#
# # Set a default serializable class mapping for errors.
# config.jsonapi_errors_class = Hash.new { |h, k|
# names = k.to_s.split('::')
# klass = names.pop
# h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize
# }.tap { |h|
# h[:'ActiveModel::Errors'] = JSONAPI::Rails::SerializableActiveModelErrors
# h[:Hash] = JSONAPI::Rails::SerializableErrorHash
# }
#
# # Set a default JSON API object.
# config.jsonapi_object = {
# version: '1.0'
# }
#
# # Set default exposures.
# config.jsonapi_expose = {
# url_helpers: ::Rails.application.routes.url_helpers
# }
#
# # Set a default pagination scheme.
# config.jsonapi_pagination = ->(_) { nil }
end
33 changes: 29 additions & 4 deletions lib/jsonapi/rails/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
require 'jsonapi/rails/serializable_active_model_errors'
require 'jsonapi/rails/serializable_error_hash'

module JSONAPI
module Rails
class Configuration < ActiveSupport::InheritableOptions; end
DEFAULT_JSONAPI_CLASS = Hash.new do |h, k|
names = k.to_s.split('::')
klass = names.pop
h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize
end.freeze

DEFAULT_JSONAPI_ERRORS_CLASS = DEFAULT_JSONAPI_CLASS.dup.merge!(
'ActiveModel::Errors'.to_sym =>
JSONAPI::Rails::SerializableActiveModelErrors,
'Hash'.to_sym => JSONAPI::Rails::SerializableErrorHash
).freeze

DEFAULT_JSONAPI_OBJECT = {
version: '1.0'
}.freeze

DEFAULT_JSONAPI_EXPOSE = {
url_helpers: ::Rails.application.routes.url_helpers
}.freeze

DEFAULT_JSONAPI_PAGINATION = ->(_) { nil }

DEFAULT_CONFIG = {
register_parameter_parser: true,
register_mime_type: true,
register_renderers: true,
extend_action_controller: true
jsonapi_class: DEFAULT_JSONAPI_CLASS,
jsonapi_errors_class: DEFAULT_JSONAPI_ERRORS_CLASS,
jsonapi_object: DEFAULT_JSONAPI_OBJECT,
jsonapi_expose: DEFAULT_JSONAPI_EXPOSE,
jsonapi_pagination: DEFAULT_JSONAPI_PAGINATION
}.freeze

def self.configure
Expand Down
80 changes: 69 additions & 11 deletions lib/jsonapi/rails/controller.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
require 'jsonapi/deserializable'
require 'jsonapi/parser'
require 'jsonapi/rails/configuration'

module JSONAPI
module Rails
module Deserializable
# @private
class Resource < JSONAPI::Deserializable::Resource
id
type
Expand All @@ -20,22 +22,56 @@ class Resource < JSONAPI::Deserializable::Resource
end
end

# ActionController methods and hooks for JSON API deserialization and
# rendering.
module Controller
extend ActiveSupport::Concern

JSONAPI_POINTERS_KEY = 'jsonapi_deserializable.jsonapi_pointers'.freeze
JSONAPI_POINTERS_KEY = 'jsonapi-rails.jsonapi_pointers'.freeze

class_methods do
# Declare a deserializable resource.
#
# @param key [Symbol] The key under which the deserialized hash will be
# available within the `params` hash.
# @param options [Hash]
# @option class [Class] A custom deserializer class. Optional.
# @option only List of actions for which deserialization should happen.
# Optional.
# @option except List of actions for which deserialization should not
# happen. Optional.
# @yieldreturn Optional block for in-line definition of custom
# deserializers.
#
# @example
# class ArticlesController < ActionController::Base
# deserializable_resource :article, only: [:create, :update]
#
# def create
# article = Article.new(params[:article])
#
# if article.save
# render jsonapi: article
# else
# render jsonapi_errors: article.errors
# end
# end
#
# # ...
# end
#
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def deserializable_resource(key, options = {}, &block)
options = options.dup
klass = options.delete(:class) ||
Class.new(JSONAPI::Rails::Deserializable::Resource, &block)

before_action(options) do |controller|
# TODO(lucas): Fail with helpful error message if _jsonapi not
# present.
hash = controller.params[:_jsonapi].to_unsafe_hash
ActiveSupport::Notifications.instrument('parse.jsonapi',
payload: hash,
class: klass) do
ActiveSupport::Notifications
.instrument('parse.jsonapi', payload: hash, class: klass) do
JSONAPI::Parser::Resource.parse!(hash)
resource = klass.new(hash[:data])
controller.request.env[JSONAPI_POINTERS_KEY] =
Expand All @@ -44,24 +80,46 @@ def deserializable_resource(key, options = {}, &block)
end
end
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
end

# Hook for serializable class mapping (for resources).
# Overridden by the `class` renderer option.
# @return [Hash{Symbol=>Class}]
def jsonapi_class
JSONAPI::Rails.config[:jsonapi_class]
end

# Hook for serializable class mapping (for errors).
# Overridden by the `class` renderer option.
# @return [Hash{Symbol=>Class}]
def jsonapi_errors_class
JSONAPI::Rails.config[:jsonapi_errors_class]
end

# Hook for the jsonapi object.
# Overridden by the `jsonapi_object` renderer option.
# @return [Hash]
def jsonapi_object
nil
JSONAPI::Rails.config[:jsonapi_object]
end

# Hook for default exposures.
# @return [Hash]
def jsonapi_expose
{
url_helpers: ::Rails.application.routes.url_helpers
}
JSONAPI::Rails.config[:jsonapi_expose]
end

def jsonapi_pagination(_collection)
nil
# Hook for pagination scheme.
# @return [Hash]
def jsonapi_pagination(resources)
instance_exec(resources, &JSONAPI::Rails.config[:jsonapi_pagination])
end

# JSON pointers for deserialized fields.
# @return [Hash{Symbol=>String}]
def jsonapi_pointers
request.env[JSONAPI_POINTERS_KEY]
request.env[JSONAPI_POINTERS_KEY] || {}
end
end
end
Expand Down
12 changes: 0 additions & 12 deletions lib/jsonapi/rails/parser.rb

This file was deleted.

72 changes: 41 additions & 31 deletions lib/jsonapi/rails/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,66 @@
require 'action_controller'
require 'active_support'

require 'jsonapi/rails/configuration'
require 'jsonapi/rails/controller'
require 'jsonapi/rails/parser'
require 'jsonapi/rails/renderer'

module JSONAPI
module Rails
# @private
class Railtie < ::Rails::Railtie
MEDIA_TYPE = 'application/vnd.api+json'.freeze
PARSER = lambda do |body|
data = JSON.parse(body)
hash = { _jsonapi: data }

hash.with_indifferent_access
end
RENDERERS = {
jsonapi: SuccessRenderer.new,
jsonapi_error: ErrorsRenderer.new
jsonapi: SuccessRenderer.new,
jsonapi_errors: ErrorsRenderer.new
}.freeze

initializer 'jsonapi.init', after: :load_config_initializers do
if JSONAPI::Rails.config.register_mime_type
Mime::Type.register MEDIA_TYPE, :jsonapi
initializer 'jsonapi-rails.init' do
register_mime_type
register_parameter_parser
register_renderers
ActiveSupport.on_load(:action_controller) do
require 'jsonapi/rails/controller'
include ::JSONAPI::Rails::Controller
end
end

if JSONAPI::Rails.config.register_parameter_parser
if ::Rails::VERSION::MAJOR >= 5
::ActionDispatch::Request.parameter_parsers[:jsonapi] = PARSER
else
::ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = PARSER
end
end
private

if JSONAPI::Rails.config.extend_action_controller
ActiveSupport.on_load(:action_controller) do
include ::JSONAPI::Rails::Controller
end
def register_mime_type
Mime::Type.register(MEDIA_TYPE, :jsonapi)
end

def register_parameter_parser
if ::Rails::VERSION::MAJOR >= 5
ActionDispatch::Request.parameter_parsers[:jsonapi] = PARSER
else
ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = PARSER
end
end

# rubocop:disable Metrics/MethodLength
def register_renderers
ActiveSupport.on_load(:action_controller) do
RENDERERS.each do |name, renderer|
::ActionController::Renderers.add(name) do |resources, options|
# Renderer proc is evaluated in the controller context.
self.content_type ||= Mime[:jsonapi]

if JSONAPI::Rails.config.register_renderers
ActiveSupport.on_load(:action_controller) do
RENDERERS.each do |name, renderer|
::ActionController::Renderers.add(name) do |resources, options|
# Renderer proc is evaluated in the controller context.
self.content_type ||= Mime[:jsonapi]

ActiveSupport::Notifications.instrument('render.jsonapi',
resources: resources,
options: options) do
renderer.render(resources, options, self).to_json
end
ActiveSupport::Notifications.instrument('render.jsonapi',
resources: resources,
options: options) do
renderer.render(resources, options, self).to_json
end
end
end
end
end
# rubocop:enable Metrics/MethodLength
end
end
end
Loading