handcuffs 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yaml +16 -0
- data/.github/workflows/auto-assign-author.yaml +5 -3
- data/.github/workflows/codeql.yaml +15 -23
- data/.github/workflows/release.yaml +16 -13
- data/.github/workflows/stale.yaml +23 -27
- data/.github/workflows/test.yaml +6 -8
- data/CHANGELOG.md +19 -6
- data/README.md +87 -57
- data/SECURITY.md +2 -2
- data/handcuffs.gemspec +2 -1
- data/lib/handcuffs/configuration.rb +4 -2
- data/lib/handcuffs/extensions.rb +4 -4
- data/lib/handcuffs/pending_filter_ext.rb +19 -0
- data/lib/handcuffs/phase_filter.rb +2 -2
- data/lib/handcuffs/phases.rb +41 -0
- data/lib/handcuffs/version.rb +3 -1
- data/lib/tasks/handcuffs.rake +26 -31
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6151bce2a0641c7d959760ed968af5ca1a6dde4961a3ac7d68182e57239ec86
|
4
|
+
data.tar.gz: ab80386265b6c8081bb00d1c7840860463098c0651d9022b6d129e175b489c28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77c5b7f5cfcb95ca3136bccd62606beb0f215046900c0d1bc70568186c693eed2d7133d622d89b73bc7e8c3ba1ac417fd18e107bb263127d02891f9483cf9c2c
|
7
|
+
data.tar.gz: 04567d40823a197a7cc428d0146903e1254fd7c408b20a155e8c8243efa82e698cc61f9fd802a53027315c78d916636527d1a8ba14d8a776120d0aee4f3869d7
|
data/.github/dependabot.yaml
CHANGED
@@ -9,6 +9,14 @@ updates:
|
|
9
9
|
- "dependabot"
|
10
10
|
- "dependencies"
|
11
11
|
- "github-actions"
|
12
|
+
commit-message:
|
13
|
+
prefix: "chore(deps)"
|
14
|
+
groups:
|
15
|
+
dependencies:
|
16
|
+
applies-to: version-updates
|
17
|
+
update-types:
|
18
|
+
- "minor"
|
19
|
+
- "patch"
|
12
20
|
- package-ecosystem: "bundler"
|
13
21
|
directory: /
|
14
22
|
schedule:
|
@@ -18,3 +26,11 @@ updates:
|
|
18
26
|
- "dependabot"
|
19
27
|
- "dependencies"
|
20
28
|
- "bundler"
|
29
|
+
commit-message:
|
30
|
+
prefix: "chore(deps)"
|
31
|
+
groups:
|
32
|
+
dependencies:
|
33
|
+
applies-to: version-updates
|
34
|
+
update-types:
|
35
|
+
- "minor"
|
36
|
+
- "patch"
|
@@ -1,13 +1,15 @@
|
|
1
1
|
name: 'Auto Author Assign'
|
2
|
-
|
3
2
|
on:
|
4
3
|
pull_request_target:
|
5
4
|
types: [opened, reopened]
|
6
|
-
|
5
|
+
permissions:
|
6
|
+
contents: read
|
7
7
|
jobs:
|
8
8
|
assign-author:
|
9
|
+
permissions:
|
10
|
+
pull-requests: write
|
9
11
|
runs-on: ubuntu-latest
|
10
12
|
steps:
|
11
|
-
- uses: toshimaru/auto-author-assign@v2.1.0
|
13
|
+
- uses: toshimaru/auto-author-assign@ebd30f10fb56e46eb0759a14951f36991426fed0 # v2.1.0
|
12
14
|
with:
|
13
15
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
@@ -1,15 +1,12 @@
|
|
1
1
|
name: "Custom CodeQL"
|
2
|
-
|
3
2
|
on:
|
4
3
|
workflow_dispatch:
|
5
4
|
push:
|
6
|
-
branches: [
|
5
|
+
branches: ["main"]
|
7
6
|
pull_request:
|
8
|
-
branches: [
|
9
|
-
|
7
|
+
branches: ["main"]
|
10
8
|
permissions:
|
11
9
|
contents: read
|
12
|
-
|
13
10
|
jobs:
|
14
11
|
analyze:
|
15
12
|
name: Analyze
|
@@ -19,25 +16,20 @@ jobs:
|
|
19
16
|
actions: read
|
20
17
|
contents: read
|
21
18
|
security-events: write
|
22
|
-
|
23
19
|
strategy:
|
24
20
|
fail-fast: false
|
25
21
|
matrix:
|
26
|
-
language: [
|
27
|
-
|
22
|
+
language: ['ruby']
|
28
23
|
steps:
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
uses: github/codeql-action/analyze@v3
|
42
|
-
with:
|
43
|
-
category: "/language:${{matrix.language}}"
|
24
|
+
- name: Checkout repository
|
25
|
+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
26
|
+
- name: Initialize CodeQL
|
27
|
+
uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3
|
28
|
+
with:
|
29
|
+
languages: ${{ matrix.language }}
|
30
|
+
- name: Autobuild
|
31
|
+
uses: github/codeql-action/autobuild@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3
|
32
|
+
- name: Perform CodeQL Analysis
|
33
|
+
uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3
|
34
|
+
with:
|
35
|
+
category: "/language:${{matrix.language}}"
|
@@ -6,35 +6,38 @@ on:
|
|
6
6
|
branches: [main]
|
7
7
|
workflow_dispatch: # allow manual deployment through GitHub Action UI
|
8
8
|
jobs:
|
9
|
-
|
9
|
+
version-check:
|
10
10
|
runs-on: ubuntu-latest
|
11
11
|
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
|
12
|
+
outputs:
|
13
|
+
changed: ${{ steps.check.outputs.any_changed }}
|
12
14
|
steps:
|
13
|
-
- uses: actions/checkout@v4
|
14
|
-
- name:
|
15
|
-
id:
|
16
|
-
uses: tj-actions/changed-files@
|
15
|
+
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
16
|
+
- name: Check if version has been updated
|
17
|
+
id: check
|
18
|
+
uses: tj-actions/changed-files@d6babd6899969df1a11d14c368283ea4436bca78 # v44
|
17
19
|
with:
|
18
20
|
files: lib/handcuffs/version.rb
|
21
|
+
release:
|
22
|
+
runs-on: ubuntu-latest
|
23
|
+
needs: version-check
|
24
|
+
if: ${{ github.event_name == 'workflow_dispatch' || needs.version-check.outputs.changed == 'true' }}
|
25
|
+
steps:
|
26
|
+
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
19
27
|
- name: Set up Ruby
|
20
|
-
|
21
|
-
uses: ruby/setup-ruby@v1
|
28
|
+
uses: ruby/setup-ruby@3783f195e29b74ae398d7caca108814bbafde90e # v1
|
22
29
|
with:
|
23
30
|
ruby-version: 3.2
|
24
31
|
bundler-cache: true
|
25
32
|
- name: Installing dependencies
|
26
|
-
if: ${{ github.event_name == 'workflow_dispatch' || steps.version-file-changed.outputs.any_changed == 'true' }}
|
27
33
|
run: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle
|
28
34
|
- name: Build gem file
|
29
|
-
if: ${{ github.event_name == 'workflow_dispatch' || steps.version-file-changed.outputs.any_changed == 'true' }}
|
30
35
|
run: bundle exec rake build
|
31
|
-
- uses: fac/ruby-gem-setup-credentials-action@v2
|
32
|
-
if: ${{ github.event_name == 'workflow_dispatch' || steps.version-file-changed.outputs.any_changed == 'true' }}
|
36
|
+
- uses: fac/ruby-gem-setup-credentials-action@5f62d5f2f56a11c7422a92f81fbb29af01e1c00f # v2
|
33
37
|
with:
|
34
38
|
user: ""
|
35
39
|
key: rubygems
|
36
40
|
token: ${{secrets.RUBY_GEMS_API_KEY}}
|
37
|
-
- uses: fac/ruby-gem-push-action@v2
|
38
|
-
if: ${{ github.event_name == 'workflow_dispatch' || steps.version-file-changed.outputs.any_changed == 'true' }}
|
41
|
+
- uses: fac/ruby-gem-push-action@81d77bf568ff6659d7fae0f0c5a036bb0aeacb1a # v2
|
39
42
|
with:
|
40
43
|
key: rubygems
|
@@ -2,37 +2,33 @@
|
|
2
2
|
name: Mark stale issues and pull requests
|
3
3
|
on:
|
4
4
|
schedule:
|
5
|
-
|
6
|
-
|
5
|
+
- cron: "30 1 * * *"
|
7
6
|
permissions:
|
8
7
|
contents: read
|
9
|
-
|
10
8
|
jobs:
|
11
9
|
stale:
|
12
10
|
permissions:
|
13
|
-
issues: write
|
14
|
-
pull-requests: write
|
11
|
+
issues: write # for actions/stale to close stale issues
|
12
|
+
pull-requests: write # for actions/stale to close stale PRs
|
15
13
|
runs-on: ubuntu-latest
|
16
14
|
steps:
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
stale-issue-label: 'no-issue-activity'
|
38
|
-
stale-pr-label: 'no-pr-activity'
|
15
|
+
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
|
16
|
+
with:
|
17
|
+
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
18
|
+
# Number of days of inactivity before an issue becomes stale
|
19
|
+
days-before-stale: 60
|
20
|
+
# Number of days of inactivity before a stale issue is closed
|
21
|
+
days-before-close: 7
|
22
|
+
# Issues with these labels will never be considered stale
|
23
|
+
exempt-issue-labels: "on-hold,pinned,security"
|
24
|
+
exempt-pr-labels: "on-hold,pinned,security"
|
25
|
+
# Comment to post when marking an issue as stale.
|
26
|
+
stale-issue-message: >
|
27
|
+
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
|
28
|
+
|
29
|
+
stale-pr-message: >
|
30
|
+
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
|
31
|
+
|
32
|
+
# Label to use when marking an issue as stale
|
33
|
+
stale-issue-label: 'no-issue-activity'
|
34
|
+
stale-pr-label: 'no-pr-activity'
|
data/.github/workflows/test.yaml
CHANGED
@@ -10,11 +10,12 @@ jobs:
|
|
10
10
|
test:
|
11
11
|
strategy:
|
12
12
|
matrix:
|
13
|
-
|
13
|
+
os: ['ubuntu-latest']
|
14
|
+
ruby: ['3.0', '3.1', '3.2', '3.3']
|
14
15
|
postgres: ['16-bullseye', '15-bullseye', '14-bullseye', '13-bullseye', '12-bullseye']
|
15
16
|
name: Ruby ${{ matrix.ruby }} - PostgreSQL ${{ matrix.postgres }}
|
16
17
|
# https://docs.github.com/en/actions/learn-github-actions/expressions#example
|
17
|
-
runs-on: ${{ matrix.
|
18
|
+
runs-on: ${{ matrix.os }}
|
18
19
|
services:
|
19
20
|
postgres:
|
20
21
|
image: postgres:${{ matrix.postgres }}
|
@@ -23,10 +24,7 @@ jobs:
|
|
23
24
|
POSTGRES_DB: handcuffs_test
|
24
25
|
POSTGRES_HOST_AUTH_METHOD: trust
|
25
26
|
options: >-
|
26
|
-
--health-cmd pg_isready
|
27
|
-
--health-interval 10s
|
28
|
-
--health-timeout 5s
|
29
|
-
--health-retries 5
|
27
|
+
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
30
28
|
ports:
|
31
29
|
- 5432:5432
|
32
30
|
env:
|
@@ -35,9 +33,9 @@ jobs:
|
|
35
33
|
RAILS_ENV: test
|
36
34
|
BUNDLER_VERSION: 2.4.22
|
37
35
|
steps:
|
38
|
-
- uses: actions/checkout@v4
|
36
|
+
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
39
37
|
- name: Set up Ruby ${{ matrix.ruby }}
|
40
|
-
uses: ruby/setup-ruby@v1
|
38
|
+
uses: ruby/setup-ruby@3783f195e29b74ae398d7caca108814bbafde90e # v1
|
41
39
|
with:
|
42
40
|
ruby-version: ${{ matrix.ruby }}
|
43
41
|
bundler: ${{ env.BUNDLER_VERSION }}
|
data/CHANGELOG.md
CHANGED
@@ -5,9 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [Unreleased]
|
8
|
+
## [Unreleased] [diff](https://github.com/procore-oss/handcuffs/compare/v2.1.0..main)
|
9
9
|
|
10
|
-
## 2.0.0
|
10
|
+
## 2.1.0 : 2025-01-24 [diff](https://github.com/procore-oss/handcuffs/compare/v2.0.0..v2.1.0)
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Ability to specify prerequisite phases in a non-linear order
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
- (internal) bumped rspec-rails gem version in development dependencies
|
19
|
+
- (internal) bumped minimum gem versions in test Rails app
|
20
|
+
- (internal) update github workflow
|
21
|
+
|
22
|
+
|
23
|
+
## 2.0.0 : 2024-02-20 [diff](https://github.com/procore-oss/handcuffs/compare/v1.4.1..v2.0.0)
|
24
|
+
|
25
|
+
### Removed
|
26
|
+
|
27
|
+
- **BREAKING CHANGE**: Removed support for Ruby < 2.7, Rails < 6.1, PostgreSQL < 12.
|
11
28
|
|
12
29
|
### Added
|
13
30
|
|
@@ -24,7 +41,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
24
41
|
- Updated Bundler to 2.4.22.
|
25
42
|
- Added Appraisal for dummy app testing.
|
26
43
|
- Moved repo to procore-oss
|
27
|
-
|
28
|
-
### Removed
|
29
|
-
|
30
|
-
- BREAKING CHANGE: Removed support for Ruby < 2.7, Rails < 6.1, PostgreSQL < 12.
|
data/README.md
CHANGED
@@ -2,59 +2,110 @@
|
|
2
2
|
|
3
3
|
[![Test](https://github.com/procore-oss/handcuffs/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/procore-oss/handcuffs/actions/workflows/test.yaml)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/handcuffs.svg)](https://badge.fury.io/rb/handcuffs)
|
5
|
-
[![Discord](https://img.shields.io/badge/Chat-EDEDED?logo=discord)](https://discord.gg/PbntEMmWws)
|
5
|
+
[![Discord](https://img.shields.io/badge/Chat-EDEDED?logo=discord)](https://discord.gg/PbntEMmWws)
|
6
6
|
|
7
|
-
Handcuffs provides an easy way to run
|
7
|
+
Handcuffs provides an easy way to run [Ruby on Rails](https://rubyonrails.org/) migrations in phases using a simple process:
|
8
8
|
|
9
|
-
|
9
|
+
1. Define a set of named phases in the order in which they should be run
|
10
|
+
2. Tag migrations with one of the defined phase names
|
11
|
+
3. Run migrations by phase at start, end or outside of application deployment
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'handcuffs'
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
```bash
|
24
|
+
bundle
|
25
|
+
```
|
26
|
+
|
27
|
+
Or install it directly on the current system using:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
gem install handcuffs
|
31
|
+
```
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
### Configuration
|
36
|
+
|
37
|
+
Create a handcuffs initializer and define the migration phases in the order in which they should be run. You should also define a default phase for pre-existing "untagged" migrations, or if you want the option to tag only custom phases.
|
38
|
+
|
39
|
+
The most basic configuration is an array of phase names, and using the first one as the default:
|
10
40
|
|
11
41
|
```ruby
|
12
42
|
# config/initializers/handcuffs.rb
|
13
43
|
|
14
44
|
Handcuffs.configure do |config|
|
45
|
+
# pre_restart migrations will/must run before post_restart migrations
|
15
46
|
config.phases = [:pre_restart, :post_restart]
|
47
|
+
config.default_phase = :pre_restart
|
16
48
|
end
|
17
49
|
```
|
18
50
|
|
19
|
-
|
51
|
+
If you have more complex or asynchrous workflows, you can use an alternate hash notation that allows prerequisite stages to be specified explicitly:
|
20
52
|
|
21
53
|
```ruby
|
22
|
-
#
|
54
|
+
# config/initializers/handcuffs.rb
|
23
55
|
|
24
|
-
|
56
|
+
Handcuffs.configure do |config|
|
57
|
+
config.phases = {
|
58
|
+
# Prevent running post_restart migrations if there are outstanding
|
59
|
+
# pre_restart migrations
|
60
|
+
post_restart: [:pre_restart],
|
61
|
+
# Require pre_restarts before data_migrations, but do not enforce ordering
|
62
|
+
# between data_migrations and post_restarts
|
63
|
+
data_migrations: [:pre_restart],
|
64
|
+
# pre_restarts have no prerequisite phases
|
65
|
+
pre_restart: []
|
66
|
+
}
|
67
|
+
end
|
68
|
+
```
|
69
|
+
The default phase order in this case is determined by [Tsort](https://github.com/ruby/tsort) (topological sort). In order to validate the configuration and expected phase order it is recommended that you check the phase configuration after any changes using the rake task:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
rake handcuffs:phase_order
|
73
|
+
```
|
74
|
+
|
75
|
+
This will display the default order in which phases will be run and list the prerequisites of each phase. It will raise an error if there are any circular dependencies or if any prerequisite is not a valid phase name.
|
76
|
+
|
77
|
+
### Tagging Migrations
|
78
|
+
|
79
|
+
Once configured, you can assign each migration to one of the defined phases using the `phase` setter method:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# db/migrate/20240318230933_add_on_sale_column.rb
|
83
|
+
|
84
|
+
class AddOnSaleColumn < ActiveRecord::Migration[7.0]
|
25
85
|
|
26
86
|
phase :pre_restart
|
27
87
|
|
28
|
-
def
|
88
|
+
def change
|
29
89
|
add_column :products, :on_sale, :boolean
|
30
90
|
end
|
31
|
-
|
32
|
-
def down
|
33
|
-
remove_column :products, :on_sale
|
34
|
-
end
|
35
|
-
|
36
91
|
end
|
37
92
|
```
|
38
93
|
|
39
94
|
```ruby
|
40
|
-
# db/migrate/
|
95
|
+
# db/migrate/20240318230988_add_on_sale_index
|
41
96
|
|
42
|
-
class AddOnSaleIndex < ActiveRecord::Migration
|
97
|
+
class AddOnSaleIndex < ActiveRecord::Migration[7.0]
|
43
98
|
|
44
99
|
phase :post_restart
|
45
100
|
|
46
|
-
def
|
101
|
+
def change
|
47
102
|
add_index :products, :on_sale, algorithm: :concurrently
|
48
103
|
end
|
49
|
-
|
50
|
-
def down
|
51
|
-
remove_index :products, :on_sale
|
52
|
-
end
|
53
|
-
|
54
104
|
end
|
55
105
|
```
|
106
|
+
### Running Migrations In Phases
|
56
107
|
|
57
|
-
|
108
|
+
After Handcuffs is configured and migrations are properly tagged, you can then run migrations in phases using the `handcuffs:migrate` rake task with the specific phase to be run:
|
58
109
|
|
59
110
|
```bash
|
60
111
|
rake 'handcuffs:migrate[pre_restart]'
|
@@ -66,53 +117,32 @@ or
|
|
66
117
|
rake 'handcuffs:migrate[post_restart]'
|
67
118
|
```
|
68
119
|
|
69
|
-
|
120
|
+
*Note:* If you run phases out of order, or attempt to run a phase before outstanding migrations with a prerequisite phase have been run, a `HandcuffsPhaseOutOfOrderError` will be raised.
|
70
121
|
|
71
|
-
|
72
|
-
rake 'handcuffs:migrate[all]'
|
73
|
-
```
|
122
|
+
### Running All Migrations
|
74
123
|
|
75
|
-
|
124
|
+
In CI and local developement you may want to run all phases at one time.
|
76
125
|
|
77
|
-
|
78
|
-
|
79
|
-
```ruby
|
80
|
-
# config/initializers/handcuffs.rb
|
81
|
-
|
82
|
-
Handcuffs.configure do |config|
|
83
|
-
config.phases = [:pre_restart, :post_restart]
|
84
|
-
config.default_phase = :pre_restart
|
85
|
-
end
|
86
|
-
```
|
87
|
-
|
88
|
-
## Installation
|
89
|
-
|
90
|
-
Add this line to your application's Gemfile:
|
91
|
-
|
92
|
-
```ruby
|
93
|
-
gem 'handcuffs'
|
94
|
-
```
|
95
|
-
|
96
|
-
And then execute:
|
97
|
-
|
98
|
-
```bash
|
99
|
-
bundle
|
100
|
-
```
|
101
|
-
|
102
|
-
Or install it yourself as:
|
126
|
+
Handcuffs offers a single command that will run all migrations in phases and in the configured order:
|
103
127
|
|
104
128
|
```bash
|
105
|
-
|
129
|
+
rake 'handcuffs:migrate[all]'
|
106
130
|
```
|
107
131
|
|
108
|
-
|
132
|
+
This differs from running `rake db:migrate` in that migrations will be run in batches corresponding to the _order that the phases are defined in the handcuffs config_. Again, you can use `rake handcuffs:phase_order` to preview the order ahead of time.
|
109
133
|
|
110
|
-
|
134
|
+
Of course, you can always run `rake db:migrate` at any time to run all migrations using the Rails default ordering and without regard to Handcuffs phase if you wish.
|
111
135
|
|
112
136
|
## Contributing
|
113
137
|
|
114
138
|
Bug reports and pull requests are welcome on GitHub at <https://github.com/procore-oss/handcuffs>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
115
139
|
|
140
|
+
## Running Tests Locally
|
141
|
+
|
142
|
+
The specs for handcuffs are in the dummy application at `/spec/dummy/spec`. The spec suite requires PostgreSQL. To run it you will have to set the environment variables `POSTGRES_DB_USERNAME` and `POSTGRES_DB_PASSWORD`.
|
143
|
+
|
144
|
+
We use [appraisal](https://github.com/thoughtbot/appraisal) to run our test suite against all Rails versions that we support, as a means of quickly identifying potential regressions. To do this locally, first run `bundle exec appraisal install` to ensure all required dependencies are setup, and then run `bundle exec appraisal rspec`.
|
145
|
+
|
116
146
|
## License
|
117
147
|
|
118
148
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -120,8 +150,8 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
120
150
|
## About Procore
|
121
151
|
|
122
152
|
<img
|
123
|
-
src="https://
|
124
|
-
alt="Procore
|
153
|
+
src="https://raw.githubusercontent.com/procore-oss/.github/main/procorelightlogo.png"
|
154
|
+
alt="Procore Open Source"
|
125
155
|
width="250px"
|
126
156
|
/>
|
127
157
|
|
data/SECURITY.md
CHANGED
@@ -6,11 +6,11 @@ Ruby versions that are currently being supported with security updates.
|
|
6
6
|
|
7
7
|
| Version | Supported |
|
8
8
|
| ------- | ------------------ |
|
9
|
-
| <=2.
|
10
|
-
| 2.7 | :white_check_mark: |
|
9
|
+
| <=2.7 | :x: |
|
11
10
|
| 3.0 | :white_check_mark: |
|
12
11
|
| 3.1 | :white_check_mark: |
|
13
12
|
| 3.2 | :white_check_mark: |
|
13
|
+
| 3.3 | :white_check_mark: |
|
14
14
|
|
15
15
|
## Reporting a Vulnerability
|
16
16
|
|
data/handcuffs.gemspec
CHANGED
@@ -13,6 +13,7 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.description = %q{Allows you to define a phase on Active Record migrations and provides rake tasks for running only migrations tagged with a certain phase}
|
14
14
|
spec.homepage = "https://github.com/procore-oss/handcuffs/"
|
15
15
|
spec.license = "MIT"
|
16
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.0')
|
16
17
|
|
17
18
|
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
19
|
# delete this section to allow pushing this gem to any host.
|
@@ -33,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
33
34
|
spec.add_development_dependency "pg"
|
34
35
|
spec.add_development_dependency "pry"
|
35
36
|
spec.add_development_dependency "pry-byebug"
|
36
|
-
spec.add_development_dependency "rspec-rails", "~>
|
37
|
+
spec.add_development_dependency "rspec-rails", "~> 6.1"
|
37
38
|
spec.add_development_dependency "simplecov"
|
38
39
|
|
39
40
|
spec.add_runtime_dependency "rails", ">= 6.1"
|
@@ -10,7 +10,7 @@ module Handcuffs
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class Configurator
|
13
|
-
|
13
|
+
attr_reader :phases
|
14
14
|
attr_accessor :default_phase
|
15
15
|
|
16
16
|
def initialize
|
@@ -18,7 +18,9 @@ module Handcuffs
|
|
18
18
|
@default_phase = nil
|
19
19
|
end
|
20
20
|
|
21
|
+
def phases=(phases)
|
22
|
+
@phases = Phases.new(phases)
|
23
|
+
end
|
21
24
|
end
|
22
|
-
|
23
25
|
end
|
24
26
|
|
data/lib/handcuffs/extensions.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Handcuffs
|
4
|
+
# Extended by ActiveRecord::Migrator in order to track the current phase
|
2
5
|
module Extensions
|
3
|
-
|
4
|
-
attr_reader :handcuffs_phase
|
6
|
+
attr_accessor :handcuffs_phase
|
5
7
|
|
6
8
|
def phase(phase)
|
7
9
|
@handcuffs_phase = phase
|
8
10
|
end
|
9
|
-
|
10
11
|
end
|
11
12
|
end
|
12
|
-
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Handcuffs
|
4
|
+
# PendingFilter is prepended to ActiveRecord::Migrator in the rake tasks
|
5
|
+
# in order to check the current phase before it is run
|
6
|
+
module PendingFilterExt
|
7
|
+
def runnable
|
8
|
+
attempted_phase = self.class.handcuffs_phase
|
9
|
+
if @direction == :up
|
10
|
+
Handcuffs::PhaseFilter.new(attempted_phase, @direction).filter(super)
|
11
|
+
else
|
12
|
+
phase_migrations = Handcuffs::PhaseFilter.new(attempted_phase, @direction).filter(migrations)
|
13
|
+
runnable = phase_migrations[start..finish]
|
14
|
+
runnable.pop if target
|
15
|
+
runnable.find_all { |m| ran?(m) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -41,7 +41,7 @@ class Handcuffs::PhaseFilter
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def check_order_up!(by_phase, defined_phases)
|
44
|
-
defined_phases.
|
44
|
+
defined_phases.prereqs(attempted_phase)
|
45
45
|
.detect { |defined_phase| by_phase.key?(defined_phase) }
|
46
46
|
.tap do |defined_phase|
|
47
47
|
raise HandcuffsPhaseOutOfOrderError.new(defined_phase, attempted_phase) if defined_phase
|
@@ -58,7 +58,7 @@ class Handcuffs::PhaseFilter
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def all_phases_by_configuration_order(by_phase, defined_phases)
|
61
|
-
defined_phases.reduce([]) do |acc, phase|
|
61
|
+
defined_phases.in_order.reduce([]) do |acc, phase|
|
62
62
|
acc | Array(by_phase[phase])
|
63
63
|
end.map { |mh| mh[:proxy] }
|
64
64
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tsort'
|
4
|
+
|
5
|
+
module Handcuffs
|
6
|
+
# Phases encapsulates the list of phases and any interdependencies
|
7
|
+
class Phases
|
8
|
+
def initialize(phases)
|
9
|
+
@phases = case phases
|
10
|
+
when Hash
|
11
|
+
phases.each_with_object({}) do |phase, acc|
|
12
|
+
acc[phase[0].to_sym] = Array(phase[1]).map(&:to_sym)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
# Assume each entry depends on all entries before it
|
16
|
+
phases.map(&:to_sym).each_with_object({}) do |phase, acc|
|
17
|
+
acc[phase] = phases.take_while { |defined_phase| defined_phase != phase }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_sentence
|
23
|
+
@phases.keys.to_sentence
|
24
|
+
end
|
25
|
+
|
26
|
+
def include?(phase)
|
27
|
+
@phases.include?(phase)
|
28
|
+
end
|
29
|
+
|
30
|
+
def in_order
|
31
|
+
TSort.tsort(
|
32
|
+
@phases.method(:each_key),
|
33
|
+
->(phase, &block) { @phases.fetch(phase).each(&block) }
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def prereqs(attempted_phase)
|
38
|
+
@phases.fetch(attempted_phase, [])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/handcuffs/version.rb
CHANGED
data/lib/tasks/handcuffs.rake
CHANGED
@@ -1,56 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
namespace :handcuffs do
|
2
|
-
task :migrate, [:phase] => :environment do |
|
4
|
+
task :migrate, [:phase] => :environment do |_t, args|
|
3
5
|
phase = setup(args, 'handcuffs:migrate')
|
4
6
|
patch_migrator!(phase)
|
5
7
|
run_task('db:migrate')
|
6
8
|
end
|
7
9
|
|
8
|
-
task :rollback, [:phase] => :environment do |
|
10
|
+
task :rollback, [:phase] => :environment do |_t, args|
|
9
11
|
phase = setup(args, 'handcuffs:rollback')
|
10
12
|
patch_migrator!(phase)
|
11
13
|
run_task('db:rollback')
|
12
14
|
end
|
13
15
|
|
16
|
+
task phase_order: :environment do
|
17
|
+
raise HandcuffsNotConfiguredError unless Handcuffs.config
|
18
|
+
|
19
|
+
puts 'Configured Handcuffs phases, in order, are:'
|
20
|
+
phases = Handcuffs.config.phases || return
|
21
|
+
|
22
|
+
phases.in_order.each_with_index do |phase, idx|
|
23
|
+
puts (idx + 1).to_s.rjust(3) + ". #{phase}, requires: #{phases.prereqs(phase).join(', ').presence || '(nothing)'}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
14
27
|
def setup(args, task)
|
15
|
-
phase = args.phase
|
28
|
+
phase = args.phase.presence&.to_sym
|
29
|
+
|
16
30
|
raise RequiresPhaseArgumentError.new(task) unless phase.present?
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
phase
|
31
|
+
|
32
|
+
raise HandcuffsNotConfiguredError unless Handcuffs.config
|
33
|
+
|
34
|
+
return phase if Handcuffs.config.phases.include?(phase) || phase == :all
|
35
|
+
|
36
|
+
raise HandcuffsUnknownPhaseError.new(phase, Handcuffs.config.phases)
|
23
37
|
end
|
24
38
|
|
25
39
|
def patch_migrator!(phase)
|
26
|
-
ActiveRecord::Migrator.
|
27
|
-
ActiveRecord::Migrator.
|
40
|
+
ActiveRecord::Migrator.extend(Handcuffs::Extensions)
|
41
|
+
ActiveRecord::Migrator.prepend(Handcuffs::PendingFilterExt)
|
28
42
|
ActiveRecord::Migrator.handcuffs_phase = phase
|
29
43
|
end
|
30
44
|
|
31
45
|
def run_task(name)
|
32
46
|
Rake::Task.clear # necessary to avoid tasks being loaded several times in dev mode
|
33
|
-
Rails.application.load_tasks
|
47
|
+
Rails.application.load_tasks
|
34
48
|
Rake::Task[name].reenable # in case you're going to invoke the same task second time.
|
35
49
|
Rake::Task[name].invoke
|
36
50
|
end
|
37
|
-
|
38
|
-
module PendingFilter
|
39
|
-
def runnable
|
40
|
-
attempted_phase = self.class.handcuffs_phase
|
41
|
-
if(@direction == :up)
|
42
|
-
Handcuffs::PhaseFilter.new(attempted_phase, @direction).filter(super)
|
43
|
-
else
|
44
|
-
phase_migrations = Handcuffs::PhaseFilter.new(attempted_phase, @direction).filter(migrations)
|
45
|
-
runnable = phase_migrations[start..finish]
|
46
|
-
runnable.pop if target
|
47
|
-
runnable.find_all { |m| ran?(m) }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
module PhaseAccessor
|
53
|
-
attr_accessor :handcuffs_phase
|
54
|
-
end
|
55
|
-
|
56
51
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: handcuffs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Procore Technologies, Inc.
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: appraisal
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '6.1'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '6.1'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: simplecov
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -152,7 +152,9 @@ files:
|
|
152
152
|
- lib/handcuffs/errors.rb
|
153
153
|
- lib/handcuffs/errors/configuration_block_missing_error.rb
|
154
154
|
- lib/handcuffs/extensions.rb
|
155
|
+
- lib/handcuffs/pending_filter_ext.rb
|
155
156
|
- lib/handcuffs/phase_filter.rb
|
157
|
+
- lib/handcuffs/phases.rb
|
156
158
|
- lib/handcuffs/railtie.rb
|
157
159
|
- lib/handcuffs/version.rb
|
158
160
|
- lib/tasks/handcuffs.rake
|
@@ -163,7 +165,7 @@ metadata:
|
|
163
165
|
allowed_push_host: https://rubygems.org
|
164
166
|
rubygems_mfa_required: 'true'
|
165
167
|
homepage_uri: https://github.com/procore-oss/handcuffs/
|
166
|
-
post_install_message:
|
168
|
+
post_install_message:
|
167
169
|
rdoc_options: []
|
168
170
|
require_paths:
|
169
171
|
- lib
|
@@ -171,7 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
171
173
|
requirements:
|
172
174
|
- - ">="
|
173
175
|
- !ruby/object:Gem::Version
|
174
|
-
version: '0'
|
176
|
+
version: '3.0'
|
175
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
178
|
requirements:
|
177
179
|
- - ">="
|
@@ -179,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
179
181
|
version: '0'
|
180
182
|
requirements: []
|
181
183
|
rubygems_version: 3.4.19
|
182
|
-
signing_key:
|
184
|
+
signing_key:
|
183
185
|
specification_version: 4
|
184
186
|
summary: A Ruby gem for running Active Record migrations in phases
|
185
187
|
test_files: []
|