Skip to content

Commit bc2662b

Browse files
committed
Merge pull request ctran#241 from subakva/features/foreign_key_annotations
Adds support for foreign key annotations
2 parents b38328d + 5322fd9 commit bc2662b

File tree

8 files changed

+91
-12
lines changed

8 files changed

+91
-12
lines changed

README.rdoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ you can do so with a simple environment variable, instead of editing the
175175
-v, --version Show the current version of this gem
176176
-m, --show-migration Include the migration version number in the annotation
177177
-i, --show-indexes List the table's database indexes in the annotation
178+
-k, --show-foreign-keys List the table's foreign key constraints in the annotation
178179
-s, --simple-indexes Concat the column's related indexes in the annotation
179180
--model-dir dir Annotate model files stored in dir rather than app/models, separate multiple dirs with comas
180181
--ignore-model-subdirects Ignore subdirectories of the models directory

bin/annotate

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ OptionParser.new do |opts|
109109
ENV['include_version'] = "yes"
110110
end
111111

112+
opts.on('-k', '--show-foreign-keys',
113+
"List the table's foreign key constraints in the annotation") do
114+
ENV['show_foreign_keys'] = "yes"
115+
end
116+
112117
opts.on('-i', '--show-indexes',
113118
"List the table's database indexes in the annotation") do
114119
ENV['show_indexes'] = "yes"

lib/annotate.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ module Annotate
2626
:show_indexes, :simple_indexes, :include_version, :exclude_tests,
2727
:exclude_fixtures, :exclude_factories, :ignore_model_sub_dir,
2828
:format_bare, :format_rdoc, :format_markdown, :sort, :force, :trace,
29-
:timestamp, :exclude_serializers, :classified_sort
29+
:timestamp, :exclude_serializers, :classified_sort, :show_foreign_keys,
3030
]
3131
OTHER_OPTIONS=[
3232
:ignore_columns

lib/annotate/annotate_models.rb

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ def get_schema_info(klass, header, options = {})
193193
info << get_index_info(klass, options)
194194
end
195195

196+
if options[:show_foreign_keys] && klass.table_exists?
197+
info << get_foreign_key_info(klass, options)
198+
end
199+
196200
if options[:format_rdoc]
197201
info << "#--\n"
198202
info << "# #{END_MARK}\n"
@@ -223,6 +227,28 @@ def get_index_info(klass, options={})
223227
return index_info
224228
end
225229

230+
def get_foreign_key_info(klass, options={})
231+
if(options[:format_markdown])
232+
fk_info = "#\n# ### Foreign Keys\n#\n"
233+
else
234+
fk_info = "#\n# Foreign Keys\n#\n"
235+
end
236+
237+
foreign_keys = klass.connection.respond_to?(:foreign_keys) ? klass.connection.foreign_keys(klass.table_name) : []
238+
return "" if foreign_keys.empty?
239+
240+
max_size = foreign_keys.collect{|fk| fk.name.size}.max + 1
241+
foreign_keys.sort_by{|fk| fk.name}.each do |fk|
242+
ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}"
243+
if(options[:format_markdown])
244+
fk_info << sprintf("# * `%s`:\n# * **`%s`**\n", fk.name, ref_info)
245+
else
246+
fk_info << sprintf("# %-#{max_size}.#{max_size}s %s", fk.name, "(#{ref_info})").rstrip + "\n"
247+
end
248+
end
249+
return fk_info
250+
end
251+
226252
# Add a schema block to a file. If the file already contains
227253
# a schema info block (a comment starting with "== Schema Information"), check if it
228254
# 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)
350376
options.merge(:position=>(options[position_in] || options[:position]))
351377
end
352378

353-
# Return a list of the model files to annotate.
379+
# Return a list of the model files to annotate.
354380
# If we have command line arguments, they're assumed to the path
355-
# of model files from root dir. Otherwise we take all the model files
381+
# of model files from root dir. Otherwise we take all the model files
356382
# in the model_dir directory.
357383
def get_model_files(options)
358384
models = []
@@ -364,7 +390,7 @@ def get_model_files(options)
364390
begin
365391
model_dir.each do |dir|
366392
Dir.chdir(dir) do
367-
lst =
393+
lst =
368394
if options[:ignore_model_sub_dir]
369395
Dir["*.rb"].map{ |f| [dir, f] }
370396
else

lib/generators/annotate/templates/auto_annotate_models.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ if Rails.env.development?
1111
'position_in_test' => "before",
1212
'position_in_fixture' => "before",
1313
'position_in_factory' => "before",
14+
'show_foreign_keys' => "true",
1415
'show_indexes' => "true",
1516
'simple_indexes' => "false",
1617
'model_dir' => "app/models",

spec/annotate/annotate_models_spec.rb

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,32 @@
44
require 'annotate/active_record_patch'
55

66
describe AnnotateModels do
7-
def mock_class(table_name, primary_key, columns)
7+
def mock_foreign_key(name, from_column, to_table, to_column = 'id')
8+
double("ForeignKeyDefinition",
9+
:name => name,
10+
:column => from_column,
11+
:to_table => to_table,
12+
:primary_key => to_column,
13+
)
14+
end
15+
16+
def mock_connection(indexes = [], foreign_keys = [])
17+
double("Conn",
18+
:indexes => indexes,
19+
:foreign_keys => foreign_keys,
20+
)
21+
end
22+
23+
def mock_class(table_name, primary_key, columns, foreign_keys = [])
824
options = {
9-
:connection => double("Conn", :indexes => []),
10-
:table_name => table_name,
11-
:primary_key => primary_key,
12-
:column_names => columns.map { |col| col.name.to_s },
13-
:columns => columns,
14-
:column_defaults => Hash[columns.map { |col|
15-
[col.name, col.default]
25+
:connection => mock_connection([], foreign_keys),
26+
:table_exists? => true,
27+
:table_name => table_name,
28+
:primary_key => primary_key,
29+
:column_names => columns.map { |col| col.name.to_s },
30+
:columns => columns,
31+
:column_defaults => Hash[columns.map { |col|
32+
[col.name, col.default]
1633
}]
1734
}
1835

@@ -127,6 +144,33 @@ def mock_column(name, type, options={})
127144
EOS
128145
end
129146

147+
it "should get foreign key info" do
148+
klass = mock_class(:users, :id, [
149+
mock_column(:id, :integer),
150+
mock_column(:foreign_thing_id, :integer),
151+
],
152+
[
153+
mock_foreign_key(
154+
'fk_rails_02e851e3b7',
155+
'foreign_thing_id',
156+
'foreign_things'
157+
)
158+
])
159+
expect(AnnotateModels.get_schema_info(klass, "Schema Info", :show_foreign_keys => true)).to eql(<<-EOS)
160+
# Schema Info
161+
#
162+
# Table name: users
163+
#
164+
# id :integer not null, primary key
165+
# foreign_thing_id :integer not null
166+
#
167+
# Foreign Keys
168+
#
169+
# fk_rails_02e851e3b7 (foreign_thing_id => foreign_things.id)
170+
#
171+
EOS
172+
end
173+
130174
it "should get schema info as RDoc" do
131175
klass = mock_class(:users, :id, [
132176
mock_column(:id, :integer),

spec/integration/rails_4.1.1/lib/tasks/auto_annotate_models.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ if Rails.env.development?
1111
'position_in_test' => "before",
1212
'position_in_fixture' => "before",
1313
'position_in_factory' => "before",
14+
'show_foreign_keys' => "true",
1415
'show_indexes' => "true",
1516
'simple_indexes' => "false",
1617
'model_dir' => "app/models",

spec/integration/rails_4.2.0/lib/tasks/auto_annotate_models.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ if Rails.env.development?
1111
'position_in_test' => "before",
1212
'position_in_fixture' => "before",
1313
'position_in_factory' => "before",
14+
'show_foreign_keys' => "true",
1415
'show_indexes' => "true",
1516
'simple_indexes' => "false",
1617
'model_dir' => "app/models",

0 commit comments

Comments
 (0)