diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e707549..d0847fb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,12 +17,13 @@ jobs: tests: strategy: + fail-fast: false matrix: - ruby-version: [2.6, 2.7, 3.0, 3.1] - rails-version: [6.0, 6.1, 7.0] + ruby-version: ["3.1", "3.2", "3.3", "3.4"] + rails-version: ["7.0", "7.1", "7.2", "8.0"] exclude: - - ruby-version: 2.6 - rails-version: 7.0 + - ruby-version: "3.1" + rails-version: "8.0" runs-on: ubuntu-latest env: RAILS_VERSION: ${{ matrix.rails-version }} diff --git a/.rubocop.yml b/.rubocop.yml index 565b3ba..446249d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,10 +1,9 @@ -inherit_from: .rubocop_todo.yml - inherit_gem: gc_ruboconfig: rubocop.yml AllCops: - TargetRubyVersion: 2.6 + TargetRubyVersion: 3.1 + NewCops: enable Metrics/MethodLength: Max: 15 @@ -29,3 +28,12 @@ Naming/MethodParameterName: Gemspec/RequiredRubyVersion: Enabled: False + +RSpec/NestedGroups: + Enabled: false + +RSpec/MultipleExpectations: + Enabled: false + +RSpec/ExampleLength: + Max: 10 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index 5a1c30f..0000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,27 +0,0 @@ -# This configuration was generated by -# `rubocop --auto-gen-config` -# on 2018-03-08 12:21:23 +0000 using RuboCop version 0.53.0. -# The point is for the user to remove these configuration records -# one by one as the offenses are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of RuboCop, may require this file to be generated again. - -# Offense count: 1 -# Configuration parameters: Max. -RSpec/ExampleLength: - Exclude: - - 'spec/lib/coach/notifications_spec.rb' - -# Offense count: 5 -# Configuration parameters: AggregateFailuresByDefault. -RSpec/MultipleExpectations: - Max: 3 - -# Offense count: 12 -# Configuration parameters: Max. -RSpec/NestedGroups: - Exclude: - - 'spec/lib/coach/handler_spec.rb' - - 'spec/lib/coach/middleware_validator_spec.rb' - - 'spec/lib/coach/request_serializer_spec.rb' - - 'spec/lib/coach/router_spec.rb' diff --git a/.ruby-version b/.ruby-version index b502146..47b322c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.0.2 +3.4.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index abec945..de0638b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ # Unreleased +# 4.0.0 / 2025-01-15 + +* Drop support for Rails < 7.0 and Ruby < 3.0 + # 3.0.1 / 2022-03-09 * [#120](https://github.com/gocardless/coach/pull/120) Fixes missing method from middleware in Rails 7 diff --git a/Gemfile b/Gemfile index 6753ad6..249c26c 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,11 @@ source 'https://rubygems.org' gemspec -gem "rails", "~> #{ENV['RAILS_VERSION']}" if ENV["RAILS_VERSION"] +group :development, :test do + gem "gc_ruboconfig", "~> 5" + gem "pry", "~> 0.10" + gem "rails", "~> #{ENV['RAILS_VERSION']}" if ENV["RAILS_VERSION"] + gem "rspec", "~> 3.13" + gem "rspec-github", "~> 2.4.0" + gem "rspec-its", "~> 1.2" +end diff --git a/README.md b/README.md index f471d41..b1e3129 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Coach [![Gem Version](https://badge.fury.io/rb/coach.svg)](http://badge.fury.io/rb/coach) -[![CircleCI](https://circleci.com/gh/gocardless/coach.svg?style=svg)](https://circleci.com/gh/gocardless/coach) -[![Code Climate](https://codeclimate.com/github/gocardless/coach.svg)](https://codeclimate.com/github/gocardless/coach) Coach improves your controller code by encouraging: @@ -23,7 +21,7 @@ To get started, just add Coach to your `Gemfile`, and then run `bundle`: gem 'coach' ``` -Coach works with Ruby versions 2.6 and onwards. +Coach works with Ruby versions 3.1 and onwards. ## Coach by example @@ -278,13 +276,13 @@ end Default actions that conform to standard REST principles can be easily loaded, with the users resource being mapped to: -| Method | URL | Description | -|--------|------------------------------|------------------------------------------------| -| `GET` | `/users` | Index all users | -| `GET` | `/users/:id` | Get user by ID | -| `POST` | `/users` | Create new user | -| `PUT` | `/users/:id` | Update user details | -| `POST` | `/users/:id/actions/disable` | Custom action routed to the given path suffix | +| Method | URL | Description | +| ------ | ---------------------------- | --------------------------------------------- | +| `GET` | `/users` | Index all users | +| `GET` | `/users/:id` | Get user by ID | +| `POST` | `/users` | Create new user | +| `PUT` | `/users/:id` | Update user details | +| `POST` | `/users/:id/actions/disable` | Custom action routed to the given path suffix | If you're using Zeitwerk, you can pass the name of the module to `#draw`, instead of the module itself. @@ -328,14 +326,13 @@ middleware. Information for how to use `ActiveSupport`s notifications can be found [here](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html). - -| Event | Arguments | -|-------------------------------|------------------------------------------------------- | -| `start_handler.coach` | `event(:middleware, :request)` | -| `start_middleware.coach` | `event(:middleware, :request)` | -| `finish_middleware.coach` | `start`, `finish`, `id`, `event(:middleware, :request)`| -| `finish_handler.coach` | `start`, `finish`, `id`, `event(:middleware, :request)`| -| `request.coach` | `event` containing request data and benchmarking | +| Event | Arguments | +| ------------------------- | ------------------------------------------------------- | +| `start_handler.coach` | `event(:middleware, :request)` | +| `start_middleware.coach` | `event(:middleware, :request)` | +| `finish_middleware.coach` | `start`, `finish`, `id`, `event(:middleware, :request)` | +| `finish_handler.coach` | `start`, `finish`, `id`, `event(:middleware, :request)` | +| `request.coach` | `event` containing request data and benchmarking | Of special interest is `request.coach`, which publishes statistics on an entire middleware chain and request. This data is particularly useful for logging, and is our @@ -435,14 +432,14 @@ around 15s to 1s. While we think the commands we've already built are useful, we do have some ideas to go further, including: - - Better formatting of provider chains - - Outputting DOT format files to visualise with Graphviz - - Editor integrations (e.g. showing the provider chains when hovering a `requires` - statement) +- Better formatting of provider chains +- Outputting DOT format files to visualise with Graphviz +- Editor integrations (e.g. showing the provider chains when hovering a `requires` + statement) # License & Contributing -* Coach is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). -* Bug reports and pull requests are welcome on GitHub at https://github.com/gocardless/coach. +- Coach is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). +- Bug reports and pull requests are welcome on GitHub at https://github.com/gocardless/coach. GoCardless ♥ open source. If you do too, come [join us](https://gocardless.com/about/jobs). diff --git a/coach.gemspec b/coach.gemspec index 8eb93e3..fd1fce5 100644 --- a/coach.gemspec +++ b/coach.gemspec @@ -12,20 +12,15 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/gocardless/coach" spec.email = %w[developers@gocardless.com] spec.license = "MIT" - spec.required_ruby_version = ">= 2.6" + spec.required_ruby_version = ">= 3.1" spec.files = `git ls-files -z`.split("\x0") spec.require_paths = ["lib"] spec.executables = ["coach"] - spec.add_dependency "actionpack", ">= 4.2" - spec.add_dependency "activesupport", ">= 4.2" + spec.add_dependency "actionpack", ">= 7.0" + spec.add_dependency "activesupport", ">= 7.0" spec.add_dependency "commander", "~> 4.5" - spec.add_development_dependency "gc_ruboconfig", "~> 3.6" - spec.add_development_dependency "pry", "~> 0.10" - spec.add_development_dependency "rspec", "~> 3.2" - spec.add_development_dependency "rspec-github", "~> 2.4.0" - spec.add_development_dependency "rspec-its", "~> 1.2" spec.metadata["rubygems_mfa_required"] = "true" end diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md index 81203fd..26e17fe 100644 --- a/docs/COMPATIBILITY.md +++ b/docs/COMPATIBILITY.md @@ -2,9 +2,9 @@ Our goal as Coach maintainers is for the library to be compatible with all supported versions of Ruby and Rails. -Specifically, any CRuby/MRI version that has not received an End of Life notice ([e.g. this notice for Ruby 2.1](https://www.ruby-lang.org/en/news/2017/04/01/support-of-ruby-2-1-has-ended/)) is supported. Similarly, any version of Rails listed as currently supported on [this page](http://guides.rubyonrails.org/maintenance_policy.html) is one we aim to support in Coach. +Specifically, any CRuby/MRI version that has not reached (End of Life)[https://endoflife.date/ruby] is supported. Similarly, any version of Rails listed as currently supported on [this page](https://endoflife.date/rails) is one we aim to support in Coach. -To that end, [our build matrix](../.circleci/config.yml) includes all these versions. +To that end, [our build matrix](../.github/tests.yml) includes all these versions. Any time Coach doesn't work on a supported combination of Ruby and Rails, it's a bug, and can be reported [here](https://github.com/gocardless/coach/issues). diff --git a/lib/coach/cli/provider_finder.rb b/lib/coach/cli/provider_finder.rb index 64dfda7..f11f444 100644 --- a/lib/coach/cli/provider_finder.rb +++ b/lib/coach/cli/provider_finder.rb @@ -44,7 +44,7 @@ def find_chain raise err end - chains.map { |chain| chain.map(&:to_s).reverse }.to_set + chains.to_set { |chain| chain.map(&:to_s).reverse } end private diff --git a/lib/coach/notifications.rb b/lib/coach/notifications.rb index 365ea16..a18beb2 100644 --- a/lib/coach/notifications.rb +++ b/lib/coach/notifications.rb @@ -46,9 +46,7 @@ def subscribe! def unsubscribe! return unless active? - while @subscriptions.any? - ActiveSupport::Notifications.unsubscribe(@subscriptions.pop) - end + ActiveSupport::Notifications.unsubscribe(@subscriptions.pop) while @subscriptions.any? true end diff --git a/lib/coach/request_serializer.rb b/lib/coach/request_serializer.rb index 87559cb..bb4e833 100644 --- a/lib/coach/request_serializer.rb +++ b/lib/coach/request_serializer.rb @@ -61,7 +61,7 @@ def request_path end def filtered_headers - header_value_pairs = @request.filtered_env.map do |key, value| + header_value_pairs = @request.filtered_env.filter_map do |key, value| key = if RACK_UNPREFIXED_HEADERS.include?(key) "http_#{key.downcase}" elsif key.start_with?("HTTP_") @@ -69,7 +69,7 @@ def filtered_headers end [key, self.class.apply_header_rule(key, value)] if key - end.compact + end header_value_pairs.to_h end diff --git a/lib/coach/rspec.rb b/lib/coach/rspec.rb index d65a535..3c79a03 100644 --- a/lib/coach/rspec.rb +++ b/lib/coach/rspec.rb @@ -17,7 +17,9 @@ def call config[:callback].call if config.include?(:callback) log_metadata(**{ name.to_sym => true }) - response = [name + config.except(:callback).inspect.to_s] + # Emulate newer hash syntax for older Ruby versions + hash_response = config.except(:callback).map { |k, v| "#{k}: #{v}" }.join(", ") + response = [name + "{#{hash_response}}"] # Build up a list of middleware called, in the order they were called if next_middleware diff --git a/lib/coach/version.rb b/lib/coach/version.rb index ce1262c..10d048f 100644 --- a/lib/coach/version.rb +++ b/lib/coach/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Coach - VERSION = "3.0.1" + VERSION = "4.0.0" end diff --git a/spec/lib/coach/cli/provider_finder_spec.rb b/spec/lib/coach/cli/provider_finder_spec.rb index bdf6d71..b0f5875 100644 --- a/spec/lib/coach/cli/provider_finder_spec.rb +++ b/spec/lib/coach/cli/provider_finder_spec.rb @@ -201,7 +201,7 @@ expect(provider_finder.find_chain). to eq([ %w[FirstProvidingMiddleware FirstIntermediateMiddleware RequiringMiddleware], - %w[SecondProvidingMiddleware SecondIntermediateMiddleware RequiringMiddleware], # rubocop:disable Layout/LineLength + %w[SecondProvidingMiddleware SecondIntermediateMiddleware RequiringMiddleware], ].to_set) end end diff --git a/spec/lib/coach/handler_spec.rb b/spec/lib/coach/handler_spec.rb index 43147c4..2ad48e4 100644 --- a/spec/lib/coach/handler_spec.rb +++ b/spec/lib/coach/handler_spec.rb @@ -32,7 +32,7 @@ expect(a_double).to receive(:call) expect(b_double).to receive(:call) result = handler.call({}) - expect(result).to eq(%w[A{} B{} Terminal{:handler=>true}]) + expect(result).to eq(["A{}", "B{}", "Terminal{handler: true}"]) end context "with an invalid chain" do @@ -66,7 +66,7 @@ result = handler.call({}) - expect(result).to eq(%w[A{} B{} Terminal{:handler=>true}]) + expect(result).to eq(["A{}", "B{}", "Terminal{handler: true}"]) end context "with an invalid chain" do @@ -158,7 +158,7 @@ before { terminal_middleware.uses(middleware_a) } it "assembles a sequence including all middleware" do - expect(sequence).to match_array([middleware_a, terminal_middleware]) + expect(sequence).to contain_exactly(middleware_a, terminal_middleware) end end @@ -170,8 +170,7 @@ end it "assembles a sequence including all middleware" do - expect(sequence).to match_array([middleware_c, middleware_b, - middleware_a, terminal_middleware]) + expect(sequence).to contain_exactly(middleware_c, middleware_b, middleware_a, terminal_middleware) end end @@ -184,17 +183,15 @@ end it "only appears once" do - expect(sequence).to match_array([middleware_c, middleware_a, - middleware_b, terminal_middleware]) + expect(sequence).to contain_exactly(middleware_c, middleware_a, middleware_b, terminal_middleware) end context "with a different config" do before { middleware_b.uses(middleware_c, foo: "bar") } it "appears more than once" do - expect(sequence).to match_array([middleware_c, middleware_a, - middleware_c, middleware_b, - terminal_middleware]) + expect(sequence).to contain_exactly(middleware_c, middleware_a, middleware_c, middleware_b, + terminal_middleware) end end end @@ -218,7 +215,7 @@ it "sets up the chain correctly, calling each item in the correct order" do expect(handler.build_request_chain(sequence, {}).call). - to eq(%w[A{} B{:b=>true} Terminal{}]) + to eq(["A{}", "B{b: true}", "Terminal{}"]) end context "with inheriting config" do @@ -229,7 +226,7 @@ it "calls lambda with parent middlewares config" do expect(handler.build_request_chain(sequence, {}).call). - to eq(%w[A{} C{:b=>true} D{} B{:b=>true} Terminal{}]) + to eq(["A{}", "C{b: true}", "D{}", "B{b: true}", "Terminal{}"]) end end end diff --git a/spec/lib/coach/request_benchmark_spec.rb b/spec/lib/coach/request_benchmark_spec.rb index abcabb3..f10cf24 100644 --- a/spec/lib/coach/request_benchmark_spec.rb +++ b/spec/lib/coach/request_benchmark_spec.rb @@ -8,7 +8,7 @@ let(:base_time) { Time.now } - let(:start) { base_time + 0 } + let(:start) { base_time } let(:a_start) { base_time + 1 } let(:b_start) { base_time + 2 } let(:b_finish) { base_time + 3 } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 83de412..729f009 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,8 +5,8 @@ require "coach" require "coach/rspec" -Dir[Pathname(__FILE__).dirname.join("support", "**", "*.rb")]. - sort. +Dir[Pathname(__FILE__).dirname.join("support", "**", "*.rb")] + . each { |path| require path } RSpec.configure do |config|