Commit 6d7bfc08 by Alex Chaffee

Merge branch 'mrjoy'

* mrjoy: (177 commits) Updating authorship. Resolve conflict between generated rakefile and CLI tool. Minor fixups. Updating readme to be RDoc compliant. fixed a few typos Updating docs to point to this fork. Update docs. Yep, I'm tired. Revert "Version bump to 0.0.0" Version bump to 0.0.0 Changing my mind on project name issue... Making project name match Github project name. Bumping version. Wider type column output, play nice with Rake CLI. Fixing behavior with some model sub-classes, adding skip-annotations feature. Making ActiveSupport dependency float, updating gemspec. Updating change log, readme and TODO list. Update generated .rake file with new options. Renaming bin to avoid conflict with ImageMagick. Accept string or symbol for positions. ... Conflicts: Gemfile Gemfile.lock History.txt README.rdoc Rakefile VERSION.yml annotate.gemspec annotate_models.gemspec lib/annotate/annotate_models.rb lib/tasks/annotate_models.rake lib/tasks/annotate_routes.rake spec/annotate/annotate_models_spec.rb spec/spec_helper.rb todo.txt
parents c52cefe2 b7735703
...@@ -6,3 +6,6 @@ spec/debug.log ...@@ -6,3 +6,6 @@ spec/debug.log
pkg/* pkg/*
dist dist
Gemfile.lock Gemfile.lock
*.gem
/.idea/
.rvmrc
rvm use ruby-1.9.2-p290@annotate_models
...@@ -3,9 +3,10 @@ source :rubygems ...@@ -3,9 +3,10 @@ source :rubygems
gem "rake" gem "rake"
group :development do group :development do
gem "rspec" gem 'mg'
gem "rdoc" gem 'rspec'
gem "mg" gem 'wrong'
gem 'activesupport', '>= 2.1.0' gem 'rdoc'
gem "wrong"
end end
gem 'activesupport', '>= 3.0.0', :require => nil
...@@ -56,7 +56,7 @@ PLATFORMS ...@@ -56,7 +56,7 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
activesupport (>= 2.1.0) activesupport (>= 3.0.0)
mg mg
rake rake
rdoc rdoc
......
...@@ -20,7 +20,53 @@ ...@@ -20,7 +20,53 @@
* Support FactoryGirl * Support FactoryGirl
* Support :change migrations (Rails 3.1) * Support :change migrations (Rails 3.1)
* Allow models with non-standard capitalization * Allow models with non-standard capitalization
* Widen type column so we can handle longtexts with chopping things off.
* Skip trying to get list of models from commandline when running via Rake (was
preventing the use of multiple rake tasks in one command if one of them was
db:migrate).
* Add ability to skip annotations for a model by adding
'# -*- SkipSchemaAnnotations' anywhere in the file.
* Don't show column limits for integer and boolean types.
* Add sorting for columns and indexes. (Helpful for out-of-order migration
execution, but use --no-sort if you don't want this.)
* Annotate unit tests in subfolders.
* Add generator to install rakefile that automatically annotates on db:migrate.
* Correct Gemfile to clarify which environments need which gems.
* Add an .rvmrc to facilitate clean development.
* Refactor out ActiveRecord monkey-patch to permit extending without
side-effects.
* Use ObjectSpace to locate models to facilitate handling of models with
non-standard capitalization.
Note that this still requires that the inflector be configured to understand
the special case.
* Shore up test cases a bit.
* Merge against many of the older branches on Github whose functionality is
already reflected to reduce confusion about what is and is not implemented
here.
* Accept String or Symbol for :position (et al) options.
* Rename "annotate" bin to "annotate_models" to avoid conflicting with
ImageMagick.
* Add RDoc output formatting as an option.
* Add Markdown output formatting as an option.
* Add option to force annotation regeneration.
* Add new configuration option for controlling where info is placed in
fixtures/factories.
* Fix for models without tables.
* Fix gemspec generation now that Jeweler looks at Gemfile.
* Fix warning: `NOTE: Gem::Specification#default_executable= is deprecated with
no replacement. It will be removed on or after 2011-10-01.`
* Fix handling of files with no trailing newline when putting annotations at
the end of the file.
== 2.4.2 2009-11-21
* Annotates (spec|test)/factories/<model>_factory.rb files
== 2.4.1 2009-11-20
* Annotates thoughtbot's factory_girl factories (test/factories/<model>_factory.rb)
* Move default annotation position back to top
>>>>>>> mrjoy
== 2.4.0 2009-12-13 == 2.4.0 2009-12-13
......
...@@ -8,6 +8,7 @@ Add a comment summarizing the current schema to the top or bottom of each of you ...@@ -8,6 +8,7 @@ Add a comment summarizing the current schema to the top or bottom of each of you
* Object Daddy exemplars * Object Daddy exemplars
* Machinist blueprints * Machinist blueprints
* Fabrication fabricators * Fabrication fabricators
* Thoughtbot's factory_girl factories, i.e. the (spec|test)/factories/<model>_factory.rb files
The schema comment looks like this: The schema comment looks like this:
...@@ -41,7 +42,7 @@ Also, if you pass the -r option, it'll annotate routes.rb with the output of "ra ...@@ -41,7 +42,7 @@ Also, if you pass the -r option, it'll annotate routes.rb with the output of "ra
Into Gemfile from Github: Into Gemfile from Github:
gem 'annotate', :git => 'git://github.com/ctran/annotate_models.git' gem 'annotate', :git => 'git://github.com/MrJoy/annotate_models.git'
Into environment gems From rubygems.org: Into environment gems From rubygems.org:
...@@ -49,8 +50,8 @@ Into environment gems From rubygems.org: ...@@ -49,8 +50,8 @@ Into environment gems From rubygems.org:
Into environment gems from Github checkout: Into environment gems from Github checkout:
git clone git://github.com/ctran/annotate_models.git annotate git clone git://github.com/MrJoy/annotate_models.git annotate_models
cd annotate cd annotate_models
rake build rake build
gem install pkg/annotate-*.gem gem install pkg/annotate-*.gem
...@@ -61,60 +62,61 @@ Into environment gems from Github checkout: ...@@ -61,60 +62,61 @@ Into environment gems from Github checkout:
To annotate all your models, tests, fixtures, etc.: To annotate all your models, tests, fixtures, etc.:
cd /path/to/app cd /path/to/app
annotate annotate_models
To annotate your models and tests: To annotate your models and tests:
annotate --exclude fixtures annotate_models --exclude fixtures
To annotate just your models: To annotate just your models:
annotate --exclude tests,fixtures annotate_models --exclude tests,fixtures
To annotate routes.rb: To annotate routes.rb:
annotate -r annotate_models -r
To automatically annotate after running 'rake db:migrate': To remove annotations:
[*needs more clarity*] unpack the gem into vendor/plugins, or maybe vendor/gems, or maybe just require tasks/migrate.rake. annotate_models -d
If you install annotate_models as a plugin, it will automatically To automatically annotate after running 'rake db:migrate', ensure you've added
adjust your <tt>rake db:migrate</tt> tasks so that they update the annotate_models to your Rails project's Gemfile, and run this:
annotations in your model files for you once the migration is
completed. To get the same behavior from a gem, add the following to
your Rakefile:
require 'annotate/tasks' rails g annotate_models:install
To customize the behavior of annotate when it is running as a Rake This will produce a .rake file that will ensure annotation happens after
task, use the following (in your Rakefile or wherever): migration (but only in development mode), and provide configuration options
you can use to tailor the output.
ENV['position_in_class'] = "before" If you want to always skip annotations on a particular model, add this string
ENV['position_in_fixture'] = "before" anywhere in the file:
ENV['show_indexes'] = "false"
ENV['include_version'] = "false"
ENV['exclude_tests'] = "false"
ENV['exclude_fixtures'] = "false"
ENV['skip_on_db_migrate'] = "false"
Warning: ImageMagick installs a tool called `annotate` too (if you're using MacPorts it's in `/opt/local/bin/annotate`. So if you see Usage: annotate imagein.jpg imageout.jpg then put `/usr/bin` ahead on the path and you'll get ours instead.
# -*- SkipSchemaAnnotations
== OPTIONS == OPTIONS
Usage: annotate [options] [model_file]* Usage: annotate_models [options] [model_file]*
-d, --delete Remove annotations from all model files -d, --delete Remove annotations from all model files
-p, --position [before|after] Place the annotations at the top (before) or the bottom (after) of the model file -p, --position [before|after] Place the annotations at the top (before) or the bottom (after) of the model file
-r, --routes Annotate routes.rb with the output of 'rake routes' -r, --routes Annotate routes.rb with the output of 'rake routes'
-v, --version Show the current version of this gem -v, --version Show the current version of this gem
-m, --show-migration Include the migration version number in the annotation -m, --show-migration Include the migration version number in the annotation
-i, --show-indexes List the table's database indexes in the annotation -i, --show-indexes List the indexes for the table in the annotation
-s, --simple-indexes Concat the column's related indexes in the annotation -s, --simple-indexes Include information about indexes inline with the relevant column
--model-dir dir Annotate model files stored in dir rather than app/models --model-dir dir Annotate model files stored in dir rather than app/models
--ignore-model-subdirs Ignore sub-directories of the models directory.
-R, --require path Additional files to require before loading models -R, --require path Additional files to require before loading models
-e, --exclude [tests,fixtures] Do not annotate fixtures, test files, or both -e [tests,fixtures] Skip annotation of fixtures/factories/test files
--exclude
-n --no-sort Sort by column creation order rather than alphabetical order
== SORTING
By default, columns will be sorted alphabetically so that the results of
annotation are consistent regardless of what order migrations are executed in.
If you prefer the old behavior, use --no-sort.
== WARNING == WARNING
...@@ -123,16 +125,16 @@ block in your models if it looks like it was previously added ...@@ -123,16 +125,16 @@ block in your models if it looks like it was previously added
by annotate models, so you don't want to add additional text by annotate models, so you don't want to add additional text
to an automatically created comment block. to an automatically created comment block.
* * Back up your model files before using... * * BACK UP YOUR MODELS BEFORE USING THIS TOOL!
== LINKS == LINKS
* Factory Girl => http://github.com/thoughtbot/factory_girl (NOT IMPLEMENTED) - Factory Girl: http://github.com/thoughtbot/factory_girl
* Object Daddy => http://github.com/flogic/object_daddy - Object Daddy: http://github.com/flogic/object_daddy
* Machinist => http://github.com/notahat/machinist - Machinist: http://github.com/notahat/machinist
* Fabrication => http://github.com/paulelliott/fabrication - Fabrication: http://github.com/paulelliott/fabrication
* SpatialAdapter => http://github.com/pdeffendol/spatial_adapter - SpatialAdapter: http://github.com/pdeffendol/spatial_adapter
* PostgisAdapter => http://github.com/nofxx/postgis_adapter - PostgisAdapter: http://github.com/nofxx/postgis_adapter
== LICENSE: == LICENSE:
...@@ -150,21 +152,23 @@ Maintained by: Alex Chaffee and Cuong Tran ...@@ -150,21 +152,23 @@ Maintained by: Alex Chaffee and Cuong Tran
Homepage: http://github.com/ctran/annotate_models Homepage: http://github.com/ctran/annotate_models
Modifications by: With help from:
- Alex Chaffee - http://github.com/alexch - alex@pivotallabs.com - Alex Chaffee - http://github.com/alexch - alex@pivotallabs.com
- Cuong Tran - http://github.com/ctran - ctran@pragmaquest.com - Cuong Tran - http://github.com/ctran - ctran@pragmaquest.com
- Jack Danger - http://github.com/JackDanger - Jack Danger - http://github.com/JackDanger
- Michael Bumann - http://github.com/bumi - Michael Bumann - http://github.com/bumi
- Henrik Nyh - http://github.com/henrik - Henrik Nyh - http://github.com/henrik
- Marcos Piccinini - http://github.com/nofxx - Marcos Piccinini - http://github.com/nofxx
- Neal Clark - http://github.com/nclark - Neal Clark - http://github.com/nclark
- Jacqui Maher - http://github.com/jacqui - Jacqui Maher - http://github.com/jacqui
- Nick Plante - http://github.com/zapnap - http://blog.zerosum.org - Nick Plante - http://github.com/zapnap - http://blog.zerosum.org
- Pedro Visintin - http://github.com/peterpunk - http://www.pedrovisintin.com - Pedro Visintin - http://github.com/peterpunk - http://www.pedrovisintin.com
- Bob Potter - http://github.com/bpot - Bob Potter - http://github.com/bpot
- Gavin Montague - http://github.com/govan/ - Gavin Montague - http://github.com/govan/
- Alexander Semyonov - http://github.com/rotuka/ - Alexander Semyonov - http://github.com/rotuka/
- Ian Duggan http://github.com/ijcd/ - Nathan Brazil - http://github.com/bitaxis/
- Ian Duggan http://github.com/ijcd/
- Jon Frisby http://github.com/mrjoy/
and many others that I may have missed to add. and many others that I may have missed to add.
here = File.dirname __FILE__ here = File.dirname __FILE__
require 'rubygems' require 'rubygems'
require 'bundler'
begin
Bundler.setup(:default, :development)
rescue Bundler::BundlerError => e
$stderr.puts e.message
$stderr.puts "Run `bundle install` to install missing gems"
exit e.status_code
end
require 'rake/dsl_definition'
require 'rake' require 'rake'
require "#{here}/lib/annotate" require "#{here}/lib/annotate"
# want other tests/tasks run by default? Add them to the list # want other tests/tasks run by default? Add them to the list
...@@ -25,12 +35,7 @@ begin ...@@ -25,12 +35,7 @@ begin
rescue LoadError rescue LoadError
abort "Please `gem install mg`" abort "Please `gem install mg`"
end end
MG.new("annotate_models.gemspec") MG.new("annotate.gemspec")
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = 'spec/**/*_spec.rb'
end
task :default => :spec task :default => :spec
...@@ -39,18 +44,10 @@ RSpec::Core::RakeTask.new(:spec) do |t| ...@@ -39,18 +44,10 @@ RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = ['spec/*_spec.rb', 'spec/**/*_spec.rb'] t.pattern = ['spec/*_spec.rb', 'spec/**/*_spec.rb']
end end
# FIXME not working yet require 'rdoc/task'
RSpec::Core::RakeTask.new(:rcov) do |t| RDoc::Task.new do |rdoc|
t.pattern = 'spec/**/*_spec.rb' rdoc.rdoc_dir = 'rdoc'
t.rcov = true rdoc.title = "annotated_models #{Annotate.version}"
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end end
# FIXME warns "already initialized constant Task"
# FIXME throws "uninitialized constant RDoc::VISIBILITIES"
# require 'rdoc/task'
# RDoc::Task.new do |rdoc|
# rdoc.main = "README.rdoc"
# rdoc.rdoc_files.include("README.rdoc", "lib/**/*.rb")
# # require 'lib/annotate'
# # rdoc.title = "annotate #{Annotate.version}"
# end
require './lib/annotate'
Gem::Specification.new do |s|
s.name = %q{annotate_models}
s.version = Annotate.version
s.description = %q{Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema.}
s.summary = %q{Annotates Rails Models, routes, fixtures, and others based on the database schema.}
s.authors = ["Cuong Tran", "Alex Chaffee", "Marcos Piccinini", "Turadg Aleahmad"]
s.email = ["alex@stinky.com", "ctran@pragmaquest.com", "x@nofxx.com", "turadg@aleahmad.net"]
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.extra_rdoc_files = ["README.rdoc"]
s.files = %w( README.rdoc VERSION.yml History.txt )
s.files += Dir.glob("lib/**/*")
s.files += Dir.glob("tasks/**/*")
s.files += ["bin/annotate"] # todo: annotate_models
s.homepage = %q{http://github.com/ctran/annotate_models}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{annotate}
s.add_runtime_dependency 'rake' # ?
end
...@@ -2,11 +2,13 @@ ...@@ -2,11 +2,13 @@
require 'optparse' require 'optparse'
require 'annotate' require 'annotate'
require 'rake/dsl_definition'
require 'rake'
task = :annotate_models task = :annotate_models
OptionParser.new do |opts| OptionParser.new do |opts|
opts.banner = "Usage: annotate [options] [model_file]*" opts.banner = "Usage: annotate_models [options] [model_file]*"
opts.on('-d', '--delete', opts.on('-d', '--delete',
"Remove annotations from all model files") do "Remove annotations from all model files") do
...@@ -48,6 +50,16 @@ OptionParser.new do |opts| ...@@ -48,6 +50,16 @@ OptionParser.new do |opts|
ENV['model_dir'] = dir ENV['model_dir'] = dir
end end
opts.on('--ignore-model-subdirects',
"Ignore subdirectories of the models directory") do |dir|
ENV['ignore_model_sub_dir'] = "yes"
end
opts.on('-n', '--no-sort',
"Sort columns in creation order rather than alphabetically") do |dir|
ENV['no_sort'] = "yes"
end
opts.on('-R', '--require path', opts.on('-R', '--require path',
"Additional files to require before loading models") do |path| "Additional files to require before loading models") do |path|
if ENV['require'] if ENV['require']
...@@ -61,8 +73,17 @@ OptionParser.new do |opts| ...@@ -61,8 +73,17 @@ OptionParser.new do |opts|
exclusions.each { |exclusion| ENV["exclude_#{exclusion}"] = "yes" } exclusions.each { |exclusion| ENV["exclude_#{exclusion}"] = "yes" }
end end
opts.on('-f', '--format [bare|rdoc|markdown]', ['bare', 'rdoc', 'markdown'], 'Render Schema Infomation as plain/RDoc/Markdown') do |fmt|
ENV["format_#{fmt}"] = 'yes'
end
opts.on('--force', 'Force new annotations even if there are no changes.') do |force|
ENV['force'] = 'yes'
end
end.parse! end.parse!
ENV['is_cli'] = '1'
if Annotate.load_tasks if Annotate.load_tasks
Rake::Task[task].invoke Rake::Task[task].invoke
else else
......
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
require 'yaml' require 'yaml'
...@@ -18,6 +16,8 @@ module Annotate ...@@ -18,6 +16,8 @@ module Annotate
if File.exists?('Rakefile') if File.exists?('Rakefile')
require 'rake' require 'rake'
load 'Rakefile' load 'Rakefile'
# Rails 3 wants to load our .rake files for us.
# TODO: selectively do this require on Rails 2.x?
Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each { |rake| load rake } Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each { |rake| load rake }
return true return true
else else
......
# monkey patches
module ::ActiveRecord
class Base
def self.method_missing(name, *args)
# ignore this, so unknown/unloaded macros won't cause parsing to fail
end
end
end
\ No newline at end of file
module AnnotateModels module AnnotateModels
class << self
# Annotate Models plugin use this header # Annotate Models plugin use this header
COMPAT_PREFIX = "== Schema Info" COMPAT_PREFIX = "== Schema Info"
COMPAT_PREFIX_MD = "## Schema Info"
PREFIX = "== Schema Information" PREFIX = "== Schema Information"
PREFIX_MD = "## Schema Information"
END_MARK = "== Schema Information End"
PATTERN = /^\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n/
FIXTURE_DIRS = ["test/fixtures","spec/fixtures"]
# File.join for windows reverse bar compat? # File.join for windows reverse bar compat?
# I dont use windows, can`t test # I dont use windows, can`t test
UNIT_TEST_DIR = File.join("test", "unit" ) UNIT_TEST_DIR = File.join("test", "unit" )
SPEC_MODEL_DIR = File.join("spec", "models") SPEC_MODEL_DIR = File.join("spec", "models")
# Object Daddy http://github.com/flogic/object_daddy FIXTURE_TEST_DIR = File.join("test", "fixtures")
FIXTURE_SPEC_DIR = File.join("spec", "fixtures")
FIXTURE_DIRS = ["test/fixtures","spec/fixtures"]
# Object Daddy http://github.com/flogic/object_daddy/tree/master
EXEMPLARS_TEST_DIR = File.join("test", "exemplars") EXEMPLARS_TEST_DIR = File.join("test", "exemplars")
EXEMPLARS_SPEC_DIR = File.join("spec", "exemplars") EXEMPLARS_SPEC_DIR = File.join("spec", "exemplars")
# Machinist http://github.com/notahat/machinist # Machinist http://github.com/notahat/machinist
BLUEPRINTS_DIR = File.join("test", "blueprints") BLUEPRINTS_TEST_DIR = File.join("test", "blueprints")
# FactoryGirl http://github.com/thoughtbot/factory_girl BLUEPRINTS_SPEC_DIR = File.join("spec", "blueprints")
FACTORIES_TEST_DIR = File.join("test", "factories")
FACTORIES_SPEC_DIR = File.join("spec", "factories") # Factory Girl http://github.com/thoughtbot/factory_girl
FACTORY_GIRL_TEST_DIR = File.join("test", "factories")
FACTORY_GIRL_SPEC_DIR = File.join("spec", "factories")
# Fabrication https://github.com/paulelliott/fabrication.git # Fabrication https://github.com/paulelliott/fabrication.git
FABRICATORS_TEST_DIR = File.join("test", "fabricators") FABRICATORS_TEST_DIR = File.join("test", "fabricators")
FABRICATORS_SPEC_DIR = File.join("spec", "fabricators") FABRICATORS_SPEC_DIR = File.join("spec", "fabricators")
# Don't show limit (#) on these column types
# Example: show "integer" instead of "integer(4)"
NO_LIMIT_COL_TYPES = ["integer", "boolean"]
class << self
def model_dir def model_dir
@model_dir || "app/models" @model_dir || "app/models"
end end
...@@ -49,21 +63,34 @@ module AnnotateModels ...@@ -49,21 +63,34 @@ module AnnotateModels
# each column. The line contains the column name, # each column. The line contains the column name,
# the type (and length), and any optional attributes # the type (and length), and any optional attributes
def get_schema_info(klass, header, options = {}) def get_schema_info(klass, header, options = {})
info = "# #{header}\n#\n" info = "# #{header}\n"
info << "# Table name: #{klass.table_name}\n#\n" info<< "#\n"
info<< "# Table name: #{klass.table_name}\n"
info<< "#\n"
max_size = klass.column_names.map{|name| name.size}.max || 0
max_size += options[:format_rdoc] ? 5 : 1
max_size = klass.column_names.collect{|name| name.size}.max + 1 if(options[:format_markdown])
klass.columns.each do |col| info<< sprintf( "# %-#{max_size + 4}.#{max_size + 4}s | %-18.18s | %s\n", 'Field', 'Type', 'Attributes' )
info<< "# #{ '-' * ( max_size + 4 ) } | #{'-' * 18} | #{ '-' * 25 }\n"
end
cols = klass.columns
cols = cols.sort_by(&:name) unless(options[:no_sort])
cols.each do |col|
attrs = [] attrs = []
attrs << "default(#{quote(col.default)})" unless col.default.nil? attrs << "default(#{quote(col.default)})" unless col.default.nil?
attrs << "not null" unless col.null attrs << "not null" unless col.null
attrs << "primary key" if col.name == klass.primary_key attrs << "primary key" if col.name.to_sym == klass.primary_key.to_sym
col_type = col.type.to_s col_type = (col.type || col.sql_type).to_s
if col_type == "decimal" if col_type == "decimal"
col_type << "(#{col.precision}, #{col.scale})" col_type << "(#{col.precision}, #{col.scale})"
else else
col_type << "(#{col.limit})" if col.limit if (col.limit)
col_type << "(#{col.limit})" unless NO_LIMIT_COL_TYPES.include?(col_type)
end
end end
# Check out if we got a geometric column # Check out if we got a geometric column
...@@ -73,8 +100,8 @@ module AnnotateModels ...@@ -73,8 +100,8 @@ module AnnotateModels
end end
# Check if the column has indices and print "indexed" if true # Check if the column has indices and print "indexed" if true
# If the indice include another colum, print it too. # If the index includes another column, print it too.
if options[:simple_indexes] # Check out if this column is indexed if options[:simple_indexes] && klass.table_exists?# Check out if this column is indexed
indices = klass.connection.indexes(klass.table_name) indices = klass.connection.indexes(klass.table_name)
if indices = indices.select { |ind| ind.columns.include? col.name } if indices = indices.select { |ind| ind.columns.include? col.name }
indices.each do |ind| indices.each do |ind|
...@@ -84,15 +111,27 @@ module AnnotateModels ...@@ -84,15 +111,27 @@ module AnnotateModels
end end
end end
info << sprintf("# %-#{max_size}.#{max_size}s:%-15.15s %s", col.name, col_type, attrs.join(", ")).rstrip + "\n" if options[:format_rdoc]
info << sprintf("# %-#{max_size}.#{max_size}s<tt>%s</tt>", "*#{col.name}*::", attrs.unshift(col_type).join(", ")).rstrip + "\n"
elsif options[:format_markdown]
info << sprintf("# **%-#{max_size}.#{max_size}s** | `%-16.16s` | `%s`", col.name, col_type, attrs.join(", ").rstrip) + "\n"
else
info << sprintf("# %-#{max_size}.#{max_size}s:%-16.16s %s", col.name, col_type, attrs.join(", ")).rstrip + "\n"
end
end end
if options[:show_indexes] if options[:show_indexes] && klass.table_exists?
info << get_index_info(klass) info << get_index_info(klass)
end end
if options[:format_rdoc]
info << "#--\n"
info << "# #{END_MARK}\n"
info << "#++\n\n"
else
info << "#\n\n" info << "#\n\n"
end end
end
def get_index_info(klass) def get_index_info(klass)
index_info = "#\n# Indexes\n#\n" index_info = "#\n# Indexes\n#\n"
...@@ -100,7 +139,8 @@ module AnnotateModels ...@@ -100,7 +139,8 @@ module AnnotateModels
indexes = klass.connection.indexes(klass.table_name) indexes = klass.connection.indexes(klass.table_name)
return "" if indexes.empty? return "" if indexes.empty?
max_size = indexes.collect{|index| index.name.size}.max + 1 max_size = indexes.collect{|index| index.name.size}.max || 0
max_size += 1
indexes.each do |index| indexes.each do |index|
index_info << sprintf("# %-#{max_size}.#{max_size}s %s %s", index.name, "(#{index.columns.join(",")})", index.unique ? "UNIQUE" : "").rstrip + "\n" index_info << sprintf("# %-#{max_size}.#{max_size}s %s %s", index.name, "(#{index.columns.join(",")})", index.unique ? "UNIQUE" : "").rstrip + "\n"
end end
...@@ -115,7 +155,7 @@ module AnnotateModels ...@@ -115,7 +155,7 @@ module AnnotateModels
# #
# === Options (opts) # === Options (opts)
# :position<Symbol>:: where to place the annotated section in fixture or model file, # :position<Symbol>:: where to place the annotated section in fixture or model file,
# "before" or "after". Default is "before". # :before or :after. Default is :before.
# :position_in_class<Symbol>:: where to place the annotated section in model file # :position_in_class<Symbol>:: where to place the annotated section in model file
# :position_in_fixture<Symbol>:: where to place the annotated section in fixture file # :position_in_fixture<Symbol>:: where to place the annotated section in fixture file
# :position_in_others<Symbol>:: where to place the annotated section in the rest of # :position_in_others<Symbol>:: where to place the annotated section in the rest of
...@@ -124,6 +164,7 @@ module AnnotateModels ...@@ -124,6 +164,7 @@ module AnnotateModels
def annotate_one_file(file_name, info_block, options={}) def annotate_one_file(file_name, info_block, options={})
if File.exist?(file_name) if File.exist?(file_name)
old_content = File.read(file_name) old_content = File.read(file_name)
return false if(old_content =~ /# -\*- SkipSchemaAnnotations.*\n/)
# Ignore the Schema version line because it changes with each migration # Ignore the Schema version line because it changes with each migration
header_pattern = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?\n)/ header_pattern = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?\n)/
...@@ -134,21 +175,32 @@ module AnnotateModels ...@@ -134,21 +175,32 @@ module AnnotateModels
old_columns = old_header && old_header.scan(column_pattern).sort old_columns = old_header && old_header.scan(column_pattern).sort
new_columns = new_header && new_header.scan(column_pattern).sort new_columns = new_header && new_header.scan(column_pattern).sort
encoding = Regexp.new(/(^# encoding:.*\n)|(^# coding:.*\n)|(^# -\*- coding:.*\n)/) encoding = Regexp.new(/(^#\s*encoding:.*\n)|(^# coding:.*\n)|(^# -\*- coding:.*\n)/)
encoding_header = old_content.match(encoding).to_s encoding_header = old_content.match(encoding).to_s
if old_columns == new_columns if old_columns == new_columns && !options[:force]
false false
else else
# Replace the old schema info with the new schema info
new_content = old_content.sub(/^# #{COMPAT_PREFIX}.*?\n(#.*\n)*\n*/, info_block) # todo: figure out if we need to extract any logic from this merge chunk
# But, if there *was* no old schema info, we simply need to insert it # <<<<<<< HEAD
if new_content == old_content # # Replace the old schema info with the new schema info
# new_content = old_content.sub(/^# #{COMPAT_PREFIX}.*?\n(#.*\n)*\n*/, info_block)
# # But, if there *was* no old schema info, we simply need to insert it
# if new_content == old_content
# old_content.sub!(encoding, '')
# new_content = options[:position] == 'after' ?
# (encoding_header + (old_content =~ /\n$/ ? old_content : old_content + "\n") + info_block) :
# (encoding_header + info_block + old_content)
# end
# =======
# Strip the old schema info, and insert new schema info.
old_content.sub!(encoding, '') old_content.sub!(encoding, '')
new_content = options[:position] == 'after' ? old_content.sub!(PATTERN, '')
(encoding_header + (old_content =~ /\n$/ ? old_content : old_content + "\n") + info_block) : new_content = (options[:position] || 'before').to_s == 'after' ?
(encoding_header + (old_content.rstrip + "\n\n" + info_block)) :
(encoding_header + info_block + old_content) (encoding_header + info_block + old_content)
end
File.open(file_name, "wb") { |f| f.puts new_content } File.open(file_name, "wb") { |f| f.puts new_content }
true true
...@@ -160,7 +212,7 @@ module AnnotateModels ...@@ -160,7 +212,7 @@ module AnnotateModels
if File.exist?(file_name) if File.exist?(file_name)
content = File.read(file_name) content = File.read(file_name)
content.sub!(/^# #{COMPAT_PREFIX}.*?\n(#.*\n)*\n*/, '') content.sub!(PATTERN, '')
File.open(file_name, "wb") { |f| f.puts content } File.open(file_name, "wb") { |f| f.puts content }
end end
...@@ -184,8 +236,8 @@ module AnnotateModels ...@@ -184,8 +236,8 @@ module AnnotateModels
unless options[:exclude_tests] unless options[:exclude_tests]
[ [
File.join(UNIT_TEST_DIR, "#{model_name}_test.rb"), # test find_test_file(UNIT_TEST_DIR, "#{model_name}_test.rb"), # test
File.join(SPEC_MODEL_DIR, "#{model_name}_spec.rb"), # spec find_test_file(SPEC_MODEL_DIR, "#{model_name}_spec.rb"), # spec
].each do |file| ].each do |file|
# todo: add an option "position_in_test" -- or maybe just ask if anyone ever wants different positions for model vs. test vs. fixture # todo: add an option "position_in_test" -- or maybe just ask if anyone ever wants different positions for model vs. test vs. fixture
if annotate_one_file(file, info, options_with_position(options, :position_in_fixture)) if annotate_one_file(file, info, options_with_position(options, :position_in_fixture))
...@@ -196,11 +248,14 @@ module AnnotateModels ...@@ -196,11 +248,14 @@ module AnnotateModels
unless options[:exclude_fixtures] unless options[:exclude_fixtures]
[ [
File.join(FIXTURE_TEST_DIR, "#{klass.table_name}.yml"), # fixture
File.join(FIXTURE_SPEC_DIR, "#{klass.table_name}.yml"), # fixture
File.join(EXEMPLARS_TEST_DIR, "#{model_name}_exemplar.rb"), # Object Daddy File.join(EXEMPLARS_TEST_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
File.join(EXEMPLARS_SPEC_DIR, "#{model_name}_exemplar.rb"), # Object Daddy File.join(EXEMPLARS_SPEC_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
File.join(BLUEPRINTS_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints File.join(BLUEPRINTS_TEST_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints
File.join(FACTORIES_TEST_DIR, "#{model_name.pluralize}.rb"), # FactoryGirl Factories File.join(BLUEPRINTS_SPEC_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints
File.join(FACTORIES_SPEC_DIR, "#{model_name.pluralize}.rb"), # FactoryGirl Factories File.join(FACTORY_GIRL_TEST_DIR, "#{model_name}_factory.rb"), # Factory Girl Factories
File.join(FACTORY_GIRL_SPEC_DIR, "#{model_name}_factory.rb"), # Factory Girl Factories
File.join(FABRICATORS_TEST_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators File.join(FABRICATORS_TEST_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators
File.join(FABRICATORS_SPEC_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators File.join(FABRICATORS_SPEC_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators
].each do |file| ].each do |file|
...@@ -208,15 +263,6 @@ module AnnotateModels ...@@ -208,15 +263,6 @@ module AnnotateModels
annotated = true annotated = true
end end
end end
FIXTURE_DIRS.each do |dir|
fixture_file_name = File.join(dir,klass.table_name + ".yml")
if File.exist?(fixture_file_name)
if annotate_one_file(fixture_file_name, info, options_with_position(options, :position_in_fixture))
annotated = true
end
end
end
end end
annotated annotated
...@@ -232,14 +278,22 @@ module AnnotateModels ...@@ -232,14 +278,22 @@ module AnnotateModels
# the underscore or CamelCase versions of model names. # the underscore or CamelCase versions of model names.
# Otherwise we take all the model files in the # Otherwise we take all the model files in the
# model_dir directory. # model_dir directory.
def get_model_files def get_model_files(options)
if(!options[:is_rake])
models = ARGV.dup models = ARGV.dup
models.shift models.shift
else
models = []
end
models.reject!{|m| m.match(/^(.*)=/)} models.reject!{|m| m.match(/^(.*)=/)}
if models.empty? if models.empty?
begin begin
Dir.chdir(model_dir) do Dir.chdir(model_dir) do
models = Dir["**/*.rb"] models = if options[:ignore_model_sub_dir]
Dir["*.rb"]
else
Dir["**/*.rb"]
end
end end
rescue SystemCallError rescue SystemCallError
puts "No models found in directory '#{model_dir}'." puts "No models found in directory '#{model_dir}'."
...@@ -255,21 +309,18 @@ module AnnotateModels ...@@ -255,21 +309,18 @@ module AnnotateModels
# Check for namespaced models in subdirectories as well as models # Check for namespaced models in subdirectories as well as models
# in subdirectories without namespacing. # in subdirectories without namespacing.
def get_model_class(file) def get_model_class(file)
# this is for non-rails projects, which don't get Rails auto-require magic # this is for non-rails projects, which don't get Rails auto-require magic
require File.expand_path("#{model_dir}/#{file}") unless Module.const_defined?(:Rails) require File.expand_path("#{model_dir}/#{file}")
model = ActiveSupport::Inflector.camelize(file.gsub(/\.rb$/, '')) model_path = file.gsub(/\.rb$/, '')
parts = model.split('::') get_loaded_model(model_path) || get_loaded_model(model_path.split('/').last)
begin
parts.inject(Object) {|klass, part| klass.const_get(part) }
rescue LoadError, NameError
begin
Object.const_get(parts.last)
rescue LoadError, NameError
Object.const_get(Module.constants.detect{|c|parts.last.downcase == c.downcase})
end
end end
# Retrieve loaded model class by path to the file where it's supposed to be defined.
def get_loaded_model(model_path)
ObjectSpace.each_object.
select { |c| c.is_a?(Class) && c.ancestors.include?(ActiveRecord::Base) }.
detect { |c| ActiveSupport::Inflector.underscore(c) == model_path }
end end
# We're passed a name of things that might be # We're passed a name of things that might be
...@@ -283,7 +334,7 @@ module AnnotateModels ...@@ -283,7 +334,7 @@ module AnnotateModels
end end
end end
header = PREFIX.dup header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
if options[:include_version] if options[:include_version]
version = ActiveRecord::Migrator.current_version rescue 0 version = ActiveRecord::Migrator.current_version rescue 0
...@@ -297,19 +348,17 @@ module AnnotateModels ...@@ -297,19 +348,17 @@ module AnnotateModels
end end
annotated = [] annotated = []
get_model_files.each do |file| get_model_files(options).each do |file|
begin begin
klass = get_model_class(file) klass = get_model_class(file)
if klass < ActiveRecord::Base && !klass.abstract_class? if klass && klass < ActiveRecord::Base && !klass.abstract_class?
if annotate(klass, file, header, options) if annotate(klass, file, header, options)
annotated << klass annotated << klass
end end
end end
rescue Exception => e rescue Exception => e
puts "Unable to annotate #{file}: #{e.inspect}" # todo: check if all backtrace lines are in "gems" -- if so, it's an annotate bug, so print the whole stack trace.
puts "" puts "Unable to annotate #{file}: #{e.message} (#{e.backtrace.first})"
# todo: check if all backtrace lines are in "gems" -- if so, it's an annotate bug, so print the whole stack trace.
# puts e.backtrace.join("\n\t")
end end
end end
if annotated.empty? if annotated.empty?
...@@ -325,22 +374,30 @@ module AnnotateModels ...@@ -325,22 +374,30 @@ module AnnotateModels
self.model_dir = options[:model_dir] self.model_dir = options[:model_dir]
end end
deannotated = [] deannotated = []
get_model_files.each do |file| get_model_files(options).each do |file|
begin begin
klass = get_model_class(file) klass = get_model_class(file)
if klass < ActiveRecord::Base && !klass.abstract_class? if klass < ActiveRecord::Base && !klass.abstract_class?
deannotated << klass deannotated << klass
model_name = klass.name.underscore
model_file_name = File.join(model_dir, file) model_file_name = File.join(model_dir, file)
remove_annotation_of_file(model_file_name) remove_annotation_of_file(model_file_name)
FIXTURE_DIRS.each do |dir| [
fixture_file_name = File.join(dir,klass.table_name + ".yml") File.join(UNIT_TEST_DIR, "#{model_name}_test.rb"),
remove_annotation_of_file(fixture_file_name) if File.exist?(fixture_file_name) File.join(SPEC_MODEL_DIR, "#{model_name}_spec.rb"),
end File.join(FIXTURE_TEST_DIR, "#{klass.table_name}.yml"), # fixture
File.join(FIXTURE_SPEC_DIR, "#{klass.table_name}.yml"), # fixture
[ File.join(UNIT_TEST_DIR, "#{klass.name.underscore}_test.rb"), File.join(EXEMPLARS_TEST_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
File.join(SPEC_MODEL_DIR,"#{klass.name.underscore}_spec.rb")].each do |file| File.join(EXEMPLARS_SPEC_DIR, "#{model_name}_exemplar.rb"), # Object Daddy
File.join(BLUEPRINTS_TEST_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints
File.join(BLUEPRINTS_SPEC_DIR, "#{model_name}_blueprint.rb"), # Machinist Blueprints
File.join(FACTORY_GIRL_TEST_DIR, "#{model_name}_factory.rb"), # Factory Girl Factories
File.join(FACTORY_GIRL_SPEC_DIR, "#{model_name}_factory.rb"), # Factory Girl Factories
File.join(FABRICATORS_TEST_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators
File.join(FABRICATORS_SPEC_DIR, "#{model_name}_fabricator.rb"), # Fabrication Fabricators
].each do |file|
remove_annotation_of_file(file) if File.exist?(file) remove_annotation_of_file(file) if File.exist?(file)
end end
...@@ -351,18 +408,9 @@ module AnnotateModels ...@@ -351,18 +408,9 @@ module AnnotateModels
end end
puts "Removed annotation from: #{deannotated.join(', ')}" puts "Removed annotation from: #{deannotated.join(', ')}"
end end
end
end
# monkey patches
module ::ActiveRecord def find_test_file(dir, file_name)
class Base Dir.glob(File.join(dir, "**", file_name)).first || File.join(dir, file_name)
def self.method_missing(name, *args)
super
rescue NoMethodError => e
# ignore this, so unknown/unloaded macros won't cause parsing to fail
warn "Annotate Models ignoring #{e.class}: #{e.message}"
end end
end end
end end
Add a .rake file that automatically annotates models when you do a db:migrate
in development mode:
rails generate annotate_models:install
module AnnotateModels
module Generators
class InstallGenerator < Rails::Generators::Base
desc "Copy annotate_models rakefiles for automatic annotation"
source_root File.expand_path('../templates', __FILE__)
# copy rake tasks
def copy_tasks
template "auto_annotate_models.rake", "lib/tasks/auto_annotate_models.rake"
end
end
end
end
\ No newline at end of file
# NOTE: only doing this in development as some production environments (Heroku)
# NOTE: are sensitive to local FS writes, and besides -- it's just not proper
# NOTE: to have a dev-mode tool do its thing in production.
if(Rails.env.development?)
task :set_annotation_options do
ENV['position_in_class'] = "before"
ENV['position_in_fixture'] = "before"
ENV['position_in_factory'] = "before"
ENV['show_indexes'] = "true"
ENV['include_version'] = "false"
ENV['exclude_tests'] = "false"
ENV['exclude_fixtures'] = "false"
ENV['ignore_model_sub_dir'] = "false"
ENV['skip_on_db_migrate'] = "false"
ENV['format_rdoc'] = "false"
ENV['format_markdown'] = "false"
ENV['no_sort'] = "false"
ENV['force'] = "false"
end
end
annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__))) annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__)))
if(!ENV['is_cli'])
task :set_annotation_options
task :annotate_models => :set_annotation_options
end
desc "Add schema information (as comments) to model and fixture files" desc "Add schema information (as comments) to model and fixture files"
task :annotate_models => :environment do task :annotate_models => :environment do
require "#{annotate_lib}/annotate/annotate_models" require "#{annotate_lib}/annotate/annotate_models"
require "#{annotate_lib}/annotate/active_record_patch"
true_re = /(true|t|yes|y|1)$/i true_re = /(true|t|yes|y|1)$/i
options={}
options={ :is_rake => true }
options[:position_in_class] = ENV['position_in_class'] || ENV['position'] || 'before' options[:position_in_class] = ENV['position_in_class'] || ENV['position'] || 'before'
options[:position_in_fixture] = ENV['position_in_fixture'] || ENV['position'] || 'before' options[:position_in_fixture] = ENV['position_in_fixture'] || ENV['position'] || 'before'
options[:position_in_factory] = ENV['position_in_factory'] || ENV['position'] || 'before'
options[:show_indexes] = ENV['show_indexes'] =~ true_re options[:show_indexes] = ENV['show_indexes'] =~ true_re
options[:simple_indexes] = ENV['simple_indexes'] =~ true_re options[:simple_indexes] = ENV['simple_indexes'] =~ true_re
options[:model_dir] = ENV['model_dir'] options[:model_dir] = ENV['model_dir']
...@@ -14,13 +23,19 @@ task :annotate_models => :environment do ...@@ -14,13 +23,19 @@ task :annotate_models => :environment do
options[:require] = ENV['require'] ? ENV['require'].split(',') : [] options[:require] = ENV['require'] ? ENV['require'].split(',') : []
options[:exclude_tests] = ENV['exclude_tests'] =~ true_re options[:exclude_tests] = ENV['exclude_tests'] =~ true_re
options[:exclude_fixtures] = ENV['exclude_fixtures'] =~ true_re options[:exclude_fixtures] = ENV['exclude_fixtures'] =~ true_re
options[:ignore_model_sub_dir] = ENV['ignore_model_sub_dir'] =~ true_re
options[:format_rdoc] = ENV['format_rdoc'] =~ true_re
options[:format_markdown] = ENV['format_markdown'] =~ true_re
options[:no_sort] = ENV['no_sort'] =~ true_re
options[:force] = ENV['force'] =~ true_re
AnnotateModels.do_annotations(options) AnnotateModels.do_annotations(options)
end end
desc "Remove schema information from model and fixture files" desc "Remove schema information from model and fixture files"
task :remove_annotation => :environment do task :remove_annotation => :environment do
require "#{annotate_lib}/annotate/annotate_models" require "#{annotate_lib}/annotate/annotate_models"
options={} require "#{annotate_lib}/annotate/active_record_patch"
options={ :is_rake => true }
options[:model_dir] = ENV['model_dir'] options[:model_dir] = ENV['model_dir']
AnnotateModels.remove_annotations(options) AnnotateModels.remove_annotations(options)
end end
desc "Prepends the route map to the top of routes.rb" desc "Prepends the route map to the top of routes.rb"
task :annotate_routes do task :annotate_routes => :environment do
annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__))) annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__)))
require "#{annotate_lib}/annotate/annotate_routes" require "#{annotate_lib}/annotate/annotate_routes"
AnnotateRoutes.do_annotate AnnotateRoutes.do_annotate
......
#encoding: utf-8
require File.dirname(__FILE__) + '/../spec_helper.rb' require File.dirname(__FILE__) + '/../spec_helper.rb'
require 'annotate/annotate_models' require 'annotate/annotate_models'
require 'rubygems' require 'annotate/active_record_patch'
require 'active_support'
describe AnnotateModels do describe AnnotateModels do
def mock_class(table_name, primary_key, columns)
options = {
:connection => mock("Conn", :indexes => []),
:table_name => table_name,
:primary_key => primary_key.to_s,
:column_names => columns.map { |col| col.name.to_s },
:columns => columns
}
def mock_klass(stubs={}) mock("An ActiveRecord class", options)
@mock_file ||= mock("Klass", stubs)
end end
def mock_column(stubs={}) def mock_column(name, type, options={})
@mock_column ||= mock("Column", stubs) default_options = {
:limit => nil,
:null => false,
:default => nil
}
stubs = default_options.dup
stubs.merge!(options)
stubs.merge!(:name => name, :type => type)
mock("Column", stubs)
end end
it { AnnotateModels.quote(nil).should eql("NULL") } it { AnnotateModels.quote(nil).should eql("NULL") }
...@@ -21,29 +38,45 @@ describe AnnotateModels do ...@@ -21,29 +38,45 @@ describe AnnotateModels do
it { AnnotateModels.quote(1e-20).should eql("1.0e-20") } it { AnnotateModels.quote(1e-20).should eql("1.0e-20") }
it "should get schema info" do it "should get schema info" do
klass = mock_class(:users, :id, [
mock_column(:id, :integer),
mock_column(:name, :string, :limit => 50)
])
AnnotateModels.get_schema_info(mock_klass( AnnotateModels.get_schema_info(klass, "Schema Info").should eql(<<-EOS)
:connection => mock("Conn", :indexes => []),
:table_name => "users",
:primary_key => "id",
:column_names => ["id","login"],
:columns => [
mock_column(:type => "integer", :default => nil, :null => false, :name => "id", :limit => nil),
mock_column(:type => "string", :default => nil, :null => false, :name => "name", :limit => 50)
]), "Schema Info").should eql(<<-EOS)
# Schema Info # Schema Info
# #
# Table name: users # Table name: users
# #
# id :integer not null, primary key # id :integer not null, primary key
# id :integer not null, primary key # name :string(50) not null
# #
EOS EOS
end
it "should get schema info as RDoc" do
klass = mock_class(:users, :id, [
mock_column(:id, :integer),
mock_column(:name, :string, :limit => 50)
])
AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, :format_rdoc => true).should eql(<<-EOS)
# #{AnnotateModels::PREFIX}
#
# Table name: users
#
# *id*:: <tt>integer, not null, primary key</tt>
# *name*:: <tt>string(50), not null</tt>
#--
# #{AnnotateModels::END_MARK}
#++
EOS
end end
describe "#get_model_class" do describe "#get_model_class" do
require "tmpdir"
module ::ActiveRecord module ::ActiveRecord
class Base class Base
def self.has_many name def self.has_many name
...@@ -53,60 +86,234 @@ EOS ...@@ -53,60 +86,234 @@ EOS
# todo: use 'files' gem instead # todo: use 'files' gem instead
def create(file, body="hi") def create(file, body="hi")
File.open(@dir + '/' + file, "w") do |f| file_path = File.join(AnnotateModels.model_dir, file)
FileUtils.mkdir_p(File.dirname(file_path))
File.open(file_path, "wb") do |f|
f.puts(body) f.puts(body)
end end
file_path
end end
before :all do def check_class_name(file, class_name)
require "tmpdir" klass = AnnotateModels.get_model_class(file)
@dir = Dir.tmpdir + "/#{Time.now.to_i}" + "/annotate_models"
FileUtils.mkdir_p(@dir) klass.should_not == nil
AnnotateModels.model_dir = @dir klass.name.should == class_name
create('foo.rb', <<-EOS) end
before :each do
AnnotateModels.model_dir = Dir.mktmpdir 'annotate_models'
end
it "should work" do
create 'foo.rb', <<-EOS
class Foo < ActiveRecord::Base class Foo < ActiveRecord::Base
end end
EOS EOS
create('foo_with_macro.rb', <<-EOS) check_class_name 'foo.rb', 'Foo'
end
it "should find models with non standard capitalization" do
create 'foo_with_capitals.rb', <<-EOS
class FooWithCAPITALS < ActiveRecord::Base
end
EOS
check_class_name 'foo_with_capitals.rb', 'FooWithCAPITALS'
end
it "should find models inside modules" do
create 'bar/foo_inside_bar.rb', <<-EOS
module Bar
class FooInsideBar < ActiveRecord::Base
end
end
EOS
check_class_name 'bar/foo_inside_bar.rb', 'Bar::FooInsideBar'
end
it "should not care about unknown macros" do
create 'foo_with_macro.rb', <<-EOS
class FooWithMacro < ActiveRecord::Base class FooWithMacro < ActiveRecord::Base
acts_as_awesome :yah acts_as_awesome :yah
end end
EOS EOS
check_class_name 'foo_with_macro.rb', 'FooWithMacro'
end
it "should not care about known macros" do
create('foo_with_known_macro.rb', <<-EOS) create('foo_with_known_macro.rb', <<-EOS)
class FooWithKnownMacro < ActiveRecord::Base class FooWithKnownMacro < ActiveRecord::Base
has_many :yah has_many :yah
end end
EOS EOS
check_class_name 'foo_with_known_macro.rb', 'FooWithKnownMacro'
end
it "should work with class names with ALL CAPS segments" do
create('foo_with_capitals.rb', <<-EOS) create('foo_with_capitals.rb', <<-EOS)
class FooWithCAPITALS < ActiveRecord::Base class FooWithCAPITALS < ActiveRecord::Base
acts_as_awesome :yah acts_as_awesome :yah
end end
EOS EOS
check_class_name 'foo_with_capitals.rb', 'FooWithCAPITALS'
end end
it "should work" do it "should not complain of invalid multibyte char (USASCII)" do
klass = AnnotateModels.get_model_class("foo.rb") create 'foo_with_utf8.rb', <<-EOS
klass.name.should == "Foo" #encoding: utf-8
class FooWithUtf8 < ActiveRecord::Base
UTF8STRINGS = %w[résumé façon âge]
end
EOS
check_class_name 'foo_with_utf8.rb', 'FooWithUtf8'
end end
it "should not care about unknown macros" do it "should find models inside modules with non standard capitalization" do
capturing(:stderr) do create 'bar/foo_inside_capitals_bar.rb', <<-EOS
klass = AnnotateModels.get_model_class("foo_with_macro.rb") module BAR
klass.name.should == "FooWithMacro" class FooInsideCapitalsBAR < ActiveRecord::Base
end.should include("undefined method `acts_as_awesome'") end
end
EOS
check_class_name 'bar/foo_inside_capitals_bar.rb', 'BAR::FooInsideCapitalsBAR'
end
it "should find non-namespaced models inside subdirectories" do
create 'bar/non_namespaced_foo_inside_bar.rb', <<-EOS
class NonNamespacedFooInsideBar < ActiveRecord::Base
end
EOS
check_class_name 'bar/non_namespaced_foo_inside_bar.rb', 'NonNamespacedFooInsideBar'
end
it "should find non-namespaced models with non standard capitalization inside subdirectories" do
create 'bar/non_namespaced_foo_with_capitals_inside_bar.rb', <<-EOS
class NonNamespacedFooWithCapitalsInsideBar < ActiveRecord::Base
end
EOS
check_class_name 'bar/non_namespaced_foo_with_capitals_inside_bar.rb', 'NonNamespacedFooWithCapitalsInsideBar'
end end
it "should allow known macros" do it "should allow known macros" do
create('foo_with_known_macro.rb', <<-EOS)
class FooWithKnownMacro < ActiveRecord::Base
has_many :yah
end
EOS
capturing(:stderr) do capturing(:stderr) do
klass = AnnotateModels.get_model_class("foo_with_known_macro.rb") check_class_name 'foo_with_known_macro.rb', 'FooWithKnownMacro'
klass.name.should == "FooWithKnownMacro"
end.should == "" end.should == ""
end end
pending it "should find models with non standard capitalization" do
klass = AnnotateModels.get_model_class("foo_with_capitals.rb")
klass.name.should == "FooWithCAPITALS"
end end
describe "#remove_annotation_of_file" do
require "tmpdir"
def create(file, body="hi")
path = File.join(@dir, file)
File.open(path, "w") do |f|
f.puts(body)
end
return path
end
def content(path)
File.read(path)
end
before :each do
@dir = Dir.mktmpdir 'annotate_models'
end
it "should remove before annotate" do
path = create "before.rb", <<-EOS
# == Schema Information
#
# Table name: foo
#
# id :integer not null, primary key
# created_at :datetime
# updated_at :datetime
#
class Foo < ActiveRecord::Base
end
EOS
AnnotateModels.remove_annotation_of_file(path)
content(path).should == <<-EOS
class Foo < ActiveRecord::Base
end
EOS
end
it "should remove after annotate" do
path = create "after.rb", <<-EOS
class Foo < ActiveRecord::Base
end
# == Schema Information
#
# Table name: foo
#
# id :integer not null, primary key
# created_at :datetime
# updated_at :datetime
#
EOS
AnnotateModels.remove_annotation_of_file(path)
content(path).should == <<-EOS
class Foo < ActiveRecord::Base
end
EOS
end
end
describe "annotating a file" do
before do
@file_name = File.join(Dir.mktmpdir('annotate_models'), "user.rb")
@file_content = <<-EOS
class User < ActiveRecord::Base
end
EOS
File.open(@file_name, "wb") { |f| f.write @file_content }
@klass = mock_class(:users, :id, [
mock_column(:id, :integer),
mock_column(:name, :string, :limit => 50)
])
@schema_info = AnnotateModels.get_schema_info(@klass, "== Schema Info")
end
it "should annotate the file before the model if position == 'before'" do
AnnotateModels.annotate_one_file(@file_name, @schema_info, :position => "before")
File.read(@file_name).should == "#{@schema_info}#{@file_content}"
end
it "should annotate before if given :position => :before" do
AnnotateModels.annotate_one_file(@file_name, @schema_info, :position => :before)
File.read(@file_name).should == "#{@schema_info}#{@file_content}"
end
it "should annotate before if given :position => :after" do
AnnotateModels.annotate_one_file(@file_name, @schema_info, :position => :after)
File.read(@file_name).should == "#{@file_content}\n#{@schema_info}"
end end
it "should update annotate position" do
AnnotateModels.annotate_one_file(@file_name, @schema_info, :position => :before)
another_schema_info = AnnotateModels.get_schema_info(mock_class(:users, :id, [mock_column(:id, :integer),]),
"== Schema Info")
AnnotateModels.annotate_one_file(@file_name, another_schema_info, :position => :after)
File.read(@file_name).should == "#{@file_content}\n#{another_schema_info}"
end
end
end end
--format=specdoc
--colour --colour
\ No newline at end of file
...@@ -9,4 +9,6 @@ end ...@@ -9,4 +9,6 @@ end
require "wrong/adapters/rspec" require "wrong/adapters/rspec"
$:.unshift(File.dirname(__FILE__) + '/../lib') $:.unshift(File.dirname(__FILE__) + '/../lib')
require 'active_support'
require 'active_support/core_ext/string/inflections'
require 'annotate' require 'annotate'
TODO TODO
----- -----
* clean up history * clean up history
* change default position back to "top" for all annotations * change default position back to "top" for all annotations
* add "top" and "bottom" as synonyms for "before" and "after" * add "top" and "bottom" as synonyms for "before" and "after"
...@@ -22,4 +23,3 @@ TODO (proposed) ...@@ -22,4 +23,3 @@ TODO (proposed)
* supply two binaries, named 'annotate' and 'annotate_models', since there's already a unix tool named 'annotate' * supply two binaries, named 'annotate' and 'annotate_models', since there's already a unix tool named 'annotate'
* test EVERYTHING * test EVERYTHING
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment