From 868ba4971100e11a2e3f17ad2e151d9a2be6e15d Mon Sep 17 00:00:00 2001 From: Lucas Hosseini Date: Sun, 10 Sep 2017 22:13:35 +0200 Subject: [PATCH 01/12] Setup test coverage with CodeClimate. (#47) --- spec/spec_helper.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8f698be..fa7d5b5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,6 @@ +require 'simplecov' +SimpleCov.start + # This file was generated by the `rails generate rspec:install` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause From 2aff9f77f7057a81c34eb14258e12b0eb2b1eb6d Mon Sep 17 00:00:00 2001 From: Lucas Hosseini Date: Thu, 18 May 2017 22:36:50 +0200 Subject: [PATCH 02/12] Add support for ActiveModel::Errors. --- lib/jsonapi/rails/active_model_errors.rb | 39 ++++++++++++++++++++++++ lib/jsonapi/rails/railtie.rb | 2 +- lib/jsonapi/rails/renderer.rb | 10 ++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 lib/jsonapi/rails/active_model_errors.rb diff --git a/lib/jsonapi/rails/active_model_errors.rb b/lib/jsonapi/rails/active_model_errors.rb new file mode 100644 index 0000000..5643320 --- /dev/null +++ b/lib/jsonapi/rails/active_model_errors.rb @@ -0,0 +1,39 @@ +module JSONAPI + module Rails + class ActiveModelError + def initialize(field, message, source) + @field = field + @message = message + @source = source + + freeze + end + + def as_jsonapi + {}.tap do |hash| + hash[:detail] = @message + hash[:title] = "Invalid #{@field}" unless @field.nil? + hash[:source] = { pointer: @source } unless @source.nil? + end + end + end + + class ActiveModelErrors + def initialize(errors, reverse_mapping) + @errors = errors + @reverse_mapping = reverse_mapping + + freeze + end + + def to_a + @errors.keys.flat_map do |key| + @errors.full_messages_for(key).map do |message| + ActiveModelError.new(key, message, @reverse_mapping[key]) + end + end + end + alias to_array to_a + end + end +end diff --git a/lib/jsonapi/rails/railtie.rb b/lib/jsonapi/rails/railtie.rb index 5aa6a7f..e066a04 100644 --- a/lib/jsonapi/rails/railtie.rb +++ b/lib/jsonapi/rails/railtie.rb @@ -13,7 +13,7 @@ class Railtie < ::Rails::Railtie MEDIA_TYPE = 'application/vnd.api+json'.freeze RENDERERS = { jsonapi: SuccessRenderer.new, - jsonapi_error: ErrorsRenderer.new + jsonapi_errors: ErrorsRenderer.new }.freeze initializer 'jsonapi.init', after: :load_config_initializers do diff --git a/lib/jsonapi/rails/renderer.rb b/lib/jsonapi/rails/renderer.rb index c0583b5..2541b85 100644 --- a/lib/jsonapi/rails/renderer.rb +++ b/lib/jsonapi/rails/renderer.rb @@ -1,3 +1,4 @@ +require 'jsonapi/rails/active_model_errors' require 'jsonapi/serializable/renderer' module JSONAPI @@ -35,6 +36,15 @@ def render(errors, options, controller) options = options.merge(_jsonapi_pointers: controller.jsonapi_pointers) # TODO(beauby): SerializableError inference on AR validation errors. + errors = [errors] unless errors.is_a?(Array) + errors = errors.flat_map do |error| + if error.is_a?(ActiveModel::Errors) + ActiveModelErrors.new(error, options[:_jsonapi_pointers]).to_a + else + error + end + end + @renderer.render(errors, options) end end From 643aa021eef1aafdaf4e8127cf39cca1d0a19784 Mon Sep 17 00:00:00 2001 From: Lucas Hosseini Date: Mon, 22 May 2017 20:57:12 +0200 Subject: [PATCH 03/12] Raise exception if error cannot be built. --- lib/jsonapi/rails/renderer.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/jsonapi/rails/renderer.rb b/lib/jsonapi/rails/renderer.rb index 2541b85..148abed 100644 --- a/lib/jsonapi/rails/renderer.rb +++ b/lib/jsonapi/rails/renderer.rb @@ -38,10 +38,12 @@ def render(errors, options, controller) errors = [errors] unless errors.is_a?(Array) errors = errors.flat_map do |error| - if error.is_a?(ActiveModel::Errors) + if error.respond_to?(:as_jsonapi) + error + elsif error.is_a?(ActiveModel::Errors) ActiveModelErrors.new(error, options[:_jsonapi_pointers]).to_a else - error + raise # TODO(lucas): Raise meaningful exception. end end From 01539ab6a1377f28d7003f07447913b44ad80801 Mon Sep 17 00:00:00 2001 From: Lucas Hosseini Date: Mon, 22 May 2017 20:57:39 +0200 Subject: [PATCH 04/12] Build errors from hashes. --- lib/jsonapi/rails/renderer.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/jsonapi/rails/renderer.rb b/lib/jsonapi/rails/renderer.rb index 148abed..37dccdf 100644 --- a/lib/jsonapi/rails/renderer.rb +++ b/lib/jsonapi/rails/renderer.rb @@ -42,6 +42,8 @@ def render(errors, options, controller) error elsif error.is_a?(ActiveModel::Errors) ActiveModelErrors.new(error, options[:_jsonapi_pointers]).to_a + elsif error.is_a?(Hash) + JSONAPI::Serializable::Error.create(error) else raise # TODO(lucas): Raise meaningful exception. end From c829066852a68ca1ab5ac02773ed21018227a8a1 Mon Sep 17 00:00:00 2001 From: Lucas Date: Fri, 14 Jul 2017 17:08:27 +0200 Subject: [PATCH 05/12] Fix errors rendering. --- lib/jsonapi/rails/active_model_errors.rb | 2 +- lib/jsonapi/rails/railtie.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/jsonapi/rails/active_model_errors.rb b/lib/jsonapi/rails/active_model_errors.rb index 5643320..2e8a5cf 100644 --- a/lib/jsonapi/rails/active_model_errors.rb +++ b/lib/jsonapi/rails/active_model_errors.rb @@ -21,7 +21,7 @@ def as_jsonapi class ActiveModelErrors def initialize(errors, reverse_mapping) @errors = errors - @reverse_mapping = reverse_mapping + @reverse_mapping = reverse_mapping || {} freeze end diff --git a/lib/jsonapi/rails/railtie.rb b/lib/jsonapi/rails/railtie.rb index e066a04..2b65b28 100644 --- a/lib/jsonapi/rails/railtie.rb +++ b/lib/jsonapi/rails/railtie.rb @@ -12,7 +12,7 @@ module Rails class Railtie < ::Rails::Railtie MEDIA_TYPE = 'application/vnd.api+json'.freeze RENDERERS = { - jsonapi: SuccessRenderer.new, + jsonapi: SuccessRenderer.new, jsonapi_errors: ErrorsRenderer.new }.freeze From ce35773ed4f7a2398466007d4b608de97c959abd Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 27 Jul 2017 14:57:52 +0200 Subject: [PATCH 06/12] Add tests. --- spec/action_controller_spec.rb | 158 ----------------------------- spec/deserialization_spec.rb | 96 ++++++++++++++++++ spec/render_jsonapi_errors_spec.rb | 113 +++++++++++++++++++++ spec/render_jsonapi_spec.rb | 62 +++++++++++ 4 files changed, 271 insertions(+), 158 deletions(-) delete mode 100644 spec/action_controller_spec.rb create mode 100644 spec/deserialization_spec.rb create mode 100644 spec/render_jsonapi_errors_spec.rb create mode 100644 spec/render_jsonapi_spec.rb diff --git a/spec/action_controller_spec.rb b/spec/action_controller_spec.rb deleted file mode 100644 index 6e1f7bb..0000000 --- a/spec/action_controller_spec.rb +++ /dev/null @@ -1,158 +0,0 @@ -require 'rails_helper' - -describe ActionController::Base, type: :controller do - describe '.deserializable_resource' do - let(:payload) do - { - _jsonapi: { - 'data' => { - 'type' => 'users', - 'attributes' => { 'name' => 'Lucas' } - } - } - } - end - - context 'when using default deserializer' do - controller do - deserializable_resource :user - - def create - render plain: 'ok' - end - end - - it 'makes the deserialized resource available in params' do - post :create, params: payload - - expected = { 'type' => 'users', 'name' => 'Lucas' } - expect(controller.params[:user]).to eq(expected) - end - - it 'makes the deserialization mapping available via #jsonapi_pointers' do - post :create, params: payload - - expected = { name: '/data/attributes/name', - type: '/data/type' } - expect(controller.jsonapi_pointers).to eq(expected) - end - end - - context 'when using a customized deserializer' do - controller do - deserializable_resource :user do - attribute(:name) do |val| - { 'first_name'.to_sym => val } - end - end - - def create - render plain: 'ok' - end - end - - it 'makes the deserialized resource available in params' do - post :create, params: payload - - expected = { 'type' => 'users', 'first_name' => 'Lucas' } - expect(controller.params[:user]).to eq(expected) - end - - it 'makes the deserialization mapping available via #jsonapi_pointers' do - post :create, params: payload - - expected = { first_name: '/data/attributes/name', - type: '/data/type' } - expect(controller.jsonapi_pointers).to eq(expected) - end - end - - context 'when using a customized deserializer with key_format' do - controller do - deserializable_resource :user do - key_format(&:capitalize) - end - - def create - render plain: 'ok' - end - end - - it 'makes the deserialized resource available in params' do - post :create, params: payload - - expected = { 'type' => 'users', 'Name' => 'Lucas' } - expect(controller.params[:user]).to eq(expected) - end - - it 'makes the deserialization mapping available via #jsonapi_pointers' do - post :create, params: payload - - expected = { Name: '/data/attributes/name', - type: '/data/type' } - expect(controller.jsonapi_pointers).to eq(expected) - end - end - end - - describe '#render' do - context 'when calling render jsonapi: user' do - controller do - def index - serializer = Class.new(JSONAPI::Serializable::Resource) do - type :users - attribute :name - end - user = OpenStruct.new(id: 1, name: 'Lucas') - - render jsonapi: user, class: serializer - end - end - - subject { JSON.parse(response.body) } - let(:serialized_user) do - { - 'data' => { - 'id' => '1', - 'type' => 'users', - 'attributes' => { 'name' => 'Lucas' } - } - } - end - - it 'renders a JSON API success document' do - get :index - - expect(response.content_type).to eq('application/vnd.api+json') - is_expected.to eq(serialized_user) - end - end - - context 'when specifying a default jsonapi object' do - controller do - def index - render jsonapi: nil - end - - def jsonapi_object - { version: '1.0' } - end - end - - subject { JSON.parse(response.body) } - let(:document) do - { - 'data' => nil, - 'jsonapi' => { 'version' => '1.0' } - } - end - - it 'renders a JSON API success document' do - get :index - - expect(response.content_type).to eq('application/vnd.api+json') - is_expected.to eq(document) - end - end - end -end diff --git a/spec/deserialization_spec.rb b/spec/deserialization_spec.rb new file mode 100644 index 0000000..f7a0454 --- /dev/null +++ b/spec/deserialization_spec.rb @@ -0,0 +1,96 @@ +require 'rails_helper' + +describe ActionController::Base, '.deserializable_resource', + type: :controller do + let(:payload) do + { + _jsonapi: { + 'data' => { + 'type' => 'users', + 'attributes' => { 'name' => 'Lucas' } + } + } + } + end + + context 'when using default deserializer' do + controller do + deserializable_resource :user + + def create + render plain: 'ok' + end + end + + it 'makes the deserialized resource available in params' do + post :create, params: payload + + expected = { 'type' => 'users', 'name' => 'Lucas' } + expect(controller.params[:user]).to eq(expected) + end + + it 'makes the deserialization mapping available via #jsonapi_pointers' do + post :create, params: payload + + expected = { name: '/data/attributes/name', + type: '/data/type' } + expect(controller.jsonapi_pointers).to eq(expected) + end + end + + context 'when using a customized deserializer' do + controller do + deserializable_resource :user do + attribute(:name) do |val| + { 'first_name'.to_sym => val } + end + end + + def create + render plain: 'ok' + end + end + + it 'makes the deserialized resource available in params' do + post :create, params: payload + + expected = { 'type' => 'users', 'first_name' => 'Lucas' } + expect(controller.params[:user]).to eq(expected) + end + + it 'makes the deserialization mapping available via #jsonapi_pointers' do + post :create, params: payload + + expected = { first_name: '/data/attributes/name', + type: '/data/type' } + expect(controller.jsonapi_pointers).to eq(expected) + end + end + + context 'when using a customized deserializer with key_format' do + controller do + deserializable_resource :user do + key_format(&:capitalize) + end + + def create + render plain: 'ok' + end + end + + it 'makes the deserialized resource available in params' do + post :create, params: payload + + expected = { 'type' => 'users', 'Name' => 'Lucas' } + expect(controller.params[:user]).to eq(expected) + end + + it 'makes the deserialization mapping available via #jsonapi_pointers' do + post :create, params: payload + + expected = { Name: '/data/attributes/name', + type: '/data/type' } + expect(controller.jsonapi_pointers).to eq(expected) + end + end +end diff --git a/spec/render_jsonapi_errors_spec.rb b/spec/render_jsonapi_errors_spec.rb new file mode 100644 index 0000000..53c21a0 --- /dev/null +++ b/spec/render_jsonapi_errors_spec.rb @@ -0,0 +1,113 @@ +require 'rails_helper' + +describe ActionController::Base, '#render', type: :controller do + let(:serialized_errors) do + { + 'errors' => [ + { + 'detail' => 'Name can\'t be blank', + 'title' => 'Invalid name', + 'source' => { 'pointer' => '/data/attributes/name' } + }, + { + 'detail' => 'Email must be a valid email', + 'title' => 'Invalid email', + 'source' => { 'pointer' => '/data/attributes/email' } + } + ] + } + end + + context 'when rendering ActiveModel::Errors' do + class User < ActiveRecord::Base + validates :name, presence: true + validates :email, format: { with: /@/, message: 'must be a valid email' } + end + + controller do + def create + user = User.new(email: 'lucas') + + unless user.valid? + render jsonapi_errors: user.errors + end + end + + def jsonapi_pointers + { + name: '/data/attributes/name', + email: '/data/attributes/email' + } + end + end + + subject { JSON.parse(response.body) } + + it 'renders a JSON API error document' do + post :create + + expect(response.content_type).to eq('application/vnd.api+json') + is_expected.to eq(serialized_errors) + end + end + + context 'when rendering JSONAPI::Serializable::Error' do + controller do + def create + errors = [ + JSONAPI::Serializable::Error.create( + detail: 'Name can\'t be blank', + title: 'Invalid name', + source: { pointer: '/data/attributes/name' } + ), + JSONAPI::Serializable::Error.create( + detail: 'Email must be a valid email', + title: 'Invalid email', + source: { pointer: '/data/attributes/email' } + ) + ] + + render jsonapi_errors: errors + end + end + + subject { JSON.parse(response.body) } + + it 'renders a JSON API error document' do + post :create + + expect(response.content_type).to eq('application/vnd.api+json') + is_expected.to eq(serialized_errors) + end + end + + context 'when rendering error hashes' do + controller do + def create + errors = [ + { + detail: 'Name can\'t be blank', + title: 'Invalid name', + source: { pointer: '/data/attributes/name' } + }, + { + detail: 'Email must be a valid email', + title: 'Invalid email', + source: { pointer: '/data/attributes/email' } + } + ] + + render jsonapi_errors: errors + end + end + + subject { JSON.parse(response.body) } + + it 'renders a JSON API error document' do + post :create + + expect(response.content_type).to eq('application/vnd.api+json') + is_expected.to eq(serialized_errors) + end + end +end diff --git a/spec/render_jsonapi_spec.rb b/spec/render_jsonapi_spec.rb new file mode 100644 index 0000000..53867b4 --- /dev/null +++ b/spec/render_jsonapi_spec.rb @@ -0,0 +1,62 @@ +require 'rails_helper' + +describe ActionController::Base, '#render', type: :controller do + context 'when calling render jsonapi: user' do + controller do + def index + serializer = Class.new(JSONAPI::Serializable::Resource) do + type :users + attribute :name + end + user = OpenStruct.new(id: 1, name: 'Lucas') + + render jsonapi: user, class: serializer + end + end + + subject { JSON.parse(response.body) } + let(:serialized_user) do + { + 'data' => { + 'id' => '1', + 'type' => 'users', + 'attributes' => { 'name' => 'Lucas' } + } + } + end + + it 'renders a JSON API success document' do + get :index + + expect(response.content_type).to eq('application/vnd.api+json') + is_expected.to eq(serialized_user) + end + end + + context 'when specifying a default jsonapi object' do + controller do + def index + render jsonapi: nil + end + + def jsonapi_object + { version: '1.0' } + end + end + + subject { JSON.parse(response.body) } + let(:document) do + { + 'data' => nil, + 'jsonapi' => { 'version' => '1.0' } + } + end + + it 'renders a JSON API success document' do + get :index + + expect(response.content_type).to eq('application/vnd.api+json') + is_expected.to eq(document) + end + end +end From 5e3f6ad24a469739285b86d9a1b46f1b9027e3cf Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 27 Jul 2017 15:13:55 +0200 Subject: [PATCH 07/12] Refactor JSONAPI::Rails::ActiveModelError. --- Gemfile | 3 + lib/jsonapi/rails/active_model_errors.rb | 29 +++++----- lib/jsonapi/rails/renderer.rb | 71 +++++++++++++++++------- 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/Gemfile b/Gemfile index fa75df1..a2f580e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,6 @@ source 'https://rubygems.org' +# TODO(beauby): Remove before merging. +gem 'jsonapi-serializable', github: 'jsonapi-rb/jsonapi-serializable', branch: 'refactor-errors-renderer' + gemspec diff --git a/lib/jsonapi/rails/active_model_errors.rb b/lib/jsonapi/rails/active_model_errors.rb index 2e8a5cf..f832848 100644 --- a/lib/jsonapi/rails/active_model_errors.rb +++ b/lib/jsonapi/rails/active_model_errors.rb @@ -1,27 +1,23 @@ module JSONAPI module Rails - class ActiveModelError - def initialize(field, message, source) - @field = field - @message = message - @source = source + class ActiveModelError < Serializable::Error + title do + "Invalid #{@field}" unless @field.nil? + end - freeze + detail do + @message end - def as_jsonapi - {}.tap do |hash| - hash[:detail] = @message - hash[:title] = "Invalid #{@field}" unless @field.nil? - hash[:source] = { pointer: @source } unless @source.nil? - end + source do + pointer @pointer unless @pointer.nil? end end class ActiveModelErrors - def initialize(errors, reverse_mapping) - @errors = errors - @reverse_mapping = reverse_mapping || {} + def initialize(exposures) + @errors = exposures[:object] + @reverse_mapping = exposures[:_jsonapi_pointers] || {} freeze end @@ -29,7 +25,8 @@ def initialize(errors, reverse_mapping) def to_a @errors.keys.flat_map do |key| @errors.full_messages_for(key).map do |message| - ActiveModelError.new(key, message, @reverse_mapping[key]) + ActiveModelError.new(field: key, message: message, + pointer: @reverse_mapping[key]) end end end diff --git a/lib/jsonapi/rails/renderer.rb b/lib/jsonapi/rails/renderer.rb index 37dccdf..8c3193a 100644 --- a/lib/jsonapi/rails/renderer.rb +++ b/lib/jsonapi/rails/renderer.rb @@ -4,52 +4,81 @@ module JSONAPI module Rails class SuccessRenderer - def initialize(renderer = JSONAPI::Serializable::SuccessRenderer.new) + def initialize(renderer = JSONAPI::Serializable::Renderer.new) @renderer = renderer freeze end def render(resources, options, controller) - options = options.dup - - if (pagination_links = controller.jsonapi_pagination(resources)) - (options[:links] ||= {}).merge!(pagination_links) - end - options[:expose] = - controller.jsonapi_expose.merge!(options[:expose] || {}) - options[:jsonapi] = - options[:jsonapi_object] || controller.jsonapi_object + options = default_options(options, controller, resources) @renderer.render(resources, options) end + + private + + # @api private + def default_options(options, controller, resources) + options.dup.tap do |options| + if (pagination_links = controller.jsonapi_pagination(resources)) + (options[:links] ||= {}).merge!(pagination_links) + end + options[:expose] = controller.jsonapi_expose + .merge!(options[:expose] || {}) + options[:jsonapi] = + options[:jsonapi_object] || controller.jsonapi_object + end + end end class ErrorsRenderer - def initialize(renderer = JSONAPI::Serializable::ErrorsRenderer.new) + def initialize(renderer = JSONAPI::Serializable::Renderer.new) @renderer = renderer freeze end def render(errors, options, controller) - options = options.merge(_jsonapi_pointers: controller.jsonapi_pointers) - # TODO(beauby): SerializableError inference on AR validation errors. + options = default_options(options, controller) errors = [errors] unless errors.is_a?(Array) errors = errors.flat_map do |error| - if error.respond_to?(:as_jsonapi) - error - elsif error.is_a?(ActiveModel::Errors) - ActiveModelErrors.new(error, options[:_jsonapi_pointers]).to_a - elsif error.is_a?(Hash) - JSONAPI::Serializable::Error.create(error) + if error.is_a?(ActiveModel::Errors) + translate_activemodel_errors(error, options) else - raise # TODO(lucas): Raise meaningful exception. + error end end - @renderer.render(errors, options) + @renderer.render_errors(errors, options) + end + + private + + # @api private + def translate_activemodel_errors(errors, options) + klass = if options[:inferrer] && + options[:inferrer].key?(:'ActiveModel::Errors') + options[:inferrer][:'ActiveModel::Errors'] + else + JSONAPI::Rails::ActiveModelErrors + end + klass = klass.safe_constantize if klass.is_a?(String) + + klass.new(options[:expose].merge(object: errors)).to_a + end + + # @api private + def default_options(options, controller) + options.dup.tap do |options| + options[:expose] = + controller.jsonapi_expose + .merge!(options[:expose] || {}) + .merge!(_jsonapi_pointers: controller.jsonapi_pointers) + options[:jsonapi] = + options[:jsonapi_object] || controller.jsonapi_object + end end end end From b36d6d18452a03a5dae88d72a82dcd61aec4a1d3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 6 Sep 2017 04:29:14 +0200 Subject: [PATCH 08/12] Make it work. --- Gemfile | 1 + lib/jsonapi/rails/renderer.rb | 58 +++++++++---------- ...rb => serializable_active_model_errors.rb} | 12 ++-- lib/jsonapi/rails/serializable_error_hash.rb | 12 ++++ spec/render_jsonapi_errors_spec.rb | 30 ---------- spec/render_jsonapi_spec.rb | 2 +- 6 files changed, 46 insertions(+), 69 deletions(-) rename lib/jsonapi/rails/{active_model_errors.rb => serializable_active_model_errors.rb} (64%) create mode 100644 lib/jsonapi/rails/serializable_error_hash.rb diff --git a/Gemfile b/Gemfile index a2f580e..21b6467 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source 'https://rubygems.org' # TODO(beauby): Remove before merging. +gem 'jsonapi-renderer', github: 'jsonapi-rb/jsonapi-renderer' gem 'jsonapi-serializable', github: 'jsonapi-rb/jsonapi-serializable', branch: 'refactor-errors-renderer' gemspec diff --git a/lib/jsonapi/rails/renderer.rb b/lib/jsonapi/rails/renderer.rb index 8c3193a..220ac04 100644 --- a/lib/jsonapi/rails/renderer.rb +++ b/lib/jsonapi/rails/renderer.rb @@ -1,8 +1,15 @@ -require 'jsonapi/rails/active_model_errors' +require 'jsonapi/rails/serializable_active_model_errors' +require 'jsonapi/rails/serializable_error_hash' require 'jsonapi/serializable/renderer' module JSONAPI module Rails + DEFAULT_INFERRER = Hash.new do |h, k| + names = k.to_s.split('::') + klass = names.pop + h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize + end + class SuccessRenderer def initialize(renderer = JSONAPI::Serializable::Renderer.new) @renderer = renderer @@ -20,14 +27,13 @@ def render(resources, options, controller) # @api private def default_options(options, controller, resources) - options.dup.tap do |options| + options.dup.tap do |opts| + opts[:class] ||= DEFAULT_INFERRER if (pagination_links = controller.jsonapi_pagination(resources)) - (options[:links] ||= {}).merge!(pagination_links) + (opts[:links] ||= {}).merge!(pagination_links) end - options[:expose] = controller.jsonapi_expose - .merge!(options[:expose] || {}) - options[:jsonapi] = - options[:jsonapi_object] || controller.jsonapi_object + opts[:expose] = controller.jsonapi_expose.merge!(opts[:expose] || {}) + opts[:jsonapi] = opts[:jsonapi_object] || controller.jsonapi_object end end end @@ -43,41 +49,29 @@ def render(errors, options, controller) options = default_options(options, controller) errors = [errors] unless errors.is_a?(Array) - errors = errors.flat_map do |error| - if error.is_a?(ActiveModel::Errors) - translate_activemodel_errors(error, options) - else - error - end - end @renderer.render_errors(errors, options) end private - # @api private - def translate_activemodel_errors(errors, options) - klass = if options[:inferrer] && - options[:inferrer].key?(:'ActiveModel::Errors') - options[:inferrer][:'ActiveModel::Errors'] - else - JSONAPI::Rails::ActiveModelErrors - end - klass = klass.safe_constantize if klass.is_a?(String) - - klass.new(options[:expose].merge(object: errors)).to_a - end - # @api private def default_options(options, controller) - options.dup.tap do |options| - options[:expose] = + options.dup.tap do |opts| + # TODO(lucas): Make this configurable. + opts[:class] ||= DEFAULT_INFERRER + unless opts[:class].key?(:'ActiveModel::Errors') + opts[:class][:'ActiveModel::Errors'] = + JSONAPI::Rails::SerializableActiveModelErrors + end + unless opts[:class].key?(:Hash) + opts[:class][:Hash] = JSONAPI::Rails::SerializableErrorHash + end + opts[:expose] = controller.jsonapi_expose - .merge!(options[:expose] || {}) + .merge!(opts[:expose] || {}) .merge!(_jsonapi_pointers: controller.jsonapi_pointers) - options[:jsonapi] = - options[:jsonapi_object] || controller.jsonapi_object + opts[:jsonapi] = opts[:jsonapi_object] || controller.jsonapi_object end end end diff --git a/lib/jsonapi/rails/active_model_errors.rb b/lib/jsonapi/rails/serializable_active_model_errors.rb similarity index 64% rename from lib/jsonapi/rails/active_model_errors.rb rename to lib/jsonapi/rails/serializable_active_model_errors.rb index f832848..7dc1894 100644 --- a/lib/jsonapi/rails/active_model_errors.rb +++ b/lib/jsonapi/rails/serializable_active_model_errors.rb @@ -1,6 +1,6 @@ module JSONAPI module Rails - class ActiveModelError < Serializable::Error + class SerializableActiveModelError < Serializable::Error title do "Invalid #{@field}" unless @field.nil? end @@ -14,7 +14,7 @@ class ActiveModelError < Serializable::Error end end - class ActiveModelErrors + class SerializableActiveModelErrors def initialize(exposures) @errors = exposures[:object] @reverse_mapping = exposures[:_jsonapi_pointers] || {} @@ -22,15 +22,15 @@ def initialize(exposures) freeze end - def to_a + def as_jsonapi @errors.keys.flat_map do |key| @errors.full_messages_for(key).map do |message| - ActiveModelError.new(field: key, message: message, - pointer: @reverse_mapping[key]) + SerializableActiveModelError.new(field: key, message: message, + pointer: @reverse_mapping[key]) + .as_jsonapi end end end - alias to_array to_a end end end diff --git a/lib/jsonapi/rails/serializable_error_hash.rb b/lib/jsonapi/rails/serializable_error_hash.rb new file mode 100644 index 0000000..eae5052 --- /dev/null +++ b/lib/jsonapi/rails/serializable_error_hash.rb @@ -0,0 +1,12 @@ +module JSONAPI + module Rails + class SerializableErrorHash < JSONAPI::Serializable::Error + def initialize(exposures) + super + exposures[:object].each do |k, v| + instance_variable_set("@_#{k}", v) + end + end + end + end +end diff --git a/spec/render_jsonapi_errors_spec.rb b/spec/render_jsonapi_errors_spec.rb index 53c21a0..6a55f64 100644 --- a/spec/render_jsonapi_errors_spec.rb +++ b/spec/render_jsonapi_errors_spec.rb @@ -51,36 +51,6 @@ def jsonapi_pointers end end - context 'when rendering JSONAPI::Serializable::Error' do - controller do - def create - errors = [ - JSONAPI::Serializable::Error.create( - detail: 'Name can\'t be blank', - title: 'Invalid name', - source: { pointer: '/data/attributes/name' } - ), - JSONAPI::Serializable::Error.create( - detail: 'Email must be a valid email', - title: 'Invalid email', - source: { pointer: '/data/attributes/email' } - ) - ] - - render jsonapi_errors: errors - end - end - - subject { JSON.parse(response.body) } - - it 'renders a JSON API error document' do - post :create - - expect(response.content_type).to eq('application/vnd.api+json') - is_expected.to eq(serialized_errors) - end - end - context 'when rendering error hashes' do controller do def create diff --git a/spec/render_jsonapi_spec.rb b/spec/render_jsonapi_spec.rb index 53867b4..ba71c45 100644 --- a/spec/render_jsonapi_spec.rb +++ b/spec/render_jsonapi_spec.rb @@ -10,7 +10,7 @@ def index end user = OpenStruct.new(id: 1, name: 'Lucas') - render jsonapi: user, class: serializer + render jsonapi: user, class: { OpenStruct: serializer } end end From 2a8782ce9a730bca7b1af9550cc358bb8f781ae3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 6 Sep 2017 04:39:28 +0200 Subject: [PATCH 09/12] Introduce jsonapi_class controller method. --- Gemfile | 2 +- lib/jsonapi/rails/controller.rb | 10 ++++++++++ lib/jsonapi/rails/renderer.rb | 13 +++---------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Gemfile b/Gemfile index 21b6467..aa9a813 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,6 @@ source 'https://rubygems.org' # TODO(beauby): Remove before merging. gem 'jsonapi-renderer', github: 'jsonapi-rb/jsonapi-renderer' -gem 'jsonapi-serializable', github: 'jsonapi-rb/jsonapi-serializable', branch: 'refactor-errors-renderer' +gem 'jsonapi-serializable', github: 'jsonapi-rb/jsonapi-serializable' gemspec diff --git a/lib/jsonapi/rails/controller.rb b/lib/jsonapi/rails/controller.rb index 8f75a33..4fad7e2 100644 --- a/lib/jsonapi/rails/controller.rb +++ b/lib/jsonapi/rails/controller.rb @@ -46,11 +46,21 @@ def deserializable_resource(key, options = {}, &block) end end + def jsonapi_class + # TODO(lucas): Make this configurable + Hash.new do |h, k| + names = k.to_s.split('::') + klass = names.pop + h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize + end + end + def jsonapi_object nil end def jsonapi_expose + # TODO(lucas): Make this configurable { url_helpers: ::Rails.application.routes.url_helpers } diff --git a/lib/jsonapi/rails/renderer.rb b/lib/jsonapi/rails/renderer.rb index 220ac04..0ba2eca 100644 --- a/lib/jsonapi/rails/renderer.rb +++ b/lib/jsonapi/rails/renderer.rb @@ -4,12 +4,6 @@ module JSONAPI module Rails - DEFAULT_INFERRER = Hash.new do |h, k| - names = k.to_s.split('::') - klass = names.pop - h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize - end - class SuccessRenderer def initialize(renderer = JSONAPI::Serializable::Renderer.new) @renderer = renderer @@ -28,12 +22,12 @@ def render(resources, options, controller) # @api private def default_options(options, controller, resources) options.dup.tap do |opts| - opts[:class] ||= DEFAULT_INFERRER + opts[:class] ||= controller.jsonapi_class if (pagination_links = controller.jsonapi_pagination(resources)) (opts[:links] ||= {}).merge!(pagination_links) end opts[:expose] = controller.jsonapi_expose.merge!(opts[:expose] || {}) - opts[:jsonapi] = opts[:jsonapi_object] || controller.jsonapi_object + opts[:jsonapi] = opts.delete(:jsonapi_object) || controller.jsonapi_object end end end @@ -58,8 +52,7 @@ def render(errors, options, controller) # @api private def default_options(options, controller) options.dup.tap do |opts| - # TODO(lucas): Make this configurable. - opts[:class] ||= DEFAULT_INFERRER + opts[:class] ||= controller.jsonapi_class unless opts[:class].key?(:'ActiveModel::Errors') opts[:class][:'ActiveModel::Errors'] = JSONAPI::Rails::SerializableActiveModelErrors From 91be2fbff731220a8932be965aabea55bd8216de Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 10 Sep 2017 22:43:43 +0200 Subject: [PATCH 10/12] Fix rebase. --- spec/spec_helper.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fa7d5b5..8f698be 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,3 @@ -require 'simplecov' -SimpleCov.start - # This file was generated by the `rails generate rspec:install` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause From 99a05099798c472f24d4afc6b1a1b766d68aab9f Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 11 Sep 2017 02:13:04 +0200 Subject: [PATCH 11/12] Refactor + add configurable defaults. --- .../initializer/templates/initializer.rb | 33 +++++++- lib/jsonapi/rails/configuration.rb | 33 +++++++- lib/jsonapi/rails/controller.rb | 84 +++++++++++++++---- lib/jsonapi/rails/parser.rb | 12 --- lib/jsonapi/rails/railtie.rb | 68 ++++++++------- lib/jsonapi/rails/renderer.rb | 29 +++---- .../rails/serializable_active_model_errors.rb | 2 + lib/jsonapi/rails/serializable_error_hash.rb | 1 + spec/config_spec.rb | 36 +------- spec/render_jsonapi_errors_spec.rb | 3 +- spec/render_jsonapi_spec.rb | 9 +- 11 files changed, 185 insertions(+), 125 deletions(-) delete mode 100644 lib/jsonapi/rails/parser.rb diff --git a/lib/generators/jsonapi/initializer/templates/initializer.rb b/lib/generators/jsonapi/initializer/templates/initializer.rb index 017e8ee..63ac518 100644 --- a/lib/generators/jsonapi/initializer/templates/initializer.rb +++ b/lib/generators/jsonapi/initializer/templates/initializer.rb @@ -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 diff --git a/lib/jsonapi/rails/configuration.rb b/lib/jsonapi/rails/configuration.rb index e1e7bd3..97f8ec9 100644 --- a/lib/jsonapi/rails/configuration.rb +++ b/lib/jsonapi/rails/configuration.rb @@ -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 diff --git a/lib/jsonapi/rails/controller.rb b/lib/jsonapi/rails/controller.rb index 4fad7e2..3899235 100644 --- a/lib/jsonapi/rails/controller.rb +++ b/lib/jsonapi/rails/controller.rb @@ -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 @@ -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] = @@ -44,34 +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 - # TODO(lucas): Make this configurable - Hash.new do |h, k| - names = k.to_s.split('::') - klass = names.pop - h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize - end + 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 - # TODO(lucas): Make this configurable - { - 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 diff --git a/lib/jsonapi/rails/parser.rb b/lib/jsonapi/rails/parser.rb deleted file mode 100644 index 6774559..0000000 --- a/lib/jsonapi/rails/parser.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'jsonapi/deserializable' - -module JSONAPI - module Rails - PARSER = lambda do |body| - data = JSON.parse(body) - hash = { _jsonapi: data } - - hash.with_indifferent_access - end - end -end diff --git a/lib/jsonapi/rails/railtie.rb b/lib/jsonapi/rails/railtie.rb index 2b65b28..b54b90d 100644 --- a/lib/jsonapi/rails/railtie.rb +++ b/lib/jsonapi/rails/railtie.rb @@ -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_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 diff --git a/lib/jsonapi/rails/renderer.rb b/lib/jsonapi/rails/renderer.rb index 0ba2eca..f74005f 100644 --- a/lib/jsonapi/rails/renderer.rb +++ b/lib/jsonapi/rails/renderer.rb @@ -1,9 +1,8 @@ -require 'jsonapi/rails/serializable_active_model_errors' -require 'jsonapi/rails/serializable_error_hash' require 'jsonapi/serializable/renderer' module JSONAPI module Rails + # @private class SuccessRenderer def initialize(renderer = JSONAPI::Serializable::Renderer.new) @renderer = renderer @@ -19,19 +18,20 @@ def render(resources, options, controller) private - # @api private def default_options(options, controller, resources) options.dup.tap do |opts| opts[:class] ||= controller.jsonapi_class if (pagination_links = controller.jsonapi_pagination(resources)) - (opts[:links] ||= {}).merge!(pagination_links) + opts[:links] = (opts[:links] || {}).merge(pagination_links) end - opts[:expose] = controller.jsonapi_expose.merge!(opts[:expose] || {}) - opts[:jsonapi] = opts.delete(:jsonapi_object) || controller.jsonapi_object + opts[:expose] = controller.jsonapi_expose.merge(opts[:expose] || {}) + opts[:jsonapi] = opts.delete(:jsonapi_object) || + controller.jsonapi_object end end end + # @private class ErrorsRenderer def initialize(renderer = JSONAPI::Serializable::Renderer.new) @renderer = renderer @@ -49,22 +49,15 @@ def render(errors, options, controller) private - # @api private def default_options(options, controller) options.dup.tap do |opts| - opts[:class] ||= controller.jsonapi_class - unless opts[:class].key?(:'ActiveModel::Errors') - opts[:class][:'ActiveModel::Errors'] = - JSONAPI::Rails::SerializableActiveModelErrors - end - unless opts[:class].key?(:Hash) - opts[:class][:Hash] = JSONAPI::Rails::SerializableErrorHash - end + opts[:class] ||= controller.jsonapi_errors_class opts[:expose] = controller.jsonapi_expose - .merge!(opts[:expose] || {}) - .merge!(_jsonapi_pointers: controller.jsonapi_pointers) - opts[:jsonapi] = opts[:jsonapi_object] || controller.jsonapi_object + .merge(opts[:expose] || {}) + .merge!(_jsonapi_pointers: controller.jsonapi_pointers) + opts[:jsonapi] = opts.delete(:jsonapi_object) || + controller.jsonapi_object end end end diff --git a/lib/jsonapi/rails/serializable_active_model_errors.rb b/lib/jsonapi/rails/serializable_active_model_errors.rb index 7dc1894..a97ec71 100644 --- a/lib/jsonapi/rails/serializable_active_model_errors.rb +++ b/lib/jsonapi/rails/serializable_active_model_errors.rb @@ -1,5 +1,6 @@ module JSONAPI module Rails + # @private class SerializableActiveModelError < Serializable::Error title do "Invalid #{@field}" unless @field.nil? @@ -14,6 +15,7 @@ class SerializableActiveModelError < Serializable::Error end end + # @private class SerializableActiveModelErrors def initialize(exposures) @errors = exposures[:object] diff --git a/lib/jsonapi/rails/serializable_error_hash.rb b/lib/jsonapi/rails/serializable_error_hash.rb index eae5052..92c34f4 100644 --- a/lib/jsonapi/rails/serializable_error_hash.rb +++ b/lib/jsonapi/rails/serializable_error_hash.rb @@ -1,5 +1,6 @@ module JSONAPI module Rails + # @private class SerializableErrorHash < JSONAPI::Serializable::Error def initialize(exposures) super diff --git a/spec/config_spec.rb b/spec/config_spec.rb index 67ec9c3..1145ac1 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -1,39 +1,5 @@ require 'rails_helper' describe JSONAPI::Rails.config do - context 'when the default configuration is used' do - it 'should register the jsonapi parameter parser' do - expect(JSONAPI::Rails.config.register_parameter_parser).to be true - end - - it 'should register the jsonapi mime type' do - expect(JSONAPI::Rails.config.register_mime_type).to be true - end - - it 'should register the jsonapi renderers' do - expect(JSONAPI::Rails.config.register_renderers).to be true - end - end - - context 'when a custom configuration is used' do - before do - JSONAPI::Rails.configure do |config| - config.register_parameter_parser = false - config.register_mime_type = false - config.register_renderers = false - end - end - - it 'should not register the jsonapi parameter parser' do - expect(JSONAPI::Rails.config.register_parameter_parser).to be false - end - - it 'should not register the jsonapi mime type' do - expect(JSONAPI::Rails.config.register_mime_type).to be false - end - - it 'should not register the jsonapi renderers' do - expect(JSONAPI::Rails.config.register_renderers).to be false - end - end + # TODO(lucas) end diff --git a/spec/render_jsonapi_errors_spec.rb b/spec/render_jsonapi_errors_spec.rb index 6a55f64..6250d36 100644 --- a/spec/render_jsonapi_errors_spec.rb +++ b/spec/render_jsonapi_errors_spec.rb @@ -14,7 +14,8 @@ 'title' => 'Invalid email', 'source' => { 'pointer' => '/data/attributes/email' } } - ] + ], + 'jsonapi' => { 'version' => '1.0' } } end diff --git a/spec/render_jsonapi_spec.rb b/spec/render_jsonapi_spec.rb index ba71c45..6cd373f 100644 --- a/spec/render_jsonapi_spec.rb +++ b/spec/render_jsonapi_spec.rb @@ -21,7 +21,8 @@ def index 'id' => '1', 'type' => 'users', 'attributes' => { 'name' => 'Lucas' } - } + }, + 'jsonapi' => { 'version' => '1.0' } } end @@ -33,14 +34,14 @@ def index end end - context 'when specifying a default jsonapi object' do + context 'when specifying a custom jsonapi object at controller level' do controller do def index render jsonapi: nil end def jsonapi_object - { version: '1.0' } + { version: '2.0' } end end @@ -48,7 +49,7 @@ def jsonapi_object let(:document) do { 'data' => nil, - 'jsonapi' => { 'version' => '1.0' } + 'jsonapi' => { 'version' => '2.0' } } end From 86fde0c95310884d4ac80cd11170ea8aebd3b958 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 11 Sep 2017 08:23:09 +0200 Subject: [PATCH 12/12] Update dependencies. --- Gemfile | 4 ---- jsonapi-rails.gemspec | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index aa9a813..fa75df1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,3 @@ source 'https://rubygems.org' -# TODO(beauby): Remove before merging. -gem 'jsonapi-renderer', github: 'jsonapi-rb/jsonapi-renderer' -gem 'jsonapi-serializable', github: 'jsonapi-rb/jsonapi-serializable' - gemspec diff --git a/jsonapi-rails.gemspec b/jsonapi-rails.gemspec index b618573..caacf32 100644 --- a/jsonapi-rails.gemspec +++ b/jsonapi-rails.gemspec @@ -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'