schema_comments 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|