db_leftovers 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.html CHANGED
@@ -1,29 +1,51 @@
1
1
  <h1>db_leftovers</h1>
2
2
 
3
- <p>db_leftovers lets you define indexes, foreign keys, and CHECK constraints for your Rails app
3
+ <p>Db_leftovers lets you define indexes, foreign keys, and CHECK constraints for your Rails app
4
4
  in one place using an easy-to-read DSL,
5
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 accorindgly.
6
+ Whenever you edit the DSL, you can re-run the rake task and db_leftovers will alter your database accordingly.
7
7
  This is useful because of the following limitations in vanilla Rails:</p>
8
8
 
9
9
  <ul>
10
10
  <li>There are no built-in migration methods to create foreign keys or CHECK constraints.</li>
11
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>
12
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>
13
14
  <li>Creating indexes in your migrations makes it hard to manage them.</li>
14
15
  </ul>
15
16
 
16
- <p>That last point deserves some elaboration. Using <code>create_index</code> in your migrations is bug-prone because without rare developer discipline (My rule is "never change a migration after a <code>git push</code>, but I haven't seen this followed elsewhere."), you wind up missing indexes in some environments. It also means you don't have a central place to see all your indexes so you can analyze which are needed. With db_leftovers, you can rest assured that each environment conforms to a definition that is easy to read and checked into version control.</p>
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>
17
24
 
18
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>
19
26
 
20
27
  <h2>Configuration File</h2>
21
28
 
22
- <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>. There are only a few methods:</p>
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 =&gt; 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>
23
45
 
24
46
  <h3>index(table_name, columns, [opts])</h3>
25
47
 
26
- <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 or a list of strings. Opts is a hash with the following possible keys:</p>
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>
27
49
 
28
50
  <ul>
29
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>
@@ -124,10 +146,30 @@ MySQL doesn't have this problem because it doesn't support CHECK constraints or
124
146
  <pre><code>rake db:leftovers DB_LEFTOVERS_VERBOSE=true
125
147
  </code></pre>
126
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 &amp;&amp; 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
+
127
168
  <h2>Known Issues</h2>
128
169
 
129
170
  <ul>
130
- <li>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).</li>
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>
131
173
  </ul>
132
174
 
133
175
  <h2>Contributing to db_leftovers</h2>
data/README.md CHANGED
@@ -1,29 +1,50 @@
1
1
  db\_leftovers
2
2
  =============
3
3
 
4
- db\_leftovers lets you define indexes, foreign keys, and CHECK constraints for your Rails app
4
+ Db\_leftovers lets you define indexes, foreign keys, and CHECK constraints for your Rails app
5
5
  in one place using an easy-to-read DSL,
6
6
  then run a rake task to bring your database up-to-date.
7
- Whenever you edit the DSL, you can re-run the rake task and db\_leftovers will alter your database accorindgly.
7
+ Whenever you edit the DSL, you can re-run the rake task and db\_leftovers will alter your database accordingly.
8
8
  This is useful because of the following limitations in vanilla Rails:
9
9
 
10
10
  * There are no built-in migration methods to create foreign keys or CHECK constraints.
11
11
  * Even if created, foreign keys and CHECK constraints won't appear in your schema.rb.
12
+ * The built-in `add_index` method doesn't support WHERE clauses on your indexes.
12
13
  * If you're using Heroku, `db:push` and `db:pull` won't transfer your foreign keys and CHECK constraints.
13
14
  * Creating indexes in your migrations makes it hard to manage them.
14
15
 
15
- That last point deserves some elaboration. Using `create_index` in your migrations is bug-prone because without rare developer discipline (My rule is "never change a migration after a `git push`, but I haven't seen this followed elsewhere."), you wind up missing indexes in some environments. It also means you don't have a central place to see all your indexes so you can analyze which are needed. With db\_leftovers, you can rest assured that each environment conforms to a definition that is easy to read and checked into version control.
16
+ That last point deserves some elaboration. First, using `add_index` 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 `git push`," 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.
17
+
18
+ Second, scattering `add_index` 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.
19
+ It's easier to vet and analyze database indexes if you can see them all in one place,
20
+ and db\_leftovers lets you do that easily.
21
+ 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.
22
+ Db\_leftovers gives you confidence that your database matches a definition that is easy to read and checked into version control.
16
23
 
17
24
  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.)
18
25
 
19
26
  Configuration File
20
27
  ------------------
21
28
 
22
- db\_leftovers reads a file named `config/db_leftovers.rb` to find out which indexes and constraints you want in your database. This file is a DSL implemented in Ruby, sort of like `config/routes.rb`. There are only a few methods:
29
+ db\_leftovers reads a file named `config/db_leftovers.rb` to find out which indexes and constraints you want in your database. This file is a DSL implemented in Ruby, sort of like `config/routes.rb`. It should look something like this:
30
+
31
+ 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
+
43
+ Within the DSL file, the following methods are supported:
23
44
 
24
45
  ### index(table\_name, columns, [opts])
25
46
 
26
- This ensures that you have an index on the given table and column(s). The `columns` parameter can be either a string or a list of strings. Opts is a hash with the following possible keys:
47
+ This ensures that you have an index on the given table and column(s). The `columns` parameter can be either a string/symbol or a list of strings/symbols. Opts is a hash with the following possible keys:
27
48
 
28
49
  * `:name` The name of the index. Defaults to `index_`*table\_name*`_on_`*column\_names*, like the `add_index` method from Rails migrations.
29
50
 
@@ -60,7 +81,7 @@ With implicit column names:
60
81
  foreign_key :books, :co_author_id, :authors
61
82
  foreign_key :books, :co_author_id, :authors, :on_delete => :cascade
62
83
 
63
- ### check(constraint\_name, on\_table, expression)
84
+ ### check(on\_table, constraint\_name, expression)
64
85
 
65
86
  This ensures that you have a CHECK constraint on the given table with the given name and expression.
66
87
  All parameters are strings or symbols.
@@ -99,7 +120,7 @@ and want books to have at least 100 pages, you can change your config file to sa
99
120
  and db\_leftovers will notice the changed expression. It will drop and re-add the constraint.
100
121
 
101
122
  One caveat, however: we pull the current expression from the database, and sometimes Postgres does things like
102
- add type conversions. If instance, suppose you said `check :users, :email_length, 'LENGTH(email) > 2'`.
123
+ add type conversions and extra parentheses. If instance, suppose you said `check :users, :email_length, 'LENGTH(email) > 2'`.
103
124
  The second time you run db\_leftovers, it will read the expression from Postgres and get `LENGTH((email)::text) > 2`,
104
125
  and so it will drop and re-create the constraint.
105
126
  It will drop and re-create it every time you run the rake task.
@@ -118,12 +139,33 @@ or
118
139
 
119
140
 
120
141
 
142
+ Capistrano Integration
143
+ ----------------------
144
+
145
+ I recommend running `rake db:migrate` any time you deploy, and then running `rake db:leftovers` after that. Here is what you need in your `config/deploy.rb` to make that happen:
146
+
147
+ set :rails_env, "production"
148
+
149
+ namespace :db do
150
+ desc "Set up constraints and indexes"
151
+ task :leftovers do
152
+ run("cd #{deploy_to}/current && bundle exec rake db:leftovers RAILS_ENV=#{rails_env}")
153
+ end
154
+ end
155
+
156
+ after :deploy, 'deploy:migrate'
157
+ after 'deploy:migrate', 'db:leftovers'
158
+
159
+ You could also change this code to *not* 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.
160
+
161
+
162
+
121
163
  Known Issues
122
164
  ------------
123
165
 
124
- * 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).
166
+ * Multi-column foreign keys are not supported. This shouldn't be a problem for a Rails project, unless you are using a legacy database. If you need this functionality, let me know and I'll look into adding it.
125
167
 
126
-
168
+ * It'd be nice to have another Rake task that will read your database and generate a new `db_leftovers.rb` file, to help people migrating existing projects that already have lots of tables.
127
169
 
128
170
 
129
171
  Contributing to db\_leftovers
data/TODO CHANGED
@@ -1,2 +1,11 @@
1
- - Make sure everything works if the current db user has access to multiple databases: only run the code on the db given in the Rails database.yml.
1
+ - It'd be nice to have another Rake task that will read your database and generate a new `db_leftovers.rb` file, to help people migrating existing projects that already have lots of tables.
2
+
3
+ - Support multi-column foreign keys.
4
+
5
+ - Support custom names for foreign keys. (But then watch out excluding these from the list of indexes on MySQL!)
6
+
7
+ - Support CREATE INDEX CONCURRENTLY on Postgres as an option to Rake or the `define` call.
8
+
9
+ - Support NOT VALID for Postgres 9.1+ foreign keys and Postgres 9.2+ CHECK constraints.
10
+
2
11
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.0.1
data/db_leftovers.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "db_leftovers"
8
- s.version = "1.0.0"
8
+ s.version = "1.0.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Paul A. Jungwirth"]
@@ -2,12 +2,12 @@ module DBLeftovers
2
2
 
3
3
  class MysqlDatabaseInterface < GenericDatabaseInterface
4
4
 
5
- def initialize(conn=nil)
5
+ def initialize(conn=nil, database_name=nil)
6
6
  @conn = conn || ActiveRecord::Base.connection
7
+ @db_name = database_name || ActiveRecord::Base.configurations[Rails.env]['database']
7
8
  end
8
9
 
9
10
  def lookup_all_indexes
10
- # TODO: Constrain it to the database for the current Rails project:
11
11
  ret = {}
12
12
  @conn.select_values("SHOW TABLES").each do |table_name|
13
13
  indexes = {}
@@ -36,7 +36,6 @@ module DBLeftovers
36
36
 
37
37
  def lookup_all_foreign_keys
38
38
  # TODO: Support multi-column foreign keys:
39
- # TODO: Constrain it to the database for the current Rails project:
40
39
  ret = {}
41
40
  sql = <<-EOQ
42
41
  SELECT c.constraint_name,
@@ -49,6 +48,7 @@ module DBLeftovers
49
48
  information_schema.key_column_usage k
50
49
  WHERE c.constraint_schema = k.constraint_schema
51
50
  AND c.constraint_name = k.constraint_name
51
+ AND c.constraint_schema IN (#{target_databases_quoted})
52
52
  EOQ
53
53
  @conn.select_rows(sql).each do |constr_name, from_table, from_column, to_table, to_column, del_type|
54
54
  del_type = case del_type
@@ -79,5 +79,16 @@ module DBLeftovers
79
79
  execute_sql %{ALTER TABLE #{from_table} DROP FOREIGN KEY #{constraint_name}}
80
80
  end
81
81
 
82
+ private
83
+
84
+ def target_databases_quoted
85
+ case @db_name
86
+ when Array
87
+ @db_name.map{|x| "'#{x}'"}.join(", ")
88
+ else
89
+ "'#{@db_name}'"
90
+ end
91
+ end
92
+
82
93
  end
83
94
  end
@@ -7,8 +7,6 @@ module DBLeftovers
7
7
  end
8
8
 
9
9
  def lookup_all_indexes
10
- # TODO: Constrain it to the database for the current Rails project:
11
- # (current_database(), current_schema())
12
10
  ret = {}
13
11
  sql = <<-EOQ
14
12
  SELECT ix.indexrelid,
@@ -58,8 +56,6 @@ module DBLeftovers
58
56
  # confdeltype: a=nil, c=cascade, n=null
59
57
  ret = {}
60
58
  # TODO: Support multi-column foreign keys:
61
- # TODO: Constrain it to the database for the current Rails project:
62
- # (current_database(), current_schema())
63
59
  sql = <<-EOQ
64
60
  SELECT c.conname,
65
61
  t1.relname,
@@ -103,8 +99,6 @@ module DBLeftovers
103
99
  end
104
100
 
105
101
  def lookup_all_constraints
106
- # TODO: Constrain it to the database for the current Rails project:
107
- # (current_database(), current_schema())
108
102
  ret = {}
109
103
  sql = <<-EOQ
110
104
  SELECT c.conname,
data/spec/mysql_spec.rb CHANGED
@@ -18,9 +18,9 @@ describe DBLeftovers::MysqlDatabaseInterface do
18
18
  it "WARN: Skipping MySQL tests because no database found. Use spec/config/database.yml to configure one."
19
19
  else
20
20
  before do
21
- y = test_database_yml('mysql')
21
+ y = test_database_yml('mysql')
22
22
  @conn = test_db_connection(nil, y)
23
- @db = DBLeftovers::MysqlDatabaseInterface.new(@conn)
23
+ @db = DBLeftovers::MysqlDatabaseInterface.new(@conn, y['database'])
24
24
  drop_all_mysql_tables(@conn, y['database'])
25
25
  fresh_tables(@conn, y['database'])
26
26
  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.0.0
4
+ version: 1.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -147,7 +147,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
147
147
  version: '0'
148
148
  segments:
149
149
  - 0
150
- hash: 3241801701891816563
150
+ hash: -672806461406731014
151
151
  required_rubygems_version: !ruby/object:Gem::Requirement
152
152
  none: false
153
153
  requirements: