Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Release

on:
push:
tags: ["v*"]

permissions:
contents: write # rake release (via rubygems/release-gem) pushes the tag; no-op here since it's already pushed
id-token: write # RubyGems.org OIDC trusted publishing
packages: write # GitHub Packages push

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false

- name: Verify tag is reachable from develop
run: |
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/develop; then
echo "::error::$GITHUB_REF_NAME is not reachable from develop. Refusing to publish."
exit 1
fi

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
ruby-version: "3.4"

- name: Publish to RubyGems.org
uses: rubygems/release-gem@v1
90 changes: 83 additions & 7 deletions .github/workflows/specs.yml
Comment thread
schlick marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Run specs
on:
workflow_dispatch:
pull_request:
branches:
- develop
Expand All @@ -8,18 +9,93 @@ on:
- develop

jobs:
build:
# This patch release changes no version constraint, so the matrix records the
# range the gem is verified against rather than narrowing what it allows -
# older Ruby and Rails remain permitted, just untested. Rails 6.1/7.0 and
# Ruby 2.7 are left out deliberately: they are long past upstream support,
# and they were the cells that needed harness pins (Rails <= 7.0 pins its
# sqlite3 adapter to `~> 1.4`, Rails < 7.1 predates concurrent-ruby 1.3.5
# dropping a Logger constant) or a pinned runner image. The Gemfile still
# accepts those overrides for reproducing an old combination locally.
specs:
name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { ruby: '3.0', rails: '~> 7.1.0' }
Comment thread
schlick marked this conversation as resolved.
- { ruby: '3.1', rails: '~> 7.1.0' }
- { ruby: '3.1', rails: '~> 7.2.0' }
- { ruby: '3.2', rails: '~> 7.1.0' }
- { ruby: '3.2', rails: '~> 7.2.0' }
- { ruby: '3.2', rails: '~> 8.0.0' }
- { ruby: '3.2', rails: '~> 8.1.0' }
- { ruby: '3.3', rails: '~> 7.1.0' }
- { ruby: '3.3', rails: '~> 7.2.0' }
- { ruby: '3.3', rails: '~> 8.0.0' }
- { ruby: '3.3', rails: '~> 8.1.0' }
- { ruby: '3.4', rails: '~> 7.2.0' }
- { ruby: '3.4', rails: '~> 8.0.0' }
- { ruby: '3.4', rails: '~> 8.1.0' }
- { ruby: '4.0', rails: '~> 7.2.0' }
- { ruby: '4.0', rails: '~> 8.0.0' }
- { ruby: '4.0', rails: '~> 8.1.0' }
env:
RAILS_VERSION: ${{ matrix.rails }}
steps:
- uses: actions/checkout@v3
- name: Set up Ruby 2.7
uses: ruby/setup-ruby@v1
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run specs
run: bundle exec rspec

# Sidekiq is optional, so it gets its own axis rather than multiplying the
# matrix above. All three states a consumer can be in are covered: absent,
# present with strict argument checking (the Sidekiq 7+ default), and present
# with it disabled. The 6.5 leg covers the Sidekiq::Worker-era fallback path.
sidekiq:
name: Sidekiq ${{ matrix.sidekiq }} (strict args ${{ matrix.strict_args || 'absent' }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { sidekiq: '~> 6.5' }
- { sidekiq: '~> 7.0', strict_args: 'raise' }
- { sidekiq: '~> 7.0', strict_args: 'false' }
- { sidekiq: '~> 8.0', strict_args: 'raise' }
- { sidekiq: '~> 8.0', strict_args: 'false' }
- { sidekiq: 'none' }
env:
SIDEKIQ_VERSION: ${{ matrix.sidekiq }}
SIDEKIQ_STRICT_ARGS: ${{ matrix.strict_args }}
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
- name: Run specs
run: |
if [ "$SIDEKIQ_VERSION" = "none" ]; then
bundle exec rspec --exclude-pattern "sidekiq/**/*_spec.rb"
else
bundle exec rspec
fi

sonarqube:
name: SonarQube
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Run specs
env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
run: bundle exec rspec

- name: SonarQube Scan
Expand Down
71 changes: 71 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,77 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

Converted to GitHub Actions for CI

## [3.6.3] - 2026-09-09

Bug fixes only. No version constraint changes, no API changes and nothing
removed from the public interface, so anything that installed 3.6.2 installs
this. First step of a staged modernisation: declaring dependencies and adding
deprecations comes in 3.7.0, raising minimums in 4.0.0.

### Fixed
- Sidekiq 7 and 8 compatibility. Strict argument checking, enabled by default
from Sidekiq 7, rejected the job arguments the workers enqueued:
`patches:run` passed the runner class object itself and `application_version`
used a symbol key, so enqueueing raised `ArgumentError: Job arguments to
Patches::Worker must be native JSON types`. The task now enqueues the runner
class name and the key is a string. The serialised payload is byte-identical
to 3.6.2's - the class already reached Redis as `"Patches::Runner"` and the
symbol key as `"application_version"` - so in-flight jobs and rolling deploys
that mix old and new workers are unaffected
- The install migration inherited from a bare `ActiveRecord::Migration`, which
Rails has refused since 5.0 with "Directly inheriting from
ActiveRecord::Migration is not supported", so
`rake patches:install:migrations && rake db:migrate` failed on every Rails
since. It now declares `ActiveRecord::Migration[5.0]`, which Rails still
supports through 8.1, so no minimum moves
- `Patches.default_path` guarded on `defined?(:Rails)`, which tests a symbol
literal and is always true; outside Rails it raised `NameError` instead of
returning nil

### Added
- Specs for the install migration, which shipped with no coverage at all, and
for `Patches.sidekiq_job_module`, including its `Sidekiq::Worker` fallback
- A release workflow and `RELEASING.md`, both matching rdytech/superset-client:
pushing a `v`-prefixed tag publishes to RubyGems through trusted publishing,
and a tag not reachable from `develop` is refused. It also waits for the
version to appear on RubyGems, so a release that does not land fails loudly -
`3.6.1` was tagged and released on GitHub but never published, and nothing
reported it
- CI now runs 17 Ruby/Rails combinations (Ruby 3.0-4.0 against Rails 7.1-8.1)
in place of a single Ruby 2.7 cell, plus a leg for each Sidekiq state:
absent, 6.5, and 7.x/8.x with strict arguments both on and off. Ruby 2.7 and
Rails 6.1/7.0 are not exercised, being well past upstream support; they are
untested rather than blocked, since no constraint changed. README.md records
the verified range and how to check an older combination locally

### Changed
- The workers resolve their mixin through `Patches.sidekiq_job_module`, which
prefers `Sidekiq::Job` (Sidekiq 6.3+) and falls back to `Sidekiq::Worker`, so
no Sidekiq version loses support
- Development dependencies pruned to those actually used: `capybara`,
`factory_girl`, `timecop`, `generator_spec`, `byebug` and `database_cleaner`
were referenced by no spec, and `rspec-rails` was never required, so plain
`rspec` replaces it. `rails`, `sqlite3`, `sidekiq` and `concurrent-ruby` moved
to the Gemfile so CI can vary them; the stale `sidekiq ~> 3.4.1` pin is what
hid the strict argument incompatibility, and it also held `json` at `~> 1.0`
- Sidekiq-dependent specs moved under `spec/sidekiq/` so the suite runs with the
gem absent

### Known issues
- The install migration now declares `ActiveRecord::Migration[5.0]`, and
`ActiveRecord::Migration.[]` does not exist before Rails 5.0. A *fresh*
install on Rails 4.2 or earlier therefore needs `patches_patches` created by
hand - the table it wants is a `path` string, timestamps, and a unique index
on `path`. Existing installs are unaffected, having already copied the
migration into the host application. Those versions are far outside the range
CI verifies, and the previous file could not be loaded by any Rails from 5.0
on, so this trades an impossible install for an unlikely one

### Removed
- `lib/generators/patches.rb`, a dead duplicate of the patch generator that
wrote to `app/db/` from a template (`patch.erb`) that does not exist. Not
reachable through Rails' generator lookup, so not part of the public interface

## [3.6.2] - 2022-08-10

Fixes incorrect release - tag and published gem back in sync
Expand Down
36 changes: 35 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,38 @@ source 'https://rubygems.org'

gemspec

gem "rails", "~> 7.0"
# rails, sqlite3, concurrent-ruby and sidekiq are declared here rather than in
# the gemspec so that CI can vary them across the support matrix in README.md.
# Reproduce any CI cell locally by setting the same variables:
#
# RAILS_VERSION="~> 7.0.0" SQLITE3_VERSION="~> 1.4" bundle install
# bundle exec rspec
#
# An unset *or empty* value falls back to the default, because GitHub Actions
# passes an empty string for matrix keys a given cell doesn't define.
def version_requirement(name, default = nil)
value = ENV.fetch(name, '').strip
value.empty? ? default : value
end

# Unset, this resolves to the newest Rails the running Ruby allows. The gemspec
# floor stays at railties >= 3.2; see Compatibility in README.md for the range
# CI verifies.
gem 'rails', version_requirement('RAILS_VERSION', '>= 7.1')

# Rails pins its sqlite3 adapter: <= 7.0 needs `~> 1.4`, 7.1 accepts either,
# 8.0+ needs `>= 2.1`. Unset, bundler picks the newest 2.x.
gem 'sqlite3', version_requirement('SQLITE3_VERSION', '>= 1.4')

# Rails < 7.1 predates concurrent-ruby 1.3.5 removing the Logger constant that
# ActiveSupport::LoggerThreadSafeLevel expects.
concurrent_ruby = version_requirement('CONCURRENT_RUBY_VERSION')
gem 'concurrent-ruby', concurrent_ruby if concurrent_ruby

# Sidekiq is an optional integration, not a runtime dependency. SIDEKIQ_VERSION
# =none leaves it out entirely so the `defined?(Sidekiq)` guards get exercised.
# The library itself still supports older Sidekiq through
# Patches.sidekiq_job_module; this default only picks what the suite runs
# against by default.
sidekiq = version_requirement('SIDEKIQ_VERSION')
gem 'sidekiq', *[sidekiq].compact unless sidekiq == 'none'
99 changes: 98 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,98 @@ Please note the breaking change release around deployment. See [docs/usage.md](d

TL;DR You need to manually declare the patches task to run in your deploy.rb

## Compatibility

This release changes no version constraint: the gemspec still requires only
`railties >= 3.2` and sets no Ruby floor, so anything that installed 3.6.2
installs 3.6.3. The table is what CI verifies, which is narrower than what the
gem permits - older Ruby and Rails are untested here, not blocked.

| | Rails 7.1 | Rails 7.2 | Rails 8.0 | Rails 8.1 |
|---|---|---|---|---|
| **Ruby 3.0** | ✅ | | | |
| **Ruby 3.1** | ✅ | ✅ | | |
| **Ruby 3.2** | ✅ | ✅ | ✅ | ✅ |
| **Ruby 3.3** | ✅ | ✅ | ✅ | ✅ |
| **Ruby 3.4** | | ✅ | ✅ | ✅ |
| **Ruby 4.0** | | ✅ | ✅ | ✅ |

A blank cell is a pairing Rails itself does not support. Ruby 2.7 and Rails
6.1/7.0 are deliberately not exercised - they are well past upstream support -
though the gem still installs and, as of this release, passes on them.

Ruby 4.0 is newer than all of these Rails releases, so those cells record that
Patches is verified on it, not that Rails claims support for it.

### Sidekiq

Sidekiq is an **optional** integration, not a runtime dependency: Patches runs
each patch inline when Sidekiq is absent. Set `config.use_sidekiq = true` to run
them in the background - see [docs/usage.md](docs/usage.md).

The workers resolve their mixin at load time via `Patches.sidekiq_job_module`,
preferring `Sidekiq::Job` (the name Sidekiq has used since 6.3) and falling back
to `Sidekiq::Worker`, so older Sidekiq keeps working. CI covers each state a
consumer can be in:

| State | Covered by |
|---|---|
| Sidekiq not installed | A leg omitting the gem entirely, exercising the `defined?(Sidekiq)` guards |
| Installed, strict arguments on | Sidekiq 7.x and 8.x at their default (`:raise`) |
| Installed, strict arguments off | Sidekiq 7.x and 8.x with `Sidekiq.strict_args!(false)` |
| Older Sidekiq | A 6.5 leg, plus a spec covering the `Sidekiq::Worker` fallback |

### Running one combination locally

The Gemfile reads the same variables CI sets, so any cell above is reproducible.
Bundler re-evaluates the Gemfile on every invocation, so **export** them rather
than prefixing a single command — otherwise `bundle exec` silently resolves the
defaults and you test something other than what you intended:

```
export RAILS_VERSION="~> 8.0.0"

bundle install
bundle exec rspec
```

Without Sidekiq at all, which is what a consumer that does not use it gets:

```
export SIDEKIQ_VERSION=none

bundle install
bundle exec rspec --exclude-pattern "sidekiq/**/*_spec.rb"
```

With Sidekiq 7 and strict argument checking turned off:

```
export SIDEKIQ_VERSION="~> 7.0" SIDEKIQ_STRICT_ARGS=false

bundle install
bundle exec rspec
```

Rails 6.1 and 7.0 need two extra pins if you do want to check them, neither
caused by Patches — Rails <= 7.0 pins its sqlite3 adapter to `~> 1.4`, and Rails
< 7.1 predates `concurrent-ruby` 1.3.5 removing a `Logger` constant it relies
on:

```
export RAILS_VERSION="~> 6.1.0" \
SQLITE3_VERSION="~> 1.4" \
CONCURRENT_RUBY_VERSION="< 1.3.5"

bundle install
bundle exec rspec
```

`unset` the variables (or use a fresh shell) before running the default
combination again, and delete `test.db` when switching between them: the suite
creates `patches_patches` only when it is missing, so a database left by an
earlier run can mask ordering problems.

## Installation

Add this line to your application's Gemfile:
Expand All @@ -41,7 +133,12 @@ docker-compose build
docker-compose run app bundle exec rspec
```

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org/gems/patches).
To install this gem onto your local machine, run `bundle exec rake install`.

## Releasing

Releases are published by GitHub Actions from a `vX.Y.Z` tag — see
[RELEASING.md](RELEASING.md).

## Contributing

Expand Down
Loading
Loading