annotate 2.4.1.beta1 → 2.5.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +75 -1
- data/README.rdoc +80 -57
- data/bin/annotate +46 -19
- data/lib/annotate.rb +4 -14
- data/lib/annotate/active_record_patch.rb +9 -0
- data/lib/annotate/annotate_models.rb +188 -112
- data/lib/annotate/annotate_routes.rb +11 -11
- data/lib/annotate/tasks.rb +6 -0
- data/lib/annotate/version.rb +5 -0
- data/lib/generators/annotate_models/USAGE +4 -0
- data/lib/generators/annotate_models/install_generator.rb +14 -0
- data/lib/generators/annotate_models/templates/auto_annotate_models.rake +20 -0
- data/lib/tasks/annotate_models.rake +29 -9
- data/lib/tasks/annotate_routes.rake +3 -2
- data/tasks/migrate.rake +4 -4
- metadata +31 -29
- data/Rakefile +0 -51
- data/VERSION.yml +0 -5
- data/annotate.gemspec +0 -58
- data/spec/annotate/annotate_models_spec.rb +0 -92
- data/spec/annotate/annotate_routes_spec.rb +0 -47
- data/spec/annotate_spec.rb +0 -9
- data/spec/spec.opts +0 -1
- data/spec/spec_helper.rb +0 -10
- data/todo.txt +0 -5
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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/
|
44
|
-
|
46
|
+
gem 'annotate', :git => 'git://github.com/MrJoy/annotate_models.git'
|
45
47
|
|
46
|
-
Into environment gems From
|
48
|
+
Into environment gems From rubygems.org:
|
47
49
|
|
48
|
-
|
50
|
+
gem install annotate
|
49
51
|
|
50
52
|
Into environment gems from Github checkout:
|
51
53
|
|
52
|
-
git clone git://github.com/
|
53
|
-
cd
|
54
|
-
rake
|
55
|
-
|
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
|
-
|
66
|
+
annotate_models
|
66
67
|
|
67
68
|
To annotate your models and tests:
|
68
69
|
|
69
|
-
|
70
|
+
annotate_models --exclude fixtures
|
70
71
|
|
71
72
|
To annotate just your models:
|
72
73
|
|
73
|
-
|
74
|
+
annotate_models --exclude tests,fixtures
|
74
75
|
|
75
76
|
To annotate routes.rb:
|
76
77
|
|
77
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
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
|
84
|
-
|
85
|
-
|
86
|
-
|
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:
|
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
|
97
|
-
-s, --simple-indexes
|
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
|
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
|
-
|
129
|
+
BACK UP YOUR MODELS BEFORE USING THIS TOOL!
|
111
130
|
|
112
131
|
== LINKS
|
113
132
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
==
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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 '
|
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:
|
16
|
+
opts.banner = "Usage: annotate_models [options] [model_file]*"
|
10
17
|
|
11
|
-
opts.on('-d', '--delete',
|
12
|
-
|
18
|
+
opts.on('-d', '--delete',
|
19
|
+
"Remove annotations from all model files") do
|
13
20
|
task = :remove_annotation
|
14
21
|
end
|
15
22
|
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
2
|
-
|
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
|
@@ -1,20 +1,41 @@
|
|
1
1
|
module AnnotateModels
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
46
|
-
info
|
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
|
-
|
49
|
-
|
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
|
-
|
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
|
70
|
-
if options[:simple_indexes]
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
123
|
-
old_header = old_content.match(
|
124
|
-
new_header = info_block.match(
|
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
|
-
|
127
|
-
|
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
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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!(
|
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
|
236
|
+
|
237
|
+
unless options[:exclude_tests]
|
174
238
|
[
|
175
|
-
|
176
|
-
|
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
|
249
|
+
unless options[:exclude_fixtures]
|
184
250
|
[
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
215
|
-
|
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 =
|
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
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
-
|
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
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
335
|
-
|
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
|