1
+ $:. unshift File . dirname ( __FILE__ )
2
+
3
+ module AnnotateModels
4
+ RAILS_ROOT = '.'
5
+ MODEL_DIR = File . join ( RAILS_ROOT , "app/models" )
6
+ FIXTURE_DIR = File . join ( RAILS_ROOT , "test/fixtures" )
7
+ PREFIX = "== Schema Information"
8
+
9
+ # Simple quoting for the default column value
10
+ def self . quote ( value )
11
+ case value
12
+ when NilClass then "NULL"
13
+ when TrueClass then "TRUE"
14
+ when FalseClass then "FALSE"
15
+ when Float , Fixnum , Bignum then value . to_s
16
+ # BigDecimals need to be output in a non-normalized form and quoted.
17
+ when BigDecimal then value . to_s ( 'F' )
18
+ else
19
+ value . inspect
20
+ end
21
+ end
22
+
23
+ # Use the column information in an ActiveRecord class
24
+ # to create a comment block containing a line for
25
+ # each column. The line contains the column name,
26
+ # the type (and length), and any optional attributes
27
+ def self . get_schema_info ( klass , header )
28
+ info = "# #{ header } \n #\n "
29
+ info << "# Table name: #{ klass . table_name } \n #\n "
30
+
31
+ max_size = klass . column_names . collect { |name | name . size } . max + 1
32
+ klass . columns . each do |col |
33
+ attrs = [ ]
34
+ attrs << "default(#{ quote ( col . default ) } )" if col . default
35
+ attrs << "not null" unless col . null
36
+ attrs << "primary key" if col . name == klass . primary_key
37
+
38
+ col_type = col . type . to_s
39
+ if col_type == "decimal"
40
+ col_type << "(#{ col . precision } , #{ col . scale } )"
41
+ else
42
+ col_type << "(#{ col . limit } )" if col . limit
43
+ end
44
+ info << sprintf ( "# %-#{ max_size } .#{ max_size } s:%-15.15s %s\n " , col . name , col_type , attrs . join ( ", " ) )
45
+ end
46
+
47
+ info << "#\n \n "
48
+ end
49
+
50
+ # Add a schema block to a file. If the file already contains
51
+ # a schema info block (a comment starting
52
+ # with "Schema as of ..."), remove it first.
53
+
54
+ def self . annotate_one_file ( file_name , info_block )
55
+ if File . exist? ( file_name )
56
+ content = File . read ( file_name )
57
+
58
+ # Remove old schema info
59
+ content . sub! ( /^# #{ PREFIX } .*?\n (#.*\n )*\n / , '' )
60
+
61
+ # Write it back
62
+ File . open ( file_name , "w" ) { |f | f . puts info_block + content }
63
+ end
64
+ end
65
+
66
+ # Given the name of an ActiveRecord class, create a schema
67
+ # info block (basically a comment containing information
68
+ # on the columns and their types) and put it at the front
69
+ # of the model and fixture source files.
70
+
71
+ def self . annotate ( klass , header )
72
+ info = get_schema_info ( klass , header )
73
+
74
+ model_file_name = File . join ( MODEL_DIR , klass . name . underscore + ".rb" )
75
+ annotate_one_file ( model_file_name , info )
76
+
77
+ fixture_file_name = File . join ( FIXTURE_DIR , klass . table_name + ".yml" )
78
+ annotate_one_file ( fixture_file_name , info )
79
+ end
80
+
81
+ # Return a list of the model files to annotate. If we have
82
+ # command line arguments, they're assumed to be either
83
+ # the underscore or CamelCase versions of model names.
84
+ # Otherwise we take all the model files in the
85
+ # app/models directory.
86
+ def self . get_model_names
87
+ models = ARGV . dup
88
+ models . shift
89
+
90
+ if models . empty?
91
+ Dir . chdir ( MODEL_DIR ) do
92
+ models = Dir [ "**/*.rb" ]
93
+ end
94
+ end
95
+ models
96
+ end
97
+
98
+ # We're passed a name of things that might be
99
+ # ActiveRecord models. If we can find the class, and
100
+ # if its a subclass of ActiveRecord::Base,
101
+ # then pas it to the associated block
102
+
103
+ def self . do_annotations
104
+ header = PREFIX . dup
105
+ version = ActiveRecord ::Migrator . current_version rescue 0
106
+ if version > 0
107
+ header << "\n # Schema version: #{ version } "
108
+ end
109
+
110
+ annotated = [ ]
111
+ self . get_model_names . each do |m |
112
+ class_name = m . sub ( /\. rb$/ , '' ) . camelize
113
+ begin
114
+ klass = class_name . split ( '::' ) . inject ( Object ) { |klass , part | klass . const_get ( part ) }
115
+ if klass < ActiveRecord ::Base && !klass . abstract_class?
116
+ annotated << class_name
117
+ self . annotate ( klass , header )
118
+ end
119
+ rescue Exception => e
120
+ puts "Unable to annotate #{ class_name } : #{ e . message } "
121
+ end
122
+
123
+ end
124
+ puts "Annotated #{ annotated . join ( ', ' ) } "
125
+ end
126
+ end
0 commit comments