A simple gem for one off tasks - the things that do not belong in a schema migration. Back-filling a column, correcting bad data, kicking off a one-time import: write it as a class, deploy it, and Patches runs it once and records that it ran.
Add the gem to your application's Gemfile:
gem 'patches'Then install the migration that records which patches have run:
bundle exec rake patches:install:migrations
bundle exec rake db:migrate
Generate a patch:
bundle exec rails g patches:patch PreferenceUpdate
Fill in run:
class PreferenceUpdate < Patches::Base
def run
User.where(preference: nil).update_all(preference: 'default')
end
endRun the pending ones - each patch runs once, and is recorded in
patches_patches:
bundle exec rake patches:run
rake patches:pending lists what has not run yet. Patches can run inline or in
the background via Sidekiq, across all tenants when you use Apartment, and
report to Slack. See docs/usage.md for configuration, tenants,
deployment and the Capistrano task.
| Minimum | |
|---|---|
| Ruby | 3.2 |
| Rails | 7.2 |
Every combination the gemspec allows is verified on each push and pull request - the floors and the matrix are the same set, which they were not before 4.0:
| Rails 7.2 | Rails 8.0 | Rails 8.1 | |
|---|---|---|---|
| Ruby 3.2 | ✅ | ✅ | ✅ |
| Ruby 3.3 | ✅ | ✅ | ✅ |
| Ruby 3.4 | ✅ | ✅ | ✅ |
| Ruby 4.0 | ✅ | ✅ | ✅ |
Ruby 4.0 is newer than all three Rails releases, so those cells record that Patches is verified on it, not that Rails claims support for it.
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.
The workers include Sidekiq::Job, so Sidekiq 6.3 or newer is required when the
integration is used - 6.2 and earlier predate that constant. 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) |
Slack notifications are optional and Patches does not install
slack-notifier - add gem 'slack-notifier' to your own Gemfile if you set
config.use_slack. CI runs a leg without it to keep that path honest.
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.
Everything 4.0 removes was deprecated in 3.7.0, so upgrade to that first and
clear its warnings - Patches.deprecator.behavior = :raise in an initializer
turns them into failures if you would rather find them that way.
| Change | What to do |
|---|---|
| Ruby 3.2 and Rails 7.2 are now the minimum | Upgrade, or stay on 3.7.x |
slack-notifier is no longer a dependency |
Add gem 'slack-notifier' to your Gemfile if you set config.use_slack |
| The Capistrano task is gone | Invoke bundle exec rake patches:run from your deployment process |
The workers include Sidekiq::Job |
Use Sidekiq 6.3 or newer - 7.x or 8.x are the supported lines |
Nothing else changed: patch classes, Patches::Base, the generator, the rake
tasks, the configuration and the patches_patches table are all as they were.
Patches.deprecator remains, so an initializer that configures it keeps
working.
In VS Code or Codespaces, open the repository in the dev container - it reuses
the app service from docker-compose.yml, so there is one definition of the
environment rather than two. Otherwise, from a shell:
docker compose build
docker compose run --rm app
The container's default command runs the specs. Any cell of the matrix above is reproducible in it: the Ruby version is a build argument, and the gem versions are passed through from your shell. The image is built with the default versions, so a non-default cell has to install them first:
docker compose build --build-arg RUBY_VERSION=3.1
RAILS_VERSION="~> 7.1.0" docker compose run --rm app \
sh -c 'bundle install && bundle exec rspec'
SIDEKIQ_VERSION=none docker compose run --rm app \
sh -c 'bundle install && bundle exec rspec --exclude-pattern "sidekiq/**/*_spec.rb"'
Or without Docker, if you have the Ruby you want on your path:
bundle install
bundle exec rspec
To install this gem onto your local machine, run bundle exec rake install.
Releases are published by GitHub Actions from a vX.Y.Z tag — see
RELEASING.md.
- Fork it ( https://github.com/rdytech/patches/fork )
- Create your feature branch (
git checkout -b feature/my-feature-name) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin feature/my-new-feature) - Create a new Pull Request against
develop
