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.
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.2.0
@@ -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
@@ -1,7 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
-
5
- bundle install
6
-
7
- # Do any other automated setup that you need to do here
data/init.rb DELETED
@@ -1,4 +0,0 @@
1
- unless ENV['SCHEMA_COMMENTS_DISABLED']
2
- require 'schema_comments'
3
- SchemaComments.setup
4
- end
@@ -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
@@ -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