migration_comments 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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.rdoc +81 -0
- data/Rakefile +1 -0
- data/lib/migration_comments/active_record/connection_adapters/abstract_adapter.rb +41 -0
- data/lib/migration_comments/active_record/connection_adapters/column_definition.rb +5 -0
- data/lib/migration_comments/active_record/connection_adapters/comment_definition.rb +22 -0
- data/lib/migration_comments/active_record/connection_adapters/mysql2_adapter.rb +9 -0
- data/lib/migration_comments/active_record/connection_adapters/mysql_adapter.rb +97 -0
- data/lib/migration_comments/active_record/connection_adapters/postgresql_adapter.rb +111 -0
- data/lib/migration_comments/active_record/connection_adapters/table.rb +12 -0
- data/lib/migration_comments/active_record/connection_adapters/table_definition.rb +30 -0
- data/lib/migration_comments/active_record/schema_dumper.rb +54 -0
- data/lib/migration_comments/annotate_models.rb +40 -0
- data/lib/migration_comments/version.rb +3 -0
- data/lib/migration_comments.rb +58 -0
- data/migration_comments.gemspec +32 -0
- data/test/add_comments_test.rb +135 -0
- data/test/annotate_models_test.rb +53 -0
- data/test/config/database.yml +12 -0
- data/test/schema_dumper_test.rb +40 -0
- data/test/test_helper.rb +30 -0
- metadata +139 -0
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
= MigrationComments
|
2
|
+
|
3
|
+
Comments for your migrations
|
4
|
+
|
5
|
+
Tested on Ruby 1.8.6 using Rails 2.3.x.
|
6
|
+
|
7
|
+
== Why?
|
8
|
+
|
9
|
+
Migrations are wonderful. They handle all your schema changes, and in a pinch they can bring
|
10
|
+
any database up to speed. However, database schemas can change rapidly while a project is
|
11
|
+
maturing, and it can be difficult to know (or remember) the purpose for each table and field.
|
12
|
+
As such, they deserve to be commented. These comments should be available for display wherever
|
13
|
+
those fields are found.
|
14
|
+
|
15
|
+
== Solution!
|
16
|
+
|
17
|
+
Using MigrationComments, you can simply add comments during your migrations. Or if you already
|
18
|
+
have existing data structures, just add the comments afterwards in a separate migration. And of
|
19
|
+
course you can always modify and delete these comments in later migrations.
|
20
|
+
|
21
|
+
So where are these comments used? Firstly, they will be included in your schema.rb dump which
|
22
|
+
is where your IDE (e.g. RubyMine) should be learning about your model structure. This means that
|
23
|
+
they'll be available at any point in your project. Additionally, if you are using the 'annotate'
|
24
|
+
gem, these comments will be added to the annotations that are generated within your model.rb
|
25
|
+
file.
|
26
|
+
|
27
|
+
== Examples
|
28
|
+
|
29
|
+
Want to add a comment to an existing structure...
|
30
|
+
|
31
|
+
self.up
|
32
|
+
add_table_comment :table_name, "A table comment"
|
33
|
+
comment_table :table_name, "A table comment" # does the same thing
|
34
|
+
|
35
|
+
add_column_comment :table_name, :column_name, "A column comment"
|
36
|
+
comment_column :table_name, :column_name, "A column comment" # does the same thing
|
37
|
+
end
|
38
|
+
|
39
|
+
Or you can use the change_table macro...
|
40
|
+
|
41
|
+
self.up
|
42
|
+
change_table :table_name do |t|
|
43
|
+
t.comment "A table comment"
|
44
|
+
t.change_comment :column_name, "A column comment"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Creating a new table?
|
49
|
+
|
50
|
+
self.up
|
51
|
+
create_table :table_name, :comment => "A table comment" do |t|
|
52
|
+
t.string :column_name, :comment => "A column comment"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
You can also remove comments...
|
57
|
+
|
58
|
+
self.up
|
59
|
+
remove_table_comment :table_name
|
60
|
+
remove_column_comment :table_name, :column_name
|
61
|
+
end
|
62
|
+
|
63
|
+
Or you can combine these commands while modifying a table...
|
64
|
+
|
65
|
+
self.up
|
66
|
+
change_table :existing_table do |t|
|
67
|
+
t.comment nil # remove an existing table comment
|
68
|
+
t.string :new_column, :comment => "a new column" # add a new column with a comment
|
69
|
+
t.change_comment :existing_column, nil # remove a comment on an existing column
|
70
|
+
t.integer :another_existing_column, :comment => nil # remove a comment on an existing column while modifying the column type
|
71
|
+
t.boolean :column_with_comment # modify an existing column without altering the comment
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
== Requirements
|
77
|
+
|
78
|
+
You must be using a DBMS that supports COMMENTing (currently only PostgreSQL and MySQL).
|
79
|
+
|
80
|
+
If this isn't an option for you, check out the 'schema_comments' gem:
|
81
|
+
https://github.com/akm/schema_comments
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module MigrationComments::ActiveRecord::ConnectionAdapters
|
2
|
+
module AbstractAdapter
|
3
|
+
def add_table_comment(table_name, comment_text)
|
4
|
+
# SQL standard doesn't support schema commenting
|
5
|
+
raise "Table comments are not supported"
|
6
|
+
end
|
7
|
+
alias comment_table :add_table_comment
|
8
|
+
|
9
|
+
def add_column_comment(table_name, column_name, comment_text)
|
10
|
+
# SQL standard doesn't support schema commenting
|
11
|
+
raise "Column comments are not supported"
|
12
|
+
end
|
13
|
+
alias comment_column :add_column_comment
|
14
|
+
|
15
|
+
def comments_supported?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
# Remove a comment on a table (if set)
|
20
|
+
def remove_table_comment(table_name)
|
21
|
+
add_table_comment(table_name, nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Remove a comment on a column (if set)
|
25
|
+
def remove_column_comment(table_name, column_name)
|
26
|
+
add_column_comment(table_name, column_name, nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
def retrieve_table_comment(table_name)
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def retrieve_column_comments(table_name, *column_names)
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
|
37
|
+
def retrieve_column_comment(table_name, column_name)
|
38
|
+
retrieve_column_comments(table_name, column_name)[column_name]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MigrationComments::ActiveRecord::ConnectionAdapters
|
2
|
+
class CommentDefinition < Struct.new(:adapter, :table, :column_name, :comment_text)
|
3
|
+
def to_dump
|
4
|
+
table_comment? ?
|
5
|
+
"add_table_comment :#{table_name}, %{#{comment_text}}" :
|
6
|
+
"add_column_comment :#{table_name}, :#{column_name}, %{#{comment_text}}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_sql
|
10
|
+
adapter.comment_sql(self)
|
11
|
+
end
|
12
|
+
alias to_s :to_sql
|
13
|
+
|
14
|
+
def table_comment?
|
15
|
+
column_name.blank?
|
16
|
+
end
|
17
|
+
|
18
|
+
def table_name
|
19
|
+
table.respond_to?(:name) ? table.name : table
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module MigrationComments::ActiveRecord::ConnectionAdapters
|
2
|
+
module MysqlAdapter
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
attr_accessor :database_name
|
6
|
+
alias_method_chain :create_table, :migration_comments
|
7
|
+
alias_method_chain :change_column, :migration_comments
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_table_comment(table_name, comment)
|
12
|
+
execute "ALTER TABLE #{table_name} COMMENT #{escaped_comment(comment)}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_column_comment(table_name, column_name, comment)
|
16
|
+
column = column_for(table_name, column_name)
|
17
|
+
change_column table_name, column_name, column.sql_type, :comment => comment
|
18
|
+
end
|
19
|
+
|
20
|
+
def retrieve_table_comment(table_name)
|
21
|
+
result = select_rows(table_comment_sql(table_name))
|
22
|
+
result[0].nil? || result[0][0].blank? ? nil : result[0][0]
|
23
|
+
end
|
24
|
+
|
25
|
+
def retrieve_column_comments(table_name, *column_names)
|
26
|
+
result = select_rows(column_comment_sql(table_name, *column_names))
|
27
|
+
return {} if result.nil?
|
28
|
+
return result.inject({}){|m, row| m[row[0].to_sym] = (row[1].blank? ? nil : row[1]); m}
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_table_with_migration_comments(table_name, options={}, &block)
|
32
|
+
local_table_definition = nil
|
33
|
+
create_table_without_migration_comments(table_name, options) do |td|
|
34
|
+
local_table_definition = td
|
35
|
+
local_table_definition.comment options[:comment] if options.has_key?(:comment)
|
36
|
+
block.call(td)
|
37
|
+
end
|
38
|
+
comments = local_table_definition.collect_comments(table_name)
|
39
|
+
comments.each do |comment_definition|
|
40
|
+
execute_comment comment_definition
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def change_column_with_migration_comments(table_name, column_name, type, options={})
|
45
|
+
unless options.keys.include?(:comment)
|
46
|
+
options.merge!(:comment => retrieve_column_comment(table_name, column_name))
|
47
|
+
end
|
48
|
+
change_column_without_migration_comments(table_name, column_name, type, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_column_options!(sql, options)
|
52
|
+
super(sql, options)
|
53
|
+
if options.keys.include?(:comment)
|
54
|
+
sql << " COMMENT #{escaped_comment(options[:comment])}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute_comment(comment_definition)
|
59
|
+
if comment_definition.table_comment?
|
60
|
+
add_table_comment comment_definition.table_name, comment_definition.comment_text
|
61
|
+
else
|
62
|
+
add_column_comment comment_definition.table_name, comment_definition.column_name, comment_definition.comment_text
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def escaped_comment(comment)
|
68
|
+
comment.nil? ? "''" : "'#{comment.gsub("'", "''").gsub("\\", "\\\\\\\\")}'"
|
69
|
+
end
|
70
|
+
|
71
|
+
def table_comment_sql(table_name)
|
72
|
+
ensure_database_name
|
73
|
+
<<SQL
|
74
|
+
SELECT table_comment FROM INFORMATION_SCHEMA.TABLES
|
75
|
+
WHERE table_schema = '#{database_name}'
|
76
|
+
AND table_name = '#{table_name}'
|
77
|
+
SQL
|
78
|
+
end
|
79
|
+
|
80
|
+
def column_comment_sql(table_name, *column_names)
|
81
|
+
ensure_database_name
|
82
|
+
col_matcher_sql = column_names.empty? ? "" : " AND column_name IN (#{column_names.map{|c_name| "'#{c_name}'"}.join(',')})"
|
83
|
+
<<SQL
|
84
|
+
SELECT column_name, column_comment FROM INFORMATION_SCHEMA.COLUMNS
|
85
|
+
WHERE table_schema = '#{database_name}'
|
86
|
+
AND table_name = '#{table_name}' #{col_matcher_sql}
|
87
|
+
SQL
|
88
|
+
end
|
89
|
+
|
90
|
+
def ensure_database_name
|
91
|
+
return if database_name
|
92
|
+
info = YAML::load(IO.read('config/database.yml'))
|
93
|
+
@database_name = info[ENV['DB'] || RAILS_ENV]["database"]
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module MigrationComments::ActiveRecord::ConnectionAdapters
|
2
|
+
module PostgreSQLAdapter
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
alias_method_chain :create_table, :migration_comments
|
6
|
+
alias_method_chain :add_column, :migration_comments
|
7
|
+
alias_method_chain :change_column, :migration_comments
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def comments_supported?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
# Set a comment on a table
|
16
|
+
def add_table_comment(table_name, comment_text)
|
17
|
+
execute CommentDefinition.new(self, table_name, nil, comment_text).to_sql
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set a comment on a column
|
21
|
+
def add_column_comment(table_name, column_name, comment_text)
|
22
|
+
execute CommentDefinition.new(self, table_name, column_name, comment_text).to_sql
|
23
|
+
end
|
24
|
+
|
25
|
+
def retrieve_table_comment(table_name)
|
26
|
+
result = execute(table_comment_sql(table_name)).result
|
27
|
+
result[0].nil? ? nil : result[0][0]
|
28
|
+
end
|
29
|
+
|
30
|
+
def retrieve_column_comments(table_name, *column_names)
|
31
|
+
result = execute(column_comment_sql(table_name, *column_names)).result
|
32
|
+
return {} if result.nil?
|
33
|
+
return result.inject({}){|m, row| m[row[0].to_sym] = row[1]; m}
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_table_with_migration_comments(table_name, options = {}, &block)
|
37
|
+
local_table_definition = nil
|
38
|
+
create_table_without_migration_comments(table_name, options) do |td|
|
39
|
+
local_table_definition = td
|
40
|
+
local_table_definition.comment options[:comment] if options.has_key?(:comment)
|
41
|
+
block.call(td)
|
42
|
+
end
|
43
|
+
comments = local_table_definition.collect_comments(table_name)
|
44
|
+
comments.each do |comment_definition|
|
45
|
+
execute comment_definition.to_sql
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_column_with_migration_comments(table_name, column_name, type, options = {})
|
50
|
+
add_column_without_migration_comments(table_name, column_name, type, options)
|
51
|
+
if options[:comment]
|
52
|
+
add_column_comment(table_name, column_name, options[:comment])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def change_column_with_migration_comments(table_name, column_name, type, options = {})
|
57
|
+
change_column_without_migration_comments(table_name, column_name, type, options)
|
58
|
+
if options.keys.include?(:comment)
|
59
|
+
add_column_comment(table_name, column_name, options[:comment])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def comment_sql(comment_definition)
|
64
|
+
"COMMENT ON #{comment_target(comment_definition)} IS #{escaped_comment(comment_definition.comment_text)}"
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
private
|
70
|
+
def comment_target(comment_definition)
|
71
|
+
comment_definition.table_comment? ?
|
72
|
+
"TABLE #{quote_table_name(comment_definition.table_name)}" :
|
73
|
+
"COLUMN #{quote_table_name(comment_definition.table_name)}.#{quote_column_name(comment_definition.column_name)}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def escaped_comment(comment)
|
77
|
+
comment.nil? ? 'NULL' : "'#{comment.gsub("'", "''")}'"
|
78
|
+
end
|
79
|
+
|
80
|
+
def table_comment_sql(table_name)
|
81
|
+
<<SQL
|
82
|
+
SELECT d.description FROM (
|
83
|
+
#{table_oids(table_name)}) tt
|
84
|
+
JOIN pg_catalog.pg_description d
|
85
|
+
ON tt.oid = d.objoid AND tt.tableoid = d.classoid AND d.objsubid = 0;
|
86
|
+
SQL
|
87
|
+
end
|
88
|
+
|
89
|
+
def column_comment_sql(table_name, *column_names)
|
90
|
+
col_matcher_sql = column_names.empty? ? "" : " a.attname IN (#{column_names.map{|c_name| "'#{c_name}'"}.join(',')}) AND "
|
91
|
+
<<SQL
|
92
|
+
SELECT a.attname, pg_catalog.col_description(a.attrelid, a.attnum)
|
93
|
+
FROM pg_catalog.pg_attribute a
|
94
|
+
JOIN (
|
95
|
+
#{table_oids(table_name)}) tt
|
96
|
+
ON tt.oid = a.attrelid
|
97
|
+
WHERE #{col_matcher_sql} a.attnum > 0 AND NOT a.attisdropped;
|
98
|
+
SQL
|
99
|
+
end
|
100
|
+
|
101
|
+
def table_oids(table_name)
|
102
|
+
<<SQL
|
103
|
+
SELECT c.oid, c.tableoid
|
104
|
+
FROM pg_catalog.pg_class c
|
105
|
+
WHERE c.relname = '#{table_name}'
|
106
|
+
AND c.relkind = 'r'
|
107
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
108
|
+
SQL
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module MigrationComments::ActiveRecord::ConnectionAdapters
|
2
|
+
module Table
|
3
|
+
def change_comment(column_name, comment_text)
|
4
|
+
@base.add_column_comment(@table_name, column_name, comment_text)
|
5
|
+
end
|
6
|
+
|
7
|
+
def change_table_comment(comment_text)
|
8
|
+
@base.add_table_comment(@table_name, comment_text)
|
9
|
+
end
|
10
|
+
alias comment :change_table_comment
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module MigrationComments::ActiveRecord::ConnectionAdapters
|
2
|
+
module TableDefinition
|
3
|
+
|
4
|
+
attr_accessor :table_comment
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
alias_method_chain :column, :migration_comments
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def comment(text)
|
12
|
+
@table_comment = CommentDefinition.new(@base, nil, nil, text)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def column_with_migration_comments(name, type, options = {})
|
17
|
+
column_without_migration_comments(name, type, options)
|
18
|
+
col = self[name]
|
19
|
+
col.comment = CommentDefinition.new(@base, nil, name, options[:comment])
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def collect_comments(table_name)
|
24
|
+
comments = []
|
25
|
+
comments << @table_comment << @columns.map(&:comment)
|
26
|
+
comments.flatten!.compact!
|
27
|
+
comments.each{|comment| comment.table = table_name}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module MigrationComments::ActiveRecord
|
2
|
+
module SchemaDumper
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
alias_method_chain :table, :migration_comments
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def table_with_migration_comments(table, stream)
|
10
|
+
tbl_stream = StringIO.new
|
11
|
+
table_without_migration_comments(table, tbl_stream)
|
12
|
+
tbl_stream.rewind
|
13
|
+
commented_stream = append_comments(table, tbl_stream)
|
14
|
+
tbl_stream.close
|
15
|
+
stream.print commented_stream.read
|
16
|
+
end
|
17
|
+
|
18
|
+
def append_comments(table, stream)
|
19
|
+
table_name = table.inspect.gsub('"', '')
|
20
|
+
table_comment = @connection.retrieve_table_comment(table_name)
|
21
|
+
column_comments = @connection.retrieve_column_comments(table_name)
|
22
|
+
comment_stream = StringIO.new
|
23
|
+
lines = []
|
24
|
+
table_line = 0
|
25
|
+
col_names = {}
|
26
|
+
while (line = stream.gets)
|
27
|
+
content = line.chomp
|
28
|
+
if content =~ /create_table\s/
|
29
|
+
table_line = lines.size
|
30
|
+
elsif content =~ /t\.\w+\s+"(\w+)"/
|
31
|
+
col_names[lines.size] = $1.to_sym
|
32
|
+
end
|
33
|
+
lines << content
|
34
|
+
end
|
35
|
+
len = col_names.keys.map{|index| lines[index]}.map(&:length).max + 2
|
36
|
+
lines.each_with_index do |line, index|
|
37
|
+
if table_line == index && table_comment.present?
|
38
|
+
block_init = " do |t|"
|
39
|
+
line.chomp!(block_init) << ", " << render_comment(table_comment) << block_init
|
40
|
+
elsif col_names[index]
|
41
|
+
comment = column_comments[col_names[index]]
|
42
|
+
line << ',' << ' ' * (len - line.length) << render_comment(comment) unless comment.blank?
|
43
|
+
end
|
44
|
+
comment_stream.puts line
|
45
|
+
end
|
46
|
+
comment_stream.rewind
|
47
|
+
comment_stream
|
48
|
+
end
|
49
|
+
|
50
|
+
def render_comment(comment)
|
51
|
+
":comment => \"#{comment}\""
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module MigrationComments
|
2
|
+
module AnnotateModels
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
class << self
|
6
|
+
include ClassMethods
|
7
|
+
alias_method_chain :get_schema_info, :migration_comments
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def get_schema_info_with_migration_comments(*args)
|
14
|
+
info = get_schema_info_without_migration_comments(*args)
|
15
|
+
klass = args[0]
|
16
|
+
commented_info(klass, info)
|
17
|
+
end
|
18
|
+
|
19
|
+
def commented_info(klass, info)
|
20
|
+
table_name = klass.table_name
|
21
|
+
adapter = klass.connection
|
22
|
+
table_comment = adapter.retrieve_table_comment(table_name)
|
23
|
+
column_comments = adapter.retrieve_column_comments(table_name)
|
24
|
+
lines = []
|
25
|
+
info.each_line{|l| lines << l.chomp}
|
26
|
+
column_regex = /^#\s+(\w+)\s+:\w+/
|
27
|
+
len = lines.select{|l| l =~ column_regex}.map{|l| l.length}.max
|
28
|
+
lines.each do |line|
|
29
|
+
if line =~ /# Table name: |# table \+\w+\+ /
|
30
|
+
line << " # #{table_comment}" if table_comment
|
31
|
+
elsif line =~ column_regex
|
32
|
+
comment = column_comments[$1.to_sym]
|
33
|
+
line << " " * (len - line.length) << " # #{comment}" if comment
|
34
|
+
end
|
35
|
+
end
|
36
|
+
lines.join($/) + $/
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "migration_comments/version"
|
2
|
+
|
3
|
+
require 'migration_comments/active_record/schema_dumper'
|
4
|
+
require 'migration_comments/active_record/connection_adapters/comment_definition'
|
5
|
+
require 'migration_comments/active_record/connection_adapters/column_definition'
|
6
|
+
require 'migration_comments/active_record/connection_adapters/table'
|
7
|
+
require 'migration_comments/active_record/connection_adapters/table_definition'
|
8
|
+
require 'migration_comments/active_record/connection_adapters/abstract_adapter'
|
9
|
+
require 'migration_comments/active_record/connection_adapters/mysql_adapter'
|
10
|
+
require 'migration_comments/active_record/connection_adapters/mysql2_adapter'
|
11
|
+
require 'migration_comments/active_record/connection_adapters/postgresql_adapter'
|
12
|
+
|
13
|
+
module MigrationComments
|
14
|
+
def self.setup
|
15
|
+
base_names = %w(SchemaDumper) +
|
16
|
+
%w(ColumnDefinition Table TableDefinition AbstractAdapter).map{|name| "ConnectionAdapters::#{name}"}
|
17
|
+
|
18
|
+
base_names.each do |base_name|
|
19
|
+
ar_class = "ActiveRecord::#{base_name}".constantize
|
20
|
+
mc_class = "MigrationComments::ActiveRecord::#{base_name}".constantize
|
21
|
+
unless ar_class.ancestors.include?(mc_class)
|
22
|
+
ar_class.__send__(:include, mc_class)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
%w(PostgreSQL Mysql Mysql2).each do |adapter|
|
27
|
+
begin
|
28
|
+
require("active_record/connection_adapters/#{adapter.downcase}_adapter")
|
29
|
+
adapter_class = ('ActiveRecord::ConnectionAdapters::' << "#{adapter}Adapter").constantize
|
30
|
+
mc_class = ('MigrationComments::ActiveRecord::ConnectionAdapters::' << "#{adapter}Adapter").constantize
|
31
|
+
adapter_class.module_eval do
|
32
|
+
adapter_class.__send__(:include, mc_class)
|
33
|
+
end
|
34
|
+
rescue Exception => ex
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# annotations are not required for this gem, but if they exist they should be updated
|
39
|
+
begin
|
40
|
+
begin # first try to load from the 'annotate' gem
|
41
|
+
require 'annotate/annotate_models'
|
42
|
+
rescue Exception => ex
|
43
|
+
# continue as it may be already accessible through a plugin
|
44
|
+
end
|
45
|
+
gem_class = AnnotateModels
|
46
|
+
# don't require this until after the original AnnotateModels loads to avoid namespace confusion
|
47
|
+
require 'migration_comments/annotate_models'
|
48
|
+
mc_class = MigrationComments::AnnotateModels
|
49
|
+
unless gem_class.ancestors.include?(mc_class)
|
50
|
+
gem_class.__send__(:include, mc_class)
|
51
|
+
end
|
52
|
+
rescue Exception => ex
|
53
|
+
# if we got here, don't bother installing comments into annotations
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
MigrationComments.setup
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "migration_comments/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "migration_comments"
|
7
|
+
s.version = MigrationComments::VERSION
|
8
|
+
s.authors = ["Pinny"]
|
9
|
+
s.email = ["pinny@medwiztech.com"]
|
10
|
+
s.homepage = "https://github.com/pinnymz/migration_comments"
|
11
|
+
s.summary = %q{Comments for your migrations}
|
12
|
+
s.description = %q{Add schema comments in your migrations, see them in model annotations and db/schema.rb dump}
|
13
|
+
|
14
|
+
s.rubyforge_project = "migration_comments"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_runtime_dependency 'rails', '~> 2.3', '>= 2.3.2'
|
22
|
+
|
23
|
+
# for development, we are testing against the 'annotate' gem
|
24
|
+
# however, the comments should work with the original 'annotate_models' plugin as well at:
|
25
|
+
# http://repo.pragprog.com/svn/Public/plugins/annotate_models
|
26
|
+
# provided the environment is not loaded until _after_ the AnnotateModels module is declared
|
27
|
+
s.add_development_dependency 'annotate'
|
28
|
+
|
29
|
+
s.add_development_dependency 'postgres-pr' # replace with other adapter as needed
|
30
|
+
# s.add_development_dependency 'mysql'
|
31
|
+
# s.add_development_dependency 'mysql2'
|
32
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class AddCommentsTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
def test_adding_a_table_comment
|
7
|
+
comment_text = "a comment on the sample table"
|
8
|
+
result = nil
|
9
|
+
ActiveRecord::Schema.define do
|
10
|
+
add_table_comment :sample, comment_text
|
11
|
+
result = retrieve_table_comment :sample
|
12
|
+
end
|
13
|
+
assert_equal comment_text, result
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_adding_a_column_comment
|
17
|
+
comment_text = "a comment on the sample table in the column field"
|
18
|
+
result_field1 = nil
|
19
|
+
result_field2 = nil
|
20
|
+
ActiveRecord::Schema.define do
|
21
|
+
add_column_comment :sample, :field1, comment_text
|
22
|
+
result_field1 = retrieve_column_comment :sample, :field1
|
23
|
+
result_field2 = retrieve_column_comment :sample, :field2
|
24
|
+
end
|
25
|
+
assert_equal comment_text, result_field1
|
26
|
+
assert_nil result_field2
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_creating_a_table_with_table_and_column_comments
|
30
|
+
table_comment = "a table comment"
|
31
|
+
column_comment = "a column comment"
|
32
|
+
result_table_comment = nil
|
33
|
+
result_column_comments = nil
|
34
|
+
ActiveRecord::Schema.define do
|
35
|
+
begin
|
36
|
+
create_table :sample2, :comment => table_comment do |t|
|
37
|
+
t.integer :field1, :comment => column_comment
|
38
|
+
t.string :field2
|
39
|
+
end
|
40
|
+
result_table_comment = retrieve_table_comment :sample2
|
41
|
+
result_column_comments = retrieve_column_comments :sample2
|
42
|
+
ensure
|
43
|
+
drop_table :sample2
|
44
|
+
end
|
45
|
+
end
|
46
|
+
assert_equal table_comment, result_table_comment
|
47
|
+
assert_equal column_comment, result_column_comments[:field1]
|
48
|
+
assert_nil result_column_comments[:field2]
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_changing_a_table_with_new_comments
|
52
|
+
table_comment = "a table comment"
|
53
|
+
column_comment1 = "a column comment"
|
54
|
+
column_comment2 = "another column comment"
|
55
|
+
result_table_comment = nil
|
56
|
+
result_column_comments = nil
|
57
|
+
ActiveRecord::Schema.define do
|
58
|
+
change_table :sample do |t|
|
59
|
+
t.comment table_comment
|
60
|
+
t.change :field1, :string, :comment => column_comment1
|
61
|
+
t.change :field2, :integer
|
62
|
+
t.boolean :field3, :comment => column_comment2
|
63
|
+
end
|
64
|
+
result_table_comment = retrieve_table_comment :sample
|
65
|
+
result_column_comments = retrieve_column_comments :sample
|
66
|
+
end
|
67
|
+
assert_equal table_comment, result_table_comment
|
68
|
+
assert_equal column_comment1, result_column_comments[:field1]
|
69
|
+
assert_equal column_comment2, result_column_comments[:field3]
|
70
|
+
assert_nil result_column_comments[:field2]
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_partially_modifying_comments_from_a_table
|
74
|
+
table_comment = "a table comment"
|
75
|
+
column_comment1 = "a column comment"
|
76
|
+
column_comment2 = "another column comment"
|
77
|
+
column_comment3 = "yet a third column comment"
|
78
|
+
modified_comment = "modified comment"
|
79
|
+
result_table_comment = nil
|
80
|
+
result_column_comments = nil
|
81
|
+
ActiveRecord::Schema.define do
|
82
|
+
change_table :sample do |t|
|
83
|
+
t.comment table_comment
|
84
|
+
t.change :field1, :string, :comment => column_comment1
|
85
|
+
t.change :field2, :integer, :comment => column_comment2
|
86
|
+
t.boolean :field3, :comment => column_comment3
|
87
|
+
end
|
88
|
+
change_table :sample do |t|
|
89
|
+
t.comment nil
|
90
|
+
t.change :field1, :string
|
91
|
+
t.change :field2, :integer, :comment => modified_comment
|
92
|
+
t.change :field3, :boolean, :comment => nil
|
93
|
+
end
|
94
|
+
result_table_comment = retrieve_table_comment :sample
|
95
|
+
result_column_comments = retrieve_column_comments :sample
|
96
|
+
end
|
97
|
+
assert_nil result_table_comment
|
98
|
+
assert_equal column_comment1, result_column_comments[:field1]
|
99
|
+
assert_equal modified_comment, result_column_comments[:field2]
|
100
|
+
assert_nil result_column_comments[:field3]
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_removing_comments_from_a_table
|
104
|
+
comment_text = "a comment on the sample table"
|
105
|
+
result = nil
|
106
|
+
ActiveRecord::Schema.define do
|
107
|
+
add_table_comment :sample, comment_text
|
108
|
+
remove_table_comment :sample
|
109
|
+
result = retrieve_table_comment :sample
|
110
|
+
end
|
111
|
+
assert_nil result
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_removing_comments_from_a_column
|
115
|
+
comment_text = "a comment on field1 of sample table"
|
116
|
+
result = nil
|
117
|
+
ActiveRecord::Schema.define do
|
118
|
+
add_column_comment :sample, :field1, comment_text
|
119
|
+
remove_column_comment :sample, :field1
|
120
|
+
result = retrieve_column_comment :sample, :field1
|
121
|
+
end
|
122
|
+
assert_nil result
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_comment_text_is_escaped_properly
|
126
|
+
comment_text = "a \"comment\" \\ that ' needs; escaping''"
|
127
|
+
result = nil
|
128
|
+
ActiveRecord::Schema.define do
|
129
|
+
add_table_comment :sample, comment_text
|
130
|
+
result = retrieve_table_comment :sample
|
131
|
+
end
|
132
|
+
assert_equal comment_text, result
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
gem 'annotate'
|
3
|
+
require 'annotate/annotate_models'
|
4
|
+
|
5
|
+
class Sample < ActiveRecord::Base
|
6
|
+
self.table_name = 'sample'
|
7
|
+
end
|
8
|
+
|
9
|
+
class AnnotateModelsTest < Test::Unit::TestCase
|
10
|
+
include TestHelper
|
11
|
+
|
12
|
+
TEST_PREFIX = "== Schema Information"
|
13
|
+
|
14
|
+
def test_annotate_includes_comments
|
15
|
+
db_type = :default
|
16
|
+
ActiveRecord::Schema.define do
|
17
|
+
db_type = :postgres if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) rescue false
|
18
|
+
|
19
|
+
add_table_comment :sample, "a table comment"
|
20
|
+
add_column_comment :sample, :field1, "a \"comment\" \\ that ' needs; escaping''"
|
21
|
+
add_column :sample, :field3, :string, :null => false, :comment => "third column comment"
|
22
|
+
end
|
23
|
+
|
24
|
+
result = AnnotateModels.get_schema_info(Sample, TEST_PREFIX)
|
25
|
+
postgres_expected = <<EOS
|
26
|
+
# #{TEST_PREFIX}
|
27
|
+
#
|
28
|
+
# Table name: sample # a table comment
|
29
|
+
#
|
30
|
+
# id :integer not null, primary key
|
31
|
+
# field1 :string(255) # a "comment" \\ that ' needs; escaping''
|
32
|
+
# field2 :integer
|
33
|
+
# field3 :string(255) not null # third column comment
|
34
|
+
#
|
35
|
+
|
36
|
+
EOS
|
37
|
+
default_expected = <<EOS
|
38
|
+
# #{TEST_PREFIX}
|
39
|
+
#
|
40
|
+
# Table name: sample # a table comment
|
41
|
+
#
|
42
|
+
# id :integer(4) not null, primary key
|
43
|
+
# field1 :string(255) # a "comment" \\ that ' needs; escaping''
|
44
|
+
# field2 :integer(4)
|
45
|
+
# field3 :string(255) not null # third column comment
|
46
|
+
#
|
47
|
+
|
48
|
+
EOS
|
49
|
+
expected = instance_eval "#{db_type}_expected"
|
50
|
+
assert_equal expected, result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SchemaDumperTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
def test_dump
|
7
|
+
ActiveRecord::Schema.define do
|
8
|
+
add_table_comment :sample, "a table comment"
|
9
|
+
add_column_comment :sample, :field1, "a \"comment\" \\ that ' needs; escaping''"
|
10
|
+
add_column :sample, :field3, :string, :null => false, :comment => "third column comment"
|
11
|
+
end
|
12
|
+
dest = StringIO.new
|
13
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, dest)
|
14
|
+
dest.rewind
|
15
|
+
result = dest.read
|
16
|
+
expected = <<EOS
|
17
|
+
# This file is auto-generated from the current state of the database. Instead of editing this file,
|
18
|
+
# please use the migrations feature of Active Record to incrementally modify your database, and
|
19
|
+
# then regenerate this schema definition.
|
20
|
+
#
|
21
|
+
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
|
22
|
+
# to create the application database on another system, you should be using db:schema:load, not running
|
23
|
+
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
24
|
+
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
25
|
+
#
|
26
|
+
# It's strongly recommended to check this file into your version control system.
|
27
|
+
|
28
|
+
ActiveRecord::Schema.define(:version => 1) do
|
29
|
+
|
30
|
+
create_table "sample", :force => true, :comment => "a table comment" do |t|
|
31
|
+
t.string "field1", :comment => "a \"comment\" \\ that ' needs; escaping''"
|
32
|
+
t.integer "field2"
|
33
|
+
t.string "field3", :null => false, :comment => "third column comment"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
EOS
|
38
|
+
assert_equal expected, result
|
39
|
+
end
|
40
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
gem 'rails', '>= 2.3.2'
|
5
|
+
require 'active_record'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
CONFIGURATIONS = YAML::load(IO.read('config/database.yml'))
|
9
|
+
|
10
|
+
ActiveRecord::Base.establish_connection(CONFIGURATIONS[ENV['DB'] || 'postgres'])
|
11
|
+
|
12
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
13
|
+
require 'migration_comments'
|
14
|
+
|
15
|
+
module TestHelper
|
16
|
+
def setup
|
17
|
+
ActiveRecord::Schema.define(:version => 1) do
|
18
|
+
create_table :sample do |t|
|
19
|
+
t.string :field1
|
20
|
+
t.integer :field2
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def teardown
|
26
|
+
ActiveRecord::Schema.define do
|
27
|
+
drop_table :sample
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: migration_comments
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Pinny
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-02-10 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rails
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 5
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 3
|
33
|
+
version: "2.3"
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
hash: 7
|
37
|
+
segments:
|
38
|
+
- 2
|
39
|
+
- 3
|
40
|
+
- 2
|
41
|
+
version: 2.3.2
|
42
|
+
type: :runtime
|
43
|
+
version_requirements: *id001
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: annotate
|
46
|
+
prerelease: false
|
47
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
hash: 3
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
type: :development
|
57
|
+
version_requirements: *id002
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: postgres-pr
|
60
|
+
prerelease: false
|
61
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
type: :development
|
71
|
+
version_requirements: *id003
|
72
|
+
description: Add schema comments in your migrations, see them in model annotations and db/schema.rb dump
|
73
|
+
email:
|
74
|
+
- pinny@medwiztech.com
|
75
|
+
executables: []
|
76
|
+
|
77
|
+
extensions: []
|
78
|
+
|
79
|
+
extra_rdoc_files: []
|
80
|
+
|
81
|
+
files:
|
82
|
+
- .gitignore
|
83
|
+
- Gemfile
|
84
|
+
- README.rdoc
|
85
|
+
- Rakefile
|
86
|
+
- lib/migration_comments.rb
|
87
|
+
- lib/migration_comments/active_record/connection_adapters/abstract_adapter.rb
|
88
|
+
- lib/migration_comments/active_record/connection_adapters/column_definition.rb
|
89
|
+
- lib/migration_comments/active_record/connection_adapters/comment_definition.rb
|
90
|
+
- lib/migration_comments/active_record/connection_adapters/mysql2_adapter.rb
|
91
|
+
- lib/migration_comments/active_record/connection_adapters/mysql_adapter.rb
|
92
|
+
- lib/migration_comments/active_record/connection_adapters/postgresql_adapter.rb
|
93
|
+
- lib/migration_comments/active_record/connection_adapters/table.rb
|
94
|
+
- lib/migration_comments/active_record/connection_adapters/table_definition.rb
|
95
|
+
- lib/migration_comments/active_record/schema_dumper.rb
|
96
|
+
- lib/migration_comments/annotate_models.rb
|
97
|
+
- lib/migration_comments/version.rb
|
98
|
+
- migration_comments.gemspec
|
99
|
+
- test/add_comments_test.rb
|
100
|
+
- test/annotate_models_test.rb
|
101
|
+
- test/config/database.yml
|
102
|
+
- test/schema_dumper_test.rb
|
103
|
+
- test/test_helper.rb
|
104
|
+
has_rdoc: true
|
105
|
+
homepage: https://github.com/pinnymz/migration_comments
|
106
|
+
licenses: []
|
107
|
+
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
hash: 3
|
119
|
+
segments:
|
120
|
+
- 0
|
121
|
+
version: "0"
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
hash: 3
|
128
|
+
segments:
|
129
|
+
- 0
|
130
|
+
version: "0"
|
131
|
+
requirements: []
|
132
|
+
|
133
|
+
rubyforge_project: migration_comments
|
134
|
+
rubygems_version: 1.4.2
|
135
|
+
signing_key:
|
136
|
+
specification_version: 3
|
137
|
+
summary: Comments for your migrations
|
138
|
+
test_files: []
|
139
|
+
|