schema_comments 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +0 -5
- data/.rspec +1 -0
- data/.travis.yml +0 -1
- data/Gemfile +12 -1
- data/MIT-LICENSE +20 -0
- data/README.md +11 -84
- data/Rakefile +6 -0
- data/gemfiles/Gemfile.rails-4.0 +2 -0
- data/gemfiles/Gemfile.rails-4.1 +2 -0
- data/gemfiles/Gemfile.rails-4.2 +2 -0
- data/lib/schema_comments.rb +25 -4
- data/lib/schema_comments/connection_adapters.rb +0 -14
- data/lib/schema_comments/dummy_migration.rb +7 -0
- data/lib/schema_comments/railtie.rb +1 -0
- data/lib/schema_comments/schema_comment.rb +42 -10
- data/lib/schema_comments/schema_dumper.rb +8 -3
- data/lib/schema_comments/schema_dumper/mysql.rb +7 -2
- data/lib/schema_comments/tasks/schema_comments.rake +23 -155
- data/lib/schema_comments/version.rb +1 -1
- data/lib/tasks/schema_comments_tasks.rake +4 -0
- data/log/.keep +0 -0
- data/schema_comments.gemspec +21 -22
- metadata +15 -22
- data/LICENSE.txt +0 -60
- data/VERSION +0 -1
- data/bin/console +0 -14
- data/bin/setup +0 -7
- data/init.rb +0 -4
- data/lib/annotate_models.rb +0 -234
- data/lib/schema_comments/base.rb +0 -69
- data/lib/schema_comments/migration.rb +0 -16
- data/lib/schema_comments/migrator.rb +0 -16
- data/lib/schema_comments/tasks/alter_comments.rake +0 -53
- data/lib/schema_comments/tasks/annotate_models_tasks.rake +0 -12
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.2.0
|
data/bin/console
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "schema_comments"
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "irb"
|
14
|
-
IRB.start
|
data/bin/setup
DELETED
data/init.rb
DELETED
data/lib/annotate_models.rb
DELETED
@@ -1,234 +0,0 @@
|
|
1
|
-
|
2
|
-
# fork from
|
3
|
-
# http://github.com/rotuka/annotate_models/blob/d2afee82020dbc592b147d92f9beeadbf665a9e0/lib/annotate_models.rb
|
4
|
-
|
5
|
-
require 'rails'
|
6
|
-
|
7
|
-
require "config/environment" if File.exist?("config/environment")
|
8
|
-
|
9
|
-
MODEL_DIR = Rails.root.join("app/models" )
|
10
|
-
UNIT_TEST_DIR = Rails.root.join("test/unit" )
|
11
|
-
SPEC_MODEL_DIR = Rails.root.join("spec/models")
|
12
|
-
FIXTURES_DIR = Rails.root.join("test/fixtures")
|
13
|
-
SPEC_FIXTURES_DIR = Rails.root.join("spec/fixtures")
|
14
|
-
|
15
|
-
module AnnotateModels
|
16
|
-
|
17
|
-
PREFIX_AT_BOTTOM = "== Schema Info"
|
18
|
-
SUFFIX_AT_BOTTOM = ""
|
19
|
-
PREFIX_ON_TOP = "== Schema Info =="
|
20
|
-
SUFFIX_ON_TOP = "=================\n# "
|
21
|
-
|
22
|
-
# ENV options
|
23
|
-
{:position => 'top', :model_dir => MODEL_DIR}.each do |name, default_value|
|
24
|
-
mattr_accessor name.to_sym
|
25
|
-
self.send("#{name}=", ENV[name.to_s.upcase] || default_value)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.models
|
29
|
-
(ENV['MODELS'] || '').split(',')
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.sort_columns
|
33
|
-
ENV['SORT'] =~ /yes|on|true/i
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.output_prefix
|
37
|
-
bottom? ? PREFIX_AT_BOTTOM : PREFIX_ON_TOP
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.output_suffix
|
41
|
-
bottom? ? SUFFIX_AT_BOTTOM : SUFFIX_ON_TOP
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.bottom?
|
45
|
-
self.position =~ /bottom/i
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.separate?
|
49
|
-
ENV['SEPARATE'] =~ /yes|on|true/i
|
50
|
-
end
|
51
|
-
|
52
|
-
|
53
|
-
# Simple quoting for the default column value
|
54
|
-
def self.quote(value)
|
55
|
-
case value
|
56
|
-
when NilClass then "NULL"
|
57
|
-
when TrueClass then "TRUE"
|
58
|
-
when FalseClass then "FALSE"
|
59
|
-
when Float, Fixnum, Bignum then value.to_s.inspect
|
60
|
-
# BigDecimals need to be output in a non-normalized form and quoted.
|
61
|
-
when BigDecimal then value.to_s('F')
|
62
|
-
else
|
63
|
-
value.inspect
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Use the column information in an ActiveRecord class
|
68
|
-
# to create a comment block containing a line for
|
69
|
-
# each column. The line contains the column name,
|
70
|
-
# the type (and length), and any optional attributes
|
71
|
-
def self.get_schema_info(klass)
|
72
|
-
table_info = "# Table name: #{klass.table_name}"
|
73
|
-
table_info << " # #{klass.table_comment}" unless klass.table_comment.blank?
|
74
|
-
table_info << "\n#\n"
|
75
|
-
max_size = klass.column_names.collect{|name| name.size}.max + 1
|
76
|
-
|
77
|
-
columns = klass.columns
|
78
|
-
|
79
|
-
cols = if self.sort_columns
|
80
|
-
pk = columns.find_all { |col| col.name == klass.primary_key }.flatten
|
81
|
-
assoc = columns.find_all { |col| col.name.match(/_id$/) }.sort_by(&:name)
|
82
|
-
dates = columns.find_all { |col| col.name.match(/_on$/) }.sort_by(&:name)
|
83
|
-
times = columns.find_all { |col| col.name.match(/_at$/) }.sort_by(&:name)
|
84
|
-
pk + assoc + (columns - pk - assoc - times - dates).compact.sort_by(&:name) + dates + times
|
85
|
-
else
|
86
|
-
columns
|
87
|
-
end
|
88
|
-
|
89
|
-
col_lines = append_comments(cols.map{|col| [col, annotate_column(col, klass, max_size)]})
|
90
|
-
cols_text = col_lines.join("\n")
|
91
|
-
|
92
|
-
result = "# #{self.output_prefix}\n# \n# Schema version: #{get_schema_version}\n#\n"
|
93
|
-
result << table_info
|
94
|
-
result << cols_text
|
95
|
-
result << "\n# \n# #{self.output_suffix}" unless self.output_suffix.blank?
|
96
|
-
result << "\n"
|
97
|
-
result
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.annotate_column(col, klass, max_size)
|
101
|
-
col_type = col.type.to_s
|
102
|
-
attrs = []
|
103
|
-
attrs << "not null" unless col.null
|
104
|
-
if col.default
|
105
|
-
default_value =
|
106
|
-
case col_type
|
107
|
-
when "decimal" then col.default.to_s.sub(/\.0+\z/, '.0')
|
108
|
-
else col.default
|
109
|
-
end
|
110
|
-
attrs << "default(#{quote(default_value)})"
|
111
|
-
end
|
112
|
-
attrs << "primary key" if col.name == klass.primary_key
|
113
|
-
|
114
|
-
case col_type
|
115
|
-
when "decimal" then
|
116
|
-
col_type << "(#{col.precision}, #{col.scale})"
|
117
|
-
else
|
118
|
-
col_type << "(#{col.limit})" if col.limit
|
119
|
-
end
|
120
|
-
sprintf("# %-#{max_size}s:%-15s %s", col.name, col_type, attrs.join(", ")).rstrip
|
121
|
-
end
|
122
|
-
|
123
|
-
def self.append_comments(col_and_lines)
|
124
|
-
max_length = col_and_lines.map{|(col, line)| line.length}.max
|
125
|
-
col_and_lines.map do |(col, line)|
|
126
|
-
if col.comment.blank?
|
127
|
-
line
|
128
|
-
else
|
129
|
-
"%-#{max_length}s # %s" % [line, col.comment]
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# Add a schema block to a file. If the file already contains
|
135
|
-
# a schema info block (a comment starting
|
136
|
-
# with "Schema as of ..."), remove it first.
|
137
|
-
# Mod to write to the end of the file
|
138
|
-
def self.annotate_one_file(file_name, info_block)
|
139
|
-
if File.exist?(file_name)
|
140
|
-
content = File.read(file_name)
|
141
|
-
|
142
|
-
encoding_comment = content.scan(/^\#\s*-\*-(.+?)-\*-/).flatten.first
|
143
|
-
content.sub!(/^\#\s*-\*-(.+?)-\*-/, '')
|
144
|
-
|
145
|
-
# Remove old schema info
|
146
|
-
content.sub!(/(\n)*^# #{PREFIX_ON_TOP}.*?\n(#.*\n)*# #{SUFFIX_ON_TOP}/, '')
|
147
|
-
content.sub!(/(\n)*^# #{PREFIX_AT_BOTTOM}.*?\n(#.*\n)*#.*(\n)*/, '')
|
148
|
-
content.sub!(/^[\n\s]*/, '')
|
149
|
-
|
150
|
-
# Write it back
|
151
|
-
File.open(file_name, "w") do |f|
|
152
|
-
f.print "# -*- #{encoding_comment.strip} -*-\n\n" unless encoding_comment.blank?
|
153
|
-
if self.bottom?
|
154
|
-
f.print content
|
155
|
-
f.print "\n\n"
|
156
|
-
f.print info_block
|
157
|
-
else
|
158
|
-
f.print info_block
|
159
|
-
f.print "\n" if self.separate?
|
160
|
-
f.print content
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
|
167
|
-
# Given the name of an ActiveRecord class, create a schema
|
168
|
-
# info block (basically a comment containing information
|
169
|
-
# on the columns and their types) and put it at the front
|
170
|
-
# of the model and fixture source files.
|
171
|
-
def self.annotate(klass)
|
172
|
-
info = get_schema_info(klass)
|
173
|
-
model_name = klass.name.underscore
|
174
|
-
fixtures_name = "#{klass.table_name}.yml"
|
175
|
-
|
176
|
-
[
|
177
|
-
File.join(self.model_dir, "#{model_name}.rb"), # model
|
178
|
-
File.join(UNIT_TEST_DIR, "#{model_name}_test.rb"), # test
|
179
|
-
File.join(FIXTURES_DIR, fixtures_name), # fixture
|
180
|
-
File.join(SPEC_MODEL_DIR, "#{model_name}_spec.rb"), # spec
|
181
|
-
File.join(SPEC_FIXTURES_DIR, fixtures_name), # spec fixture
|
182
|
-
Rails.root.join( 'test', 'factories.rb'), # factories file
|
183
|
-
Rails.root.join( 'spec', 'factories.rb'), # factories file
|
184
|
-
].each { |file| annotate_one_file(file, info) }
|
185
|
-
end
|
186
|
-
|
187
|
-
# Return a list of the model files to annotate. If we have
|
188
|
-
# command line arguments, they're assumed to be either
|
189
|
-
# the underscore or CamelCase versions of model names.
|
190
|
-
# Otherwise we take all the model files in the
|
191
|
-
# app/models directory.
|
192
|
-
def self.get_model_names
|
193
|
-
result = nil
|
194
|
-
if self.models.empty?
|
195
|
-
Dir.chdir(self.model_dir) do
|
196
|
-
result = Dir["**/*.rb"].map do |filename|
|
197
|
-
filename.sub(/\.rb$/, '').camelize
|
198
|
-
end
|
199
|
-
end
|
200
|
-
else
|
201
|
-
result = self.models.dup
|
202
|
-
end
|
203
|
-
result
|
204
|
-
end
|
205
|
-
|
206
|
-
# We're passed a name of things that might be
|
207
|
-
# ActiveRecord models. If we can find the class, and
|
208
|
-
# if its a subclass of ActiveRecord::Base,
|
209
|
-
# then pas it to the associated block
|
210
|
-
def self.do_annotations
|
211
|
-
annotated = self.get_model_names.inject([]) do |list, class_name|
|
212
|
-
begin
|
213
|
-
# klass = class_name.split('::').inject(Object){ |klass,part| klass.const_get(part) }
|
214
|
-
klass = class_name.constantize
|
215
|
-
if klass < ActiveRecord::Base && !klass.abstract_class?
|
216
|
-
list << class_name
|
217
|
-
self.annotate(klass)
|
218
|
-
end
|
219
|
-
rescue Exception => e
|
220
|
-
puts "Unable to annotate #{class_name}: #{e.message}"
|
221
|
-
end
|
222
|
-
list
|
223
|
-
end
|
224
|
-
puts "Annotated #{annotated.join(', ')}"
|
225
|
-
end
|
226
|
-
|
227
|
-
def self.get_schema_version
|
228
|
-
unless @schema_version
|
229
|
-
version = ActiveRecord::Migrator.current_version rescue 0
|
230
|
-
@schema_version = version > 0 ? version : ''
|
231
|
-
end
|
232
|
-
@schema_version
|
233
|
-
end
|
234
|
-
end
|
data/lib/schema_comments/base.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
module SchemaComments
|
2
|
-
module Base
|
3
|
-
def self.prepended(mod)
|
4
|
-
mod.singleton_class.prepend ClassMethods
|
5
|
-
mod.ignore_pattern_to_export_i18n = /\[.*\]/
|
6
|
-
end
|
7
|
-
|
8
|
-
module ClassMethods
|
9
|
-
def table_comment
|
10
|
-
@table_comment ||= connection.table_comment(table_name)
|
11
|
-
end
|
12
|
-
|
13
|
-
def columns
|
14
|
-
result = super
|
15
|
-
unless @column_comments_loaded
|
16
|
-
column_comment_hash = connection.column_comments(table_name)
|
17
|
-
result.each do |column|
|
18
|
-
column.comment = column_comment_hash[column.name.to_s]
|
19
|
-
end
|
20
|
-
@column_comments_loaded = true
|
21
|
-
end
|
22
|
-
result
|
23
|
-
end
|
24
|
-
|
25
|
-
def reset_column_comments
|
26
|
-
@column_comments_loaded = false
|
27
|
-
end
|
28
|
-
|
29
|
-
def reset_table_comments
|
30
|
-
@table_comment = nil
|
31
|
-
end
|
32
|
-
|
33
|
-
attr_accessor :ignore_pattern_to_export_i18n
|
34
|
-
|
35
|
-
def export_i18n_models
|
36
|
-
subclasses = ActiveRecord::Base.send(:subclasses).select do |klass|
|
37
|
-
(klass != SchemaComments::SchemaComment) and
|
38
|
-
klass.respond_to?(:table_exists?) and klass.table_exists?
|
39
|
-
end
|
40
|
-
subclasses.inject({}) do |d, m|
|
41
|
-
comment = m.table_comment || ''
|
42
|
-
comment.gsub!(ignore_pattern_to_export_i18n, '') if ignore_pattern_to_export_i18n
|
43
|
-
d[m.name.underscore] = comment
|
44
|
-
d
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def export_i18n_attributes(connection = ActiveRecord::Base.connection)
|
49
|
-
subclasses = ActiveRecord::Base.send(:subclasses).select do |klass|
|
50
|
-
(klass != SchemaComments::SchemaComment) and
|
51
|
-
klass.respond_to?(:table_exists?) and klass.table_exists?
|
52
|
-
end
|
53
|
-
subclasses.inject({}) do |d, m|
|
54
|
-
attrs = {}
|
55
|
-
m.columns.each do |col|
|
56
|
-
next if col.name == 'id'
|
57
|
-
comment = (col.comment || '').dup
|
58
|
-
comment.gsub!(ignore_pattern_to_export_i18n, '') if ignore_pattern_to_export_i18n
|
59
|
-
attrs[col.name] = comment
|
60
|
-
end
|
61
|
-
d[m.name.underscore] = attrs
|
62
|
-
d
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
end
|
69
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module SchemaComments
|
2
|
-
module Migration
|
3
|
-
def self.prepended(mod)
|
4
|
-
mod.singleton_class.prepend(ClassMethods)
|
5
|
-
end
|
6
|
-
|
7
|
-
module ClassMethods
|
8
|
-
def migrate(*args, &block)
|
9
|
-
SchemaComments::SchemaComment.yaml_access do
|
10
|
-
super(*args, &block)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module SchemaComments
|
2
|
-
module Migrator
|
3
|
-
def self.prepend(mod)
|
4
|
-
mod.singleton_class.prepend(ClassMethods)
|
5
|
-
end
|
6
|
-
|
7
|
-
module ClassMethods
|
8
|
-
def migrate(*args, &block)
|
9
|
-
SchemaComments::SchemaComment.yaml_access do
|
10
|
-
super(*args, &block)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
namespace :db do
|
2
|
-
namespace :schema do
|
3
|
-
namespace :comments do
|
4
|
-
|
5
|
-
desc "update #{SchemaComments.yaml_path} from mysql comments of tables and columns"
|
6
|
-
task :dump => :environment do
|
7
|
-
conn = ActiveRecord::Base.connection
|
8
|
-
db_table_comments = conn.select_all("SHOW TABLE STATUS").each_with_object({}){|row, d| d[row["Name"]] = row["Comment"]}
|
9
|
-
column_comments = {}
|
10
|
-
db_table_comments.keys.each do |t|
|
11
|
-
column_comments[t] = conn.select_all("SHOW FULL COLUMNS FROM #{t}").each_with_object({}){|row, d| d[row["Field"]] = row["Comment"]}
|
12
|
-
end
|
13
|
-
root = {"table_comments" => db_table_comments, "column_comments" => column_comments}
|
14
|
-
SchemaComments::SchemaComment::SortedStore.sort_yaml_content!(root)
|
15
|
-
open(SchemaComments.yaml_path, "w"){|f| f.puts root.to_yaml }
|
16
|
-
end
|
17
|
-
|
18
|
-
desc "load #{SchemaComments.yaml_path} and alter table for mysql comments"
|
19
|
-
task :load => :environment do
|
20
|
-
conn = ActiveRecord::Base.connection
|
21
|
-
creation = ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation.new(conn)
|
22
|
-
|
23
|
-
db_tables = conn.tables
|
24
|
-
db_table_comments = conn.select_all("SHOW TABLE STATUS").each_with_object({}){|row, d| d[row["Name"]] = row["Comment"]}
|
25
|
-
root = YAML.load_file(SchemaComments.yaml_path)
|
26
|
-
column_comments_root = root["column_comments"]
|
27
|
-
root["table_comments"].each do |t, t_comment|
|
28
|
-
unless db_tables.include?(t)
|
29
|
-
$stderr.puts "\e[33mtable #{t.inspect} doesn't exist\e[0m"
|
30
|
-
next
|
31
|
-
end
|
32
|
-
unless t_comment == db_table_comments[t]
|
33
|
-
conn.execute("ALTER TABLE #{t} COMMENT '#{t_comment}'")
|
34
|
-
end
|
35
|
-
|
36
|
-
column_hash = conn.columns(t).each_with_object({}){|c, d| d[c.name] = c}
|
37
|
-
db_column_comments = conn.select_all("SHOW FULL COLUMNS FROM #{t}").each_with_object({}){|row, d| d[row["Field"]] = row["Comment"]}
|
38
|
-
column_comments = column_comments_root[t]
|
39
|
-
column_comments.each do |c, c_comment|
|
40
|
-
unless c_comment == db_column_comments[t]
|
41
|
-
if col = column_hash[c]
|
42
|
-
opts = col.sql_type.dup
|
43
|
-
creation.send(:add_column_options!, opts, column: col, null: col.null, default: col.default, auto_increment: (col.extra == "auto_increment"))
|
44
|
-
conn.execute("ALTER TABLE #{t} MODIFY #{c} #{opts} COMMENT '#{c_comment}'")
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
# Original file
|
2
|
-
# http://github.com/rotuka/annotate_models/blob/d2afee82020dbc592b147d92f9beeadbf665a9e0/tasks/annotate_models_tasks.rake
|
3
|
-
namespace :db do
|
4
|
-
desc "Add schema information (as comments) to model files"
|
5
|
-
task :annotate => :environment do
|
6
|
-
require File.join(File.dirname(__FILE__), "../lib/annotate_models.rb")
|
7
|
-
AnnotateModels.do_annotations
|
8
|
-
end
|
9
|
-
|
10
|
-
desc "Updates database (migrate and annotate models)"
|
11
|
-
task :update => %w(migrate annotate)
|
12
|
-
end
|