handcuffs 1.4.1 → 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/CODEOWNERS +1 -0
- data/.github/ISSUE_TEMPLATE/bug.yml +66 -0
- data/.github/ISSUE_TEMPLATE/config.yml +1 -0
- data/.github/ISSUE_TEMPLATE/docs.yml +18 -0
- data/.github/ISSUE_TEMPLATE/feature-request.yml +36 -0
- data/.github/ISSUE_TEMPLATE/question-support.yml +18 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- data/.github/dependabot.yaml +36 -0
- data/.github/workflows/auto-assign-author.yaml +15 -0
- data/.github/workflows/codeql.yaml +35 -0
- data/.github/workflows/release.yaml +43 -0
- data/.github/workflows/stale.yaml +34 -0
- data/.github/workflows/test.yaml +52 -0
- data/.gitignore +2 -1
- data/Appraisals +6 -27
- data/CHANGELOG.md +43 -0
- data/CODE_OF_CONDUCT.md +117 -36
- data/CONTRIBUTING.md +37 -0
- data/README.md +92 -59
- data/SECURITY.md +19 -0
- data/gemfiles/rails_6.1.gemfile +1 -1
- data/gemfiles/{rails_5.2.gemfile → rails_7.0.gemfile} +1 -1
- data/gemfiles/{rails_6.gemfile → rails_7.1.gemfile} +1 -1
- data/handcuffs.gemspec +9 -5
- data/lib/handcuffs/configuration.rb +4 -2
- data/lib/handcuffs/errors.rb +1 -1
- 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 +50 -21
- data/.circleci/config.yml +0 -98
- data/gemfiles/rails_4.gemfile +0 -8
- data/gemfiles/rails_5.1.gemfile +0 -8
- data/gemfiles/rails_5.gemfile +0 -8
- /data/{LICENSE.txt → LICENSE.md} +0 -0
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# Contributing to Procore Projects
|
2
|
+
|
3
|
+
This document explains the common procedures expected by contributors while submitting code to Procore open source projects.
|
4
|
+
|
5
|
+
## Code of Conduct
|
6
|
+
|
7
|
+
Please read and abide by the [Code of Conduct](CODE_OF_CONDUCT.md)
|
8
|
+
|
9
|
+
## General workflow
|
10
|
+
|
11
|
+
Once a GitHub issue is accepted and assigned to you, please follow general workflow in order to submit your contribution:
|
12
|
+
|
13
|
+
1. Fork the target repository under your GitHub username.
|
14
|
+
2. Create a branch in your forked repository for the changes you are about to make.
|
15
|
+
3. Commit your changes in the branch you created in step 2. All commits need to be signed-off. Check the [legal](#legal) section bellow for more details.
|
16
|
+
4. Push your commits to your remote fork.
|
17
|
+
5. Create a Pull Request from your remote fork pointing to the HEAD branch (usually `main` branch) of the target repository.
|
18
|
+
6. Check the GitHub build and ensure that all checks are green.
|
19
|
+
|
20
|
+
## Legal
|
21
|
+
|
22
|
+
Procore projects use Developer Certificate of Origin ([DCO](https://GitHub.com/apps/dco/)).
|
23
|
+
|
24
|
+
Please sign-off your contributions by doing ONE of the following:
|
25
|
+
|
26
|
+
* Use `git commit -s ...` with each commit to add the sign-off or
|
27
|
+
* Manually add a `Signed-off-by: Your Name <your.email@example.com>` to each commit message.
|
28
|
+
|
29
|
+
The email address must match your primary GitHub email. You do NOT need cryptographic (e.g. gpg) signing.
|
30
|
+
|
31
|
+
* Use `git commit -s --amend ...` to add a sign-off to the latest commit, if you forgot.
|
32
|
+
|
33
|
+
*Note*: Some projects will provide specific configuration to ensure all commits are signed-off. Please check the project's documentation for more details.
|
34
|
+
|
35
|
+
## Tests
|
36
|
+
|
37
|
+
Make sure your changes are properly covered by automated tests. We aim to build an efficient test suite that is low cost to maintain and bring value to project. Prefer writing unit-tests over heavy end-to-end (e2e) tests. However, sometimes e2e tests are necessary. If you aren't sure, ask one of the maintainers about the requirements for your pull-request.
|
data/README.md
CHANGED
@@ -1,114 +1,147 @@
|
|
1
1
|
# Handcuffs
|
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
|
+
[![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)
|
4
6
|
|
5
|
-
Handcuffs provides an easy way to run migrations in phases
|
6
|
-
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:
|
7
8
|
|
8
|
-
|
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:
|
9
40
|
|
10
41
|
```ruby
|
11
42
|
# config/initializers/handcuffs.rb
|
12
43
|
|
13
44
|
Handcuffs.configure do |config|
|
45
|
+
# pre_restart migrations will/must run before post_restart migrations
|
14
46
|
config.phases = [:pre_restart, :post_restart]
|
47
|
+
config.default_phase = :pre_restart
|
15
48
|
end
|
16
49
|
```
|
17
50
|
|
18
|
-
|
51
|
+
If you have more complex or asynchrous workflows, you can use an alternate hash notation that allows prerequisite stages to be specified explicitly:
|
19
52
|
|
20
53
|
```ruby
|
21
|
-
#
|
54
|
+
# config/initializers/handcuffs.rb
|
22
55
|
|
23
|
-
|
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]
|
24
85
|
|
25
86
|
phase :pre_restart
|
26
87
|
|
27
|
-
def
|
88
|
+
def change
|
28
89
|
add_column :products, :on_sale, :boolean
|
29
90
|
end
|
30
|
-
|
31
|
-
def down
|
32
|
-
remove_column :products, :on_sale
|
33
|
-
end
|
34
|
-
|
35
91
|
end
|
36
92
|
```
|
37
93
|
|
38
94
|
```ruby
|
39
|
-
# db/migrate/
|
95
|
+
# db/migrate/20240318230988_add_on_sale_index
|
40
96
|
|
41
|
-
class AddOnSaleIndex < ActiveRecord::Migration
|
97
|
+
class AddOnSaleIndex < ActiveRecord::Migration[7.0]
|
42
98
|
|
43
99
|
phase :post_restart
|
44
100
|
|
45
|
-
def
|
101
|
+
def change
|
46
102
|
add_index :products, :on_sale, algorithm: :concurrently
|
47
103
|
end
|
48
|
-
|
49
|
-
def down
|
50
|
-
remove_index :products, :on_sale
|
51
|
-
end
|
52
|
-
|
53
104
|
end
|
54
105
|
```
|
106
|
+
### Running Migrations In Phases
|
107
|
+
|
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:
|
55
109
|
|
56
|
-
You can then run your migrations in phases using
|
57
110
|
```bash
|
58
111
|
rake 'handcuffs:migrate[pre_restart]'
|
59
112
|
```
|
113
|
+
|
60
114
|
or
|
115
|
+
|
61
116
|
```bash
|
62
117
|
rake 'handcuffs:migrate[post_restart]'
|
63
118
|
```
|
64
119
|
|
65
|
-
|
66
|
-
```bash
|
67
|
-
rake 'handcuffs:migrate[all]'
|
68
|
-
```
|
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.
|
69
121
|
|
70
|
-
|
71
|
-
the _order that the phases are defined in the handcuffs config_.
|
122
|
+
### Running All Migrations
|
72
123
|
|
73
|
-
|
74
|
-
defined, an error will be raised before any migrations are run. To prevent this
|
75
|
-
error, you can define a default phase for migrations that don't define one.
|
76
|
-
```ruby
|
77
|
-
# config/initializers/handcuffs.rb
|
124
|
+
In CI and local developement you may want to run all phases at one time.
|
78
125
|
|
79
|
-
Handcuffs
|
80
|
-
config.phases = [:pre_restart, :post_restart]
|
81
|
-
config.default_phase = :pre_restart
|
82
|
-
end
|
83
|
-
```
|
84
|
-
|
85
|
-
## Installation
|
86
|
-
|
87
|
-
Add this line to your application's Gemfile:
|
126
|
+
Handcuffs offers a single command that will run all migrations in phases and in the configured order:
|
88
127
|
|
89
|
-
```
|
90
|
-
|
128
|
+
```bash
|
129
|
+
rake 'handcuffs:migrate[all]'
|
91
130
|
```
|
92
131
|
|
93
|
-
|
94
|
-
|
95
|
-
$ bundle
|
96
|
-
|
97
|
-
Or install it yourself as:
|
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.
|
98
133
|
|
99
|
-
|
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.
|
100
135
|
|
101
|
-
##
|
136
|
+
## Contributing
|
102
137
|
|
103
|
-
|
104
|
-
spec suite requires PostgreSQL. To run it you will have to set the environment
|
105
|
-
variables `POSTGRES_DB_USERNAME` and `POSTGRES_DB_PASSWORD`. You can then run
|
106
|
-
the suite using `rake spec`
|
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.
|
107
139
|
|
108
|
-
##
|
140
|
+
## Running Tests Locally
|
109
141
|
|
110
|
-
|
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`.
|
111
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`.
|
112
145
|
|
113
146
|
## License
|
114
147
|
|
@@ -117,8 +150,8 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
117
150
|
## About Procore
|
118
151
|
|
119
152
|
<img
|
120
|
-
src="https://
|
121
|
-
alt="Procore
|
153
|
+
src="https://raw.githubusercontent.com/procore-oss/.github/main/procorelightlogo.png"
|
154
|
+
alt="Procore Open Source"
|
122
155
|
width="250px"
|
123
156
|
/>
|
124
157
|
|
data/SECURITY.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
## Supported Versions
|
4
|
+
|
5
|
+
Ruby versions that are currently being supported with security updates.
|
6
|
+
|
7
|
+
| Version | Supported |
|
8
|
+
| ------- | ------------------ |
|
9
|
+
| <=2.7 | :x: |
|
10
|
+
| 3.0 | :white_check_mark: |
|
11
|
+
| 3.1 | :white_check_mark: |
|
12
|
+
| 3.2 | :white_check_mark: |
|
13
|
+
| 3.3 | :white_check_mark: |
|
14
|
+
|
15
|
+
## Reporting a Vulnerability
|
16
|
+
|
17
|
+
Please click the `Report a vulnerability` button [here](https://github.com/procore-oss/handcuffs/security) to report a vulnerability.
|
18
|
+
|
19
|
+
A maintainer will respond to you as soon as possible and discuss the process to get the vulnerability fixed.
|
data/gemfiles/rails_6.1.gemfile
CHANGED
data/handcuffs.gemspec
CHANGED
@@ -6,18 +6,21 @@ require 'handcuffs/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "handcuffs"
|
8
8
|
spec.version = Handcuffs::VERSION
|
9
|
-
spec.authors = ["
|
10
|
-
spec.email = ["
|
9
|
+
spec.authors = ["Procore Technologies, Inc."]
|
10
|
+
spec.email = ["opensource@procore.com"]
|
11
11
|
|
12
12
|
spec.summary = %q{A Ruby gem for running Active Record migrations in phases}
|
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
|
-
spec.homepage = "https://github.com/procore/handcuffs/"
|
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.
|
19
20
|
if spec.respond_to?(:metadata)
|
20
21
|
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
23
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
24
|
else
|
22
25
|
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
26
|
end
|
@@ -31,7 +34,8 @@ Gem::Specification.new do |spec|
|
|
31
34
|
spec.add_development_dependency "pg"
|
32
35
|
spec.add_development_dependency "pry"
|
33
36
|
spec.add_development_dependency "pry-byebug"
|
34
|
-
spec.add_development_dependency "rspec-rails", "~>
|
37
|
+
spec.add_development_dependency "rspec-rails", "~> 6.1"
|
38
|
+
spec.add_development_dependency "simplecov"
|
35
39
|
|
36
|
-
spec.add_runtime_dependency "rails", ">=
|
40
|
+
spec.add_runtime_dependency "rails", ">= 6.1"
|
37
41
|
end
|
@@ -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/errors.rb
CHANGED
@@ -44,7 +44,7 @@
|
|
44
44
|
class HandcuffsPhaseOutOfOrderError < HandcuffsError
|
45
45
|
def initialize(not_run_phase, attempted_phase)
|
46
46
|
msg = <<-MESSAGE
|
47
|
-
|
47
|
+
You tried to run #{attempted_phase.to_s}, but #{not_run_phase.to_s} has not been run
|
48
48
|
MESSAGE
|
49
49
|
super msg
|
50
50
|
end
|
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
|