annotate 2.4.1.beta1 → 2.5.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,77 @@
1
+ == 2.5.0
2
+
3
+ * Fixed that schema kept prepending additional newlines
4
+ * Updates to make annotate smarter about when to touch a model
5
+ * Recognize column+type, and don't change a file unless the column+type combination of the new schema are different than that of the old (i.e., don't regenerate if columns happen to be in a different order. That's just how life is sometimes)
6
+ * Grab old specification even if it has \r\n as line endings rather than pure \ns
7
+ * Various warning and specification fixes
8
+ * Fix "no such file to load -- annotate/annotate_models (MissingSourceFile)" error (require statements in tasks now use full path to lib files)
9
+ * warn about macros, to mitigate when we're included during a production run, not just a rakefile run -- possibly at the expense of too much noise
10
+ * Adding rake as a runtime dependency
11
+ * If the schema is already in the model file, it will be replaced into the same location. If it didn't previously exist, it'll be placed according to the "position", as before.
12
+ * Allow task loading from Rakefile for gems (plugin installation already auto-detects).
13
+ * Add skip_on_db_migrate option as well for people that don't want it
14
+ * Fix options parsing to convert strings to proper booleans. Change annotate to use opt
15
+ ions hash instead of ENV.
16
+ * Add support for Fabrication fabricators
17
+ * Leave magic encoding comment intact
18
+ * Fix issue #14 - RuntimeError: Already memoized
19
+ * Count a model as 'annotated' if any of its tests/fixtures are annotated
20
+ * Support FactoryGirl
21
+ * Support :change migrations (Rails 3.1)
22
+ * Allow models with non-standard capitalization
23
+ * Widen type column so we can handle longtexts with chopping things off.
24
+ * Skip trying to get list of models from commandline when running via Rake (was
25
+ preventing the use of multiple rake tasks in one command if one of them was
26
+ db:migrate).
27
+ * Add ability to skip annotations for a model by adding
28
+ '# -*- SkipSchemaAnnotations' anywhere in the file.
29
+ * Don't show column limits for integer and boolean types.
30
+ * Add sorting for columns and indexes. (Helpful for out-of-order migration
31
+ execution, but use --no-sort if you don't want this.)
32
+ * Annotate unit tests in subfolders.
33
+ * Add generator to install rakefile that automatically annotates on db:migrate.
34
+ * Correct Gemfile to clarify which environments need which gems.
35
+ * Add an .rvmrc to facilitate clean development.
36
+ * Refactor out ActiveRecord monkey-patch to permit extending without
37
+ side-effects.
38
+ * Use ObjectSpace to locate models to facilitate handling of models with
39
+ non-standard capitalization.
40
+ Note that this still requires that the inflector be configured to understand
41
+ the special case.
42
+ * Shore up test cases a bit.
43
+ * Merge against many of the older branches on Github whose functionality is
44
+ already reflected to reduce confusion about what is and is not implemented
45
+ here.
46
+ * Accept String or Symbol for :position (et al) options.
47
+ * Rename "annotate" bin to "annotate_models" to avoid conflicting with
48
+ ImageMagick.
49
+ * Add RDoc output formatting as an option.
50
+ * Add Markdown output formatting as an option.
51
+ * Add option to force annotation regeneration.
52
+ * Add new configuration option for controlling where info is placed in
53
+ fixtures/factories.
54
+ * Fix for models without tables.
55
+ * Fix gemspec generation now that Jeweler looks at Gemfile.
56
+ * Fix warning: `NOTE: Gem::Specification#default_executable= is deprecated with
57
+ no replacement. It will be removed on or after 2011-10-01.`
58
+ * Fix handling of files with no trailing newline when putting annotations at
59
+ the end of the file.
60
+
61
+ == 2.4.2 2009-11-21
62
+
63
+ * Annotates (spec|test)/factories/<model>_factory.rb files
64
+
65
+ == 2.4.1 2009-11-20
66
+
67
+ * Annotates thoughtbot's factory_girl factories (test/factories/<model>_factory.rb)
68
+ * Move default annotation position back to top
69
+
70
+ == 2.4.0 2009-12-13
71
+
72
+ * Incorporated lots of patches from the Github community, including support for Blueprints fixtures
73
+ * Several bug fixes
74
+
1
75
  == 2.1 2009-10-18
2
76
 
3
77
  * New options
@@ -5,7 +79,7 @@
5
79
  * -i to show database indexes in annotations
6
80
  * -e to exclude annotating tests or fixtures
7
81
  * -m to include the migration version number in the annotation
8
- * --model-dir to annotate model files stored a different place than app/models
82
+ * --model-dir to annotate model files stored a different place than app/models
9
83
  * Ignore unknown macros ('acts_as_whatever')
10
84
 
11
85
  == 2.0 2009-02-03
data/README.rdoc CHANGED
@@ -2,11 +2,14 @@
2
2
 
3
3
  Add a comment summarizing the current schema to the top or bottom of each of your...
4
4
 
5
- * ActiveRecord models
6
- * Fixture files
7
- * Tests and Specs
8
- * Object Daddy exemplars
9
- * Machinist blueprints
5
+ - ActiveRecord models
6
+ - Fixture files
7
+ - Tests and Specs
8
+ - Object Daddy exemplars
9
+ - Machinist blueprints
10
+ - Fabrication fabricators
11
+ - Thoughtbot's factory_girl factories, i.e. the (spec|test)/factories/<model>_factory.rb files
12
+ - routes.rb file
10
13
 
11
14
  The schema comment looks like this:
12
15
 
@@ -40,20 +43,18 @@ Also, if you pass the -r option, it'll annotate routes.rb with the output of "ra
40
43
 
41
44
  Into Gemfile from Github:
42
45
 
43
- gem 'annotate', :git => 'git://github.com/ctran/annotate_models.git'
44
-
46
+ gem 'annotate', :git => 'git://github.com/MrJoy/annotate_models.git'
45
47
 
46
- Into environment gems From Rubygems.org:
48
+ Into environment gems From rubygems.org:
47
49
 
48
- sudo gem install annotate
50
+ gem install annotate
49
51
 
50
52
  Into environment gems from Github checkout:
51
53
 
52
- git clone git://github.com/ctran/annotate_models.git annotate
53
- cd annotate
54
- rake gem
55
- sudo gem install pkg/annotate-*.gem
56
-
54
+ git clone git://github.com/MrJoy/annotate_models.git annotate_models
55
+ cd annotate_models
56
+ rake build
57
+ gem install pkg/annotate-*.gem
57
58
 
58
59
  == USAGE
59
60
 
@@ -62,43 +63,61 @@ Into environment gems from Github checkout:
62
63
  To annotate all your models, tests, fixtures, etc.:
63
64
 
64
65
  cd /path/to/app
65
- annotate
66
+ annotate_models
66
67
 
67
68
  To annotate your models and tests:
68
69
 
69
- annotate --exclude fixtures
70
+ annotate_models --exclude fixtures
70
71
 
71
72
  To annotate just your models:
72
73
 
73
- annotate --exclude tests,fixtures
74
+ annotate_models --exclude tests,fixtures
74
75
 
75
76
  To annotate routes.rb:
76
77
 
77
- annotate -r
78
+ annotate_models -r
79
+
80
+ To remove annotations:
81
+
82
+ annotate_models -d
83
+
84
+ To automatically annotate after running 'rake db:migrate', ensure you've added
85
+ annotate_models to your Rails project's Gemfile, and run this:
86
+
87
+ rails g annotate_models:install
78
88
 
79
- To automatically annotate after running 'rake db:migrate':
80
-
81
- [*needs more clarity*] unpack the gem into vendor/plugins, or maybe vendor/gems, or maybe just require tasks/migrate.rake.
89
+ This will produce a .rake file that will ensure annotation happens after
90
+ migration (but only in development mode), and provide configuration options
91
+ you can use to tailor the output.
82
92
 
83
- If you install annotate_models as a plugin, it will automatically
84
- adjust your <tt>rake db:migrate</tt> tasks so that they update the
85
- annotations in your model files for you once the migration is
86
- completed.
93
+ If you want to always skip annotations on a particular model, add this string
94
+ anywhere in the file:
95
+
96
+ # -*- SkipSchemaAnnotations
87
97
 
88
98
  == OPTIONS
89
99
 
90
- Usage: annotate [options] [model_file]*
100
+ Usage: annotate_models [options] [model_file]*
91
101
  -d, --delete Remove annotations from all model files
92
102
  -p, --position [before|after] Place the annotations at the top (before) or the bottom (after) of the model file
93
103
  -r, --routes Annotate routes.rb with the output of 'rake routes'
94
104
  -v, --version Show the current version of this gem
95
105
  -m, --show-migration Include the migration version number in the annotation
96
- -i, --show-indexes List the table's database indexes in the annotation
97
- -s, --simple-indexes Concat the column's related indexes in the annotation
106
+ -i, --show-indexes List the indexes for the table in the annotation
107
+ -s, --simple-indexes Include information about indexes inline with the relevant column
98
108
  --model-dir dir Annotate model files stored in dir rather than app/models
109
+ --ignore-model-subdirs Ignore sub-directories of the models directory.
99
110
  -R, --require path Additional files to require before loading models
100
- -e, --exclude [tests,fixtures] Do not annotate fixtures, test files, or both
111
+ -e [tests,fixtures] Skip annotation of fixtures/factories/test files
112
+ --exclude
113
+ -n --no-sort Sort by column creation order rather than alphabetical order
114
+
115
+ == SORTING
101
116
 
117
+ By default, columns will be sorted alphabetically so that the results of
118
+ annotation are consistent regardless of what order migrations are executed in.
119
+
120
+ If you prefer the old behavior, use --no-sort.
102
121
 
103
122
  == WARNING
104
123
 
@@ -107,40 +126,44 @@ block in your models if it looks like it was previously added
107
126
  by annotate models, so you don't want to add additional text
108
127
  to an automatically created comment block.
109
128
 
110
- * * Back up your model files before using... * *
129
+ BACK UP YOUR MODELS BEFORE USING THIS TOOL!
111
130
 
112
131
  == LINKS
113
132
 
114
- * Factory Girl => http://github.com/thoughtbot/factory_girl (NOT IMPLEMENTED)
115
- * Object Daddy => http://github.com/flogic/object_daddy
116
- * SpatialAdapter => http://github.com/pdeffendol/spatial_adapter
117
- * PostgisAdapter => http://github.com/nofxx/postgis_adapter
133
+ - Factory Girl: http://github.com/thoughtbot/factory_girl
134
+ - Object Daddy: http://github.com/flogic/object_daddy
135
+ - Machinist: http://github.com/notahat/machinist
136
+ - Fabrication: http://github.com/paulelliott/fabrication
137
+ - SpatialAdapter: http://github.com/pdeffendol/spatial_adapter
138
+ - PostgisAdapter: http://github.com/nofxx/postgis_adapter
118
139
 
119
140
  == LICENSE:
120
141
 
121
142
  Released under the same license as Ruby. No Support. No Warranty.
122
143
 
123
- == AUTHOR:
124
-
125
- Original code by: Dave Thomas -- Pragmatic Programmers, LLC
126
- Overhauled by: Alex Chaffee
127
- Gemmed by: Cuong Tran
128
- Maintained by: Alex Chaffee and Cuong Tran
129
-
130
- Modifications by:
131
-
132
- - Alex Chaffee - http://github.com/alexch - alex@pivotallabs.com
133
- - Cuong Tran - http://github.com/ctran - ctran@pragmaquest.com
134
- - Jack Danger - http://github.com/JackDanger
135
- - Michael Bumann - http://github.com/bumi
136
- - Henrik Nyh - http://github.com/henrik
137
- - Marcos Piccinini - http://github.com/nofxx
138
- - Neal Clark - http://github.com/nclark
139
- - Jacqui Maher - http://github.com/jacqui
140
- - Nick Plante - http://github.com/zapnap - http://blog.zerosum.org
141
- - Pedro Visintin - http://github.com/peterpunk - http://www.pedrovisintin.com
142
- - Bob Potter - http://github.com/bpot
143
- - Gavin Montague - http://github.com/govan/
144
- - Alexander Semyonov - http://github.com/rotuka/
145
-
144
+ == AUTHORS:
145
+
146
+ - Original code by: Dave Thomas -- Pragmatic Programmers, LLC <http://agilewebdevelopment.com/plugins/annotate_models>
147
+ - Overhauled by: Alex Chaffee <http://alexch.github.com> alex@stinky.com
148
+ - Gemmed by: Cuong Tran <http://github.com/ctran> ctran@pragmaquest.com
149
+ - Maintained by: Alex Chaffee and Cuong Tran
150
+ - Homepage: http://github.com/ctran/annotate_models
151
+
152
+ With help from:
153
+
154
+ - Jack Danger - http://github.com/JackDanger
155
+ - Michael Bumann - http://github.com/bumi
156
+ - Henrik Nyh - http://github.com/henrik
157
+ - Marcos Piccinini - http://github.com/nofxx
158
+ - Neal Clark - http://github.com/nclark
159
+ - Jacqui Maher - http://github.com/jacqui
160
+ - Nick Plante - http://github.com/zapnap - http://blog.zerosum.org
161
+ - Pedro Visintin - http://github.com/peterpunk - http://www.pedrovisintin.com
162
+ - Bob Potter - http://github.com/bpot
163
+ - Gavin Montague - http://github.com/govan
164
+ - Alexander Semyonov - http://github.com/rotuka
165
+ - Nathan Brazil - http://github.com/bitaxis
166
+ - Ian Duggan http://github.com/ijcd
167
+ - Jon Frisby http://github.com/mrjoy
168
+
146
169
  and many others that I may have missed to add.
data/bin/annotate CHANGED
@@ -1,55 +1,73 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  require 'optparse'
4
- require 'annotate'
3
+ require 'rake/dsl_definition'
4
+ require 'rake'
5
+ begin
6
+ require "annotate"
7
+ rescue LoadError
8
+ here = File.expand_path(File.dirname __FILE__)
9
+ $:<< "#{here}/../lib"
10
+ require "annotate"
11
+ end
5
12
 
6
13
  task = :annotate_models
7
14
 
8
15
  OptionParser.new do |opts|
9
- opts.banner = "Usage: annotate [options] [model_file]*"
16
+ opts.banner = "Usage: annotate_models [options] [model_file]*"
10
17
 
11
- opts.on('-d', '--delete',
12
- "Remove annotations from all model files") do
18
+ opts.on('-d', '--delete',
19
+ "Remove annotations from all model files") do
13
20
  task = :remove_annotation
14
21
  end
15
22
 
16
- opts.on('-p', '--position [before|after]', ['before', 'after'],
17
- "Place the annotations at the top (before) or the bottom (after) of the model file") do |p|
23
+ ENV['position'] = 'before' # hack: make sure default position is "before"
24
+ opts.on('-p', '--position [before|after]', ['before', 'after'],
25
+ "Place the annotations at the top (before) or the bottom (after) of the model file") do |p|
18
26
  ENV['position'] = p
19
27
  end
20
28
 
21
- opts.on('-r', '--routes',
22
- "Annotate routes.rb with the output of 'rake routes'") do
29
+ opts.on('-r', '--routes',
30
+ "Annotate routes.rb with the output of 'rake routes'") do
23
31
  task = :annotate_routes
24
32
  end
25
33
 
26
- opts.on('-v', '--version',
27
- "Show the current version of this gem") do
34
+ opts.on('-v', '--version',
35
+ "Show the current version of this gem") do
28
36
  puts "annotate v#{Annotate.version}"; exit
29
37
  end
30
38
 
31
- opts.on('-m', '--show-migration',
32
- "Include the migration version number in the annotation") do
39
+ opts.on('-m', '--show-migration',
40
+ "Include the migration version number in the annotation") do
33
41
  ENV['include_version'] = "yes"
34
42
  end
35
43
 
36
- opts.on('-i', '--show-indexes',
37
- "List the table's database indexes in the annotation") do
44
+ opts.on('-i', '--show-indexes',
45
+ "List the table's database indexes in the annotation") do
38
46
  ENV['show_indexes'] = "yes"
39
47
  end
40
48
 
41
49
  opts.on('-s', '--simple-indexes',
42
- "Concat the column's related indexes in the annotation") do
50
+ "Concat the column's related indexes in the annotation") do
43
51
  ENV['simple_indexes'] = "yes"
44
52
  end
45
53
 
46
- opts.on('--model-dir dir',
47
- "Annotate model files stored in dir rather than app/models") do |dir|
54
+ opts.on('--model-dir dir',
55
+ "Annotate model files stored in dir rather than app/models") do |dir|
48
56
  ENV['model_dir'] = dir
49
57
  end
50
58
 
59
+ opts.on('--ignore-model-subdirects',
60
+ "Ignore subdirectories of the models directory") do |dir|
61
+ ENV['ignore_model_sub_dir'] = "yes"
62
+ end
63
+
64
+ opts.on('-n', '--no-sort',
65
+ "Sort columns in creation order rather than alphabetically") do |dir|
66
+ ENV['no_sort'] = "yes"
67
+ end
68
+
51
69
  opts.on('-R', '--require path',
52
- "Additional files to require before loading models") do |path|
70
+ "Additional files to require before loading models") do |path|
53
71
  if ENV['require']
54
72
  ENV['require'] = ENV['require'] + ",#{path}"
55
73
  else
@@ -61,8 +79,17 @@ OptionParser.new do |opts|
61
79
  exclusions.each { |exclusion| ENV["exclude_#{exclusion}"] = "yes" }
62
80
  end
63
81
 
82
+ opts.on('-f', '--format [bare|rdoc|markdown]', ['bare', 'rdoc', 'markdown'], 'Render Schema Infomation as plain/RDoc/Markdown') do |fmt|
83
+ ENV["format_#{fmt}"] = 'yes'
84
+ end
85
+
86
+ opts.on('--force', 'Force new annotations even if there are no changes.') do |force|
87
+ ENV['force'] = 'yes'
88
+ end
89
+
64
90
  end.parse!
65
91
 
92
+ ENV['is_cli'] = '1'
66
93
  if Annotate.load_tasks
67
94
  Rake::Task[task].invoke
68
95
  else
data/lib/annotate.rb CHANGED
@@ -1,23 +1,13 @@
1
- $:.unshift(File.dirname(__FILE__)) unless
2
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
-
4
- require 'yaml'
1
+ here = File.dirname __FILE__
2
+ require "#{here}/annotate/version"
5
3
 
6
4
  module Annotate
7
- def self.version
8
- version_file = File.dirname(__FILE__) + "/../VERSION.yml"
9
- if File.exist?(version_file)
10
- config = YAML.load(File.read(version_file))
11
- version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
12
- else
13
- version = "0.0.0"
14
- end
15
- end
16
-
17
5
  def self.load_tasks
18
6
  if File.exists?('Rakefile')
19
7
  require 'rake'
20
8
  load 'Rakefile'
9
+ # Rails 3 wants to load our .rake files for us.
10
+ # TODO: selectively do this require on Rails 2.x?
21
11
  Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each { |rake| load rake }
22
12
  return true
23
13
  else
@@ -0,0 +1,9 @@
1
+ # monkey patches
2
+
3
+ module ::ActiveRecord
4
+ class Base
5
+ def self.method_missing(name, *args)
6
+ # ignore this, so unknown/unloaded macros won't cause parsing to fail
7
+ end
8
+ end
9
+ end
@@ -1,20 +1,41 @@
1
1
  module AnnotateModels
2
- class << self
3
- # Annotate Models plugin use this header
4
- COMPAT_PREFIX = "== Schema Info"
5
- PREFIX = "== Schema Information"
6
-
7
- FIXTURE_DIRS = ["test/fixtures","spec/fixtures"]
8
- # File.join for windows reverse bar compat?
9
- # I dont use windows, can`t test
10
- UNIT_TEST_DIR = File.join("test", "unit" )
11
- SPEC_MODEL_DIR = File.join("spec", "models")
12
- # Object Daddy http://github.com/flogic/object_daddy/tree/master
13
- EXEMPLARS_TEST_DIR = File.join("test", "exemplars")
14
- EXEMPLARS_SPEC_DIR = File.join("spec", "exemplars")
15
- # Machinist http://github.com/notahat/machinist
16
- BLUEPRINTS_DIR = File.join("test", "blueprints")
2
+ # Annotate Models plugin use this header
3
+ COMPAT_PREFIX = "== Schema Info"
4
+ COMPAT_PREFIX_MD = "## Schema Info"
5
+ PREFIX = "== Schema Information"
6
+ PREFIX_MD = "## Schema Information"
7
+ END_MARK = "== Schema Information End"
8
+ PATTERN = /^\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n/
9
+
10
+ # File.join for windows reverse bar compat?
11
+ # I dont use windows, can`t test
12
+ UNIT_TEST_DIR = File.join("test", "unit" )
13
+ SPEC_MODEL_DIR = File.join("spec", "models")
14
+ FIXTURE_TEST_DIR = File.join("test", "fixtures")
15
+ FIXTURE_SPEC_DIR = File.join("spec", "fixtures")
16
+ FIXTURE_DIRS = ["test/fixtures","spec/fixtures"]
17
+
18
+ # Object Daddy http://github.com/flogic/object_daddy/tree/master
19
+ EXEMPLARS_TEST_DIR = File.join("test", "exemplars")
20
+ EXEMPLARS_SPEC_DIR = File.join("spec", "exemplars")
21
+
22
+ # Machinist http://github.com/notahat/machinist
23
+ BLUEPRINTS_TEST_DIR = File.join("test", "blueprints")
24
+ BLUEPRINTS_SPEC_DIR = File.join("spec", "blueprints")
25
+
26
+ # Factory Girl http://github.com/thoughtbot/factory_girl
27
+ FACTORY_GIRL_TEST_DIR = File.join("test", "factories")
28
+ FACTORY_GIRL_SPEC_DIR = File.join("spec", "factories")
29
+
30
+ # Fabrication https://github.com/paulelliott/fabrication.git
31
+ FABRICATORS_TEST_DIR = File.join("test", "fabricators")
32
+ FABRICATORS_SPEC_DIR = File.join("spec", "fabricators")
33
+
34
+ # Don't show limit (#) on these column types
35
+ # Example: show "integer" instead of "integer(4)"
36
+ NO_LIMIT_COL_TYPES = ["integer", "boolean"]
17
37
 
38
+ class << self
18
39
  def model_dir
19
40
  @model_dir || "app/models"
20
41
  end
@@ -26,14 +47,14 @@ module AnnotateModels
26
47
  # Simple quoting for the default column value
27
48
  def quote(value)
28
49
  case value
29
- when NilClass then "NULL"
30
- when TrueClass then "TRUE"
31
- when FalseClass then "FALSE"
32
- when Float, Fixnum, Bignum then value.to_s
50
+ when NilClass then "NULL"
51
+ when TrueClass then "TRUE"
52
+ when FalseClass then "FALSE"
53
+ when Float, Fixnum, Bignum then value.to_s
33
54
  # BigDecimals need to be output in a non-normalized form and quoted.
34
- when BigDecimal then value.to_s('F')
35
- else
36
- value.inspect
55
+ when BigDecimal then value.to_s('F')
56
+ else
57
+ value.inspect
37
58
  end
38
59
  end
39
60
 
@@ -42,21 +63,34 @@ module AnnotateModels
42
63
  # each column. The line contains the column name,
43
64
  # the type (and length), and any optional attributes
44
65
  def get_schema_info(klass, header, options = {})
45
- info = "# #{header}\n#\n"
46
- info << "# Table name: #{klass.table_name}\n#\n"
66
+ info = "# #{header}\n"
67
+ info<< "#\n"
68
+ info<< "# Table name: #{klass.table_name}\n"
69
+ info<< "#\n"
70
+
71
+ max_size = klass.column_names.map{|name| name.size}.max || 0
72
+ max_size += options[:format_rdoc] ? 5 : 1
47
73
 
48
- max_size = klass.column_names.collect{|name| name.size}.max + 1
49
- klass.columns.each do |col|
74
+ if(options[:format_markdown])
75
+ info<< sprintf( "# %-#{max_size + 4}.#{max_size + 4}s | %-18.18s | %s\n", 'Field', 'Type', 'Attributes' )
76
+ info<< "# #{ '-' * ( max_size + 4 ) } | #{'-' * 18} | #{ '-' * 25 }\n"
77
+ end
78
+
79
+ cols = klass.columns
80
+ cols = cols.sort_by(&:name) unless(options[:no_sort])
81
+ cols.each do |col|
50
82
  attrs = []
51
83
  attrs << "default(#{quote(col.default)})" unless col.default.nil?
52
84
  attrs << "not null" unless col.null
53
- attrs << "primary key" if col.name == klass.primary_key
85
+ attrs << "primary key" if col.name.to_sym == klass.primary_key.to_sym
54
86
 
55
- col_type = col.type.to_s
87
+ col_type = (col.type || col.sql_type).to_s
56
88
  if col_type == "decimal"
57
89
  col_type << "(#{col.precision}, #{col.scale})"
58
90
  else
59
- col_type << "(#{col.limit})" if col.limit
91
+ if (col.limit)
92
+ col_type << "(#{col.limit})" unless NO_LIMIT_COL_TYPES.include?(col_type)
93
+ end
60
94
  end
61
95
 
62
96
  # Check out if we got a geometric column
@@ -66,8 +100,8 @@ module AnnotateModels
66
100
  end
67
101
 
68
102
  # Check if the column has indices and print "indexed" if true
69
- # If the indice include another colum, print it too.
70
- if options[:simple_indexes] # Check out if this column is indexed
103
+ # If the index includes another column, print it too.
104
+ if options[:simple_indexes] && klass.table_exists?# Check out if this column is indexed
71
105
  indices = klass.connection.indexes(klass.table_name)
72
106
  if indices = indices.select { |ind| ind.columns.include? col.name }
73
107
  indices.each do |ind|
@@ -77,14 +111,26 @@ module AnnotateModels
77
111
  end
78
112
  end
79
113
 
80
- info << sprintf("# %-#{max_size}.#{max_size}s:%-15.15s %s", col.name, col_type, attrs.join(", ")).rstrip + "\n"
114
+ if options[:format_rdoc]
115
+ info << sprintf("# %-#{max_size}.#{max_size}s<tt>%s</tt>", "*#{col.name}*::", attrs.unshift(col_type).join(", ")).rstrip + "\n"
116
+ elsif options[:format_markdown]
117
+ info << sprintf("# **%-#{max_size}.#{max_size}s** | `%-16.16s` | `%s`", col.name, col_type, attrs.join(", ").rstrip) + "\n"
118
+ else
119
+ info << sprintf("# %-#{max_size}.#{max_size}s:%-16.16s %s", col.name, col_type, attrs.join(", ")).rstrip + "\n"
120
+ end
81
121
  end
82
122
 
83
- if options[:show_indexes]
123
+ if options[:show_indexes] && klass.table_exists?
84
124
  info << get_index_info(klass)
85
125
  end
86
126
 
87
- info << "#\n\n"
127
+ if options[:format_rdoc]
128
+ info << "#--\n"
129
+ info << "# #{END_MARK}\n"
130
+ info << "#++\n\n"
131
+ else
132
+ info << "#\n\n"
133
+ end
88
134
  end
89
135
 
90
136
  def get_index_info(klass)
@@ -94,7 +140,7 @@ module AnnotateModels
94
140
  return "" if indexes.empty?
95
141
 
96
142
  max_size = indexes.collect{|index| index.name.size}.max + 1
97
- indexes.each do |index|
143
+ indexes.sort_by{|index| index.name}.each do |index|
98
144
  index_info << sprintf("# %-#{max_size}.#{max_size}s %s %s", index.name, "(#{index.columns.join(",")})", index.unique ? "UNIQUE" : "").rstrip + "\n"
99
145
  end
100
146
  return index_info
@@ -108,7 +154,7 @@ module AnnotateModels
108
154
  #
109
155
  # === Options (opts)
110
156
  # :position<Symbol>:: where to place the annotated section in fixture or model file,
111
- # "before" or "after". Default is "before".
157
+ # :before or :after. Default is :before.
112
158
  # :position_in_class<Symbol>:: where to place the annotated section in model file
113
159
  # :position_in_fixture<Symbol>:: where to place the annotated section in fixture file
114
160
  # :position_in_others<Symbol>:: where to place the annotated section in the rest of
@@ -117,26 +163,44 @@ module AnnotateModels
117
163
  def annotate_one_file(file_name, info_block, options={})
118
164
  if File.exist?(file_name)
119
165
  old_content = File.read(file_name)
166
+ return false if(old_content =~ /# -\*- SkipSchemaAnnotations.*\n/)
120
167
 
121
168
  # Ignore the Schema version line because it changes with each migration
122
- header = Regexp.new(/(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?\n)/)
123
- old_header = old_content.match(header).to_s
124
- new_header = info_block.match(header).to_s
169
+ header_pattern = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?\n)/
170
+ old_header = old_content.match(header_pattern).to_s
171
+ new_header = info_block.match(header_pattern).to_s
125
172
 
126
- old_columns = old_header && old_header.scan(/#[\t\s]+([\w\d]+)[\t\s]+\:([\d\w]+)/).sort
127
- new_columns = new_header && new_header.scan(/#[\t\s]+([\w\d]+)[\t\s]+\:([\d\w]+)/).sort
173
+ column_pattern = /^#[\t ]+\w+[\t ]+.+$/
174
+ old_columns = old_header && old_header.scan(column_pattern).sort
175
+ new_columns = new_header && new_header.scan(column_pattern).sort
128
176
 
129
- if old_columns == new_columns
177
+ encoding = Regexp.new(/(^#\s*encoding:.*\n)|(^# coding:.*\n)|(^# -\*- coding:.*\n)/)
178
+ encoding_header = old_content.match(encoding).to_s
179
+
180
+ if old_columns == new_columns && !options[:force]
130
181
  false
131
182
  else
132
- # Replace the old schema info with the new schema info
133
- new_content = old_content.sub(/^# #{COMPAT_PREFIX}.*?\n(#.*\n)*\n/, info_block)
134
- # But, if there *was* no old schema info, we simply need to insert it
135
- if new_content == old_content
136
- new_content = options[:position] == 'before' ?
137
- (info_block + old_content) :
138
- ((old_content =~ /\n$/ ? old_content : old_content + '\n') + info_block)
139
- end
183
+
184
+ # todo: figure out if we need to extract any logic from this merge chunk
185
+ # <<<<<<< HEAD
186
+ # # Replace the old schema info with the new schema info
187
+ # new_content = old_content.sub(/^# #{COMPAT_PREFIX}.*?\n(#.*\n)*\n*/, info_block)
188
+ # # But, if there *was* no old schema info, we simply need to insert it
189
+ # if new_content == old_content
190
+ # old_content.sub!(encoding, '')
191
+ # new_content = options[:position] == 'after' ?
192
+ # (encoding_header + (old_content =~ /\n$/ ? old_content : old_content + "\n") + info_block) :
193
+ # (encoding_header + info_block + old_content)
194
+ # end
195
+ # =======
196
+
197
+ # Strip the old schema info, and insert new schema info.
198
+ old_content.sub!(encoding, '')
199
+ old_content.sub!(PATTERN, '')
200
+
201
+ new_content = (options[:position] || 'before').to_s == 'after' ?
202
+ (encoding_header + (old_content.rstrip + "\n\n" + info_block)) :
203
+ (encoding_header + info_block + old_content)
140
204
 
141
205
  File.open(file_name, "wb") { |f| f.puts new_content }
142
206
  true
@@ -148,7 +212,7 @@ module AnnotateModels
148
212
  if File.exist?(file_name)
149
213
  content = File.read(file_name)
150
214
 
151
- content.sub!(/^# #{COMPAT_PREFIX}.*?\n(#.*\n)*\n/, '')
215
+ content.sub!(PATTERN, '')
152
216
 
153
217
  File.open(file_name, "wb") { |f| f.puts content }
154
218
  end
@@ -160,7 +224,7 @@ module AnnotateModels
160
224
  # of the model and fixture source files.
161
225
  # Returns true or false depending on whether the source
162
226
  # files were modified.
163
- def annotate(klass, file, header,options={})
227
+ def annotate(klass, file, header, options={})
164
228
  info = get_schema_info(klass, header, options)
165
229
  annotated = false
166
230
  model_name = klass.name.underscore
@@ -169,37 +233,41 @@ module AnnotateModels
169
233
  if annotate_one_file(model_file_name, info, options_with_position(options, :position_in_class))
170
234
  annotated = true
171
235
  end
172
-
173
- unless ENV['exclude_tests']
236
+
237
+ unless options[:exclude_tests]
174
238
  [
175
- File.join(UNIT_TEST_DIR, "#{model_name}_test.rb"), # test
176
- File.join(SPEC_MODEL_DIR, "#{model_name}_spec.rb"), # spec
177
- ].each do |file|
239
+ find_test_file(UNIT_TEST_DIR, "#{model_name}_test.rb"), # test
240
+ find_test_file(SPEC_MODEL_DIR, "#{model_name}_spec.rb"), # spec
241
+ ].each do |file|
178
242
  # todo: add an option "position_in_test" -- or maybe just ask if anyone ever wants different positions for model vs. test vs. fixture
179
- annotate_one_file(file, info, options_with_position(options, :position_in_fixture))
243
+ if annotate_one_file(file, info, options_with_position(options, :position_in_fixture))
244
+ annotated = true
245
+ end
180
246
  end
181
247
  end
182
248
 
183
- unless ENV['exclude_fixtures']
249
+ unless options[:exclude_fixtures]
184
250
  [
185
- File.join(EXEMPLARS_TEST_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
186
- File.join(EXEMPLARS_SPEC_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
187
- File.join(BLUEPRINTS_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints
188
- ].each do |file|
189
- annotate_one_file(file, info, options_with_position(options, :position_in_fixture))
190
- end
191
-
192
- FIXTURE_DIRS.each do |dir|
193
- fixture_file_name = File.join(dir,klass.table_name + ".yml")
194
- if File.exist?(fixture_file_name)
195
- annotate_one_file(fixture_file_name, info, options_with_position(options, :position_in_fixture))
251
+ File.join(FIXTURE_TEST_DIR, "#{klass.table_name}.yml"), # fixture
252
+ File.join(FIXTURE_SPEC_DIR, "#{klass.table_name}.yml"), # fixture
253
+ File.join(EXEMPLARS_TEST_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
254
+ File.join(EXEMPLARS_SPEC_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
255
+ File.join(BLUEPRINTS_TEST_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints
256
+ File.join(BLUEPRINTS_SPEC_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints
257
+ File.join(FACTORY_GIRL_TEST_DIR, "#{model_name}_factory.rb"), # Factory Girl Factories
258
+ File.join(FACTORY_GIRL_SPEC_DIR, "#{model_name}_factory.rb"), # Factory Girl Factories
259
+ File.join(FABRICATORS_TEST_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators
260
+ File.join(FABRICATORS_SPEC_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators
261
+ ].each do |file|
262
+ if annotate_one_file(file, info, options_with_position(options, :position_in_fixture))
263
+ annotated = true
196
264
  end
197
265
  end
198
266
  end
199
-
267
+
200
268
  annotated
201
269
  end
202
-
270
+
203
271
  # position = :position_in_fixture or :position_in_class
204
272
  def options_with_position(options, position_in)
205
273
  options.merge(:position=>(options[position_in] || options[:position]))
@@ -210,14 +278,22 @@ module AnnotateModels
210
278
  # the underscore or CamelCase versions of model names.
211
279
  # Otherwise we take all the model files in the
212
280
  # model_dir directory.
213
- def get_model_files
214
- models = ARGV.dup
215
- models.shift
281
+ def get_model_files(options)
282
+ if(!options[:is_rake])
283
+ models = ARGV.dup
284
+ models.shift
285
+ else
286
+ models = []
287
+ end
216
288
  models.reject!{|m| m.match(/^(.*)=/)}
217
289
  if models.empty?
218
290
  begin
219
291
  Dir.chdir(model_dir) do
220
- models = Dir["**/*.rb"]
292
+ models = if options[:ignore_model_sub_dir]
293
+ Dir["*.rb"]
294
+ else
295
+ Dir["**/*.rb"]
296
+ end
221
297
  end
222
298
  rescue SystemCallError
223
299
  puts "No models found in directory '#{model_dir}'."
@@ -233,18 +309,18 @@ module AnnotateModels
233
309
  # Check for namespaced models in subdirectories as well as models
234
310
  # in subdirectories without namespacing.
235
311
  def get_model_class(file)
236
- require File.expand_path("#{model_dir}/#{file}") # this is for non-rails projects, which don't get Rails auto-require magic
237
- model = ActiveSupport::Inflector.camelize(file.gsub(/\.rb$/, ''))
238
- parts = model.split('::')
239
- begin
240
- parts.inject(Object) {|klass, part| klass.const_get(part) }
241
- rescue LoadError, NameError
242
- begin
243
- Object.const_get(parts.last)
244
- rescue LoadError, NameError
245
- Object.const_get(Module.constants.detect{|c|parts.last.downcase == c.downcase})
246
- end
247
- end
312
+ # this is for non-rails projects, which don't get Rails auto-require magic
313
+ require File.expand_path("#{model_dir}/#{file}")
314
+
315
+ model_path = file.gsub(/\.rb$/, '')
316
+ get_loaded_model(model_path) || get_loaded_model(model_path.split('/').last)
317
+ end
318
+
319
+ # Retrieve loaded model class by path to the file where it's supposed to be defined.
320
+ def get_loaded_model(model_path)
321
+ ObjectSpace.each_object.
322
+ select { |c| c.is_a?(Class) && c.ancestors.include?(ActiveRecord::Base) }.
323
+ detect { |c| ActiveSupport::Inflector.underscore(c) == model_path }
248
324
  end
249
325
 
250
326
  # We're passed a name of things that might be
@@ -258,7 +334,7 @@ module AnnotateModels
258
334
  end
259
335
  end
260
336
 
261
- header = PREFIX.dup
337
+ header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
262
338
 
263
339
  if options[:include_version]
264
340
  version = ActiveRecord::Migrator.current_version rescue 0
@@ -272,19 +348,17 @@ module AnnotateModels
272
348
  end
273
349
 
274
350
  annotated = []
275
- get_model_files.each do |file|
351
+ get_model_files(options).each do |file|
276
352
  begin
277
353
  klass = get_model_class(file)
278
- if klass < ActiveRecord::Base && !klass.abstract_class?
354
+ if klass && klass < ActiveRecord::Base && !klass.abstract_class?
279
355
  if annotate(klass, file, header, options)
280
356
  annotated << klass
281
357
  end
282
358
  end
283
359
  rescue Exception => e
284
- puts "Unable to annotate #{file}: #{e.inspect}"
285
- puts ""
286
- # todo: check if all backtrace lines are in "gems" -- if so, it's an annotate bug, so print the whole stack trace.
287
- # puts e.backtrace.join("\n\t")
360
+ # todo: check if all backtrace lines are in "gems" -- if so, it's an annotate bug, so print the whole stack trace.
361
+ puts "Unable to annotate #{file}: #{e.message} (#{e.backtrace.first})"
288
362
  end
289
363
  end
290
364
  if annotated.empty?
@@ -300,25 +374,33 @@ module AnnotateModels
300
374
  self.model_dir = options[:model_dir]
301
375
  end
302
376
  deannotated = []
303
- get_model_files.each do |file|
377
+ get_model_files(options).each do |file|
304
378
  begin
305
379
  klass = get_model_class(file)
306
380
  if klass < ActiveRecord::Base && !klass.abstract_class?
307
381
  deannotated << klass
308
382
 
383
+ model_name = klass.name.underscore
309
384
  model_file_name = File.join(model_dir, file)
310
385
  remove_annotation_of_file(model_file_name)
311
386
 
312
- FIXTURE_DIRS.each do |dir|
313
- fixture_file_name = File.join(dir,klass.table_name + ".yml")
314
- remove_annotation_of_file(fixture_file_name) if File.exist?(fixture_file_name)
315
- end
316
-
317
- [ File.join(UNIT_TEST_DIR, "#{klass.name.underscore}_test.rb"),
318
- File.join(SPEC_MODEL_DIR,"#{klass.name.underscore}_spec.rb")].each do |file|
387
+ [
388
+ File.join(UNIT_TEST_DIR, "#{model_name}_test.rb"),
389
+ File.join(SPEC_MODEL_DIR, "#{model_name}_spec.rb"),
390
+ File.join(FIXTURE_TEST_DIR, "#{klass.table_name}.yml"), # fixture
391
+ File.join(FIXTURE_SPEC_DIR, "#{klass.table_name}.yml"), # fixture
392
+ File.join(EXEMPLARS_TEST_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
393
+ File.join(EXEMPLARS_SPEC_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
394
+ File.join(BLUEPRINTS_TEST_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints
395
+ File.join(BLUEPRINTS_SPEC_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints
396
+ File.join(FACTORY_GIRL_TEST_DIR, "#{model_name}_factory.rb"), # Factory Girl Factories
397
+ File.join(FACTORY_GIRL_SPEC_DIR, "#{model_name}_factory.rb"), # Factory Girl Factories
398
+ File.join(FABRICATORS_TEST_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators
399
+ File.join(FABRICATORS_SPEC_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators
400
+ ].each do |file|
319
401
  remove_annotation_of_file(file) if File.exist?(file)
320
402
  end
321
-
403
+
322
404
  end
323
405
  rescue Exception => e
324
406
  puts "Unable to annotate #{file}: #{e.message}"
@@ -326,15 +408,9 @@ module AnnotateModels
326
408
  end
327
409
  puts "Removed annotation from: #{deannotated.join(', ')}"
328
410
  end
329
- end
330
- end
331
-
332
- # monkey patches
333
411
 
334
- module ::ActiveRecord
335
- class Base
336
- def self.method_missing(name, *args)
337
- # ignore this, so unknown/unloaded macros won't cause parsing to fail
412
+ def find_test_file(dir, file_name)
413
+ Dir.glob(File.join(dir, "**", file_name)).first || File.join(dir, file_name)
338
414
  end
339
415
  end
340
416
  end