diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 8fd0da3c2..f159e5cbd 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ 'master' ] + branches: ["master"] pull_request: - branches: ['**'] + branches: ["**"] jobs: tests: @@ -13,13 +13,14 @@ jobs: fail-fast: false matrix: ruby: - - '3.4' - - '3.3' - - '3.2' + - "3.2" + - "3.3" + - "3.4" rails: - - '7.1' - - '7.0' - - '8.0.2' + - "7.1" + - "7.2" + - "8.0" + - "8.1" database_url: - sqlite3:test_db env: @@ -27,7 +28,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: diff --git a/.gitignore b/.gitignore index 800c71c6a..ba614b629 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,6 @@ coverage test/log test_db test_db-journal +test/test_db-* .idea *.iml diff --git a/Gemfile b/Gemfile index f022a438b..1b438ec56 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,8 @@ platforms :jruby do end version = ENV['RAILS_VERSION'] || 'default' +# If version is like 'x.y' add a '.0' to make it 'x.y.0' for correct resolution +version = "#{version}.0" if version =~ /^\d+\.\d+$/ platforms :ruby do gem 'pg' @@ -20,7 +22,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', '>= 6.0' + gem 'railties', '~> 8.1.0' else gem 'railties', "~> #{version}" end diff --git a/README.md b/README.md index b7f2f18f2..6c0b80af5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# JSONAPI::Resources [![Gem Version](https://badge.fury.io/rb/jsonapi-resources.svg)](https://badge.fury.io/rb/jsonapi-resources) [![Build Status](https://secure.travis-ci.org/cerebris/jsonapi-resources.svg?branch=master)](http://travis-ci.org/cerebris/jsonapi-resources) [![Code Climate](https://codeclimate.com/github/cerebris/jsonapi-resources/badges/gpa.svg)](https://codeclimate.com/github/cerebris/jsonapi-resources) - -[![Join the chat at https://gitter.im/cerebris/jsonapi-resources](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cerebris/jsonapi-resources?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# JSONAPI::Resources (Sanger fork) `JSONAPI::Resources`, or "JR", provides a framework for developing an API server that complies with the [JSON:API](http://jsonapi.org/) specification. @@ -13,7 +11,7 @@ backed by ActiveRecord models or by custom objects. ## Documentation -Full documentation can be found at [http://jsonapi-resources.com](http://jsonapi-resources.com), including the [v0.10 alpha Guide](http://jsonapi-resources.com/v0.10/guide/) specific to this version. +Full documentation can be found at [http://jsonapi-resources.com](http://jsonapi-resources.com), including the [v0.10 alpha Guide](http://jsonapi-resources.com/v0.10/guide/) specific to this version. ## Demo App @@ -22,31 +20,73 @@ We have a simple demo app, called [Peeps](https://github.com/cerebris/peeps), av ## Client Libraries JSON:API maintains a (non-verified) listing of [client libraries](http://jsonapi.org/implementations/#client-libraries) -which *should* be compatible with JSON:API compliant server implementations such as JR. +which _should_ be compatible with JSON:API compliant server implementations such as JR. ## Installation Add JR to your application's `Gemfile`: -``` +``` gem 'jsonapi-resources' ``` And then execute: -```bash +```bash bundle ``` Or install it yourself as: -```bash +```bash gem install jsonapi-resources ``` **For further usage see the [v0.10 alpha Guide](http://jsonapi-resources.com/v0.10/guide/)** +## Sanger-specific release process + +There are two versions of the gem which we use for production. The version 1 series has unique customisations and is used by Sequencescape. The version 2 series is a more generic version which is used by Traction. + +The aim is to support the 3 most recent Rails versions for each series to allow for seamless upgrades between releases. + +Check which versions we have published at https://rubygems.org/gems/sanger-jsonapi-resources + +### For version 1 series + +- Create a branch from **develop**, apply fixes. +- Change version number in `lib/jsonapi/resources/version.rb` . This file is read by the gemspec during publication. +- Test Sequencescape with the gem from the branch. +- Merge the branch into develop. +- Create a release from the develop branch with the new version number as the tag. Set Release label to _None_. + +- Checkout the newly-created tag on develop. +- Execute `bundle install` to ensure the gemspec is up to date. +- Execute `gem build jsonapi-resources.gemspec` which builds sanger-jsonapi-resources-0.1.x.gem in this case. +- Execute `gem push sanger-jsonapi-resources-0.1.x.gem` which publishes the gem. + +### For version 2 series + +- Create a branch from **master**, apply fixes. +- Change version number in `lib/jsonapi/resources/version.rb` . This file is read by the gemspec during publication. +- Test Traction with the gem from the branch. +- Merge the branch into master. +- Create a release from the master branch with the new version number as the tag. Set Release label to _Latest_. + +- Checkout the newly-created tag on master. +- Execute `bundle install` to ensure the gemspec is up to date. +- Execute `gem build jsonapi-resources.gemspec` which builds sanger-jsonapi-resources-0.2.x.gem in this case. +- Execute `gem push sanger-jsonapi-resources-0.2.x.gem` which publishes the gem. + +### Publication + +You will be notified by email for each. You will also receive one email for API key setup. The first push will ask email and password for the account, which are in KeePass (search for "gem"). + +> [!NOTE] +> The email and password for gem publication is a recent addition to the credentials database. Pull latest changes in the credentials repo. + ## Contributing + See CONTRIBUTING.md for details. ## License diff --git a/Rakefile b/Rakefile index 01619ed8e..699237f4b 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,20 @@ #!/usr/bin/env rake require 'bundler/gem_tasks' +require 'fileutils' require 'rake/testtask' +task :remove_test_db do + FileUtils.rm_f(File.expand_path('test/test_db', __dir__)) +end + Rake::TestTask.new do |t| - t.verbose = true + t.verbose = false t.warning = false t.test_files = FileList['test/**/*_test.rb'] end +Rake::Task[:test].enhance([:remove_test_db]) + task default: [:test] desc 'Run benchmarks' diff --git a/jsonapi-resources.gemspec b/jsonapi-resources.gemspec index 22745c81d..7be374b3d 100644 --- a/jsonapi-resources.gemspec +++ b/jsonapi-resources.gemspec @@ -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.3' + spec.required_ruby_version = '>= 3.2' spec.add_development_dependency 'bundler', '>= 1.17' spec.add_development_dependency 'rake' - spec.add_development_dependency 'minitest', '~> 5.10', '!= 5.10.2' + 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_development_dependency 'database_cleaner' - spec.add_dependency 'activerecord', '>= 5.1' - spec.add_dependency 'railties', '>= 5.1' + spec.add_dependency 'activerecord', '>= 7.1', '< 9.0' # versions 7.1, 7.2, 8.0, 8.1, and above, but not 9.0 + spec.add_dependency 'railties', '>= 7.1', '< 9.0' # versions 7.1, 7.2, 8.0, 8.1, and above, but not 9.0 + spec.add_dependency 'rack', '~> 2.0' spec.add_dependency 'concurrent-ruby' end diff --git a/lib/jsonapi/compatibility_helper.rb b/lib/jsonapi/compatibility_helper.rb index 516609349..be6379914 100644 --- a/lib/jsonapi/compatibility_helper.rb +++ b/lib/jsonapi/compatibility_helper.rb @@ -9,8 +9,7 @@ # JSONAPI::CompatibilityHelper.deprecation_warn("Your deprecation message") # # The method will use the public `warn` method if available, otherwise it will -# use `send(:warn, ...)` to maintain compatibility with Rails 8+ where `warn` -# is private. +# use Rails 8+ style deprecation warnings. # # Example: # JSONAPI::CompatibilityHelper.deprecation_warn("This feature is deprecated.") @@ -18,11 +17,7 @@ module JSONAPI module CompatibilityHelper def deprecation_warn(message) - if ActiveSupport::Deprecation.respond_to?(:warn) && ActiveSupport::Deprecation.public_method_defined?(:warn) - ActiveSupport::Deprecation.warn(message) - else - ActiveSupport::Deprecation.send(:warn, message) - end + ActiveSupport::Deprecation.new(nil, 'JSONAPI').warn(message) end module_function :deprecation_warn end diff --git a/lib/jsonapi/resources/version.rb b/lib/jsonapi/resources/version.rb index b25e5b708..f54a58b95 100644 --- a/lib/jsonapi/resources/version.rb +++ b/lib/jsonapi/resources/version.rb @@ -1,5 +1,5 @@ module JSONAPI module Resources - VERSION = '0.3.0' + VERSION = '0.4.0' end end diff --git a/lib/jsonapi/routing_ext.rb b/lib/jsonapi/routing_ext.rb index 2bc480feb..8a37b8246 100644 --- a/lib/jsonapi/routing_ext.rb +++ b/lib/jsonapi/routing_ext.rb @@ -46,28 +46,14 @@ def jsonapi_resource(*resources, &_block) options[:except] << :destroy unless options[:except].include?(:destroy) || options[:except].include?('destroy') end - resource @resource_type, options do - # :nocov: - if @scope.respond_to? :[]= - # Rails 4 - @scope[:jsonapi_resource] = @resource_type - + resource @resource_type, **options do + jsonapi_resource_scope(SingletonResource.new(@resource_type, api_only?, @scope[:shallow], **options), @resource_type) do if block_given? yield else jsonapi_relationships end - else - # Rails 5 - jsonapi_resource_scope(SingletonResource.new(@resource_type, api_only?, @scope[:shallow], **options), @resource_type) do - if block_given? - yield - else - jsonapi_relationships - end - end end - # :nocov: end end @@ -121,27 +107,14 @@ def jsonapi_resources(*resources, &_block) options[:except] << :destroy unless options[:except].include?(:destroy) || options[:except].include?('destroy') end - resources @resource_type, options do - # :nocov: - if @scope.respond_to? :[]= - # Rails 4 - @scope[:jsonapi_resource] = @resource_type + resources @resource_type, **options do + jsonapi_resource_scope(Resource.new(@resource_type, api_only?, @scope[:shallow], **options), @resource_type) do if block_given? yield else jsonapi_relationships end - else - # Rails 5 - jsonapi_resource_scope(Resource.new(@resource_type, api_only?, @scope[:shallow], **options), @resource_type) do - if block_given? - yield - else - jsonapi_relationships - end - end end - # :nocov: end end diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index e6866785c..67fd44362 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -1679,7 +1679,7 @@ class PlanetResource < JSONAPI::Resource attribute :description has_many :moons - belongs_to :planet_type + has_one :planet_type has_many :tags, acts_as_set: true end diff --git a/test/integration/requests/request_test.rb b/test/integration/requests/request_test.rb index 1863b5c7d..6ce002c7e 100644 --- a/test/integration/requests/request_test.rb +++ b/test/integration/requests/request_test.rb @@ -578,7 +578,9 @@ def test_put_invalid_json assert_equal 400, status assert_equal 'Bad Request', json_response['errors'][0]['title'] - assert_match 'unexpected token at', json_response['errors'][0]['detail'] + rails_old_msg = 'unexpected token at' + rails_8_1_msg = "expected ',' or '}' after object value, got: '\"attributes\":'" + assert_match (/(#{rails_old_msg}|#{rails_8_1_msg})/), json_response['errors'][0]['detail'] end def test_put_valid_json_but_array @@ -1367,7 +1369,7 @@ def test_deprecated_include_parameter_not_allowed end def test_deprecated_include_message - ActiveSupport::Deprecation.silenced = false + set_deprecation_behavior(:report) original_config = JSONAPI.configuration.dup _out, err = capture_io do eval <<-CODE @@ -1377,7 +1379,7 @@ def test_deprecated_include_message assert_match /DEPRECATION WARNING: `allow_include` has been replaced by `default_allow_include_to_one` and `default_allow_include_to_many` options./, err ensure JSONAPI.configuration = original_config - ActiveSupport::Deprecation.silenced = true + set_deprecation_behavior(:silence) end diff --git a/test/test_helper.rb b/test/test_helper.rb index f6f9c1b70..8bd1bc236 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -24,6 +24,8 @@ require 'active_record/railtie' require 'minitest/mock' +require 'minitest/autorun' +require 'minitest/reporters' require 'jsonapi-resources' require 'pry' @@ -32,6 +34,13 @@ require File.expand_path('../helpers/functional_helpers', __FILE__) require File.expand_path('../helpers/configuration_helpers', __FILE__) +if ENV['CI'] == 'true' + # The SpecReporter is easier to read on GitHub + Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new +else + Minitest::Reporters.use! +end + Rails.env = 'test' I18n.load_path += Dir[File.expand_path("../../locales/*.yml", __FILE__)] @@ -41,13 +50,19 @@ config.json_key_format = :camelized_key end -if ActiveSupport::Deprecation.respond_to?(:behavior=) - ActiveSupport::Deprecation.behavior = :silence -elsif ActiveSupport::Deprecation.respond_to?(:silenced=) - ActiveSupport::Deprecation.silenced = true +def set_deprecation_behavior(mode) + if ActiveSupport::Deprecation.respond_to?(:behavior=) + ActiveSupport::Deprecation.behavior = mode + elsif ActiveSupport::Deprecation.respond_to?(:silenced=) + ActiveSupport::Deprecation.silenced = (mode == :silence) + end end +set_deprecation_behavior(:silence) + +puts "-" * 32 puts "Testing With RAILS VERSION #{Rails.version}" +puts "-" * 32 class TestApp < Rails::Application config.eager_load = false @@ -65,9 +80,8 @@ class TestApp < Rails::Application config.active_support.halt_callback_chains_on_return_false = false config.active_record.time_zone_aware_types = [:time, :datetime] config.active_record.belongs_to_required_by_default = false - if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR == 2 - config.active_record.sqlite3.represent_boolean_as_integer = true - end + config.active_support.cache_format_version = 7.1 + config.active_support.to_time_preserves_timezone = :zone end DatabaseCleaner.allow_remote_database_url = true @@ -87,9 +101,9 @@ class Engine < ::Rails::Engine # Monkeypatch ActionController::TestCase to delete the RAW_POST_DATA on subsequent calls in the same test. module ClearRawPostHeader - def process(action, **args) + def process(action, *args, **kwargs) @request.delete_header 'RAW_POST_DATA' - super action, **args + super(action, *args, **kwargs) end end @@ -524,13 +538,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_all_backtraces non_caching_status = response.status @@ -566,7 +580,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 diff --git a/test/unit/active_relation_resource_finder/join_manager_test.rb b/test/unit/active_relation_resource_finder/join_manager_test.rb index 840c90ee2..e76f1e3c7 100644 --- a/test/unit/active_relation_resource_finder/join_manager_test.rb +++ b/test/unit/active_relation_resource_finder/join_manager_test.rb @@ -4,12 +4,16 @@ class JoinTreeTest < ActiveSupport::TestCase def db_true + rails_major = 8 + rails_minor = 1 + case ActiveRecord::Base.connection.adapter_name when 'SQLite' - if Rails::VERSION::MAJOR >= 6 || (Rails::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MINOR >= 2) - "1" + if Rails::VERSION::MAJOR >= rails_major + 1 || + (Rails::VERSION::MAJOR >= rails_major && ActiveRecord::VERSION::MINOR >= rails_minor) + "TRUE" else - "'t'" + "1" end when 'PostgreSQL' 'TRUE' diff --git a/test/unit/resource/resource_test.rb b/test/unit/resource/resource_test.rb index df2df1730..464b9049d 100644 --- a/test/unit/resource/resource_test.rb +++ b/test/unit/resource/resource_test.rb @@ -434,7 +434,7 @@ def test_key_type_proc def test_id_attr_deprecation - ActiveSupport::Deprecation.silenced = false + set_deprecation_behavior(:report) _out, err = capture_io do eval <<-CODE class ProblemResource < JSONAPI::Resource @@ -442,9 +442,11 @@ class ProblemResource < JSONAPI::Resource end CODE end - assert_match /DEPRECATION WARNING: Id without format is no longer supported. Please remove ids from attributes, or specify a format./, err + err_msg = /DEPRECATION WARNING: Id without format is no longer supported. Please remove ids from attributes, or specify a format./ + err_msg_rails_8 = /\[DUPLICATE ATTRIBUTE\] `id` has already been defined in ProblemResource\./ + assert(err.match?(err_msg) || err.match?(err_msg_rails_8), "Expected either deprecation or duplicate-attribute warning, got: #{err}") ensure - ActiveSupport::Deprecation.silenced = true + set_deprecation_behavior(:silence) end def test_id_attr_with_format