Skip to content

Commit

Permalink
Merge pull request #241 from subakva/features/foreign_key_annotations
Browse files Browse the repository at this point in the history
Adds support for foreign key annotations
  • Loading branch information
ctran committed Mar 27, 2015
2 parents b38328d + 5322fd9 commit bc2662b
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 12 deletions.
1 change: 1 addition & 0 deletions README.rdoc
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions bin/annotate
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion lib/annotate.rb
Expand Up @@ -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
Expand Down
32 changes: 29 additions & 3 deletions lib/annotate/annotate_models.rb
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = []
Expand All @@ -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
Expand Down
Expand Up @@ -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",
Expand Down
60 changes: 52 additions & 8 deletions spec/annotate/annotate_models_spec.rb
Expand Up @@ -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]
}]
}

Expand Down Expand Up @@ -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),
Expand Down
Expand Up @@ -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",
Expand Down
Expand Up @@ -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",
Expand Down

0 comments on commit bc2662b

Please sign in to comment.