Skip to content

Commit

Permalink
Support composite foreign keys (#126)
Browse files Browse the repository at this point in the history
This PR adds support for composite foreign keys. Credit goes to @carldr
for ctran/annotate_models#1013.

Closes #121.
  • Loading branch information
drwl authored Jun 27, 2024
1 parent ab35bc4 commit befdc5e
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ def build
return "" if foreign_keys.empty?

format_name = lambda do |fk|
return fk.options[:column] if fk.name.blank?
return fk.column if fk.name.blank?

@options[:show_complete_foreign_keys] ? fk.name : fk.name.gsub(/(?<=^fk_rails_)[0-9a-f]{10}$/, "...")
end

max_size = foreign_keys.map(&format_name).map(&:size).max + 1
foreign_keys.sort_by { |fk| [format_name.call(fk), fk.column] }.each do |fk|
ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}"
foreign_keys.sort_by { |fk| [format_name.call(fk), stringify_columns(fk.column)] }.each do |fk|
ref_info = if fk.column.is_a?(Array) # Composite foreign key using multiple columns
"#{stringify_columns(fk.column)} => #{fk.to_table}#{stringify_columns(fk.primary_key)}"
else
"#{fk.column} => #{fk.to_table}.#{fk.primary_key}"
end

constraints_info = ""
constraints_info += "ON DELETE => #{fk.on_delete} " if fk.on_delete
constraints_info += "ON UPDATE => #{fk.on_update} " if fk.on_update
Expand All @@ -51,6 +56,13 @@ def build

fk_info
end

private

# The fk columns might be composite keys, so format them into a string for the annotation
def stringify_columns(columns)
columns.is_a?(Array) ? "[#{columns.join(", ")}]" : columns
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

RSpec.describe AnnotateRb::ModelAnnotator::ForeignKeyAnnotation::AnnotationBuilder do
include AnnotateTestHelpers

describe "#build" do
subject { described_class.new(model, options).build }

let(:model) do
klass = begin
primary_key = nil
columns = []
indexes = []

mock_class(
:users,
primary_key,
columns,
indexes,
foreign_keys
)
end

::AnnotateRb::ModelAnnotator::ModelWrapper.new(klass, options)
end
let(:options) { ::AnnotateRb::Options.new({show_complete_foreign_keys: true}) }

context "without foreign keys" do
let(:foreign_keys) { [] }

it { is_expected.to be_blank }
end

context "with foreign keys" do
let(:foreign_keys) do
[
mock_foreign_key("fk_rails_cf2568e89e", "foreign_thing_id", "foreign_things"),
mock_foreign_key("custom_fk_name", "other_thing_id", "other_things"),
mock_foreign_key("fk_rails_a70234b26c", "third_thing_id", "third_things")
]
end
let(:expected_output) do
<<~OUTPUT
#
# Foreign Keys
#
# custom_fk_name (other_thing_id => other_things.id)
# fk_rails_a70234b26c (third_thing_id => third_things.id)
# fk_rails_cf2568e89e (foreign_thing_id => foreign_things.id)
OUTPUT
end

it { is_expected.to eq(expected_output) }
end

context "with a composite foreign key" do
let(:foreign_keys) do
[
mock_foreign_key("fk_rails_cf2568e89e", "foreign_thing_id", "foreign_things"),
mock_foreign_key("custom_fk_name", ["tenant_id", "customer_id"], "customers", ["tenant_id", "id"])
]
end
let(:expected_output) do
<<~OUTPUT
#
# Foreign Keys
#
# custom_fk_name ([tenant_id, customer_id] => customers[tenant_id, id])
# fk_rails_cf2568e89e (foreign_thing_id => foreign_things.id)
OUTPUT
end

it { is_expected.to eq(expected_output) }
end
end
end

0 comments on commit befdc5e

Please sign in to comment.