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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd48a0c4c4c2948afced7735d5f699648c000e5bbbcc3e4e795dff0f44f15c04
4
- data.tar.gz: a6471b2358a4f1dae25251de3ae20665878e816e31a4f49687a6603673f69d82
3
+ metadata.gz: a6151bce2a0641c7d959760ed968af5ca1a6dde4961a3ac7d68182e57239ec86
4
+ data.tar.gz: ab80386265b6c8081bb00d1c7840860463098c0651d9022b6d129e175b489c28
5
5
  SHA512:
6
- metadata.gz: 873b8306a43cc09b1c524f1b4debdf4fd96bb47ab0ac8fd80a2b14287610b15ef596c13bc4ad65741507ec4a6be686f54763de0da84eefe7b28bcc14741a0be5
7
- data.tar.gz: d1395678197fd1f74847970e3145002f67c535ac6beb8912b7c7a7990da80cebfee67bf29992f48f0383b834d2d991a5a7726e9c513280ffd81a5fc31a9a157f
6
+ metadata.gz: 77c5b7f5cfcb95ca3136bccd62606beb0f215046900c0d1bc70568186c693eed2d7133d622d89b73bc7e8c3ba1ac417fd18e107bb263127d02891f9483cf9c2c
7
+ data.tar.gz: 04567d40823a197a7cc428d0146903e1254fd7c408b20a155e8c8243efa82e698cc61f9fd802a53027315c78d916636527d1a8ba14d8a776120d0aee4f3869d7
@@ -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: [ "main" ]
5
+ branches: ["main"]
7
6
  pull_request:
8
- branches: [ "main" ]
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: [ 'ruby' ]
27
-
22
+ language: ['ruby']
28
23
  steps:
29
- - name: Checkout repository
30
- uses: actions/checkout@v4
31
-
32
- - name: Initialize CodeQL
33
- uses: github/codeql-action/init@v3
34
- with:
35
- languages: ${{ matrix.language }}
36
-
37
- - name: Autobuild
38
- uses: github/codeql-action/autobuild@v3
39
-
40
- - name: Perform CodeQL Analysis
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
- release:
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: Version file changed
15
- id: version-file-changed
16
- uses: tj-actions/changed-files@v42
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
- if: ${{ github.event_name == 'workflow_dispatch' || steps.version-file-changed.outputs.any_changed == 'true' }}
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
- - cron: "30 1 * * *"
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 # for actions/stale to close stale issues
14
- pull-requests: write # for actions/stale to close stale PRs
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
- - uses: actions/stale@v9
18
- with:
19
- repo-token: ${{ secrets.GITHUB_TOKEN }}
20
- # Number of days of inactivity before an issue becomes stale
21
- days-before-stale: 60
22
- # Number of days of inactivity before a stale issue is closed
23
- days-before-close: 7
24
- # Issues with these labels will never be considered stale
25
- exempt-issue-labels: "on-hold,pinned,security"
26
- exempt-pr-labels: "on-hold,pinned,security"
27
- # Comment to post when marking an issue as stale.
28
- stale-issue-message: >
29
- This issue has been automatically marked as stale because it has not had
30
- recent activity. It will be closed if no further activity occurs. Thank you
31
- for your contributions.
32
- stale-pr-message: >
33
- This pull request has been automatically marked as stale because it has not had
34
- recent activity. It will be closed if no further activity occurs. Thank you
35
- for your contributions.
36
- # Label to use when marking an issue as stale
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'
@@ -10,11 +10,12 @@ jobs:
10
10
  test:
11
11
  strategy:
12
12
  matrix:
13
- ruby: ['2.7', '3.0', '3.1', '3.2', '3.3']
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.ruby == '2.7' && 'ubuntu-20.04' || 'ubuntu-latest' }}
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 migrations in phases in your [Ruby on Rails](https://rubyonrails.org/) application.
7
+ Handcuffs provides an easy way to run [Ruby on Rails](https://rubyonrails.org/) migrations in phases using a simple process:
8
8
 
9
- To configure, first create a handcuff initializer and define a configuration
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
- Then call `phase` from inside your migrations
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
- # db/migrate/20160318230933_add_on_sale_column.rb
54
+ # config/initializers/handcuffs.rb
23
55
 
24
- class AddOnSaleColumn < ActiveRecord::Migration
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 up
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/20160318230988_add_on_sale_index
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 up
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
- You can then run your migrations in phases using
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
- You can run all migrations using
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
- ```bash
72
- rake 'handcuffs:migrate[all]'
73
- ```
122
+ ### Running All Migrations
74
123
 
75
- This differs from running `rake db:migrate` in that migrations will be run in the _order that the phases are defined in the handcuffs config_.
124
+ In CI and local developement you may want to run all phases at one time.
76
125
 
77
- If you run a handcuffs rake task and any migration does not have a phase defined, an error will be raised before any migrations are run. To prevent this error, you can define a default phase for migrations that don't define one.
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
- gem install handcuffs
129
+ rake 'handcuffs:migrate[all]'
106
130
  ```
107
131
 
108
- ## Running specs
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
- 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`. You can then run the suite using `rake spec`
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://www.procore.com/images/procore_logo.png"
124
- alt="Procore Logo"
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.6 | :x: |
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", "~> 3.0"
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
- attr_accessor :phases
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
 
@@ -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.take_while { |defined_phase| defined_phase != attempted_phase }
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Handcuffs
2
- VERSION = "2.0.0"
4
+ VERSION = '2.1.0'
3
5
  end
@@ -1,56 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :handcuffs do
2
- task :migrate, [:phase] => :environment do |t,args|
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 |t,args|
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
- raise HandcuffsNotConfiguredError.new unless Handcuffs.config
18
- phase = phase.to_sym
19
- unless Handcuffs.config.phases.include?(phase) || phase == :all
20
- raise HandcuffsUnknownPhaseError.new(phase, Handcuffs.config.phases)
21
- end
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.prepend(PendingFilter)
27
- ActiveRecord::Migrator.extend(PhaseAccessor)
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.0.0
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: 2024-02-20 00:00:00.000000000 Z
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: '3.0'
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: '3.0'
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: []