Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7745ac3
ci: add latest versions to ci
StephenHulme Jul 3, 2026
b625b34
test: repair deprecation warning
StephenHulme Jul 3, 2026
cc3678d
build: update min versions
StephenHulme Jul 3, 2026
4644f64
build: add rack 3 as a dependency
StephenHulme Aug 11, 2026
91db2a0
build: add minitest-mock as a development dependency
StephenHulme Jul 3, 2026
e09c2be
fix: unprocessable_entity -> unprocessable_content
StephenHulme Aug 11, 2026
a1a6477
build: limit rails versions to 8.0.x
StephenHulme Aug 11, 2026
f611b05
build: relax rails versions to include 7.1 and 7.2
StephenHulme Aug 11, 2026
6e06185
build: fix version restiction typo
StephenHulme Aug 11, 2026
f043d67
release: bump version
StephenHulme Aug 11, 2026
564c9d8
fix: replace updated Rack status_code function with legacy version
StephenHulme Aug 11, 2026
1ad6726
test: repair test runner not being invoked
StephenHulme Aug 11, 2026
1f08ebd
fix: remove use of deprecated URI::Escape
StephenHulme Aug 11, 2026
d1da226
test: repair old ruby 2 -> 3 errors
StephenHulme Aug 11, 2026
f93435f
test: add errors using modern rails idiom
StephenHulme Aug 11, 2026
0f4f8c2
test: fix nil assertion warnings
StephenHulme Aug 11, 2026
4ae344d
build: add minitest reporters for better test feedback
StephenHulme Aug 11, 2026
c823ea9
fix: repair method typo
StephenHulme Aug 11, 2026
609d51b
fix: use Rails 7 deprecation warnings
StephenHulme Aug 11, 2026
2255afb
test: add additional unprocessable content tests
StephenHulme Aug 11, 2026
ef3aa76
test: add json-api error tests
StephenHulme Aug 12, 2026
f068db9
test: ignore temporary test files
StephenHulme Aug 12, 2026
73b5ed1
fix: raise ArgumentError for nil status
StephenHulme Aug 12, 2026
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
6 changes: 4 additions & 2 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ 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:
RAILS_VERSION: ${{ matrix.rails }}
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:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ coverage
test/log
test_db
test_db-journal
test/test_db-*
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ source 'https://rubygems.org'
gemspec

platforms :ruby do
gem 'sqlite3', '>= 1.4'
gem 'sqlite3', '>= 2.1'
end

platforms :jruby do
Expand All @@ -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
9 changes: 6 additions & 3 deletions jsonapi-resources.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ 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'
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'
spec.add_dependency 'activerecord', '>= 4.1'
spec.add_dependency 'railties', '>= 4.1'
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'
end
17 changes: 16 additions & 1 deletion lib/jsonapi/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Rack 3 warning fix

@meta = options[:meta]
end

Expand All @@ -24,6 +24,21 @@ 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)
Comment thread
BenTopping marked this conversation as resolved.
if status.nil?
raise ArgumentError, "Status code is required"
end

if status.is_a?(Symbol)
Comment thread
BenTopping marked this conversation as resolved.
Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
else
status.to_i
end
end
end

class Warning
Expand Down
4 changes: 2 additions & 2 deletions lib/jsonapi/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
Expand All @@ -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',
Expand Down
3 changes: 2 additions & 1 deletion lib/jsonapi/request_parser.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'jsonapi/operation'
require 'jsonapi/paginator'
require 'cgi'

module JSONAPI
class RequestParser
Expand Down Expand Up @@ -272,7 +273,7 @@ def parse_sort_criteria(sort_criteria)

sorts = []
begin
raw = URI.unescape(sort_criteria)
raw = CGI.unescape(sort_criteria)
Comment thread
BenTopping marked this conversation as resolved.
sorts += CSV.parse_line(raw)
rescue CSV::MalformedCSVError
fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(@resource_klass._type), raw)
Expand Down
2 changes: 1 addition & 1 deletion lib/jsonapi/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
2 changes: 1 addition & 1 deletion lib/jsonapi/resources/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module JSONAPI
module Resources
VERSION = '0.1.2'
VERSION = '0.1.3'
end
end
48 changes: 24 additions & 24 deletions test/controllers/controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -615,10 +615,10 @@ 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
assert_nil response.location
end

def test_create_extra_param
Expand All @@ -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
Expand Down Expand Up @@ -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']
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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?([],
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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']
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
3 changes: 3 additions & 0 deletions test/integration/requests/request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading