From 7745ac375d1c8e96a24564e350a176291f6cdb23 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Fri, 3 Jul 2026 13:16:48 +0100 Subject: [PATCH 01/23] ci: add latest versions to ci --- .github/workflows/ruby.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 60f05624..5cc998fa 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -13,11 +13,13 @@ jobs: fail-fast: false matrix: ruby: - - '3.3' - '3.2' + - '3.3' + - '3.4' rails: - '7.1' - '7.2' + - '8.0' database_url: - sqlite3:test_db env: @@ -25,7 +27,7 @@ jobs: DATABASE_URL: ${{ matrix.database_url }} name: Ruby ${{ matrix.ruby }} Rails ${{ matrix.rails }} DB ${{ matrix.database_url }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From b625b34ba2b8ac31d3816a01126b0fa06d069ccf Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Fri, 3 Jul 2026 13:26:58 +0100 Subject: [PATCH 02/23] test: repair deprecation warning --- test/test_helper.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index 206fe8bd..dd956e07 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -56,6 +56,11 @@ class TestApp < Rails::Application config.active_record.time_zone_aware_types = [:time, :datetime] config.active_record.belongs_to_required_by_default = false end + + # Use a modern cache serialization format to avoid Rails 7.2+ deprecations. + if Rails::VERSION::MAJOR >= 7 && config.active_support.respond_to?(:cache_format_version=) + config.active_support.cache_format_version = 7.1 + end end module MyEngine From cc3678d4e1eedbbb530c7cd11bd13ac07397d69c Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Fri, 3 Jul 2026 13:34:19 +0100 Subject: [PATCH 03/23] build: update min versions --- Gemfile | 4 ++-- jsonapi-resources.gemspec | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 805949eb..2dc5e6be 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' gemspec platforms :ruby do - gem 'sqlite3', '>= 1.4' + gem 'sqlite3', '>= 2.1' end platforms :jruby do @@ -17,7 +17,7 @@ when 'master' gem 'railties', { git: 'https://github.com/rails/rails.git' } gem 'arel', { git: 'https://github.com/rails/arel.git' } when 'default' - gem 'railties', '~> 7.1.0' + gem 'railties', '~> 8.0.0' else gem 'railties', "~> #{version}" end diff --git a/jsonapi-resources.gemspec b/jsonapi-resources.gemspec index 9942ddf9..9df9d299 100644 --- a/jsonapi-resources.gemspec +++ b/jsonapi-resources.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] - spec.required_ruby_version = '>= 2.1' + spec.required_ruby_version = '>= 3.2' spec.add_development_dependency 'bundler', '>= 1.5', '< 3.0' spec.add_development_dependency 'rake' From 4644f642503066cef8c9ee88b2a5003b8af9cf18 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 11:35:55 +0100 Subject: [PATCH 04/23] build: add rack 3 as a dependency --- jsonapi-resources.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/jsonapi-resources.gemspec b/jsonapi-resources.gemspec index 9df9d299..1f2e37b4 100644 --- a/jsonapi-resources.gemspec +++ b/jsonapi-resources.gemspec @@ -28,6 +28,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'concurrent-ruby-ext' spec.add_dependency 'activerecord', '>= 4.1' spec.add_dependency 'railties', '>= 4.1' + spec.add_dependency 'rack', '~> 3.0' spec.add_dependency 'concurrent-ruby' spec.add_runtime_dependency 'csv' end From 91db2a05afc20acb777f94936cd0b6c44861ae07 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Fri, 3 Jul 2026 13:58:51 +0100 Subject: [PATCH 05/23] build: add minitest-mock as a development dependency cannot load such file -- minitest/mock (LoadError) --- jsonapi-resources.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/jsonapi-resources.gemspec b/jsonapi-resources.gemspec index 1f2e37b4..c860df50 100644 --- a/jsonapi-resources.gemspec +++ b/jsonapi-resources.gemspec @@ -22,6 +22,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '>= 1.5', '< 3.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'minitest' + spec.add_development_dependency 'minitest-mock' spec.add_development_dependency 'minitest-spec-rails' spec.add_development_dependency 'simplecov' spec.add_development_dependency 'pry' From e09c2be2ab5dc5912ce9e6c25e46de643ac22929 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 11:38:18 +0100 Subject: [PATCH 06/23] fix: unprocessable_entity -> unprocessable_content --- lib/jsonapi/exceptions.rb | 4 ++-- test/controllers/controller_test.rb | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/jsonapi/exceptions.rb b/lib/jsonapi/exceptions.rb index cf77dfd1..feabb58c 100644 --- a/lib/jsonapi/exceptions.rb +++ b/lib/jsonapi/exceptions.rb @@ -475,7 +475,7 @@ def errors def json_api_error(attr_key, message) create_error_object(code: JSONAPI::VALIDATION_ERROR, - status: :unprocessable_entity, + status: :unprocessable_content, title: message, detail: "#{format_key(attr_key)} - #{message}", source: { pointer: pointer(attr_key) }, @@ -500,7 +500,7 @@ def pointer(attr_or_relationship_name) class SaveFailed < Error def errors [create_error_object(code: JSONAPI::SAVE_FAILED, - status: :unprocessable_entity, + status: :unprocessable_content, title: I18n.translate('jsonapi-resources.exceptions.save_failed.title', default: 'Save failed or was cancelled'), detail: I18n.translate('jsonapi-resources.exceptions.save_failed.detail', diff --git a/test/controllers/controller_test.rb b/test/controllers/controller_test.rb index 7bdf19c2..fb4de647 100644 --- a/test/controllers/controller_test.rb +++ b/test/controllers/controller_test.rb @@ -615,7 +615,7 @@ def test_create_link_to_missing_object } } - assert_response :unprocessable_entity + assert_response :unprocessable_content # TODO: check if this validation is working assert_match /author - can't be blank/, response.body assert_equal nil, response.location @@ -698,7 +698,7 @@ def test_create_with_invalid_data } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_equal "/data/relationships/author", json_response['errors'][0]['source']['pointer'] assert_equal "can't be blank", json_response['errors'][0]['title'] @@ -1832,7 +1832,7 @@ def test_delete_with_validation_error delete :destroy, params: { id: post.id } assert_equal "can't destroy me", json_response['errors'][0]['title'] - assert_response :unprocessable_entity + assert_response :unprocessable_content end def test_delete_single @@ -2380,7 +2380,7 @@ def test_create_validations_missing_attribute } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_equal 2, json_response['errors'].size assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code'] assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][1]['code'] @@ -2402,7 +2402,7 @@ def test_update_validations_missing_attribute } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_equal 1, json_response['errors'].size assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code'] assert_match /name - can't be blank/, response.body @@ -2854,7 +2854,7 @@ def test_create_with_invalid_data } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_equal "/data/attributes/spouse-name", json_response['errors'][0]['source']['pointer'] assert_equal "can't be blank", json_response['errors'][0]['title'] @@ -3402,7 +3402,7 @@ def test_save_model_callbacks_fail } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_match /Save failed or was cancelled/, json_response['errors'][0]['detail'] end end From a1a647752eb0998b1d7430b892c6257aaf97b2e8 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 11:47:22 +0100 Subject: [PATCH 07/23] build: limit rails versions to 8.0.x --- jsonapi-resources.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsonapi-resources.gemspec b/jsonapi-resources.gemspec index c860df50..b238f854 100644 --- a/jsonapi-resources.gemspec +++ b/jsonapi-resources.gemspec @@ -27,8 +27,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'simplecov' spec.add_development_dependency 'pry' spec.add_development_dependency 'concurrent-ruby-ext' - spec.add_dependency 'activerecord', '>= 4.1' - spec.add_dependency 'railties', '>= 4.1' + spec.add_dependency 'activerecord', '~> 8.0.0' + spec.add_dependency 'railties', '~> 8.0.0' spec.add_dependency 'rack', '~> 3.0' spec.add_dependency 'concurrent-ruby' spec.add_runtime_dependency 'csv' From f611b05df4c1f8a46a63ddb370a80e65c06ffc4c Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 11:54:32 +0100 Subject: [PATCH 08/23] build: relax rails versions to include 7.1 and 7.2 --- jsonapi-resources.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsonapi-resources.gemspec b/jsonapi-resources.gemspec index b238f854..148f54f1 100644 --- a/jsonapi-resources.gemspec +++ b/jsonapi-resources.gemspec @@ -27,8 +27,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'simplecov' spec.add_development_dependency 'pry' spec.add_development_dependency 'concurrent-ruby-ext' - spec.add_dependency 'activerecord', '~> 8.0.0' - spec.add_dependency 'railties', '~> 8.0.0' + spec.add_dependency 'activerecord', '>= 7.1', '<= 8.1' # versions 7.1, 7.2, 8.0 + spec.add_dependency 'railties', '>= 7.1', '<= 8.1' # versions 7.1, 7.2, 8.0 spec.add_dependency 'rack', '~> 3.0' spec.add_dependency 'concurrent-ruby' spec.add_runtime_dependency 'csv' From 6e06185aad06820b6aa0c4e3aefc1d2399a23243 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 11:56:55 +0100 Subject: [PATCH 09/23] build: fix version restiction typo --- jsonapi-resources.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsonapi-resources.gemspec b/jsonapi-resources.gemspec index 148f54f1..ef0cb2d4 100644 --- a/jsonapi-resources.gemspec +++ b/jsonapi-resources.gemspec @@ -27,8 +27,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'simplecov' spec.add_development_dependency 'pry' spec.add_development_dependency 'concurrent-ruby-ext' - spec.add_dependency 'activerecord', '>= 7.1', '<= 8.1' # versions 7.1, 7.2, 8.0 - spec.add_dependency 'railties', '>= 7.1', '<= 8.1' # versions 7.1, 7.2, 8.0 + spec.add_dependency 'activerecord', '>= 7.1', '< 8.1' # versions 7.1, 7.2, 8.0 + spec.add_dependency 'railties', '>= 7.1', '< 8.1' # versions 7.1, 7.2, 8.0 spec.add_dependency 'rack', '~> 3.0' spec.add_dependency 'concurrent-ruby' spec.add_runtime_dependency 'csv' From f043d674d1a813ef60b5c494c51427251cda156e Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 11:57:26 +0100 Subject: [PATCH 10/23] release: bump version --- lib/jsonapi/resources/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jsonapi/resources/version.rb b/lib/jsonapi/resources/version.rb index 07ba6bd5..d9a2c2ba 100644 --- a/lib/jsonapi/resources/version.rb +++ b/lib/jsonapi/resources/version.rb @@ -1,5 +1,5 @@ module JSONAPI module Resources - VERSION = '0.1.2' + VERSION = '0.1.3' end end From 564c9d80a2bf14c7cbe85d7263466e1a27b4c704 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 12:48:17 +0100 Subject: [PATCH 11/23] fix: replace updated Rack status_code function with legacy version --- lib/jsonapi/error.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/jsonapi/error.rb b/lib/jsonapi/error.rb index fc39d93a..00bee7a2 100644 --- a/lib/jsonapi/error.rb +++ b/lib/jsonapi/error.rb @@ -15,7 +15,7 @@ def initialize(options = {}) @source = options[:source] @links = options[:links] - @status = Rack::Utils.status_code(options[:status]).to_s + @status = status_code(options[:status]).to_s @meta = options[:meta] end @@ -24,6 +24,17 @@ def to_hash instance_variables.each {|var| hash[var.to_s.delete('@')] = instance_variable_get(var) unless instance_variable_get(var).nil? } hash end + + private + + # Extracted from Rack 2 + def status_code(status) + if status.is_a?(Symbol) + Rack::Utils.SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } + else + status.to_i + end + end end class Warning From 1ad672697ecb48b3c8d7a1b4b182b9ebb8d37a2d Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:02:43 +0100 Subject: [PATCH 12/23] test: repair test runner not being invoked --- test/test_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index dd956e07..f0a968e8 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -20,6 +20,7 @@ require 'active_record/railtie' require 'minitest/mock' +require 'minitest/autorun' require 'jsonapi-resources' require 'pry' From 1f08ebd27e3963ca5d0a24a3b922970f0b2a7dd9 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:06:27 +0100 Subject: [PATCH 13/23] fix: remove use of deprecated URI::Escape --- lib/jsonapi/request_parser.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/jsonapi/request_parser.rb b/lib/jsonapi/request_parser.rb index c7e0d6ef..aa34e4f3 100644 --- a/lib/jsonapi/request_parser.rb +++ b/lib/jsonapi/request_parser.rb @@ -1,5 +1,6 @@ require 'jsonapi/operation' require 'jsonapi/paginator' +require 'cgi' module JSONAPI class RequestParser @@ -272,7 +273,7 @@ def parse_sort_criteria(sort_criteria) sorts = [] begin - raw = URI.unescape(sort_criteria) + raw = CGI.unescape(sort_criteria) sorts += CSV.parse_line(raw) rescue CSV::MalformedCSVError fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(@resource_klass._type), raw) From d1da2261258e26562d616a036557eb9962e1508e Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:09:43 +0100 Subject: [PATCH 14/23] test: repair old ruby 2 -> 3 errors --- test/fixtures/active_record.rb | 2 +- test/test_helper.rb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index 9a958fa2..d595785e 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -350,7 +350,7 @@ class Post < ActiveRecord::Base has_many :comments has_and_belongs_to_many :tags, join_table: :posts_tags has_many :special_post_tags - has_many :special_tags, through: :special_post_tags + has_many :special_tags, through: :special_post_tags, source: :tag belongs_to :section has_one :parent_post, class_name: 'Post', foreign_key: 'parent_post_id' diff --git a/test/test_helper.rb b/test/test_helper.rb index f0a968e8..3003ad02 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -79,9 +79,9 @@ class Engine < ::Rails::Engine # Monkeypatch ActionController::TestCase to delete the RAW_POST_DATA on subsequent calls in the same test. if Rails::VERSION::MAJOR >= 5 module ClearRawPostHeader - def process(action, *args) + def process(action, *args, **kwargs) @request.delete_header 'RAW_POST_DATA' - super + super(action, *args, **kwargs) end end @@ -490,13 +490,13 @@ def assert_cacheable_jsonapi_get(url, cached_classes = :all) end class ActionController::TestCase - def assert_cacheable_get(action, *args) + def assert_cacheable_get(action, **request_options) assert_nil JSONAPI.configuration.resource_cache normal_queries = [] normal_query_callback = lambda {|_, _, _, _, payload| normal_queries.push payload[:sql] } ActiveSupport::Notifications.subscribed(normal_query_callback, 'sql.active_record') do - get action, *args + get action, **request_options end non_caching_response = json_response_sans_backtraces non_caching_status = response.status @@ -530,7 +530,7 @@ def assert_cacheable_get(action, *args) @controller = nil setup_controller_request_and_response @request.headers.merge!(orig_request_headers.dup) - get action, *args + get action, **request_options end end rescue Exception From f93435f087a59550b4af25f6857b840654a16bc9 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:12:20 +0100 Subject: [PATCH 15/23] test: add errors using modern rails idiom --- test/unit/resource/resource_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/resource/resource_test.rb b/test/unit/resource/resource_test.rb index 83c417c6..46c19b80 100644 --- a/test/unit/resource/resource_test.rb +++ b/test/unit/resource/resource_test.rb @@ -13,7 +13,7 @@ class PostWithBadAfterSave < ActiveRecord::Base after_save :do_some_after_save_stuff def do_some_after_save_stuff - errors[:base] << 'Boom! Error added in after_save callback.' + errors.add(:base, 'Boom! Error added in after_save callback.') raise ActiveRecord::RecordInvalid.new(self) end end @@ -23,7 +23,7 @@ class PostWithCustomValidationContext < ActiveRecord::Base validate :api_specific_check, on: :json_api_create def api_specific_check - errors[:base] << 'Record is invalid' + errors.add(:base, 'Record is invalid') end end From 0f4f8c295750119694a778d5c2895edee7abf043 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:13:48 +0100 Subject: [PATCH 16/23] test: fix nil assertion warnings --- test/controllers/controller_test.rb | 34 +++++++++---------- .../operation/operation_dispatcher_test.rb | 2 +- test/unit/serializer/link_builder_test.rb | 3 +- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/test/controllers/controller_test.rb b/test/controllers/controller_test.rb index fb4de647..48cde639 100644 --- a/test/controllers/controller_test.rb +++ b/test/controllers/controller_test.rb @@ -483,7 +483,7 @@ def test_show_does_not_include_records_count_in_meta JSONAPI.configuration.top_level_meta_include_record_count = true assert_cacheable_get :show, params: { id: Post.first.id } assert_response :success - assert_equal json_response['meta'], nil + assert_nil json_response['meta'] ensure JSONAPI.configuration.top_level_meta_include_record_count = false end @@ -492,7 +492,7 @@ def test_show_does_not_include_pages_count_in_meta JSONAPI.configuration.top_level_meta_include_page_count = true assert_cacheable_get :show, params: { id: Post.first.id } assert_response :success - assert_equal json_response['meta'], nil + assert_nil json_response['meta'] ensure JSONAPI.configuration.top_level_meta_include_page_count = false end @@ -596,7 +596,7 @@ def test_create_simple_id_not_allowed assert_response :bad_request assert_match /id is not allowed/, response.body - assert_equal nil,response.location + assert_nil response.location end def test_create_link_to_missing_object @@ -618,7 +618,7 @@ def test_create_link_to_missing_object assert_response :unprocessable_content # TODO: check if this validation is working assert_match /author - can't be blank/, response.body - assert_equal nil, response.location + assert_nil response.location end def test_create_extra_param @@ -640,7 +640,7 @@ def test_create_extra_param assert_response :bad_request assert_match /asdfg is not allowed/, response.body - assert_equal nil,response.location + assert_nil response.location end def test_create_extra_param_allow_extra_params @@ -707,7 +707,7 @@ def test_create_with_invalid_data assert_equal "/data/attributes/title", json_response['errors'][1]['source']['pointer'] assert_equal "is too long (maximum is 35 characters)", json_response['errors'][1]['title'] assert_equal "title - is too long (maximum is 35 characters)", json_response['errors'][1]['detail'] - assert_equal nil, response.location + assert_nil response.location end def test_create_multiple @@ -760,7 +760,7 @@ def test_create_simple_missing_posts assert_response :bad_request assert_match /The required parameter, data, is missing./, json_response['errors'][0]['detail'] - assert_equal nil, response.location + assert_nil response.location end def test_create_simple_wrong_type @@ -781,7 +781,7 @@ def test_create_simple_wrong_type assert_response :bad_request assert_match /posts_spelled_wrong is not a valid resource./, json_response['errors'][0]['detail'] - assert_equal nil, response.location + assert_nil response.location end def test_create_simple_missing_type @@ -801,7 +801,7 @@ def test_create_simple_missing_type assert_response :bad_request assert_match /The required parameter, type, is missing./, json_response['errors'][0]['detail'] - assert_equal nil, response.location + assert_nil response.location end def test_create_simple_unpermitted_attributes @@ -822,7 +822,7 @@ def test_create_simple_unpermitted_attributes assert_response :bad_request assert_match /subject/, json_response['errors'][0]['detail'] - assert_equal nil, response.location + assert_nil response.location end def test_create_simple_unpermitted_attributes_allow_extra_params @@ -1086,7 +1086,7 @@ def test_update_remove_links assert_response :success assert json_response['data'].is_a?(Hash) assert_equal '3', json_response['data']['relationships']['author']['data']['id'] - assert_equal nil, json_response['data']['relationships']['section']['data'] + assert_nil json_response['data']['relationships']['section']['data'] assert_equal 'A great new Post', json_response['data']['attributes']['title'] assert_equal 'AAAA', json_response['data']['attributes']['body'] assert matches_array?([], @@ -1116,7 +1116,7 @@ def test_update_relationship_to_one_nil assert_response :no_content post_object = Post.find(4) - assert_equal nil, post_object.section_id + assert_nil post_object.section_id end def test_update_relationship_to_one_invalid_links_hash_keys_ids @@ -1233,7 +1233,7 @@ def test_update_relationship_to_one_singular_param_id_nil put :update_relationship, params: {post_id: 3, relationship: 'section', data: {type: 'sections', id: nil}} assert_response :no_content - assert_equal nil, post_object.reload.section_id + assert_nil post_object.reload.section_id end def test_update_relationship_to_one_data_nil @@ -1246,7 +1246,7 @@ def test_update_relationship_to_one_data_nil put :update_relationship, params: {post_id: 3, relationship: 'section', data: nil} assert_response :no_content - assert_equal nil, post_object.reload.section_id + assert_nil post_object.reload.section_id end def test_remove_relationship_to_one @@ -1260,7 +1260,7 @@ def test_remove_relationship_to_one assert_response :no_content post_object = Post.find(3) - assert_equal nil, post_object.section_id + assert_nil post_object.section_id end def test_update_relationship_to_one_singular_param @@ -2546,7 +2546,7 @@ def test_get_person_as_author assert_equal '1', json_response['data'][0]['id'] assert_equal 'authors', json_response['data'][0]['type'] assert_equal 'Joe Author', json_response['data'][0]['attributes']['name'] - assert_equal nil, json_response['data'][0]['attributes']['email'] + assert_nil json_response['data'][0]['attributes']['email'] end def test_show_person_as_author @@ -2555,7 +2555,7 @@ def test_show_person_as_author assert_equal '1', json_response['data']['id'] assert_equal 'authors', json_response['data']['type'] assert_equal 'Joe Author', json_response['data']['attributes']['name'] - assert_equal nil, json_response['data']['attributes']['email'] + assert_nil json_response['data']['attributes']['email'] end def test_get_person_as_author_by_name_filter diff --git a/test/unit/operation/operation_dispatcher_test.rb b/test/unit/operation/operation_dispatcher_test.rb index 278ebd6d..f7816e58 100644 --- a/test/unit/operation/operation_dispatcher_test.rb +++ b/test/unit/operation/operation_dispatcher_test.rb @@ -85,7 +85,7 @@ def test_replace_to_one_relationship op.process(operations) saturn.reload - assert_equal(saturn.planet_type_id, nil) + assert_nil(saturn.planet_type_id) # Reset operations = [ diff --git a/test/unit/serializer/link_builder_test.rb b/test/unit/serializer/link_builder_test.rb index e1062280..0935474a 100644 --- a/test/unit/serializer/link_builder_test.rb +++ b/test/unit/serializer/link_builder_test.rb @@ -39,8 +39,7 @@ def test_engine_name primary_resource_klass: ApiV2Engine::PersonResource ).engine_name - assert_equal nil, - JSONAPI::LinkBuilder.new( + assert_nil JSONAPI::LinkBuilder.new( primary_resource_klass: Api::V1::PersonResource ).engine_name end From 4ae344d42ca635fc40874b7e03d6487da035cb7b Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:19:15 +0100 Subject: [PATCH 17/23] build: add minitest reporters for better test feedback --- jsonapi-resources.gemspec | 1 + test/test_helper.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/jsonapi-resources.gemspec b/jsonapi-resources.gemspec index ef0cb2d4..7b5e1397 100644 --- a/jsonapi-resources.gemspec +++ b/jsonapi-resources.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest' spec.add_development_dependency 'minitest-mock' spec.add_development_dependency 'minitest-spec-rails' + spec.add_development_dependency 'minitest-reporters' spec.add_development_dependency 'simplecov' spec.add_development_dependency 'pry' spec.add_development_dependency 'concurrent-ruby-ext' diff --git a/test/test_helper.rb b/test/test_helper.rb index 3003ad02..a74f4a18 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -21,6 +21,7 @@ require 'active_record/railtie' require 'minitest/mock' require 'minitest/autorun' +require 'minitest/reporters' require 'jsonapi-resources' require 'pry' @@ -29,6 +30,8 @@ require File.expand_path('../helpers/functional_helpers', __FILE__) require File.expand_path('../helpers/configuration_helpers', __FILE__) +Minitest::Reporters.use! + Rails.env = 'test' I18n.load_path += Dir[File.expand_path("../../locales/*.yml", __FILE__)] From c823ea9f125b9e059490fd493685bdfc3e4087c6 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:23:30 +0100 Subject: [PATCH 18/23] fix: repair method typo --- lib/jsonapi/error.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jsonapi/error.rb b/lib/jsonapi/error.rb index 00bee7a2..89ff6590 100644 --- a/lib/jsonapi/error.rb +++ b/lib/jsonapi/error.rb @@ -30,7 +30,7 @@ def to_hash # Extracted from Rack 2 def status_code(status) if status.is_a?(Symbol) - Rack::Utils.SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } + Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } else status.to_i end From 609d51b977e882dab4661c84b413eea4d7f81535 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:34:28 +0100 Subject: [PATCH 19/23] fix: use Rails 7 deprecation warnings --- lib/jsonapi/resource.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jsonapi/resource.rb b/lib/jsonapi/resource.rb index efaae463..05eff792 100644 --- a/lib/jsonapi/resource.rb +++ b/lib/jsonapi/resource.rb @@ -511,7 +511,7 @@ def attribute(attr, options = {}) check_reserved_attribute_name(attr) if (attr.to_sym == :id) && (options[:format].nil?) - ActiveSupport::Deprecation.warn('Id without format is no longer supported. Please remove ids from attributes, or specify a format.') + ActiveSupport::Deprecation.new(nil, 'JSONAPI').warn('Id without format is no longer supported. Please remove ids from attributes, or specify a format.') end check_duplicate_attribute_name(attr) if options[:format].nil? From 2255afb7d416fe83cf175d66d3c33d694e5765d1 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:42:57 +0100 Subject: [PATCH 20/23] test: add additional unprocessable content tests --- test/integration/requests/request_test.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/requests/request_test.rb b/test/integration/requests/request_test.rb index 31821f8f..f6ff20e7 100644 --- a/test/integration/requests/request_test.rb +++ b/test/integration/requests/request_test.rb @@ -256,6 +256,9 @@ def test_post_single_minimal_invalid } assert_jsonapi_response 422 + assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code'] + assert_equal '422', json_response['errors'][0]['status'] + assert_match "can't be blank", json_response['errors'][0]['title'] end def test_update_relationship_without_content_type From ef3aa762dfa87a15e3011aa8d7fc97702cf6acbf Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 12 Aug 2026 14:30:10 +0100 Subject: [PATCH 21/23] test: add json-api error tests --- .../jsonapi_request/jsonapi_error_test.rb | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/unit/jsonapi_request/jsonapi_error_test.rb diff --git a/test/unit/jsonapi_request/jsonapi_error_test.rb b/test/unit/jsonapi_request/jsonapi_error_test.rb new file mode 100644 index 00000000..078971d8 --- /dev/null +++ b/test/unit/jsonapi_request/jsonapi_error_test.rb @@ -0,0 +1,41 @@ +require File.expand_path('../../../test_helper', __FILE__) + +class JSONAPIErrorTest < Minitest::Test + def test_status_code_no_status + error = JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST) + + assert_equal('0', error.status) + end + + def test_status_code_accepts_symbol + error = JSONAPI::Error.new(code: JSONAPI::VALIDATION_ERROR, status: :unprocessable_content) + + assert_equal('422', error.status) + end + + def test_status_code_accepts_integer + error = JSONAPI::Error.new(code: JSONAPI::VALIDATION_ERROR, status: 422) + + assert_equal('422', error.status) + end + + def test_status_code_accepts_string + error = JSONAPI::Error.new(code: JSONAPI::VALIDATION_ERROR, status: '422') + + assert_equal('422', error.status) + end + + def test_status_code_rejects_unknown_symbol + error = assert_raises(ArgumentError) do + JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: :not_a_real_status) + end + + assert_equal('Unrecognized status code :not_a_real_status', error.message) + end + + def test_status_code_handles_nil + error = JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: nil) + + assert_equal('0', error.status) + end +end From f068db948684511f5b58394e01c2ccc52afff572 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 12 Aug 2026 14:33:44 +0100 Subject: [PATCH 22/23] test: ignore temporary test files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6cc125d6..ec4e88af 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ coverage test/log test_db test_db-journal +test/test_db-* From 73b5ed1213013b721fba787eab6d2fd34e44bb79 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 12 Aug 2026 14:35:03 +0100 Subject: [PATCH 23/23] fix: raise ArgumentError for nil status --- lib/jsonapi/error.rb | 4 ++++ test/unit/jsonapi_request/jsonapi_error_test.rb | 16 ++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/jsonapi/error.rb b/lib/jsonapi/error.rb index 89ff6590..3fafdd2a 100644 --- a/lib/jsonapi/error.rb +++ b/lib/jsonapi/error.rb @@ -29,6 +29,10 @@ def to_hash # Extracted from Rack 2 def status_code(status) + if status.nil? + raise ArgumentError, "Status code is required" + end + if status.is_a?(Symbol) Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } else diff --git a/test/unit/jsonapi_request/jsonapi_error_test.rb b/test/unit/jsonapi_request/jsonapi_error_test.rb index 078971d8..801b8204 100644 --- a/test/unit/jsonapi_request/jsonapi_error_test.rb +++ b/test/unit/jsonapi_request/jsonapi_error_test.rb @@ -1,10 +1,12 @@ require File.expand_path('../../../test_helper', __FILE__) class JSONAPIErrorTest < Minitest::Test - def test_status_code_no_status - error = JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST) + def test_status_code_requires_status + error = assert_raises(ArgumentError) do + JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST) + end - assert_equal('0', error.status) + assert_equal('Status code is required', error.message) end def test_status_code_accepts_symbol @@ -33,9 +35,11 @@ def test_status_code_rejects_unknown_symbol assert_equal('Unrecognized status code :not_a_real_status', error.message) end - def test_status_code_handles_nil - error = JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: nil) + def test_status_code_rejects_nil + error = assert_raises(ArgumentError) do + JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: nil) + end - assert_equal('0', error.status) + assert_equal('Status code is required', error.message) end end