safe_migrations 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +124 -0
- data/README.md +99 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/fix_bundle +9 -0
- data/bin/setup +8 -0
- data/lib/safe_migrations/command_recorder_extension.rb +65 -0
- data/lib/safe_migrations/migration_helper.rb +72 -0
- data/lib/safe_migrations/version.rb +3 -0
- data/lib/safe_migrations.rb +14 -0
- data/safe_migrations.gemspec +47 -0
- metadata +137 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7650ddb667009ee039677b92f7bb557db0f92b9b12441ff96de8bf9c512d6bb3
|
|
4
|
+
data.tar.gz: c3248fee222eb0a13a332aa234719f16947f93e05a5cf4250f4f8a16f066ebb9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 47cdc90dee9cd19aa2ae5968c596cc8a77d0b30b7123693946bed97de9ed3083c8bfd2a66166ba0ac75c0d74a82418adb5788c5d28a20796e8503669da2c9399
|
|
7
|
+
data.tar.gz: 34f0f835de314a7311bebc89a48f413c58020d0f0ff90b4a738a26fe2e4d854816c9bb9af8029499d3f140a3c606a7b422ab0dce4f7763ae49a2101b38c97934
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.2.9
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2025-10-24
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Initial release of CHANGELOG.md, the all-in-one safe rails migrations.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- N/A (initial release)
|
|
15
|
+
|
|
16
|
+
[1.0.0]: https://github.com/moskvin/safe_migrations/releases/tag/v1.0.0
|
data/Gemfile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
|
6
|
+
|
|
7
|
+
# Specify your gem's dependencies in safe_migrations.gemspec
|
|
8
|
+
gemspec
|
|
9
|
+
|
|
10
|
+
group :test do
|
|
11
|
+
gem 'rspec', '~> 3.12'
|
|
12
|
+
gem 'sqlite3', '~> 2.7'
|
|
13
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
safe_migrations (1.0.0)
|
|
5
|
+
activerecord (>= 7.2)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
activemodel (8.1.0)
|
|
11
|
+
activesupport (= 8.1.0)
|
|
12
|
+
activerecord (8.1.0)
|
|
13
|
+
activemodel (= 8.1.0)
|
|
14
|
+
activesupport (= 8.1.0)
|
|
15
|
+
timeout (>= 0.4.0)
|
|
16
|
+
activesupport (8.1.0)
|
|
17
|
+
base64
|
|
18
|
+
bigdecimal
|
|
19
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
20
|
+
connection_pool (>= 2.2.5)
|
|
21
|
+
drb
|
|
22
|
+
i18n (>= 1.6, < 2)
|
|
23
|
+
json
|
|
24
|
+
logger (>= 1.4.2)
|
|
25
|
+
minitest (>= 5.1)
|
|
26
|
+
securerandom (>= 0.3)
|
|
27
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
28
|
+
uri (>= 0.13.1)
|
|
29
|
+
ast (2.4.3)
|
|
30
|
+
base64 (0.3.0)
|
|
31
|
+
bigdecimal (3.3.1)
|
|
32
|
+
concurrent-ruby (1.3.5)
|
|
33
|
+
connection_pool (2.5.4)
|
|
34
|
+
diff-lcs (1.6.2)
|
|
35
|
+
drb (2.2.3)
|
|
36
|
+
i18n (1.14.7)
|
|
37
|
+
concurrent-ruby (~> 1.0)
|
|
38
|
+
json (2.15.1)
|
|
39
|
+
language_server-protocol (3.17.0.5)
|
|
40
|
+
lint_roller (1.1.0)
|
|
41
|
+
logger (1.7.0)
|
|
42
|
+
mini_portile2 (2.8.9)
|
|
43
|
+
minitest (5.26.0)
|
|
44
|
+
parallel (1.27.0)
|
|
45
|
+
parser (3.3.9.0)
|
|
46
|
+
ast (~> 2.4.1)
|
|
47
|
+
racc
|
|
48
|
+
prism (1.6.0)
|
|
49
|
+
racc (1.8.1)
|
|
50
|
+
rainbow (3.1.1)
|
|
51
|
+
rake (13.3.0)
|
|
52
|
+
regexp_parser (2.11.3)
|
|
53
|
+
rspec (3.13.2)
|
|
54
|
+
rspec-core (~> 3.13.0)
|
|
55
|
+
rspec-expectations (~> 3.13.0)
|
|
56
|
+
rspec-mocks (~> 3.13.0)
|
|
57
|
+
rspec-core (3.13.6)
|
|
58
|
+
rspec-support (~> 3.13.0)
|
|
59
|
+
rspec-expectations (3.13.5)
|
|
60
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
61
|
+
rspec-support (~> 3.13.0)
|
|
62
|
+
rspec-mocks (3.13.6)
|
|
63
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
64
|
+
rspec-support (~> 3.13.0)
|
|
65
|
+
rspec-support (3.13.6)
|
|
66
|
+
rubocop (1.81.6)
|
|
67
|
+
json (~> 2.3)
|
|
68
|
+
language_server-protocol (~> 3.17.0.2)
|
|
69
|
+
lint_roller (~> 1.1.0)
|
|
70
|
+
parallel (~> 1.10)
|
|
71
|
+
parser (>= 3.3.0.2)
|
|
72
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
73
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
74
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
75
|
+
ruby-progressbar (~> 1.7)
|
|
76
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
77
|
+
rubocop-ast (1.47.1)
|
|
78
|
+
parser (>= 3.3.7.2)
|
|
79
|
+
prism (~> 1.4)
|
|
80
|
+
ruby-progressbar (1.13.0)
|
|
81
|
+
securerandom (0.4.1)
|
|
82
|
+
sqlite3 (2.7.4)
|
|
83
|
+
mini_portile2 (~> 2.8.0)
|
|
84
|
+
sqlite3 (2.7.4-aarch64-linux-gnu)
|
|
85
|
+
sqlite3 (2.7.4-aarch64-linux-musl)
|
|
86
|
+
sqlite3 (2.7.4-arm-linux-gnu)
|
|
87
|
+
sqlite3 (2.7.4-arm-linux-musl)
|
|
88
|
+
sqlite3 (2.7.4-arm64-darwin)
|
|
89
|
+
sqlite3 (2.7.4-x86-linux-gnu)
|
|
90
|
+
sqlite3 (2.7.4-x86-linux-musl)
|
|
91
|
+
sqlite3 (2.7.4-x86_64-darwin)
|
|
92
|
+
sqlite3 (2.7.4-x86_64-linux-gnu)
|
|
93
|
+
sqlite3 (2.7.4-x86_64-linux-musl)
|
|
94
|
+
timeout (0.4.3)
|
|
95
|
+
tzinfo (2.0.6)
|
|
96
|
+
concurrent-ruby (~> 1.0)
|
|
97
|
+
unicode-display_width (3.2.0)
|
|
98
|
+
unicode-emoji (~> 4.1)
|
|
99
|
+
unicode-emoji (4.1.0)
|
|
100
|
+
uri (1.0.4)
|
|
101
|
+
|
|
102
|
+
PLATFORMS
|
|
103
|
+
aarch64-linux-gnu
|
|
104
|
+
aarch64-linux-musl
|
|
105
|
+
arm-linux-gnu
|
|
106
|
+
arm-linux-musl
|
|
107
|
+
arm64-darwin
|
|
108
|
+
ruby
|
|
109
|
+
x86-linux-gnu
|
|
110
|
+
x86-linux-musl
|
|
111
|
+
x86_64-darwin
|
|
112
|
+
x86_64-linux-gnu
|
|
113
|
+
x86_64-linux-musl
|
|
114
|
+
|
|
115
|
+
DEPENDENCIES
|
|
116
|
+
bundler (~> 2.7)
|
|
117
|
+
rake (~> 13.0)
|
|
118
|
+
rspec (~> 3.12, ~> 3.0)
|
|
119
|
+
rubocop
|
|
120
|
+
safe_migrations!
|
|
121
|
+
sqlite3 (~> 2.7)
|
|
122
|
+
|
|
123
|
+
BUNDLED WITH
|
|
124
|
+
2.7.2
|
data/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# SafeMigrations
|
|
2
|
+
|
|
3
|
+
G'day, mate! Welcome to **SafeMigrations**, a ripper of a gem that makes your Rails migrations as safe as a kangaroo in the outback. With `safe_` prefixed methods, this gem ensures your database schema changes are idempotent—no dramas if you run 'em twice. Built to play nice with Rails' `CommandRecorder`, it auto-reverses your migrations in the `change` method, so you can crack on with building your app without worrying about dodgy rollbacks.
|
|
4
|
+
|
|
5
|
+
## Why SafeMigrations?
|
|
6
|
+
|
|
7
|
+
Tired of migrations chucking a wobbly when tables or columns already exist? SafeMigrations wraps Rails migration methods with `safe_` prefixes (e.g., `safe_create_table`, `safe_add_column`) to check for existing schema elements before making changes. It hooks into Rails' `CommandRecorder` for automatic reversals in `change`-based migrations, keeping your database fair dinkum.
|
|
8
|
+
**Warning**: Since it uses `CommandRecorder`, rollbacks may affect pre-existing schema elements—use with care or chuck in a `reversible` block for complex stuff.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Idempotent Migrations**: `safe_create_table`, `safe_add_column`, `safe_add_index`, and more only run if needed.
|
|
13
|
+
- **Auto-Reversal**: Integrates with Rails' `CommandRecorder` to invert `safe_` methods (e.g., `safe_create_table` → `safe_drop_table`) in `change` rollbacks.
|
|
14
|
+
- **Rails 7.2+ Ready**: Built for ActiveRecord 7.2, with support for modern Ruby 3.2.
|
|
15
|
+
- **Aussie Spirit**: Crafted with a bit of outback grit to keep your migrations smooth as a cold one on a summer arvo.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add this line to your application's Gemfile:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem 'safe_migrations'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then give it a burl:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
$ bundle install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or install it yourself faster than a dingo nicks your lunch:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
$ gem install safe_migrations
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Use `safe_` methods in your Rails migrations’ `change` block, and let Rails’ `CommandRecorder` handle the rollback. Here’s a cracking example:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
class CreateUsers < ActiveRecord::Migration[7.2]
|
|
43
|
+
def change
|
|
44
|
+
safe_create_table(:users) do |t|
|
|
45
|
+
t.string :name
|
|
46
|
+
t.string :email
|
|
47
|
+
t.timestamps
|
|
48
|
+
end
|
|
49
|
+
safe_add_column(:users, :role, :string, default: 'user')
|
|
50
|
+
safe_add_index(:users, :email, unique: true)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- **Running**: `rails db:migrate` creates the table, column, and index only if they don’t exist.
|
|
56
|
+
- **Rolling Back**: `rails db:rollback` inverts to `safe_drop_table`, `safe_remove_column`, `safe_remove_index` (in reverse order).
|
|
57
|
+
- **Heads Up**: If a table exists before the migration, `safe_create_table` skips it, but rollback may still call `safe_drop_table`. For critical cases, use `reversible`:
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
No worries, mate! To get started:
|
|
62
|
+
|
|
63
|
+
1. Clone the repo and run `bin/setup` to install dependencies.
|
|
64
|
+
2. Run `rake spec` to give the tests a fair go.
|
|
65
|
+
3. Use `bin/console` for an interactive prompt to muck around with the code.
|
|
66
|
+
|
|
67
|
+
To install the gem locally:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
$ bundle exec rake install
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
To release a new version:
|
|
74
|
+
1. Update the version in `lib/safe_migrations/version.rb`.
|
|
75
|
+
2. Run `bundle exec rake release` to tag the version, push to Git, and ship the gem to [RubyGems.org](https://rubygems.org).
|
|
76
|
+
|
|
77
|
+
## Testing
|
|
78
|
+
|
|
79
|
+
The gem includes an RSpec suite to ensure your migrations are as solid as Uluru:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
$ rake spec
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Tests check idempotency (re-running migrations) and rollback behavior. Note the `CommandRecorder` limitation: pre-existing tables may get dropped on rollback. See `spec/migration_spec.rb` for details.
|
|
86
|
+
|
|
87
|
+
## Contributing
|
|
88
|
+
|
|
89
|
+
Got a ripper idea or found a bug? Chuck us a pull request or bug report on GitHub at https://github.com/moskvin/safe_migrations. We’re keen as mustard to make this gem top-notch!
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
This gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
94
|
+
|
|
95
|
+
## Acknowledgements
|
|
96
|
+
|
|
97
|
+
Built with a nod to the Aussie spirit—because who doesn’t love a bit of fair dinkum coding? Cheers to the Rails community for the migration framework that makes this possible.
|
|
98
|
+
|
|
99
|
+
Crack on and make your migrations safe as, mate!
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'safe_migrations'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/fix_bundle
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
version=${1:-2.7.2}
|
|
4
|
+
echo "Bundle will be updated to" "$version"
|
|
5
|
+
|
|
6
|
+
# Fix the bundler version in the Gemfile.lock and remove all platforms except the ruby one
|
|
7
|
+
bundle _"${version}"_ update --bundler \
|
|
8
|
+
&& bundle lock --add-platform ruby x86_64-linux x86_64-darwin-19 x86_64-darwin-20 x86_64-darwin-22 x86_64-darwin-23 arm64-darwin-22 arm64-darwin-23 arm64-darwin-24 \
|
|
9
|
+
&& bundle lock --remove-platform x86_64-linux x86_64-darwin-19 x86_64-darwin-20 x86_64-darwin-22 x86_64-darwin-23 arm64-darwin-22 arm64-darwin-23 arm64-darwin-24
|
data/bin/setup
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SafeMigrations
|
|
4
|
+
module CommandRecorderExtension
|
|
5
|
+
SAFE_METHODS = %i[
|
|
6
|
+
safe_create_table
|
|
7
|
+
safe_drop_table
|
|
8
|
+
safe_add_column
|
|
9
|
+
safe_remove_column
|
|
10
|
+
safe_rename_column
|
|
11
|
+
safe_add_index
|
|
12
|
+
safe_remove_index
|
|
13
|
+
safe_add_foreign_key
|
|
14
|
+
safe_remove_foreign_key
|
|
15
|
+
safe_add_column_and_index
|
|
16
|
+
safe_remove_column_and_index
|
|
17
|
+
safe_change_column
|
|
18
|
+
safe_change_column_null
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
SAFE_REVERSIBLE_MAP = {
|
|
22
|
+
safe_create_table: :safe_drop_table,
|
|
23
|
+
safe_add_column: :safe_remove_column,
|
|
24
|
+
safe_remove_column: :safe_add_column,
|
|
25
|
+
safe_add_index: :safe_remove_index,
|
|
26
|
+
safe_remove_index: :safe_add_index,
|
|
27
|
+
safe_add_foreign_key: :safe_remove_foreign_key,
|
|
28
|
+
safe_remove_foreign_key: :safe_add_foreign_key,
|
|
29
|
+
safe_add_column_and_index: :safe_remove_column_and_index,
|
|
30
|
+
safe_remove_column_and_index: :safe_add_column_and_index,
|
|
31
|
+
safe_change_column_null: :safe_change_column_null
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
def self.apply
|
|
35
|
+
ActiveRecord::Migration::CommandRecorder.class_eval do
|
|
36
|
+
SAFE_METHODS.each do |method|
|
|
37
|
+
define_method(:"#{method}") do |*args, &block|
|
|
38
|
+
record(:"#{method}", args, &block)
|
|
39
|
+
end
|
|
40
|
+
ruby2_keywords(method)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
SAFE_REVERSIBLE_MAP.each do |cmd, inv|
|
|
44
|
+
define_method(:"invert_#{cmd}") do |args, &block|
|
|
45
|
+
[inv, args, block]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Special case for safe_rename_column (swap column names)
|
|
50
|
+
def invert_safe_rename_column(args)
|
|
51
|
+
table, old_col, new_col = args
|
|
52
|
+
[:safe_rename_column, [table, new_col, old_col]]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Special case for safe_change_column (needs type tracking)
|
|
56
|
+
def invert_safe_change_column(args)
|
|
57
|
+
table, column, type, options = args
|
|
58
|
+
# If column existed, invert to original type (not tracked, so warn)
|
|
59
|
+
# If column was added, invert to remove
|
|
60
|
+
[:safe_remove_column, [table, column, type, options]]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module SafeMigrations
|
|
2
|
+
module MigrationHelper
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.send(:include, InstanceMethods)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module InstanceMethods
|
|
8
|
+
def safe_add_column(table, column, type, **options)
|
|
9
|
+
return unless table_exists?(table)
|
|
10
|
+
return if column_exists?(table, column)
|
|
11
|
+
|
|
12
|
+
add_column(table, column, type, **options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def safe_remove_column(table, column, type = nil, **options)
|
|
16
|
+
table_exists?(table) && column_exists?(table, column) && remove_column(table, column, type, **options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def safe_rename_column(table_name, column_name, new_column_name)
|
|
20
|
+
column_exists?(table_name, column_name) && !column_exists?(table_name, new_column_name) && rename_column(table_name, column_name, new_column_name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def safe_add_index(table, column, **options)
|
|
24
|
+
return unless table_exists?(table)
|
|
25
|
+
return if index_exists?(table, column, **options)
|
|
26
|
+
|
|
27
|
+
add_index(table, column, **options)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def safe_remove_index(table, column_name = nil, **options)
|
|
31
|
+
index_exists?(table, column_name, **options) && remove_index(table, column_name, **options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def safe_add_column_and_index(table, column, type, column_options = {}, index_options = {})
|
|
35
|
+
safe_add_column(table, column, type, **column_options)
|
|
36
|
+
safe_add_index(table, column, **index_options)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def safe_remove_column_and_index(table, column, column_options = {}, index_options = {})
|
|
40
|
+
safe_remove_index(table, column, **index_options)
|
|
41
|
+
safe_remove_column(table, column, **column_options)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def safe_change_column(table, column, type, **options)
|
|
45
|
+
column_exists?(table, column) ? change_column(table, column, type, **options) : add_column(table, column, type, **options)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def safe_change_column_null(table, column, null, default = nil)
|
|
49
|
+
column_exists?(table, column) && change_column_null(table, column, null, default)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def safe_create_table(table, **options, &block)
|
|
53
|
+
create_table(table, **options, &block) unless table_exists?(table)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def safe_drop_table(table)
|
|
57
|
+
drop_table(table, if_exists: true) if table_exists?(table)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def safe_add_foreign_key(from_table, to_table, **options)
|
|
61
|
+
return unless table_exists?(from_table) && table_exists?(to_table)
|
|
62
|
+
return if foreign_key_exists?(from_table, to_table, **options)
|
|
63
|
+
|
|
64
|
+
add_foreign_key(from_table, to_table, **options)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def safe_remove_foreign_key(from_table, to_table, **options)
|
|
68
|
+
table_exists?(from_table) && table_exists?(to_table) && foreign_key_exists?(from_table, to_table, **options) && remove_foreign_key(from_table, to_table, **options)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_record'
|
|
4
|
+
require 'safe_migrations/version'
|
|
5
|
+
require 'safe_migrations/migration_helper'
|
|
6
|
+
require 'safe_migrations/command_recorder_extension'
|
|
7
|
+
|
|
8
|
+
module SafeMigrations
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
# Your code goes here...
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.include(SafeMigrations::MigrationHelper)
|
|
14
|
+
SafeMigrations::CommandRecorderExtension.apply
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'safe_migrations/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'safe_migrations'
|
|
9
|
+
spec.version = SafeMigrations::VERSION
|
|
10
|
+
spec.authors = ['Nikolay Moskvin']
|
|
11
|
+
spec.email = ['nikolay.moskvin@gmail.com']
|
|
12
|
+
|
|
13
|
+
spec.summary = 'Idempotent migration helpers for Rails with safe, reversible operations.'
|
|
14
|
+
spec.description = "SafeMigrations enhances Rails migrations with safe_ prefixed methods that prevent errors by checking for existing schema elements before execution. It integrates with Rails' CommandRecorder for automatic reversal in change-based migrations, ensuring safe and reliable database schema management."
|
|
15
|
+
spec.homepage = 'https://github.com/moskvin/safe_migrations'
|
|
16
|
+
|
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
19
|
+
if spec.respond_to?(:metadata)
|
|
20
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
21
|
+
|
|
22
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
23
|
+
spec.metadata['source_code_uri'] = 'https://github.com/moskvin/safe_migrations'
|
|
24
|
+
spec.metadata['changelog_uri'] = 'https://github.com/moskvin/safe_migrations/CHANGELOG.md'
|
|
25
|
+
else
|
|
26
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
|
27
|
+
'public gem pushes.'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Specify which files should be added to the gem when it is released.
|
|
31
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
32
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
33
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
34
|
+
end
|
|
35
|
+
spec.bindir = 'exe'
|
|
36
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
37
|
+
spec.require_paths = ['lib']
|
|
38
|
+
|
|
39
|
+
spec.required_ruby_version = '>= 3.2'
|
|
40
|
+
|
|
41
|
+
spec.add_dependency 'activerecord', '>= 7.2'
|
|
42
|
+
|
|
43
|
+
spec.add_development_dependency 'bundler', '~> 2.7'
|
|
44
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
45
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
46
|
+
spec.add_development_dependency 'rubocop'
|
|
47
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: safe_migrations
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nikolay Moskvin
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-10-24 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activerecord
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.2'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.2'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.7'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.7'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '13.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '13.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rubocop
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
description: SafeMigrations enhances Rails migrations with safe_ prefixed methods
|
|
84
|
+
that prevent errors by checking for existing schema elements before execution. It
|
|
85
|
+
integrates with Rails' CommandRecorder for automatic reversal in change-based migrations,
|
|
86
|
+
ensuring safe and reliable database schema management.
|
|
87
|
+
email:
|
|
88
|
+
- nikolay.moskvin@gmail.com
|
|
89
|
+
executables: []
|
|
90
|
+
extensions: []
|
|
91
|
+
extra_rdoc_files: []
|
|
92
|
+
files:
|
|
93
|
+
- ".gitignore"
|
|
94
|
+
- ".rspec"
|
|
95
|
+
- ".rubocop.yml"
|
|
96
|
+
- ".ruby-version"
|
|
97
|
+
- ".travis.yml"
|
|
98
|
+
- CHANGELOG.md
|
|
99
|
+
- Gemfile
|
|
100
|
+
- Gemfile.lock
|
|
101
|
+
- README.md
|
|
102
|
+
- Rakefile
|
|
103
|
+
- bin/console
|
|
104
|
+
- bin/fix_bundle
|
|
105
|
+
- bin/setup
|
|
106
|
+
- lib/safe_migrations.rb
|
|
107
|
+
- lib/safe_migrations/command_recorder_extension.rb
|
|
108
|
+
- lib/safe_migrations/migration_helper.rb
|
|
109
|
+
- lib/safe_migrations/version.rb
|
|
110
|
+
- safe_migrations.gemspec
|
|
111
|
+
homepage: https://github.com/moskvin/safe_migrations
|
|
112
|
+
licenses: []
|
|
113
|
+
metadata:
|
|
114
|
+
allowed_push_host: https://rubygems.org
|
|
115
|
+
homepage_uri: https://github.com/moskvin/safe_migrations
|
|
116
|
+
source_code_uri: https://github.com/moskvin/safe_migrations
|
|
117
|
+
changelog_uri: https://github.com/moskvin/safe_migrations/CHANGELOG.md
|
|
118
|
+
post_install_message:
|
|
119
|
+
rdoc_options: []
|
|
120
|
+
require_paths:
|
|
121
|
+
- lib
|
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: '3.2'
|
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
requirements: []
|
|
133
|
+
rubygems_version: 3.4.19
|
|
134
|
+
signing_key:
|
|
135
|
+
specification_version: 4
|
|
136
|
+
summary: Idempotent migration helpers for Rails with safe, reversible operations.
|
|
137
|
+
test_files: []
|