Skip to content

Commit 8feac0d

Browse files
author
Michael Deutsch
committed
Merge remote-tracking branch 'upstream/master'
* upstream/master: Conditionally include controller mixin (jsonapi-rb#40) Add initializer generator. (jsonapi-rb#39) Add ActiveSupport::Notifications instrumentation. (jsonapi-rb#41) Add library configuration object (jsonapi-rb#17) Rename JSONAPI::Rails::ActionController to JSONAPI::Rails::Controller. (jsonapi-rb#38) Add hook for default jsonapi object. (jsonapi-rb#37) Add hook for default exposures. (jsonapi-rb#36) Add hook for automatic pagination links. (jsonapi-rb#35) Upgrade to new jsonapi-deserializable version. (jsonapi-rb#34) Prepare for v0.2.1 release
2 parents 569ae69 + 481b5a3 commit 8feac0d

13 files changed

+262
-152
lines changed

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.2
1+
0.2.1

jsonapi-rails.gemspec

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ Gem::Specification.new do |spec|
1414
spec.files = Dir['README.md', 'lib/**/*']
1515
spec.require_path = 'lib'
1616

17-
spec.add_dependency 'jsonapi-rb', '~> 0.2.1'
17+
spec.add_dependency 'jsonapi-rb', '~> 0.3.0'
18+
spec.add_dependency 'jsonapi-parser', '~> 0.1.0'
1819

1920
spec.add_development_dependency 'rails', '~> 5.0'
2021
spec.add_development_dependency 'sqlite3'
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Description:
2+
Generates an initializer for jsonapi-rails.
3+
4+
Example:
5+
`rails generate jsonapi:initializer`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class InitializerGenerator < Rails::Generators::Base
2+
source_root File.expand_path('../templates', __FILE__)
3+
4+
def copy_initializer_file
5+
copy_file 'initializer.rb', 'config/initializers/jsonapi.rb'
6+
end
7+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
JSONAPI::Rails.configure do |config|
2+
# config.register_mime_type = true
3+
# config.register_param_parser = true
4+
# config.register_renderers = true
5+
# config.extend_action_controller = true
6+
end

lib/jsonapi/rails/action_controller.rb

-40
This file was deleted.

lib/jsonapi/rails/configuration.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ class Configuration < ActiveSupport::InheritableOptions; end
55
DEFAULT_CONFIG = {
66
register_parameter_parser: true,
77
register_mime_type: true,
8-
register_renderers: true
8+
register_renderers: true,
9+
extend_action_controller: true
910
}.freeze
1011

11-
def self.configure(&block)
12+
def self.configure
1213
yield config
1314
end
1415

lib/jsonapi/rails/controller.rb

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
require 'jsonapi/deserializable'
2+
require 'jsonapi/parser'
3+
4+
module JSONAPI
5+
module Rails
6+
module Deserializable
7+
class Resource < JSONAPI::Deserializable::Resource
8+
id
9+
type
10+
attributes
11+
has_one do |_rel, id, type, key|
12+
type = type.to_s.singularize.camelize
13+
{ "#{key}_id".to_sym => id, "#{key}_type".to_sym => type }
14+
end
15+
has_many do |_rel, ids, types, key|
16+
key = key.to_s.singularize
17+
types = types.map { |t| t.to_s.singularize.camelize }
18+
{ "#{key}_ids".to_sym => ids, "#{key}_types".to_sym => types }
19+
end
20+
end
21+
end
22+
23+
module Controller
24+
extend ActiveSupport::Concern
25+
26+
JSONAPI_POINTERS_KEY = 'jsonapi_deserializable.jsonapi_pointers'.freeze
27+
28+
class_methods do
29+
def deserializable_resource(key, options = {}, &block)
30+
options = options.dup
31+
klass = options.delete(:class) ||
32+
Class.new(JSONAPI::Rails::Deserializable::Resource, &block)
33+
34+
before_action(options) do |controller|
35+
hash = controller.params[:_jsonapi].to_unsafe_hash
36+
ActiveSupport::Notifications.instrument('parse.jsonapi',
37+
payload: hash,
38+
class: klass) do
39+
JSONAPI::Parser::Resource.parse!(hash)
40+
resource = klass.new(hash[:data])
41+
controller.request.env[JSONAPI_POINTERS_KEY] =
42+
resource.reverse_mapping
43+
controller.params[key.to_sym] = resource.to_hash
44+
end
45+
end
46+
end
47+
end
48+
49+
def jsonapi_object
50+
nil
51+
end
52+
53+
def jsonapi_expose
54+
{
55+
url_helpers: ::Rails.application.routes.url_helpers
56+
}
57+
end
58+
59+
def jsonapi_pagination(_collection)
60+
nil
61+
end
62+
63+
def jsonapi_pointers
64+
request.env[JSONAPI_POINTERS_KEY]
65+
end
66+
end
67+
end
68+
end

lib/jsonapi/rails/railtie.rb

+28-40
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'active_model'
55

66
require 'jsonapi/rails/configuration'
7+
require 'jsonapi/rails/controller'
78
require 'jsonapi/rails/parser'
89
require 'jsonapi/rails/renderer'
910

@@ -16,51 +17,38 @@ class Railtie < ::Rails::Railtie
1617
jsonapi_error: ErrorsRenderer.new
1718
}.freeze
1819

19-
initializer 'jsonapi-rails.action_controller' do
20-
ActiveSupport.on_load(:action_controller) do
21-
require 'jsonapi/rails/action_controller'
22-
include ::JSONAPI::Rails::ActionController
23-
24-
if JSONAPI::Rails.config.register_mime_type
25-
Mime::Type.register MEDIA_TYPE, :jsonapi
26-
end
20+
initializer 'jsonapi.init', after: :load_config_initializers do
21+
if JSONAPI::Rails.config.register_mime_type
22+
Mime::Type.register MEDIA_TYPE, :jsonapi
23+
end
2724

28-
if JSONAPI::Rails.config.register_parameter_parser
29-
if ::Rails::VERSION::MAJOR >= 5
30-
::ActionDispatch::Request.parameter_parsers[:jsonapi] = PARSER
31-
else
32-
::ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = PARSER
33-
end
25+
if JSONAPI::Rails.config.register_parameter_parser
26+
if ::Rails::VERSION::MAJOR >= 5
27+
::ActionDispatch::Request.parameter_parsers[:jsonapi] = PARSER
28+
else
29+
::ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = PARSER
3430
end
31+
end
3532

36-
if JSONAPI::Rails.config.register_renderers
37-
::ActionController::Renderers.add(:jsonapi) do |resources, options|
38-
self.content_type ||= Mime[:jsonapi]
39-
40-
RENDERERS[:jsonapi].render(resources, options).to_json
41-
end
42-
43-
::ActionController::Renderers.add(:jsonapi_error) do |errors, options|
44-
# Renderer proc is evaluated in the controller context, so it
45-
# has access to the jsonapi_pointers method.
46-
options = options.merge(_jsonapi_pointers: jsonapi_pointers)
47-
self.content_type ||= Mime[:jsonapi]
48-
49-
RENDERERS[:jsonapi_error].render(errors, options).to_json
50-
end
33+
if JSONAPI::Rails.config.extend_action_controller
34+
ActiveSupport.on_load(:action_controller) do
35+
include ::JSONAPI::Rails::Controller
5136
end
37+
end
5238

53-
JSONAPI::Deserializable::Resource.configure do |config|
54-
config.default_has_one do |key, _rel, id, type|
55-
key = key.to_s.singularize
56-
type = type.to_s.singularize.camelize
57-
{ "#{key}_id".to_sym => id, "#{key}_type".to_sym => type }
58-
end
59-
60-
config.default_has_many do |key, _rel, ids, types|
61-
key = key.to_s.singularize
62-
types = types.map { |t| t.to_s.singularize.camelize }
63-
{ "#{key}_ids".to_sym => ids, "#{key}_types".to_sym => types }
39+
if JSONAPI::Rails.config.register_renderers
40+
ActiveSupport.on_load(:action_controller) do
41+
RENDERERS.each do |name, renderer|
42+
::ActionController::Renderers.add(name) do |resources, options|
43+
# Renderer proc is evaluated in the controller context.
44+
self.content_type ||= Mime[:jsonapi]
45+
46+
ActiveSupport::Notifications.instrument('render.jsonapi',
47+
resources: resources,
48+
options: options) do
49+
renderer.render(resources, options, self).to_json
50+
end
51+
end
6452
end
6553
end
6654
end

lib/jsonapi/rails/renderer.rb

+15-24
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ def initialize(renderer = JSONAPI::Serializable::SuccessRenderer.new)
1010
freeze
1111
end
1212

13-
def render(resources, options)
14-
opts = options.dup
15-
# TODO(beauby): Move this to a global configuration.
16-
default_exposures = {
17-
url_helpers: ::Rails.application.routes.url_helpers
18-
}
19-
opts[:expose] = default_exposures.merge!(opts[:expose] || {})
20-
opts[:jsonapi] = opts.delete(:jsonapi_object)
21-
22-
@renderer.render(resources, opts)
13+
def render(resources, options, controller)
14+
options = options.dup
15+
16+
if (pagination_links = controller.jsonapi_pagination(resources))
17+
(options[:links] ||= {}).merge!(pagination_links)
18+
end
19+
options[:expose] =
20+
controller.jsonapi_expose.merge!(options[:expose] || {})
21+
options[:jsonapi] =
22+
options[:jsonapi_object] || controller.jsonapi_object
23+
24+
@renderer.render(resources, options)
2325
end
2426
end
2527

@@ -30,21 +32,10 @@ def initialize(renderer = JSONAPI::Serializable::ErrorsRenderer.new)
3032
freeze
3133
end
3234

33-
def render(errors, options)
34-
errors = [errors] unless errors.is_a?(Array)
35-
errors = errors.flat_map do |error|
36-
if error.respond_to?(:as_jsonapi)
37-
error
38-
elsif error.is_a?(ActiveModel::Errors)
39-
ActiveModelError.from_errors(error, options[:_jsonapi_pointers]).to_a
40-
elsif error.is_a?(Hash)
41-
JSONAPI::Serializable::Error.create(error)
42-
else
43-
raise # TODO(lucas): Raise meaningful exception.
44-
end
45-
end
46-
35+
def render(errors, options, controller)
36+
options = options.merge(_jsonapi_pointers: controller.jsonapi_pointers)
4737
# TODO(beauby): SerializableError inference on AR validation errors.
38+
4839
@renderer.render(errors, options)
4940
end
5041
end

0 commit comments

Comments
 (0)