db_leftovers 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +53 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +0 -9
- data/README.md +8 -1
- data/Rakefile +3 -32
- data/db_leftovers.gemspec +16 -69
- data/lib/db_leftovers/dsl.rb +26 -0
- data/lib/db_leftovers/postgres_database_interface.rb +0 -1
- data/lib/db_leftovers/version.rb +3 -0
- data/spec/db_leftovers_spec.rb +41 -0
- metadata +6 -42
- data/README.html +0 -190
- data/VERSION +0 -1
data/.gitignore
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
|
4
|
+
# rdoc generated
|
5
|
+
rdoc
|
6
|
+
|
7
|
+
# yard generated
|
8
|
+
doc
|
9
|
+
.yardoc
|
10
|
+
|
11
|
+
# bundler
|
12
|
+
.bundle
|
13
|
+
|
14
|
+
# jeweler generated
|
15
|
+
pkg
|
16
|
+
|
17
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
18
|
+
#
|
19
|
+
# * Create a file at ~/.gitignore
|
20
|
+
# * Include files you want ignored
|
21
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
22
|
+
#
|
23
|
+
# After doing this, these files will be ignored in all your git projects,
|
24
|
+
# saving you from having to 'pollute' every project you touch with them
|
25
|
+
#
|
26
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
27
|
+
#
|
28
|
+
# For MacOS:
|
29
|
+
#
|
30
|
+
#.DS_Store
|
31
|
+
|
32
|
+
# For TextMate
|
33
|
+
#*.tmproj
|
34
|
+
#tmtags
|
35
|
+
|
36
|
+
# For emacs:
|
37
|
+
#*~
|
38
|
+
#\#*
|
39
|
+
#.\#*
|
40
|
+
|
41
|
+
# For vim:
|
42
|
+
.*.swp
|
43
|
+
.*.swo
|
44
|
+
|
45
|
+
# For redcar:
|
46
|
+
#.redcar
|
47
|
+
|
48
|
+
# For rubinius:
|
49
|
+
#*.rbc
|
50
|
+
|
51
|
+
tmp
|
52
|
+
README.html
|
53
|
+
spec/config/database.yml
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -44,15 +44,10 @@ GEM
|
|
44
44
|
builder (3.0.0)
|
45
45
|
diff-lcs (1.1.3)
|
46
46
|
erubis (2.7.0)
|
47
|
-
git (1.2.5)
|
48
47
|
hike (1.2.1)
|
49
48
|
i18n (0.6.0)
|
50
49
|
jdbc-mysql (5.1.13)
|
51
50
|
jdbc-postgres (9.1.901)
|
52
|
-
jeweler (1.6.4)
|
53
|
-
bundler (~> 1.0)
|
54
|
-
git (>= 1.2.5)
|
55
|
-
rake
|
56
51
|
json (1.6.5)
|
57
52
|
json (1.6.5-java)
|
58
53
|
mail (2.3.0)
|
@@ -89,8 +84,6 @@ GEM
|
|
89
84
|
rdoc (~> 3.4)
|
90
85
|
thor (~> 0.14.6)
|
91
86
|
rake (0.9.2.2)
|
92
|
-
rcov (0.9.11)
|
93
|
-
rcov (0.9.11-java)
|
94
87
|
rdoc (3.12)
|
95
88
|
json (~> 1.4)
|
96
89
|
rspec (2.4.0)
|
@@ -122,7 +115,5 @@ DEPENDENCIES
|
|
122
115
|
activerecord-mysql2-adapter
|
123
116
|
activerecord-postgresql-adapter
|
124
117
|
bundler
|
125
|
-
jeweler (~> 1.6.4)
|
126
118
|
rails (>= 3.0.0)
|
127
|
-
rcov
|
128
119
|
rspec (~> 2.4.0)
|
data/README.md
CHANGED
@@ -102,6 +102,13 @@ The `table` call is just a convenience so you can group all a table's indexes et
|
|
102
102
|
|
103
103
|
You can repeat `table` calls for the same table several times if you like. This lets you put your indexes in one place and your foreign keys in another, for example.
|
104
104
|
|
105
|
+
### ignore(table\_name, [table\_name, ...])
|
106
|
+
|
107
|
+
Lets you specify one or more tables you'd like db\_leftovers to ignore completely. No objects will be added/removed to these tables. This is useful if you have tables that shouldn't be managed under db\_leftovers.
|
108
|
+
|
109
|
+
If you don't call `ignore`, the list of ignored tables defaults to `schema_migrations` and `delayed_jobs`. If you do call `ignore`, you should probably include those in your list. If you do want db\_leftovers to manage those tables, just say `ignore []`.
|
110
|
+
|
111
|
+
|
105
112
|
|
106
113
|
Running db\_leftovers
|
107
114
|
---------------------
|
@@ -120,7 +127,7 @@ and want books to have at least 100 pages, you can change your config file to sa
|
|
120
127
|
and db\_leftovers will notice the changed expression. It will drop and re-add the constraint.
|
121
128
|
|
122
129
|
One caveat, however: we pull the current expression from the database, and sometimes Postgres does things like
|
123
|
-
add type conversions and extra parentheses.
|
130
|
+
add type conversions and extra parentheses. For instance, suppose you said `check :users, :email_length, 'LENGTH(email) > 2'`.
|
124
131
|
The second time you run db\_leftovers, it will read the expression from Postgres and get `length((email)::text) > 2`,
|
125
132
|
and so it will drop and re-create the constraint.
|
126
133
|
It will drop and re-create it every time you run the rake task.
|
data/Rakefile
CHANGED
@@ -9,48 +9,19 @@ rescue Bundler::BundlerError => e
|
|
9
9
|
$stderr.puts "Run `bundle install` to install missing gems"
|
10
10
|
exit e.status_code
|
11
11
|
end
|
12
|
-
require 'rake'
|
13
12
|
|
14
|
-
require 'jeweler'
|
15
|
-
Jeweler::Tasks.new do |gem|
|
16
|
-
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
-
gem.name = "db_leftovers"
|
18
|
-
gem.homepage = "http://github.com/pjungwir/db_leftovers"
|
19
|
-
gem.license = "MIT"
|
20
|
-
gem.summary = 'Used to define indexes and foreign keys for your Rails app'
|
21
|
-
gem.description = <<-EOT
|
22
|
-
Define indexes and foreign keys for your Rails app
|
23
|
-
in one place using an easy-to-read DSL,
|
24
|
-
then run a rake task to bring your database up-to-date.
|
25
|
-
EOT
|
26
|
-
gem.email = "pj@illuminatedcomputing.com"
|
27
|
-
gem.authors = ["Paul A. Jungwirth"]
|
28
|
-
# dependencies defined in Gemfile
|
29
|
-
end
|
30
|
-
Jeweler::RubygemsDotOrgTasks.new
|
31
13
|
|
32
14
|
require 'rspec/core'
|
33
15
|
require 'rspec/core/rake_task'
|
34
16
|
RSpec::Core::RakeTask.new(:spec) do |spec|
|
35
17
|
spec.pattern = FileList['spec/**/*_spec.rb']
|
36
18
|
end
|
37
|
-
|
38
|
-
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
39
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
40
|
-
spec.rcov = true
|
41
|
-
end
|
42
|
-
|
19
|
+
desc 'Default: run specs'
|
43
20
|
task :default => :spec
|
44
21
|
|
45
|
-
require 'rdoc/task'
|
46
|
-
Rake::RDocTask.new do |rdoc|
|
47
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
22
|
|
49
|
-
|
50
|
-
|
51
|
-
rdoc.rdoc_files.include('README*')
|
52
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
-
end
|
23
|
+
Bundler::GemHelper.install_tasks
|
24
|
+
|
54
25
|
|
55
26
|
task :readme => [] do |task|
|
56
27
|
`markdown README.md >README.html`
|
data/db_leftovers.gemspec
CHANGED
@@ -1,81 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
-
# -*- encoding: utf-8 -*-
|
1
|
+
$:.push File.dirname(__FILE__) + '/lib'
|
2
|
+
require 'db_leftovers/version'
|
5
3
|
|
6
4
|
Gem::Specification.new do |s|
|
7
5
|
s.name = "db_leftovers"
|
8
|
-
s.version =
|
6
|
+
s.version = DbLeftovers::VERSION
|
7
|
+
s.date = "2013-01-15"
|
9
8
|
|
10
|
-
s.
|
11
|
-
s.authors = ["Paul A. Jungwirth"]
|
12
|
-
s.date = "2012-10-11"
|
9
|
+
s.summary = "Used to define indexes and foreign keys for your Rails app"
|
13
10
|
s.description = " Define indexes and foreign keys for your Rails app\n in one place using an easy-to-read DSL,\n then run a rake task to bring your database up-to-date.\n"
|
14
|
-
|
15
|
-
s.
|
16
|
-
"LICENSE.txt",
|
17
|
-
"README.html",
|
18
|
-
"README.md",
|
19
|
-
"TODO"
|
20
|
-
]
|
21
|
-
s.files = [
|
22
|
-
".document",
|
23
|
-
"Gemfile",
|
24
|
-
"Gemfile.lock",
|
25
|
-
"LICENSE.txt",
|
26
|
-
"README.md",
|
27
|
-
"Rakefile",
|
28
|
-
"TODO",
|
29
|
-
"VERSION",
|
30
|
-
"db_leftovers.gemspec",
|
31
|
-
"lib/db_leftovers.rb",
|
32
|
-
"lib/db_leftovers/constraint.rb",
|
33
|
-
"lib/db_leftovers/definition.rb",
|
34
|
-
"lib/db_leftovers/dsl.rb",
|
35
|
-
"lib/db_leftovers/foreign_key.rb",
|
36
|
-
"lib/db_leftovers/generic_database_interface.rb",
|
37
|
-
"lib/db_leftovers/index.rb",
|
38
|
-
"lib/db_leftovers/mysql_database_interface.rb",
|
39
|
-
"lib/db_leftovers/postgres_database_interface.rb",
|
40
|
-
"lib/db_leftovers/table_dsl.rb",
|
41
|
-
"lib/tasks/leftovers.rake",
|
42
|
-
"spec/config/database.yml.sample",
|
43
|
-
"spec/db_leftovers_spec.rb",
|
44
|
-
"spec/mysql_spec.rb",
|
45
|
-
"spec/postgres_spec.rb",
|
46
|
-
"spec/spec_helper.rb",
|
47
|
-
"spec/support/mock_database_interface.rb",
|
48
|
-
"spec/support/shared_db_tests.rb",
|
49
|
-
"spec/support/sql_matcher.rb"
|
50
|
-
]
|
11
|
+
|
12
|
+
s.authors = ["Paul A. Jungwirth"]
|
51
13
|
s.homepage = "http://github.com/pjungwir/db_leftovers"
|
14
|
+
s.email = "pj@illuminatedcomputing.com"
|
15
|
+
|
52
16
|
s.licenses = ["MIT"]
|
17
|
+
|
53
18
|
s.require_paths = ["lib"]
|
54
|
-
s.
|
55
|
-
s.
|
19
|
+
s.executables = []
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,fixtures}/*`.split("\n")
|
56
22
|
|
57
|
-
|
58
|
-
|
23
|
+
s.add_runtime_dependency 'rails', '>= 3.0.0'
|
24
|
+
s.add_development_dependency 'rspec', '~> 2.4.0'
|
25
|
+
s.add_development_dependency 'bundler', '>= 0'
|
59
26
|
|
60
|
-
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
61
|
-
s.add_runtime_dependency(%q<rails>, [">= 3.0.0"])
|
62
|
-
s.add_development_dependency(%q<rspec>, ["~> 2.4.0"])
|
63
|
-
s.add_development_dependency(%q<bundler>, [">= 0"])
|
64
|
-
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
65
|
-
s.add_development_dependency(%q<rcov>, [">= 0"])
|
66
|
-
else
|
67
|
-
s.add_dependency(%q<rails>, [">= 3.0.0"])
|
68
|
-
s.add_dependency(%q<rspec>, ["~> 2.4.0"])
|
69
|
-
s.add_dependency(%q<bundler>, [">= 0"])
|
70
|
-
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
71
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
72
|
-
end
|
73
|
-
else
|
74
|
-
s.add_dependency(%q<rails>, [">= 3.0.0"])
|
75
|
-
s.add_dependency(%q<rspec>, ["~> 2.4.0"])
|
76
|
-
s.add_dependency(%q<bundler>, [">= 0"])
|
77
|
-
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
78
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
79
|
-
end
|
80
27
|
end
|
81
28
|
|
data/lib/db_leftovers/dsl.rb
CHANGED
@@ -10,6 +10,8 @@ module DBLeftovers
|
|
10
10
|
@verbose = !!opts[:verbose]
|
11
11
|
@db = opts[:db_interface] || get_database_interface
|
12
12
|
|
13
|
+
@ignored_tables = Set.new(['delayed_jobs', 'schema_migrations'].map{|x| [x.to_s, x.to_sym]}.flatten)
|
14
|
+
|
13
15
|
@indexes_by_table = {} # Set from the DSL
|
14
16
|
@old_indexes = @db.lookup_all_indexes
|
15
17
|
@new_indexes = {}
|
@@ -27,6 +29,12 @@ module DBLeftovers
|
|
27
29
|
instance_eval(&block)
|
28
30
|
end
|
29
31
|
|
32
|
+
def ignore(*table_names)
|
33
|
+
table_names = [table_names] unless table_names.is_a? Array
|
34
|
+
table_names = table_names.map{|x| [x.to_s, x.to_sym]}.flatten
|
35
|
+
@ignored_tables = Set.new(table_names)
|
36
|
+
end
|
37
|
+
|
30
38
|
def table(table_name, &block)
|
31
39
|
table_dsl = TableDSL.new(self, table_name)
|
32
40
|
table_dsl.define(&block)
|
@@ -73,6 +81,7 @@ module DBLeftovers
|
|
73
81
|
# First create any new indexes:
|
74
82
|
@indexes_by_table.each do |table_name, indexes|
|
75
83
|
indexes.each do |idx|
|
84
|
+
next if ignore_index?(idx)
|
76
85
|
# puts "#{idx.table_name}.[#{idx.column_names.join(',')}]"
|
77
86
|
case index_status(idx)
|
78
87
|
when STATUS_EXISTS
|
@@ -91,6 +100,7 @@ module DBLeftovers
|
|
91
100
|
|
92
101
|
# Now drop any old indexes that are no longer in the definition file:
|
93
102
|
@old_indexes.each do |index_name, idx|
|
103
|
+
next if ignore_index?(idx)
|
94
104
|
if not @new_indexes[index_name]
|
95
105
|
# puts "#{index_name} #{table_name}"
|
96
106
|
@db.execute_drop_index(idx.table_name, index_name)
|
@@ -103,6 +113,7 @@ module DBLeftovers
|
|
103
113
|
# First create any new foreign keys:
|
104
114
|
@foreign_keys_by_table.each do |table_name, fks|
|
105
115
|
fks.each do |fk|
|
116
|
+
next if ignore_foreign_key?(fk)
|
106
117
|
case foreign_key_status(fk)
|
107
118
|
when STATUS_EXISTS
|
108
119
|
puts "Foreign Key already exists: #{fk.constraint_name} on #{fk.from_table}" if @verbose
|
@@ -120,6 +131,7 @@ module DBLeftovers
|
|
120
131
|
|
121
132
|
# Now drop any old foreign keys that are no longer in the definition file:
|
122
133
|
@old_foreign_keys.each do |constraint_name, fk|
|
134
|
+
next if ignore_foreign_key?(fk)
|
123
135
|
if not @new_foreign_keys[constraint_name]
|
124
136
|
@db.execute_drop_foreign_key(constraint_name, fk.from_table, fk.from_column)
|
125
137
|
puts "Dropped foreign key: #{constraint_name} on #{fk.from_table}"
|
@@ -131,6 +143,7 @@ module DBLeftovers
|
|
131
143
|
# First create any new constraints:
|
132
144
|
@constraints_by_table.each do |table_name, chks|
|
133
145
|
chks.each do |chk|
|
146
|
+
next if ignore_constraint?(chk)
|
134
147
|
case constraint_status(chk)
|
135
148
|
when STATUS_EXISTS
|
136
149
|
puts "Constraint already exists: #{chk.constraint_name} on #{chk.on_table}" if @verbose
|
@@ -148,6 +161,7 @@ module DBLeftovers
|
|
148
161
|
|
149
162
|
# Now drop any old constraints that are no longer in the definition file:
|
150
163
|
@old_constraints.each do |constraint_name, chk|
|
164
|
+
next if ignore_constraint?(chk)
|
151
165
|
if not @new_constraints[constraint_name]
|
152
166
|
@db.execute_drop_constraint(constraint_name, chk.on_table)
|
153
167
|
puts "Dropped CHECK constraint: #{constraint_name} on #{chk.on_table}"
|
@@ -234,6 +248,18 @@ module DBLeftovers
|
|
234
248
|
end
|
235
249
|
end
|
236
250
|
|
251
|
+
def ignore_index?(idx)
|
252
|
+
@ignored_tables.include?(idx.table_name)
|
253
|
+
end
|
254
|
+
|
255
|
+
def ignore_foreign_key?(fk)
|
256
|
+
@ignored_tables.include?(fk.from_table)
|
257
|
+
end
|
258
|
+
|
259
|
+
def ignore_constraint?(chk)
|
260
|
+
@ignored_tables.include?(chk.on_table)
|
261
|
+
end
|
262
|
+
|
237
263
|
end
|
238
264
|
|
239
265
|
end
|
@@ -26,7 +26,6 @@ module DBLeftovers
|
|
26
26
|
AND t.relkind = 'r'
|
27
27
|
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
28
28
|
AND pg_catalog.pg_table_is_visible(t.oid)
|
29
|
-
AND t.relname NOT IN ('delayed_jobs', 'schema_migrations')
|
30
29
|
AND NOT ix.indisprimary
|
31
30
|
GROUP BY t.relname,
|
32
31
|
i.relname,
|
data/spec/db_leftovers_spec.rb
CHANGED
@@ -459,4 +459,45 @@ describe DBLeftovers do
|
|
459
459
|
EOQ
|
460
460
|
end
|
461
461
|
|
462
|
+
it "should not try to change anything on an ignored table" do
|
463
|
+
@db.starts_with([
|
464
|
+
DBLeftovers::Index.new(:books, :shelf_id),
|
465
|
+
], [
|
466
|
+
DBLeftovers::ForeignKey.new('fk_books_shelf_id', 'books', 'shelf_id', 'shelves', 'id'),
|
467
|
+
DBLeftovers::ForeignKey.new('fk_books_author_id', 'books', 'author_id', 'authors', 'id')
|
468
|
+
], [
|
469
|
+
DBLeftovers::Constraint.new(:books_have_positive_pages, :books, 'pages_count > 0')
|
470
|
+
])
|
471
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
472
|
+
ignore :books
|
473
|
+
end
|
474
|
+
@db.sqls.should have(0).items
|
475
|
+
end
|
476
|
+
|
477
|
+
it "should accept multiple ignored tables" do
|
478
|
+
@db.starts_with([
|
479
|
+
DBLeftovers::Index.new(:books, :shelf_id),
|
480
|
+
DBLeftovers::Index.new(:authors, :last_name),
|
481
|
+
], [
|
482
|
+
DBLeftovers::ForeignKey.new('fk_books_shelf_id', 'books', 'shelf_id', 'shelves', 'id'),
|
483
|
+
DBLeftovers::ForeignKey.new('fk_books_author_id', 'books', 'author_id', 'authors', 'id')
|
484
|
+
], [
|
485
|
+
DBLeftovers::Constraint.new(:books_have_positive_pages, :books, 'pages_count > 0')
|
486
|
+
])
|
487
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
488
|
+
ignore :books, :authors
|
489
|
+
end
|
490
|
+
@db.sqls.should have(0).items
|
491
|
+
end
|
492
|
+
|
493
|
+
it "should ignore schema_migrations and delayed_jobs by default" do
|
494
|
+
@db.starts_with([
|
495
|
+
DBLeftovers::Index.new(:schema_migrations, :foo),
|
496
|
+
DBLeftovers::Index.new(:delayed_jobs, :bar),
|
497
|
+
], [], [])
|
498
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
499
|
+
end
|
500
|
+
@db.sqls.should have(0).items
|
501
|
+
end
|
502
|
+
|
462
503
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: db_leftovers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -59,58 +59,22 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: jeweler
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ~>
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 1.6.4
|
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: 1.6.4
|
78
|
-
- !ruby/object:Gem::Dependency
|
79
|
-
name: rcov
|
80
|
-
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
|
-
requirements:
|
83
|
-
- - ! '>='
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
version: '0'
|
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: '0'
|
94
62
|
description: ! " Define indexes and foreign keys for your Rails app\n in
|
95
63
|
one place using an easy-to-read DSL,\n then run a rake task to bring your
|
96
64
|
database up-to-date.\n"
|
97
65
|
email: pj@illuminatedcomputing.com
|
98
66
|
executables: []
|
99
67
|
extensions: []
|
100
|
-
extra_rdoc_files:
|
101
|
-
- LICENSE.txt
|
102
|
-
- README.html
|
103
|
-
- README.md
|
104
|
-
- TODO
|
68
|
+
extra_rdoc_files: []
|
105
69
|
files:
|
106
70
|
- .document
|
71
|
+
- .gitignore
|
107
72
|
- Gemfile
|
108
73
|
- Gemfile.lock
|
109
74
|
- LICENSE.txt
|
110
75
|
- README.md
|
111
76
|
- Rakefile
|
112
77
|
- TODO
|
113
|
-
- VERSION
|
114
78
|
- db_leftovers.gemspec
|
115
79
|
- lib/db_leftovers.rb
|
116
80
|
- lib/db_leftovers/constraint.rb
|
@@ -122,6 +86,7 @@ files:
|
|
122
86
|
- lib/db_leftovers/mysql_database_interface.rb
|
123
87
|
- lib/db_leftovers/postgres_database_interface.rb
|
124
88
|
- lib/db_leftovers/table_dsl.rb
|
89
|
+
- lib/db_leftovers/version.rb
|
125
90
|
- lib/tasks/leftovers.rake
|
126
91
|
- spec/config/database.yml.sample
|
127
92
|
- spec/db_leftovers_spec.rb
|
@@ -131,7 +96,6 @@ files:
|
|
131
96
|
- spec/support/mock_database_interface.rb
|
132
97
|
- spec/support/shared_db_tests.rb
|
133
98
|
- spec/support/sql_matcher.rb
|
134
|
-
- README.html
|
135
99
|
homepage: http://github.com/pjungwir/db_leftovers
|
136
100
|
licenses:
|
137
101
|
- MIT
|
@@ -147,7 +111,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
147
111
|
version: '0'
|
148
112
|
segments:
|
149
113
|
- 0
|
150
|
-
hash:
|
114
|
+
hash: 1094789418074050073
|
151
115
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
116
|
none: false
|
153
117
|
requirements:
|
data/README.html
DELETED
@@ -1,190 +0,0 @@
|
|
1
|
-
<h1>db_leftovers</h1>
|
2
|
-
|
3
|
-
<p>Db_leftovers lets you define indexes, foreign keys, and CHECK constraints for your Rails app
|
4
|
-
in one place using an easy-to-read DSL,
|
5
|
-
then run a rake task to bring your database up-to-date.
|
6
|
-
Whenever you edit the DSL, you can re-run the rake task and db_leftovers will alter your database accordingly.
|
7
|
-
This is useful because of the following limitations in vanilla Rails:</p>
|
8
|
-
|
9
|
-
<ul>
|
10
|
-
<li>There are no built-in migration methods to create foreign keys or CHECK constraints.</li>
|
11
|
-
<li>Even if created, foreign keys and CHECK constraints won't appear in your schema.rb.</li>
|
12
|
-
<li>The built-in <code>add_index</code> method doesn't support WHERE clauses on your indexes.</li>
|
13
|
-
<li>If you're using Heroku, <code>db:push</code> and <code>db:pull</code> won't transfer your foreign keys and CHECK constraints.</li>
|
14
|
-
<li>Creating indexes in your migrations makes it hard to manage them.</li>
|
15
|
-
</ul>
|
16
|
-
|
17
|
-
<p>That last point deserves some elaboration. First, using <code>add_index</code> in your migrations is bug-prone because without rare developer discipline, you wind up missing indexes in some environments. (My rule is "never change a migration after a <code>git push</code>," but I haven't seen this followed elsewhere, and there is nothing that automatically enforces it.) Also, since missing indexes don't cause errors, it's easy to be missing one and not notice until users start complaining about performance.</p>
|
18
|
-
|
19
|
-
<p>Second, Scattering <code>add_index</code> methods throughout migrations also doesn't match the workflow of optimizing database queries. Hopefully you create appropriate indexes when you set up your tables originally, but in practice you often need to go back later and add/remove indexes according to your database usage patterns. Or you just forget the indexes, because you're thinking about modeling the data, not optimizing the queries.
|
20
|
-
It's easier to vet and analyze database indexes if you can see them all in one place,
|
21
|
-
and db_leftovers lets you do that easily.
|
22
|
-
And since you can rest assured that each environment conforms to the same definition, you don't need to second-guess yourself about indexes that are present in development but missing in production.
|
23
|
-
Db_leftovers lets you rest assured that each environment conforms to a definition that is easy to read and checked into version control.</p>
|
24
|
-
|
25
|
-
<p>At present db_leftovers supports PostgreSQL and MySQL, although since MySQL doesn't support index WHERE clauses or CHECK constraints, using that functionality will raise errors. (If you need to share the same definitions across Postgres and MySQL, you can run arbitrary Ruby code inside the DSL to avoid defining unsupported objects when run against MySQL.)</p>
|
26
|
-
|
27
|
-
<h2>Configuration File</h2>
|
28
|
-
|
29
|
-
<p>db_leftovers reads a file named <code>config/db_leftovers.rb</code> to find out which indexes and constraints you want in your database. This file is a DSL implemented in Ruby, sort of like <code>config/routes.rb</code>. It should look something like this:</p>
|
30
|
-
|
31
|
-
<pre><code>DBLeftovers::Definition.define do
|
32
|
-
|
33
|
-
table :users do
|
34
|
-
index :email, :unique => true
|
35
|
-
check :registered_at_unless_guest, "role_name = 'guest' OR registered_at IS NOT NULL"
|
36
|
-
end
|
37
|
-
|
38
|
-
foreign_key :orders, :users
|
39
|
-
|
40
|
-
# . .
|
41
|
-
end
|
42
|
-
</code></pre>
|
43
|
-
|
44
|
-
<p>Within the DSL file, the following methods are supported:</p>
|
45
|
-
|
46
|
-
<h3>index(table_name, columns, [opts])</h3>
|
47
|
-
|
48
|
-
<p>This ensures that you have an index on the given table and column(s). The <code>columns</code> parameter can be either a string/symbol or a list of strings/symbols. Opts is a hash with the following possible keys:</p>
|
49
|
-
|
50
|
-
<ul>
|
51
|
-
<li><p><code>:name</code> The name of the index. Defaults to <code>index_</code><em>table_name</em><code>_on_</code><em>column_names</em>, like the <code>add_index</code> method from Rails migrations.</p></li>
|
52
|
-
<li><p><code>:unique</code> Set this to <code>true</code> if you'd like a unique index.</p></li>
|
53
|
-
<li><p><code>:where</code> Accepts SQL to include in the <code>WHERE</code> part of the <code>CREATE INDEX</code> command, in case you want to limit the index to a subset of the table's rows.</p></li>
|
54
|
-
</ul>
|
55
|
-
|
56
|
-
<h4>Examples</h4>
|
57
|
-
|
58
|
-
<pre><code>index :books, :author_id
|
59
|
-
index :books, [:publisher_id, :published_at]
|
60
|
-
index :books, :isbn, :unique => true
|
61
|
-
</code></pre>
|
62
|
-
|
63
|
-
<h3>foreign_key(from_table, [from_column], to_table, [to_column], [opts])</h3>
|
64
|
-
|
65
|
-
<p>This ensures that you have a foreign key relating the given tables and columns.
|
66
|
-
All parameters are strings/symbols except <code>opts</code>, which is a hash.
|
67
|
-
If you omit the column names, db_leftovers will infer them based on Rails conventions. (See examples below.)
|
68
|
-
The only option that is supported is <code>:on_delete</code>, which may have any of these values:</p>
|
69
|
-
|
70
|
-
<ul>
|
71
|
-
<li><code>nil</code> Indicates that attempting to delete the referenced row should fail (the default).</li>
|
72
|
-
<li><code>:set_null</code> Indicates that the foreign key should be set to null if the referenced row is deleted.</li>
|
73
|
-
<li><code>:cascade</code> Indicates that the referencing row should be deleted if the referenced row is deleted.</li>
|
74
|
-
</ul>
|
75
|
-
|
76
|
-
<h4>Examples</h4>
|
77
|
-
|
78
|
-
<pre><code>foreign_key :books, :author_id, :authors, :id
|
79
|
-
foreign_key :pages, :book_id, :books, :id, :on_delete => :cascade
|
80
|
-
</code></pre>
|
81
|
-
|
82
|
-
<p>With implicit column names:</p>
|
83
|
-
|
84
|
-
<pre><code>foreign_key :books, :authors
|
85
|
-
foreign_key :books, :authors, :on_delete => :cascade
|
86
|
-
foreign_key :books, :co_author_id, :authors
|
87
|
-
foreign_key :books, :co_author_id, :authors, :on_delete => :cascade
|
88
|
-
</code></pre>
|
89
|
-
|
90
|
-
<h3>check(constraint_name, on_table, expression)</h3>
|
91
|
-
|
92
|
-
<p>This ensures that you have a CHECK constraint on the given table with the given name and expression.
|
93
|
-
All parameters are strings or symbols.</p>
|
94
|
-
|
95
|
-
<h4>Examples</h4>
|
96
|
-
|
97
|
-
<pre><code>check :books, :books_have_positive_pages, 'page_count > 0'
|
98
|
-
</code></pre>
|
99
|
-
|
100
|
-
<h3>table(table_name, &block)</h3>
|
101
|
-
|
102
|
-
<p>The <code>table</code> call is just a convenience so you can group all a table's indexes et cetera together and not keep repeating the table name. You use it like this:</p>
|
103
|
-
|
104
|
-
<pre><code>table :books do
|
105
|
-
index :author_id
|
106
|
-
foreign_key :publisher_id, :publishers
|
107
|
-
check :books_have_positive_pages, 'page_count > 0'
|
108
|
-
end
|
109
|
-
</code></pre>
|
110
|
-
|
111
|
-
<p>You can repeat <code>table</code> calls for the same table several times if you like. This lets you put your indexes in one place and your foreign keys in another, for example.</p>
|
112
|
-
|
113
|
-
<h2>Running db_leftovers</h2>
|
114
|
-
|
115
|
-
<p>db_leftovers comes with a Rake task named <code>db:leftovers</code>. So you can update your database to match your config file by saying this:</p>
|
116
|
-
|
117
|
-
<pre><code>rake db:leftovers
|
118
|
-
</code></pre>
|
119
|
-
|
120
|
-
<p>db_leftovers will notice whenever an index, foreign key, or CHECK constraint needs to be added, dropped, or changed.
|
121
|
-
It will even catch cases where the name of the managed object is the same, but its attributes have changed.
|
122
|
-
For instance, if you previously required books to have at least 1 page, but now you are introducing a "pamphlet"
|
123
|
-
and want books to have at least 100 pages, you can change your config file to say:</p>
|
124
|
-
|
125
|
-
<pre><code>check :books, :books_have_positive_pages, 'page_count >= 100'
|
126
|
-
</code></pre>
|
127
|
-
|
128
|
-
<p>and db_leftovers will notice the changed expression. It will drop and re-add the constraint.</p>
|
129
|
-
|
130
|
-
<p>One caveat, however: we pull the current expression from the database, and sometimes Postgres does things like
|
131
|
-
add type conversions. If instance, suppose you said <code>check :users, :email_length, 'LENGTH(email) > 2'</code>.
|
132
|
-
The second time you run db_leftovers, it will read the expression from Postgres and get <code>LENGTH((email)::text) > 2</code>,
|
133
|
-
and so it will drop and re-create the constraint.
|
134
|
-
It will drop and re-create it every time you run the rake task.
|
135
|
-
To get around this, make sure your config file uses the same expression as printed by db_leftovers in the rake output.
|
136
|
-
This can also happen for index WHERE clauses, fixable by a similar workaround.
|
137
|
-
MySQL doesn't have this problem because it doesn't support CHECK constraints or index WHERE clauses.</p>
|
138
|
-
|
139
|
-
<p>To print messages even about indexes/foreign keys/constraints that haven't changed, you can say:</p>
|
140
|
-
|
141
|
-
<pre><code>rake db:leftovers VERBOSE=true
|
142
|
-
</code></pre>
|
143
|
-
|
144
|
-
<p>or</p>
|
145
|
-
|
146
|
-
<pre><code>rake db:leftovers DB_LEFTOVERS_VERBOSE=true
|
147
|
-
</code></pre>
|
148
|
-
|
149
|
-
<h2>Capistrano Integration</h2>
|
150
|
-
|
151
|
-
<p>I recommend running <code>rake db:migrate</code> any time you deploy, and then running <code>rake db:leftovers</code> after that. Here is what you need in your <code>config/deploy.rb</code> to make that happen:</p>
|
152
|
-
|
153
|
-
<pre><code>set :rails_env, "production"
|
154
|
-
|
155
|
-
namespace :db do
|
156
|
-
desc "Set up constraints and indexes"
|
157
|
-
task :leftovers do
|
158
|
-
run("cd #{deploy_to}/current && bundle exec rake db:leftovers RAILS_ENV=#{rails_env}")
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
after :deploy, 'deploy:migrate'
|
163
|
-
after 'deploy:migrate', 'db:leftovers'
|
164
|
-
</code></pre>
|
165
|
-
|
166
|
-
<p>You could also change this code to <em>not</em> run migrations after each deploy, if you like. But in that case I'd recommend not running db:leftovers until after the latest migrations (if any), since new entries in the DSL are likely to reference newly-created tables/columns.</p>
|
167
|
-
|
168
|
-
<h2>Known Issues</h2>
|
169
|
-
|
170
|
-
<ul>
|
171
|
-
<li><p>When db_leftovers interrogates your database for the currently-defined indexes et cetera, it doesn't filter things by the current database name. So if you have mutliple Rails projects all accessible to the same user, you'll wind up changing more than you like (probably by DROPing things).</p></li>
|
172
|
-
<li><p>It'd be nice to have another Rake task that will read your database and generate a new <code>db_leftovers.rb</code> file, to help people migrating existing projects that already have lots of tables.</p></li>
|
173
|
-
</ul>
|
174
|
-
|
175
|
-
<h2>Contributing to db_leftovers</h2>
|
176
|
-
|
177
|
-
<ul>
|
178
|
-
<li>Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.</li>
|
179
|
-
<li>Check out the issue tracker to make sure someone hasn't already requested and/or contributed it.</li>
|
180
|
-
<li>Fork the project.</li>
|
181
|
-
<li>Start a feature/bugfix branch.</li>
|
182
|
-
<li>Commit and push until you are happy with your contribution.</li>
|
183
|
-
<li>Make be sure to add tests for it. This is important so I don't break it in a future version unintentionally.</li>
|
184
|
-
<li>Please try not to mess with the Rakefile, version, or history. If you want to have your own version, that is fine, but please isolate that change to its own commit so I can cherry-pick around it.</li>
|
185
|
-
</ul>
|
186
|
-
|
187
|
-
<h2>Copyright</h2>
|
188
|
-
|
189
|
-
<p>Copyright (c) 2012 Paul A. Jungwirth.
|
190
|
-
See LICENSE.txt for further details.</p>
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
1.1.1
|