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