strong_migrations 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a0871a07d2e13749e0f984373c303dd837d7ea95
4
+ data.tar.gz: 146fa1034aa8c8c8d5094b0bffa5079e728e6c54
5
+ SHA512:
6
+ metadata.gz: 4743a779ecede6c6fca241a2f7c0eee01e46c24c79b5af071f850fb4ced23c852f9390f49afbe97323dd247d1dd0a378a3001f0ed6ba57839a34ce3e29ab9b0c
7
+ data.tar.gz: 8ed8682c0b22ba7c0f7881b9036e6c6e02dc40ffd7f112c08403c0276cbc986f8672e4f3b759ddaeb2b2dd4fc6b7825923917e078c9861a4f887713098cf005c
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ *.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2
4
+ gemfile:
5
+ - Gemfile
6
+ - test/gemfiles/activerecord32.gemfile
7
+ - test/gemfiles/activerecord40.gemfile
8
+ - test/gemfiles/activerecord41.gemfile
9
+ script: bundle exec rake test
10
+ before_script:
11
+ - psql -c 'create database strong_migrations_test;' -U postgres
12
+ notifications:
13
+ email:
14
+ on_success: never
15
+ on_failure: change
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in strong_migrations.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Bob Remeika and David Waller, 2015 Andrew Kane
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # Strong Migrations
2
+
3
+ Catch unsafe migrations at dev time
4
+
5
+ [![Build Status](https://travis-ci.org/ankane/strong_migrations.svg)](https://travis-ci.org/ankane/strong_migrations)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application’s Gemfile:
10
+
11
+ ```ruby
12
+ gem 'strong_migrations'
13
+ ```
14
+
15
+ ## Dangerous Operations
16
+
17
+ - adding an index non-concurrently
18
+ - adding a column with a non-null default value
19
+ - changing the type of a column
20
+ - renaming a table
21
+ - renaming a column
22
+ - removing a column
23
+ - adding a `json` column (Postgres only)
24
+
25
+ For more info, check out:
26
+
27
+ - [Rails Migrations with No Downtime](http://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/)
28
+ - [Safe Operations For High Volume PostgreSQL](https://www.braintreepayments.com/blog/safe-operations-for-high-volume-postgresql/) (if it’s relevant)
29
+
30
+ ## The Zero Downtime Way
31
+
32
+ ### Adding an index
33
+
34
+ Add indexes concurrently.
35
+
36
+ ```ruby
37
+ class AddSomeIndexToUsers < ActiveRecord::Migration
38
+ def change
39
+ commit_db_transaction
40
+ add_index :users, :some_index, algorithm: :concurrently
41
+ end
42
+ end
43
+ ```
44
+
45
+ ### Adding a column with a default value
46
+
47
+ 1. Add the column without a default value
48
+ 2. Commit the transaction
49
+ 3. Backfill the column
50
+ 4. Add the default value
51
+
52
+ ```ruby
53
+ class AddSomeColumnToUsers < ActiveRecord::Migration
54
+ def up
55
+ # 1
56
+ add_column :users, :some_column, :text
57
+
58
+ # 2
59
+ commit_db_transaction
60
+
61
+ # 3
62
+ User.find_in_batches do |users|
63
+ User.where(id: users.map(&:id)).update_all some_column: "default_value"
64
+ end
65
+
66
+ # 4
67
+ change_column_default :users, :some_column, "default_value"
68
+ end
69
+
70
+ def down
71
+ remove_column :users, :some_column
72
+ end
73
+ end
74
+ ```
75
+
76
+ ### Renaming or changing the type of a column
77
+
78
+ There’s no way to do this without downtime.
79
+
80
+ If you really have to:
81
+
82
+ 1. Create a new column
83
+ 2. Write to both columns
84
+ 3. Backfill data from the old column to the new column
85
+ 4. Move reads from the old column to the new column
86
+ 5. Stop writing to the old column
87
+ 6. Drop the old column
88
+
89
+ ### Renaming a table
90
+
91
+ Same as renaming a column - see above.
92
+
93
+ ### Removing a column
94
+
95
+ Tell ActiveRecord to [ignore the column](http://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/) from its cache.
96
+
97
+ ```ruby
98
+ class User
99
+ def self.columns
100
+ super.reject { |c| c.name == "some_column" }
101
+ end
102
+ end
103
+ ```
104
+
105
+ Once it’s deployed, create a migration to remove the column.
106
+
107
+ ### Adding a json column
108
+
109
+ There’s no equality operator for the `json` column type. Replace all calls to `uniq` with a custom scope.
110
+
111
+ ```ruby
112
+ scope :uniq_on_id, -> { select("DISTINCT ON (your_table.id) your_table.*") }
113
+ ```
114
+
115
+ ## Assuring Safety
116
+
117
+ To mark a step in the migration as safe, despite using method that might otherwise be dangerous, wrap it in a `safety_assured` block.
118
+
119
+ ```ruby
120
+ class MySafeMigration < ActiveRecord::Migration
121
+ def change
122
+ safety_assured { remove_column :users, :some_column }
123
+ end
124
+ end
125
+ ```
126
+
127
+ ## Credits
128
+
129
+ Thanks to Bob Remeika and David Waller for the [original code](https://github.com/foobarfighter/safe-migrations).
130
+
131
+ ## Contributing
132
+
133
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
134
+
135
+ - [Report bugs](https://github.com/ankane/strong_migrations/issues)
136
+ - Fix bugs and [submit pull requests](https://github.com/ankane/strong_migrations/pulls)
137
+ - Write, clarify, or fix documentation
138
+ - Suggest or add new features
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,6 @@
1
+ require "active_record"
2
+ require "strong_migrations/version"
3
+ require "strong_migrations/unsafe_migration"
4
+ require "strong_migrations/migration"
5
+
6
+ ActiveRecord::Migration.send(:prepend, StrongMigrations::Migration)
@@ -0,0 +1,135 @@
1
+ module StrongMigrations
2
+ module Migration
3
+ def safety_assured
4
+ previous_value = @safe
5
+ @safe = true
6
+ yield
7
+ ensure
8
+ @safe = previous_value
9
+ end
10
+
11
+ def method_missing(method, *args, &block)
12
+ unless @safe || is_a?(ActiveRecord::Schema)
13
+ case method
14
+ when :remove_column
15
+ raise_error :remove_column
16
+ when :remove_timestamps
17
+ raise_error :remove_column
18
+ when :change_table
19
+ raise_error :change_table
20
+ when :rename_table
21
+ raise_error :rename_table
22
+ when :rename_column
23
+ raise_error :rename_column
24
+ when :add_index
25
+ options = args[2]
26
+ unless options && options[:algorithm] == :concurrently
27
+ raise_error :add_index
28
+ end
29
+ when :add_column
30
+ type = args[2]
31
+ options = args[3]
32
+ raise_error :add_column_default if options && !options[:default].nil?
33
+ raise_error :add_column_json if type.to_s == "json"
34
+ when :change_column
35
+ raise_error :change_column
36
+ end
37
+ end
38
+
39
+ super
40
+ end
41
+
42
+ private
43
+
44
+ def raise_error(message_key)
45
+ message =
46
+ case message_key
47
+ when :add_column_default
48
+ "Adding a column with a non-null default requires
49
+ the entire table and indexes to be rewritten. Instead:
50
+
51
+ 1. Add the column without a default value
52
+ 2. Commit the transaction
53
+ 3. Backfill the column
54
+ 4. Add the default value"
55
+ when :add_column_json
56
+ "There's no equality operator for the json column type.
57
+ Replace all calls to uniq with a custom scope.
58
+
59
+ scope :uniq_on_id, -> { select(\"DISTINCT ON (your_table.id) your_table.*\") }
60
+
61
+ Once it's deployed, wrap this step in a safety_assured { ... } block."
62
+ when :change_column
63
+ "Changing the type of an existing column requires
64
+ the entire table and indexes to be rewritten.
65
+
66
+ If you really have to:
67
+
68
+ 1. Create a new column
69
+ 2. Write to both columns
70
+ 3. Backfill data from the old column to the new column
71
+ 4. Move reads from the old column to the new column
72
+ 5. Stop writing to the old column
73
+ 6. Drop the old column"
74
+ when :remove_column
75
+ "ActiveRecord caches attributes which causes problems
76
+ when removing columns. Be sure to ignored the column:
77
+
78
+ class User
79
+ def self.columns
80
+ super.reject { |c| c.name == \"some_column\" }
81
+ end
82
+ end
83
+
84
+ Once it's deployed, wrap this step in a safety_assured { ... } block."
85
+ when :rename_column
86
+ "There's no way to rename a column without downtime.
87
+
88
+ If you really have to:
89
+
90
+ 1. Create a new column
91
+ 2. Write to both columns
92
+ 3. Backfill data from the old column to new column
93
+ 4. Move reads from the old column to the new column
94
+ 5. Stop writing to the old column
95
+ 6. Drop the old column"
96
+ when :rename_table
97
+ "There's no way to rename a table without downtime.
98
+
99
+ If you really have to:
100
+
101
+ 1. Create a new table
102
+ 2. Write to both tables
103
+ 3. Backfill data from the old table to new table
104
+ 4. Move reads from the old table to the new table
105
+ 5. Stop writing to the old table
106
+ 6. Drop the old table"
107
+ when :add_index
108
+ "Adding a non-concurrent index locks the table. Instead, use:
109
+
110
+ def change
111
+ commit_db_transaction
112
+ add_index :users, :some_column, algorithm: :concurrently
113
+ end"
114
+ when :change_table
115
+ "The strong_migrations gem does not support inspecting what happens inside a
116
+ change_table block, so cannot help you here. Please make really sure that what
117
+ you're doing is safe before proceding, then wrap it in a safety_assured { ... } block."
118
+ end
119
+
120
+
121
+ message.prepend '
122
+ __ __ _____ _______ _
123
+ \ \ / /\ |_ _|__ __| |
124
+ \ \ /\ / / \ | | | | | |
125
+ \ \/ \/ / /\ \ | | | | | |
126
+ \ /\ / ____ \ _| |_ | | |_|
127
+ \/ \/_/ \_\_____| |_| (_)
128
+
129
+ '
130
+ message << "\n"
131
+
132
+ raise StrongMigrations::UnsafeMigration, message
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,4 @@
1
+ module StrongMigrations
2
+ class UnsafeMigration < StandardError
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module StrongMigrations
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "strong_migrations/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "strong_migrations"
8
+ spec.version = StrongMigrations::VERSION
9
+ spec.authors = ["Bob Remeika", "David Waller", "Andrew Kane"]
10
+ spec.email = ["bob.remeika@gmail.com", "andrew@chartkick.com"]
11
+
12
+ spec.summary = "Catch unsafe migrations at dev time"
13
+ spec.homepage = "https://github.com/ankane/strong_migrations"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "activerecord", ">= 3.2.0"
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.10"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest"
25
+ spec.add_development_dependency "pg"
26
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: strong_migrations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bob Remeika
8
+ - David Waller
9
+ - Andrew Kane
10
+ autorequire:
11
+ bindir: exe
12
+ cert_chain: []
13
+ date: 2015-11-23 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 3.2.0
29
+ - !ruby/object:Gem::Dependency
30
+ name: bundler
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.10'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.10'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rake
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '10.0'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '10.0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: minitest
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: pg
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ description:
86
+ email:
87
+ - bob.remeika@gmail.com
88
+ - andrew@chartkick.com
89
+ executables: []
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - ".gitignore"
94
+ - ".travis.yml"
95
+ - Gemfile
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - lib/strong_migrations.rb
100
+ - lib/strong_migrations/migration.rb
101
+ - lib/strong_migrations/unsafe_migration.rb
102
+ - lib/strong_migrations/version.rb
103
+ - strong_migrations.gemspec
104
+ homepage: https://github.com/ankane/strong_migrations
105
+ licenses: []
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.4.5.1
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Catch unsafe migrations at dev time
127
+ test_files: []