From 5322fd96bcb908e4fe460fd588ccf11ce66b6953 Mon Sep 17 00:00:00 2001 From: Jason Wadsworth Date: Fri, 27 Mar 2015 11:17:49 -0400 Subject: [PATCH] Adds support for foreign key annotations --- README.rdoc | 1 + bin/annotate | 5 ++ lib/annotate.rb | 2 +- lib/annotate/annotate_models.rb | 32 +++++++++- .../templates/auto_annotate_models.rake | 1 + spec/annotate/annotate_models_spec.rb | 60 ++++++++++++++++--- .../lib/tasks/auto_annotate_models.rake | 1 + .../lib/tasks/auto_annotate_models.rake | 1 + 8 files changed, 91 insertions(+), 12 deletions(-) diff --git a/README.rdoc b/README.rdoc index cf8391334..f3190056c 100755 --- a/README.rdoc +++ b/README.rdoc @@ -175,6 +175,7 @@ you can do so with a simple environment variable, instead of editing the -v, --version Show the current version of this gem -m, --show-migration Include the migration version number in the annotation -i, --show-indexes List the table's database indexes in the annotation + -k, --show-foreign-keys List the table's foreign key constraints in the annotation -s, --simple-indexes Concat the column's related indexes in the annotation --model-dir dir Annotate model files stored in dir rather than app/models, separate multiple dirs with comas --ignore-model-subdirects Ignore subdirectories of the models directory diff --git a/bin/annotate b/bin/annotate index 2b9fd7c9d..780dfbe8a 100755 --- a/bin/annotate +++ b/bin/annotate @@ -109,6 +109,11 @@ OptionParser.new do |opts| ENV['include_version'] = "yes" end + opts.on('-k', '--show-foreign-keys', + "List the table's foreign key constraints in the annotation") do + ENV['show_foreign_keys'] = "yes" + end + opts.on('-i', '--show-indexes', "List the table's database indexes in the annotation") do ENV['show_indexes'] = "yes" diff --git a/lib/annotate.rb b/lib/annotate.rb index 8ab0fe99f..9de815140 100755 --- a/lib/annotate.rb +++ b/lib/annotate.rb @@ -26,7 +26,7 @@ module Annotate :show_indexes, :simple_indexes, :include_version, :exclude_tests, :exclude_fixtures, :exclude_factories, :ignore_model_sub_dir, :format_bare, :format_rdoc, :format_markdown, :sort, :force, :trace, - :timestamp, :exclude_serializers, :classified_sort + :timestamp, :exclude_serializers, :classified_sort, :show_foreign_keys, ] OTHER_OPTIONS=[ :ignore_columns diff --git a/lib/annotate/annotate_models.rb b/lib/annotate/annotate_models.rb index 01cd5c82b..3786d221f 100644 --- a/lib/annotate/annotate_models.rb +++ b/lib/annotate/annotate_models.rb @@ -193,6 +193,10 @@ def get_schema_info(klass, header, options = {}) info << get_index_info(klass, options) end + if options[:show_foreign_keys] && klass.table_exists? + info << get_foreign_key_info(klass, options) + end + if options[:format_rdoc] info << "#--\n" info << "# #{END_MARK}\n" @@ -223,6 +227,28 @@ def get_index_info(klass, options={}) return index_info end + def get_foreign_key_info(klass, options={}) + if(options[:format_markdown]) + fk_info = "#\n# ### Foreign Keys\n#\n" + else + fk_info = "#\n# Foreign Keys\n#\n" + end + + foreign_keys = klass.connection.respond_to?(:foreign_keys) ? klass.connection.foreign_keys(klass.table_name) : [] + return "" if foreign_keys.empty? + + max_size = foreign_keys.collect{|fk| fk.name.size}.max + 1 + foreign_keys.sort_by{|fk| fk.name}.each do |fk| + ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}" + if(options[:format_markdown]) + fk_info << sprintf("# * `%s`:\n# * **`%s`**\n", fk.name, ref_info) + else + fk_info << sprintf("# %-#{max_size}.#{max_size}s %s", fk.name, "(#{ref_info})").rstrip + "\n" + end + end + return fk_info + end + # Add a schema block to a file. If the file already contains # a schema info block (a comment starting with "== Schema Information"), check if it # matches the block that is already there. If so, leave it be. If not, remove the old @@ -350,9 +376,9 @@ def options_with_position(options, position_in) options.merge(:position=>(options[position_in] || options[:position])) end - # Return a list of the model files to annotate. + # Return a list of the model files to annotate. # If we have command line arguments, they're assumed to the path - # of model files from root dir. Otherwise we take all the model files + # of model files from root dir. Otherwise we take all the model files # in the model_dir directory. def get_model_files(options) models = [] @@ -364,7 +390,7 @@ def get_model_files(options) begin model_dir.each do |dir| Dir.chdir(dir) do - lst = + lst = if options[:ignore_model_sub_dir] Dir["*.rb"].map{ |f| [dir, f] } else diff --git a/lib/generators/annotate/templates/auto_annotate_models.rake b/lib/generators/annotate/templates/auto_annotate_models.rake index f85503d5b..842eade72 100644 --- a/lib/generators/annotate/templates/auto_annotate_models.rake +++ b/lib/generators/annotate/templates/auto_annotate_models.rake @@ -11,6 +11,7 @@ if Rails.env.development? 'position_in_test' => "before", 'position_in_fixture' => "before", 'position_in_factory' => "before", + 'show_foreign_keys' => "true", 'show_indexes' => "true", 'simple_indexes' => "false", 'model_dir' => "app/models", diff --git a/spec/annotate/annotate_models_spec.rb b/spec/annotate/annotate_models_spec.rb index 76d60f216..f2b0f13db 100755 --- a/spec/annotate/annotate_models_spec.rb +++ b/spec/annotate/annotate_models_spec.rb @@ -4,15 +4,32 @@ require 'annotate/active_record_patch' describe AnnotateModels do - def mock_class(table_name, primary_key, columns) + def mock_foreign_key(name, from_column, to_table, to_column = 'id') + double("ForeignKeyDefinition", + :name => name, + :column => from_column, + :to_table => to_table, + :primary_key => to_column, + ) + end + + def mock_connection(indexes = [], foreign_keys = []) + double("Conn", + :indexes => indexes, + :foreign_keys => foreign_keys, + ) + end + + def mock_class(table_name, primary_key, columns, foreign_keys = []) options = { - :connection => double("Conn", :indexes => []), - :table_name => table_name, - :primary_key => primary_key, - :column_names => columns.map { |col| col.name.to_s }, - :columns => columns, - :column_defaults => Hash[columns.map { |col| - [col.name, col.default] + :connection => mock_connection([], foreign_keys), + :table_exists? => true, + :table_name => table_name, + :primary_key => primary_key, + :column_names => columns.map { |col| col.name.to_s }, + :columns => columns, + :column_defaults => Hash[columns.map { |col| + [col.name, col.default] }] } @@ -127,6 +144,33 @@ def mock_column(name, type, options={}) EOS end + it "should get foreign key info" do + klass = mock_class(:users, :id, [ + mock_column(:id, :integer), + mock_column(:foreign_thing_id, :integer), + ], + [ + mock_foreign_key( + 'fk_rails_02e851e3b7', + 'foreign_thing_id', + 'foreign_things' + ) + ]) + expect(AnnotateModels.get_schema_info(klass, "Schema Info", :show_foreign_keys => true)).to eql(<<-EOS) +# Schema Info +# +# Table name: users +# +# id :integer not null, primary key +# foreign_thing_id :integer not null +# +# Foreign Keys +# +# fk_rails_02e851e3b7 (foreign_thing_id => foreign_things.id) +# +EOS + end + it "should get schema info as RDoc" do klass = mock_class(:users, :id, [ mock_column(:id, :integer), diff --git a/spec/integration/rails_4.1.1/lib/tasks/auto_annotate_models.rake b/spec/integration/rails_4.1.1/lib/tasks/auto_annotate_models.rake index f85503d5b..842eade72 100644 --- a/spec/integration/rails_4.1.1/lib/tasks/auto_annotate_models.rake +++ b/spec/integration/rails_4.1.1/lib/tasks/auto_annotate_models.rake @@ -11,6 +11,7 @@ if Rails.env.development? 'position_in_test' => "before", 'position_in_fixture' => "before", 'position_in_factory' => "before", + 'show_foreign_keys' => "true", 'show_indexes' => "true", 'simple_indexes' => "false", 'model_dir' => "app/models", diff --git a/spec/integration/rails_4.2.0/lib/tasks/auto_annotate_models.rake b/spec/integration/rails_4.2.0/lib/tasks/auto_annotate_models.rake index f85503d5b..842eade72 100644 --- a/spec/integration/rails_4.2.0/lib/tasks/auto_annotate_models.rake +++ b/spec/integration/rails_4.2.0/lib/tasks/auto_annotate_models.rake @@ -11,6 +11,7 @@ if Rails.env.development? 'position_in_test' => "before", 'position_in_fixture' => "before", 'position_in_factory' => "before", + 'show_foreign_keys' => "true", 'show_indexes' => "true", 'simple_indexes' => "false", 'model_dir' => "app/models",