db_nazi 0.0.1
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.
- data/.gitignore +1 -0
- data/.travis.yml +9 -0
- data/CHANGELOG +3 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/README.markdown +87 -0
- data/Rakefile +6 -0
- data/db_nazi.gemspec +25 -0
- data/lib/db_nazi/abstract_adapter.rb +31 -0
- data/lib/db_nazi/migration.rb +21 -0
- data/lib/db_nazi/migration_proxy.rb +17 -0
- data/lib/db_nazi/table_definition.rb +23 -0
- data/lib/db_nazi/version.rb +11 -0
- data/lib/db_nazi.rb +66 -0
- data/test/database.yml +2 -0
- data/test/integration/test_integration.rb +76 -0
- data/test/test_helper.rb +46 -0
- data/test/unit/db_nazi/test_abstract_adapter.rb +99 -0
- data/test/unit/db_nazi/test_migration.rb +10 -0
- data/test/unit/db_nazi/test_migration_proxy.rb +68 -0
- data/test/unit/db_nazi/test_table_definition.rb +75 -0
- data/test/unit/test_db_nazi.rb +51 -0
- metadata +178 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/Gemfile.lock
|
data/.travis.yml
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) George Ogata
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
## DB Nazi
|
2
|
+
|
3
|
+
Encourages good DB practices in Active Record migrations.
|
4
|
+
|
5
|
+
## What?
|
6
|
+
|
7
|
+
Active Record makes schema changes wonderfully easy, but due to some unfortunate
|
8
|
+
defaults, it can be easy to forget to adhere to some basic database best
|
9
|
+
practices, such as restricting columns to be non-nullable, or setting meaningful
|
10
|
+
varchar limits. DB Nazi forces you to be explicit about these things so you
|
11
|
+
can't simply forget.
|
12
|
+
|
13
|
+
It might take another 2 seconds to type `null: true`, but it may save you hours
|
14
|
+
resolving integrity issues down the line!
|
15
|
+
|
16
|
+
### Nullability
|
17
|
+
|
18
|
+
If `DBNazi.require_nullability` is set to `true` (the default), you must specify
|
19
|
+
a `:null` option for all columns.
|
20
|
+
|
21
|
+
add_column :users, :awesome, :boolean # raises DBNazi::NullabilityRequired
|
22
|
+
add_column :users, :awesome, :boolean, null: true # ok
|
23
|
+
|
24
|
+
### Varchar limits
|
25
|
+
|
26
|
+
If `DBNazi.require_varchar_limits` is set to `true` (the default), you must
|
27
|
+
specify a `:limit` option for all `:string` columns.
|
28
|
+
|
29
|
+
add_column :users, :name, :string # raises DBNazi::VarcharLimitRequired
|
30
|
+
add_column :users, :name, :string, limit: 100 # ok
|
31
|
+
|
32
|
+
### Index uniqueness
|
33
|
+
|
34
|
+
If `DBNazi.require_index_uniqueness` is set the `true` (the default), you must
|
35
|
+
specify a `:unique` option for all indexes.
|
36
|
+
|
37
|
+
add_index :users, :email # raises DBNazi::IndexUniquenessRequired
|
38
|
+
add_index :users, :email, unique: false # ok
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
Since this tool is about enforcing developer discipline, I suggest including
|
43
|
+
this only in the `:development` group in your `Gemfile`.
|
44
|
+
|
45
|
+
group :development do
|
46
|
+
gem 'db_nazi'
|
47
|
+
end
|
48
|
+
|
49
|
+
If you have an established project, you probably don't want to lay the hard line
|
50
|
+
on all your existing migrations. You can do this by specifying a minimum
|
51
|
+
migration version in `config/environments/development.rb`:
|
52
|
+
|
53
|
+
DBNazi.from_version = 20120623000000
|
54
|
+
|
55
|
+
This means "only be a nazi from migration 20120623000000 onwards."
|
56
|
+
|
57
|
+
If you're using a migration written by a 3rd party, such as a generator you're
|
58
|
+
using, I recommend editing it to conform to the rules above. After all, perhaps
|
59
|
+
the 3rd party forgot a 'NOT NULL' or two.
|
60
|
+
|
61
|
+
If you're *really* sure you want to subvert DB Nazi for whatever reason, you may
|
62
|
+
do so like this:
|
63
|
+
|
64
|
+
class BeAJerk < ActiveRecord::Migration
|
65
|
+
no_nazi
|
66
|
+
...
|
67
|
+
end
|
68
|
+
|
69
|
+
Or just for a given block like this:
|
70
|
+
|
71
|
+
DBNazi.disable do
|
72
|
+
...
|
73
|
+
end
|
74
|
+
|
75
|
+
But no soup for you!
|
76
|
+
|
77
|
+
## Contributing
|
78
|
+
|
79
|
+
* [Bug reports](https://github.com/oggy/db_nazi/issues)
|
80
|
+
* [Source](https://github.com/oggy/db_nazi)
|
81
|
+
* Patches: Fork on Github, send pull request.
|
82
|
+
* Include tests where practical.
|
83
|
+
* Leave the version alone, or bump it in a separate commit.
|
84
|
+
|
85
|
+
## Copyright
|
86
|
+
|
87
|
+
Copyright (c) George Ogata. See LICENSE for details.
|
data/Rakefile
ADDED
data/db_nazi.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.unshift File.expand_path('lib', File.dirname(__FILE__))
|
3
|
+
require 'db_nazi/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'db_nazi'
|
7
|
+
gem.version = DBNazi::VERSION
|
8
|
+
gem.authors = ['George Ogata']
|
9
|
+
gem.email = ['george.ogata@gmail.com']
|
10
|
+
gem.license = 'MIT'
|
11
|
+
gem.description = ""
|
12
|
+
gem.summary = "Encourage good DB practices in ActiveRecord migrations."
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
gem.files = `git ls-files`.split("\n")
|
17
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
|
19
|
+
gem.add_runtime_dependency 'activerecord', '~> 3.2.6'
|
20
|
+
gem.add_development_dependency 'ritual', '~> 0.4.1'
|
21
|
+
gem.add_development_dependency 'temporaries', '~> 0.2.0'
|
22
|
+
gem.add_development_dependency 'looksee', '~> 0.2.0'
|
23
|
+
gem.add_development_dependency 'debugger', '~> 1.1.3'
|
24
|
+
gem.add_development_dependency 'sqlite3', '~> 1.3.6'
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module DBNazi
|
2
|
+
module AbstractAdapter
|
3
|
+
def add_column(table_name, column_name, type, options = {})
|
4
|
+
if DBNazi.enabled?(:require_nullability)
|
5
|
+
options.key?(:null) or
|
6
|
+
raise NullabilityRequired, "[db_nazi] :null parameter required"
|
7
|
+
end
|
8
|
+
if DBNazi.enabled?(:require_varchar_limits)
|
9
|
+
# AR calls #to_sym on type, so do the same here.
|
10
|
+
type.to_sym == :string && !options.key?(:limit) and
|
11
|
+
raise VarcharLimitRequired, "[db_nazi] string column requires :limit parameter"
|
12
|
+
end
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_index(table_name, column_name, options = {})
|
17
|
+
if DBNazi.enabled?(:require_index_uniqueness)
|
18
|
+
options.key?(:unique) or
|
19
|
+
raise IndexUniquenessRequired, "[db_nazi] :unique parameter required"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_table(name, *)
|
24
|
+
if name.to_s == ActiveRecord::Migrator.schema_migrations_table_name.to_s
|
25
|
+
DBNazi.disable { super }
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DBNazi
|
2
|
+
module Migration
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
def nazi_disabled?
|
8
|
+
self.class.nazi_disabled?
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def no_nazi
|
13
|
+
@nazi_disabled = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def nazi_disabled?
|
17
|
+
@nazi_disabled
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DBNazi
|
2
|
+
module MigrationProxy
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
alias migrate_without_db_nazi migrate
|
6
|
+
alias migrate migrate_with_db_nazi
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def migrate_with_db_nazi(direction)
|
11
|
+
action = DBNazi.enabled_for_migration?(migration, version) ? :enable : :disable
|
12
|
+
DBNazi.send(action) do
|
13
|
+
migrate_without_db_nazi(direction)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DBNazi
|
2
|
+
module TableDefinition
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
alias column_without_db_nazi column
|
6
|
+
alias column column_with_db_nazi
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def column_with_db_nazi(name, type, options = {})
|
11
|
+
if DBNazi.enabled?(:require_nullability) && type != :primary_key
|
12
|
+
options.key?(:null) or
|
13
|
+
raise NullabilityRequired, "[db_nazi] :null parameter required"
|
14
|
+
end
|
15
|
+
if DBNazi.enabled?(:require_varchar_limits)
|
16
|
+
# AR calls #to_sym on type, so do the same here.
|
17
|
+
type.to_sym == :string && !options.key?(:limit) and
|
18
|
+
raise VarcharLimitRequired, "[db_nazi] string column requires :limit parameter"
|
19
|
+
end
|
20
|
+
column_without_db_nazi(name, type, options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/db_nazi.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module DBNazi
|
2
|
+
autoload :AbstractAdapter, 'db_nazi/abstract_adapter'
|
3
|
+
autoload :Migration, 'db_nazi/migration'
|
4
|
+
autoload :MigrationProxy, 'db_nazi/migration_proxy'
|
5
|
+
autoload :TableDefinition, 'db_nazi/table_definition'
|
6
|
+
autoload :VERSION, 'db_nazi/version'
|
7
|
+
|
8
|
+
ArgumentError = Class.new(::ArgumentError)
|
9
|
+
NullabilityRequired = Class.new(ArgumentError)
|
10
|
+
VarcharLimitRequired = Class.new(ArgumentError)
|
11
|
+
IndexUniquenessRequired = Class.new(ArgumentError)
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :enabled
|
15
|
+
attr_accessor :from_version
|
16
|
+
attr_accessor :require_nullability
|
17
|
+
attr_accessor :require_varchar_limits
|
18
|
+
attr_accessor :require_index_uniqueness
|
19
|
+
|
20
|
+
def enabled?(setting)
|
21
|
+
@enabled && send(setting)
|
22
|
+
end
|
23
|
+
|
24
|
+
def enable
|
25
|
+
original_enabled = @enabled
|
26
|
+
@enabled = true
|
27
|
+
begin
|
28
|
+
yield
|
29
|
+
ensure
|
30
|
+
@enabled = original_enabled
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def disable
|
35
|
+
original_enabled = @enabled
|
36
|
+
@enabled = false
|
37
|
+
begin
|
38
|
+
yield
|
39
|
+
ensure
|
40
|
+
@enabled = original_enabled
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def enabled_for_migration?(migration, version)
|
45
|
+
return false if !@enabled
|
46
|
+
return false if migration.nazi_disabled?
|
47
|
+
return false if DBNazi.from_version && DBNazi.from_version > version
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def reset
|
52
|
+
self.enabled = true
|
53
|
+
self.from_version = nil
|
54
|
+
self.require_nullability = true
|
55
|
+
self.require_varchar_limits = true
|
56
|
+
self.require_index_uniqueness = true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
reset
|
61
|
+
end
|
62
|
+
|
63
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.__send__ :include, DBNazi::AbstractAdapter
|
64
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.__send__ :include, DBNazi::TableDefinition
|
65
|
+
ActiveRecord::Migration.__send__ :include, DBNazi::Migration
|
66
|
+
ActiveRecord::MigrationProxy.__send__ :include, DBNazi::MigrationProxy
|
data/test/database.yml
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
describe DBNazi do
|
4
|
+
use_database
|
5
|
+
use_temporary_directory "#{ROOT}/test/tmp"
|
6
|
+
|
7
|
+
# Migrations use Kernel.puts. Lame.
|
8
|
+
out = Class.new { def write(*) end; def flush(*) end }.new
|
9
|
+
use_global_value :stdout, out
|
10
|
+
|
11
|
+
before do
|
12
|
+
DBNazi.reset
|
13
|
+
@original_pwd = Dir.pwd
|
14
|
+
Dir.chdir "#{ROOT}/test/tmp"
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
Dir.chdir @original_pwd
|
19
|
+
end
|
20
|
+
|
21
|
+
before do
|
22
|
+
DBNazi.reset
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises an error when there is a careless migration" do
|
26
|
+
create_bad_migration(1)
|
27
|
+
run_migrations
|
28
|
+
errors.size.must_equal 1
|
29
|
+
end
|
30
|
+
|
31
|
+
it "only applies nazism to selected migrations" do
|
32
|
+
create_bad_migration(1)
|
33
|
+
create_good_migration(2)
|
34
|
+
DBNazi.from_version = 2
|
35
|
+
run_migrations
|
36
|
+
errors.size.must_equal 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_bad_migration(version)
|
40
|
+
create_migration(version, :bad)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_good_migration(version)
|
44
|
+
create_migration(version, :good)
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_migration(version, good_or_bad)
|
48
|
+
write_file "#{version}_test_migration_#{version}.rb", <<-EOS
|
49
|
+
|class TestMigration#{version} < ActiveRecord::Migration
|
50
|
+
| def up
|
51
|
+
| create_table 'table_#{version}' do |t|
|
52
|
+
| t.boolean :test_column #{good_or_bad == :good ? ', null: true' : ''}
|
53
|
+
| end
|
54
|
+
| end
|
55
|
+
|end
|
56
|
+
EOS
|
57
|
+
end
|
58
|
+
|
59
|
+
def run_migrations
|
60
|
+
ActiveRecord::Migrator.migrate('.', nil)
|
61
|
+
rescue StandardError => e
|
62
|
+
if e.message.include?('[db_nazi]')
|
63
|
+
errors << e
|
64
|
+
else
|
65
|
+
raise
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def errors
|
70
|
+
@errors ||= []
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_file(name, content)
|
74
|
+
open(name, 'w') { |f| f.print content.gsub(/^ *\|/, '') }
|
75
|
+
end
|
76
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
$:.unshift File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
ROOT = File.expand_path('..', File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'minitest/spec'
|
5
|
+
require 'yaml'
|
6
|
+
require 'active_record'
|
7
|
+
require 'temporaries'
|
8
|
+
require 'debugger'
|
9
|
+
require 'db_nazi'
|
10
|
+
|
11
|
+
ADAPTER = ENV['DBNAZI_ADAPTER'] || 'sqlite3'
|
12
|
+
CONNECTION = YAML.load_file("#{ROOT}/test/database.yml")[ADAPTER].merge(adapter: ADAPTER)
|
13
|
+
ActiveRecord::Base.establish_connection(CONNECTION)
|
14
|
+
|
15
|
+
MiniTest::Spec.class_eval do
|
16
|
+
def recreate_database
|
17
|
+
drop_database
|
18
|
+
case ADAPTER
|
19
|
+
when 'sqlite3'
|
20
|
+
ActiveRecord::Base.establish_connection(CONNECTION)
|
21
|
+
when 'mysql2', 'postgresql'
|
22
|
+
ActiveRecord::Base.connection.create_database 'db_nazi_test'
|
23
|
+
else
|
24
|
+
raise "can't create database for #{ADAPTER}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def drop_database
|
29
|
+
case ADAPTER
|
30
|
+
when 'sqlite3'
|
31
|
+
when 'mysql2', 'postgresql'
|
32
|
+
ActiveRecord::Base.connection.drop_database 'db_nazi_test'
|
33
|
+
else
|
34
|
+
raise "can't drop database for #{ADAPTER}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def connection
|
39
|
+
ActiveRecord::Base.connection
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.use_database
|
43
|
+
before { recreate_database }
|
44
|
+
after { drop_database }
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
describe DBNazi::AbstractAdapter do
|
4
|
+
use_database
|
5
|
+
|
6
|
+
before do
|
7
|
+
DBNazi.reset
|
8
|
+
connection.create_table 'test_table'
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "nullability" do
|
12
|
+
describe "when it is required" do
|
13
|
+
use_attribute_value DBNazi, :require_nullability, true
|
14
|
+
|
15
|
+
it "raises a DBNazi::NullabilityRequired if :null is not specified when adding a column" do
|
16
|
+
lambda do
|
17
|
+
connection.add_column 'test_table', 'test_column', :boolean
|
18
|
+
end.must_raise(DBNazi::NullabilityRequired)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "does not raise a DBNazi::NullabilityRequired if :null is true when adding a column" do
|
22
|
+
connection.add_column 'test_table', 'test_column', :boolean, null: true
|
23
|
+
end
|
24
|
+
|
25
|
+
it "does not raise a DBNazi::NullabilityRequired if :null is false when adding a column" do
|
26
|
+
connection.add_column 'test_table', 'test_column', :boolean, null: false, default: false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "when it is not required" do
|
31
|
+
use_attribute_value DBNazi, :require_nullability, false
|
32
|
+
|
33
|
+
it "does not raise a DBNazi::NullabilityRequired if :null is not specified when adding a column" do
|
34
|
+
connection.add_column 'test_table', 'test_column', :boolean
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "varchar limits" do
|
40
|
+
describe "when they are required" do
|
41
|
+
use_attribute_value DBNazi, :require_varchar_limits, true
|
42
|
+
|
43
|
+
it "raises a DBNazi::VarcharLimitRequired if :limit is not specified for a :string column" do
|
44
|
+
lambda do
|
45
|
+
connection.add_column 'test_table', 'test_column', :string, null: true
|
46
|
+
end.must_raise(DBNazi::VarcharLimitRequired)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "does not raise a DBNazi::VarcharLimitRequired if :limit is specified for a :string column" do
|
50
|
+
connection.add_column 'test_table', 'test_column', :string, limit: 255, null: true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "when they are not required" do
|
55
|
+
use_attribute_value DBNazi, :require_varchar_limits, false
|
56
|
+
|
57
|
+
it "does not raise a DBNazi::VarcharLimitRequired if :limit is not specified for a :string column" do
|
58
|
+
connection.add_column 'test_table', 'test_column', :string, null: true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "index uniqueness" do
|
64
|
+
before do
|
65
|
+
connection.add_column 'test_table', 'test_column', :boolean, null: true
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "when it is required" do
|
69
|
+
use_attribute_value DBNazi, :require_index_uniqueness, true
|
70
|
+
|
71
|
+
it "raises a DBNazi::IndexUniquenessRequired if :unique is not specified for an index" do
|
72
|
+
lambda do
|
73
|
+
connection.add_index 'test_table', 'test_column'
|
74
|
+
end.must_raise(DBNazi::IndexUniquenessRequired)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "does not raise a DBNazi::IndexUniquenessRequired if :unique is true for an index" do
|
78
|
+
connection.add_index 'test_table', 'test_column', unique: true
|
79
|
+
end
|
80
|
+
|
81
|
+
it "does not raise a DBNazi::IndexUniquenessRequired if :unique is false for an index" do
|
82
|
+
connection.add_index 'test_table', 'test_column', unique: false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "when it is not required" do
|
87
|
+
use_attribute_value DBNazi, :require_index_uniqueness, false
|
88
|
+
|
89
|
+
it "does not raise a DBNazi::IndexUniquenessRequired if :unique is not specified for an index" do
|
90
|
+
connection.add_index 'test_table', 'test_column'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it "does not prevent construction of the schema migrations table" do
|
96
|
+
# AR doesn't specify a varchar limit for the version column. Lame.
|
97
|
+
connection.initialize_schema_migrations_table
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
describe DBNazi::Migration do
|
4
|
+
describe "#nazi_disabled?" do
|
5
|
+
it "is true if no_nazi was called on the migration class" do
|
6
|
+
klass = Class.new(ActiveRecord::Migration) { no_nazi }
|
7
|
+
klass.new.nazi_disabled?.must_equal true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
describe DBNazi::MigrationProxy do
|
4
|
+
use_database
|
5
|
+
use_temporary_directory "#{ROOT}/test/tmp"
|
6
|
+
|
7
|
+
# Migrations use Kernel.puts. Lame.
|
8
|
+
out = Class.new { def write(*) end; def flush(*) end }.new
|
9
|
+
use_global_value :stdout, out
|
10
|
+
|
11
|
+
before do
|
12
|
+
@original_pwd = Dir.pwd
|
13
|
+
Dir.chdir "#{ROOT}/test/tmp"
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
Dir.chdir @original_pwd
|
18
|
+
end
|
19
|
+
|
20
|
+
before do
|
21
|
+
@migration_proxy = ActiveRecord::MigrationProxy.new("test_migration", 10, "test_migration.rb", nil)
|
22
|
+
@migration_class = Class.new(ActiveRecord::Migration) do
|
23
|
+
attr_reader :enabled
|
24
|
+
|
25
|
+
def up
|
26
|
+
@enabled = DBNazi.enabled
|
27
|
+
end
|
28
|
+
end
|
29
|
+
migration = @migration = @migration_class.new
|
30
|
+
@migration_proxy.singleton_class.send(:define_method, :migration) { migration }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#migrate" do
|
34
|
+
it "disables DBNazi when no_nazi is used" do
|
35
|
+
@migration_class.no_nazi
|
36
|
+
@migration_proxy.migrate(:up)
|
37
|
+
@migration.enabled.must_equal false
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "when DBNazi.from_version is set" do
|
41
|
+
it "disables DBNazi if this migration comes before the minimum version" do
|
42
|
+
with_attribute_value DBNazi, :from_version, 11 do
|
43
|
+
@migration_proxy.migrate(:up)
|
44
|
+
@migration.enabled.must_equal false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "does not disable DBNazi if this migration is the minimum version" do
|
49
|
+
with_attribute_value DBNazi, :from_version, 10 do
|
50
|
+
@migration_proxy.migrate(:up)
|
51
|
+
@migration.enabled.must_equal true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "does not disable DBNazi if this migration comes after the minimum version" do
|
56
|
+
with_attribute_value DBNazi, :from_version, 9 do
|
57
|
+
@migration_proxy.migrate(:up)
|
58
|
+
@migration.enabled.must_equal true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "enables DBNazi otherwise" do
|
64
|
+
@migration_proxy.migrate(:up)
|
65
|
+
@migration.enabled.must_equal true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
describe DBNazi::TableDefinition do
|
4
|
+
use_database
|
5
|
+
|
6
|
+
before do
|
7
|
+
DBNazi.reset
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "nullability" do
|
11
|
+
describe "when it is required" do
|
12
|
+
use_attribute_value DBNazi, :require_nullability, true
|
13
|
+
|
14
|
+
it "raises a DBNazi::NullabilityRequired if :null is not specified when adding a column" do
|
15
|
+
connection.create_table 'test_table' do |t|
|
16
|
+
lambda do
|
17
|
+
t.column 'test_column', :boolean
|
18
|
+
end.must_raise(DBNazi::NullabilityRequired)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "does not raise a DBNazi::NullabilityRequired if :null is true when adding a column" do
|
23
|
+
connection.create_table 'test_table' do |t|
|
24
|
+
t.column 'test_column', :boolean, null: true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "does not raise a DBNazi::NullabilityRequired if :null is false when adding a column" do
|
29
|
+
connection.create_table 'test_table' do |t|
|
30
|
+
t.column 'test_column', :boolean, null: false, default: false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "when it is not required" do
|
36
|
+
use_attribute_value DBNazi, :require_nullability, false
|
37
|
+
|
38
|
+
it "does not raise a DBNazi::NullabilityRequired if :null is not specified when adding a column" do
|
39
|
+
connection.create_table 'test_table' do |t|
|
40
|
+
t.column 'test_column', :boolean
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "varchar limits" do
|
47
|
+
describe "when they are required" do
|
48
|
+
use_attribute_value DBNazi, :require_varchar_limits, true
|
49
|
+
|
50
|
+
it "raises a DBNazi::VarcharLimitRequired if :limit is not specified for a :string column" do
|
51
|
+
connection.create_table 'test_table' do |t|
|
52
|
+
lambda do
|
53
|
+
t.column 'test_column', :string, null: true
|
54
|
+
end.must_raise(DBNazi::VarcharLimitRequired)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "does not raise a DBNazi::VarcharLimitRequired if :limit is specified for a :string column" do
|
59
|
+
connection.create_table 'test_table' do |t|
|
60
|
+
t.column 'test_column', :string, limit: 255, null: true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "when they are not required" do
|
66
|
+
use_attribute_value DBNazi, :require_varchar_limits, false
|
67
|
+
|
68
|
+
it "does not raise a DBNazi::VarcharLimitRequired if :limit is not specified for a :string column" do
|
69
|
+
connection.create_table 'test_table' do |t|
|
70
|
+
t.column 'test_column', :string, null: true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
describe DBNazi do
|
4
|
+
before do
|
5
|
+
DBNazi.reset
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".enable" do
|
9
|
+
it "should turn on all feature flags for the duration of the block" do
|
10
|
+
DBNazi.enabled = false
|
11
|
+
DBNazi.enable do
|
12
|
+
DBNazi.enabled?(:require_nullability).must_equal true
|
13
|
+
DBNazi.enabled?(:require_varchar_limits).must_equal true
|
14
|
+
DBNazi.enabled?(:require_index_uniqueness).must_equal true
|
15
|
+
end
|
16
|
+
DBNazi.enabled?(:require_nullability).must_equal false
|
17
|
+
DBNazi.enabled?(:require_varchar_limits).must_equal false
|
18
|
+
DBNazi.enabled?(:require_index_uniqueness).must_equal false
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should restore a flag to true if it was already enabled" do
|
22
|
+
DBNazi.enabled = true
|
23
|
+
DBNazi.enable do
|
24
|
+
DBNazi.enabled?(:require_nullability).must_equal true
|
25
|
+
end
|
26
|
+
DBNazi.enabled?(:require_nullability).must_equal true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ".disable" do
|
31
|
+
it "should turn off all feature flags for the duration of the block" do
|
32
|
+
DBNazi.enabled = true
|
33
|
+
DBNazi.disable do
|
34
|
+
DBNazi.enabled?(:require_nullability).must_equal false
|
35
|
+
DBNazi.enabled?(:require_varchar_limits).must_equal false
|
36
|
+
DBNazi.enabled?(:require_index_uniqueness).must_equal false
|
37
|
+
end
|
38
|
+
DBNazi.enabled?(:require_nullability).must_equal true
|
39
|
+
DBNazi.enabled?(:require_varchar_limits).must_equal true
|
40
|
+
DBNazi.enabled?(:require_index_uniqueness).must_equal true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should restore a flag to false if it was already disabled" do
|
44
|
+
DBNazi.enabled = false
|
45
|
+
DBNazi.disable do
|
46
|
+
DBNazi.enabled?(:require_nullability).must_equal false
|
47
|
+
end
|
48
|
+
DBNazi.enabled?(:require_nullability).must_equal false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: db_nazi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- George Ogata
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.6
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.6
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: ritual
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.4.1
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.4.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: temporaries
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.2.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.2.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: looksee
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.2.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.2.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: debugger
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.1.3
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.1.3
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: sqlite3
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.3.6
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.3.6
|
110
|
+
description: ''
|
111
|
+
email:
|
112
|
+
- george.ogata@gmail.com
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- .gitignore
|
118
|
+
- .travis.yml
|
119
|
+
- CHANGELOG
|
120
|
+
- Gemfile
|
121
|
+
- LICENSE
|
122
|
+
- README.markdown
|
123
|
+
- Rakefile
|
124
|
+
- db_nazi.gemspec
|
125
|
+
- lib/db_nazi.rb
|
126
|
+
- lib/db_nazi/abstract_adapter.rb
|
127
|
+
- lib/db_nazi/migration.rb
|
128
|
+
- lib/db_nazi/migration_proxy.rb
|
129
|
+
- lib/db_nazi/table_definition.rb
|
130
|
+
- lib/db_nazi/version.rb
|
131
|
+
- test/database.yml
|
132
|
+
- test/integration/test_integration.rb
|
133
|
+
- test/test_helper.rb
|
134
|
+
- test/unit/db_nazi/test_abstract_adapter.rb
|
135
|
+
- test/unit/db_nazi/test_migration.rb
|
136
|
+
- test/unit/db_nazi/test_migration_proxy.rb
|
137
|
+
- test/unit/db_nazi/test_table_definition.rb
|
138
|
+
- test/unit/test_db_nazi.rb
|
139
|
+
homepage: ''
|
140
|
+
licenses:
|
141
|
+
- MIT
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ! '>='
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
segments:
|
153
|
+
- 0
|
154
|
+
hash: 2002143158366431765
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
none: false
|
157
|
+
requirements:
|
158
|
+
- - ! '>='
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
segments:
|
162
|
+
- 0
|
163
|
+
hash: 2002143158366431765
|
164
|
+
requirements: []
|
165
|
+
rubyforge_project:
|
166
|
+
rubygems_version: 1.8.24
|
167
|
+
signing_key:
|
168
|
+
specification_version: 3
|
169
|
+
summary: Encourage good DB practices in ActiveRecord migrations.
|
170
|
+
test_files:
|
171
|
+
- test/database.yml
|
172
|
+
- test/integration/test_integration.rb
|
173
|
+
- test/test_helper.rb
|
174
|
+
- test/unit/db_nazi/test_abstract_adapter.rb
|
175
|
+
- test/unit/db_nazi/test_migration.rb
|
176
|
+
- test/unit/db_nazi/test_migration_proxy.rb
|
177
|
+
- test/unit/db_nazi/test_table_definition.rb
|
178
|
+
- test/unit/test_db_nazi.rb
|