Skip to content

Commit b413bcf

Browse files
committed
Add support for annotating check constraints
Signed-off-by: Lovro Bikic <[email protected]>
1 parent 76a1804 commit b413bcf

File tree

8 files changed

+173
-6
lines changed

8 files changed

+173
-6
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ you can do so with a simple environment variable, instead of editing the
224224
-a, --active-admin Annotate active_admin models
225225
-v, --version Show the current version of this gem
226226
-m, --show-migration Include the migration version number in the annotation
227+
-c, --show-check-constraints List the table's check constraints in the annotation
227228
-k, --show-foreign-keys List the table's foreign key constraints in the annotation
228229
--ck, --complete-foreign-keys
229230
Complete foreign key names in the annotation

lib/annotate/annotate_models.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ def get_schema_info(klass, header, options = {})
178178
info << get_foreign_key_info(klass, options)
179179
end
180180

181+
if options[:show_check_constraints] && klass.table_exists?
182+
info << get_check_constraint_info(klass, options)
183+
end
184+
181185
info << get_schema_footer_text(klass, options)
182186
end
183187

@@ -352,6 +356,31 @@ def get_foreign_key_info(klass, options = {})
352356
fk_info
353357
end
354358

359+
def get_check_constraint_info(klass, options = {})
360+
cc_info = if options[:format_markdown]
361+
"#\n# ### Check Constraints\n#\n"
362+
else
363+
"#\n# Check Constraints\n#\n"
364+
end
365+
366+
return '' unless klass.connection.respond_to?(:supports_check_constraints?) &&
367+
klass.connection.supports_check_constraints? && klass.connection.respond_to?(:check_constraints)
368+
369+
check_constraints = klass.connection.check_constraints(klass.table_name)
370+
return '' if check_constraints.empty?
371+
372+
max_size = check_constraints.map { |check_constraint| check_constraint.name.size }.max + 1
373+
check_constraints.sort_by(&:name).each do |check_constraint|
374+
cc_info << if options[:format_markdown]
375+
sprintf("# * `%s`: `(%s)`\n", check_constraint.name, check_constraint.expression)
376+
else
377+
sprintf("# %-#{max_size}.#{max_size}s (%s)\n", check_constraint.name, check_constraint.expression)
378+
end
379+
end
380+
381+
cc_info
382+
end
383+
355384
# Add a schema block to a file. If the file already contains
356385
# a schema info block (a comment starting with "== Schema Information"),
357386
# check if it matches the block that is already there. If so, leave it be.

lib/annotate/constants.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ module Constants
1818
:trace, :timestamp, :exclude_serializers, :classified_sort,
1919
:show_foreign_keys, :show_complete_foreign_keys,
2020
:exclude_scaffolds, :exclude_controllers, :exclude_helpers,
21-
:exclude_sti_subclasses, :ignore_unknown_models, :with_comment
21+
:exclude_sti_subclasses, :ignore_unknown_models, :with_comment,
22+
:show_check_constraints
2223
].freeze
2324

2425
OTHER_OPTIONS = [

lib/annotate/parser.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ def add_options_to_parser(option_parser) # rubocop:disable Metrics/MethodLength
173173
env['include_version'] = 'yes'
174174
end
175175

176+
option_parser.on('-c',
177+
'--show-check-constraints',
178+
"List the table's check constraints in the annotation") do
179+
env['show_check_constraints'] = 'yes'
180+
end
181+
176182
option_parser.on('-k',
177183
'--show-foreign-keys',
178184
"List the table's foreign key constraints in the annotation") do

lib/generators/annotate/templates/auto_annotate_models.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ if Rails.env.development?
1717
'position_in_fixture' => 'before',
1818
'position_in_factory' => 'before',
1919
'position_in_serializer' => 'before',
20+
'show_check_constraints' => 'false',
2021
'show_foreign_keys' => 'true',
2122
'show_complete_foreign_keys' => 'false',
2223
'show_indexes' => 'true',

lib/tasks/annotate_models.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ task annotate_models: :environment do
1818
options[:position_in_factory] = Annotate::Helpers.fallback(ENV['position_in_factory'], ENV['position'])
1919
options[:position_in_test] = Annotate::Helpers.fallback(ENV['position_in_test'], ENV['position'])
2020
options[:position_in_serializer] = Annotate::Helpers.fallback(ENV['position_in_serializer'], ENV['position'])
21+
options[:show_check_constraints] = Annotate::Helpers.true?(ENV['show_check_constraints'])
2122
options[:show_foreign_keys] = Annotate::Helpers.true?(ENV['show_foreign_keys'])
2223
options[:show_complete_foreign_keys] = Annotate::Helpers.true?(ENV['show_complete_foreign_keys'])
2324
options[:show_indexes] = Annotate::Helpers.true?(ENV['show_indexes'])

spec/lib/annotate/annotate_models_spec.rb

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,24 @@ def mock_foreign_key(name, from_column, to_table, to_column = 'id', constraints
4141
on_update: constraints[:on_update])
4242
end
4343

44-
def mock_connection(indexes = [], foreign_keys = [])
44+
def mock_check_constraint(name, expression)
45+
double('CheckConstraintDefinition',
46+
name: name,
47+
expression: expression)
48+
end
49+
50+
def mock_connection(indexes = [], foreign_keys = [], check_constraints = [])
4551
double('Conn',
4652
indexes: indexes,
4753
foreign_keys: foreign_keys,
48-
supports_foreign_keys?: true)
54+
check_constraints: check_constraints,
55+
supports_foreign_keys?: true,
56+
supports_check_constraints?: true)
4957
end
5058

51-
def mock_class(table_name, primary_key, columns, indexes = [], foreign_keys = [])
59+
def mock_class(table_name, primary_key, columns, indexes = [], foreign_keys = [], check_constraints = [])
5260
options = {
53-
connection: mock_connection(indexes, foreign_keys),
61+
connection: mock_connection(indexes, foreign_keys, check_constraints),
5462
table_exists?: true,
5563
table_name: table_name,
5664
primary_key: primary_key,
@@ -221,7 +229,7 @@ def mock_column(name, type, options = {})
221229
end
222230

223231
let :klass do
224-
mock_class(:users, primary_key, columns, indexes, foreign_keys)
232+
mock_class(:users, primary_key, columns, indexes, foreign_keys, check_constraints)
225233
end
226234

227235
let :indexes do
@@ -232,6 +240,10 @@ def mock_column(name, type, options = {})
232240
[]
233241
end
234242

243+
let :check_constraints do
244+
[]
245+
end
246+
235247
context 'when option is not present' do
236248
let :options do
237249
{}
@@ -756,6 +768,73 @@ def mock_column(name, type, options = {})
756768
end
757769
end
758770

771+
context 'when check constraints exist' do
772+
let :columns do
773+
[
774+
mock_column(:id, :integer),
775+
mock_column(:age, :integer)
776+
]
777+
end
778+
779+
context 'when option "show_check_constraints" is true' do
780+
let :options do
781+
{ show_check_constraints: true }
782+
end
783+
784+
context 'when check constraints are defined' do
785+
let :check_constraints do
786+
[
787+
mock_check_constraint('alive', 'age < 150'),
788+
mock_check_constraint('must_be_adult', 'age >= 18')
789+
]
790+
end
791+
792+
let :expected_result do
793+
<<~EOS
794+
# Schema Info
795+
#
796+
# Table name: users
797+
#
798+
# id :integer not null, primary key
799+
# age :integer not null
800+
#
801+
# Check Constraints
802+
#
803+
# alive (age < 150)
804+
# must_be_adult (age >= 18)
805+
#
806+
EOS
807+
end
808+
809+
it 'returns schema info with check constraint information' do
810+
is_expected.to eq expected_result
811+
end
812+
end
813+
814+
context 'when check constraint is not defined' do
815+
let :check_constraints do
816+
[]
817+
end
818+
819+
let :expected_result do
820+
<<~EOS
821+
# Schema Info
822+
#
823+
# Table name: users
824+
#
825+
# id :integer not null, primary key
826+
# age :integer not null
827+
#
828+
EOS
829+
end
830+
831+
it 'returns schema info without check constraint information' do
832+
is_expected.to eq expected_result
833+
end
834+
end
835+
end
836+
end
837+
759838
context 'when foreign keys exist' do
760839
let :columns do
761840
[
@@ -1492,6 +1571,44 @@ def mock_column(name, type, options = {})
14921571
end
14931572
end
14941573

1574+
context 'when option "show_check_constraints" is true' do
1575+
let :options do
1576+
{ format_markdown: true, show_check_constraints: true }
1577+
end
1578+
1579+
context 'when check constraints are defined' do
1580+
let :check_constraints do
1581+
[
1582+
mock_check_constraint('min_name_length', 'LENGTH(name) > 2')
1583+
]
1584+
end
1585+
1586+
let :expected_result do
1587+
<<~EOS
1588+
# == Schema Information
1589+
#
1590+
# Table name: `users`
1591+
#
1592+
# ### Columns
1593+
#
1594+
# Name | Type | Attributes
1595+
# ----------- | ------------------ | ---------------------------
1596+
# **`id`** | `integer` | `not null, primary key`
1597+
# **`name`** | `string(50)` | `not null`
1598+
#
1599+
# ### Check Constraints
1600+
#
1601+
# * `min_name_length`: `(LENGTH(name) > 2)`
1602+
#
1603+
EOS
1604+
end
1605+
1606+
it 'returns schema info with check constraint information in Markdown format' do
1607+
is_expected.to eq expected_result
1608+
end
1609+
end
1610+
end
1611+
14951612
context 'when option "show_foreign_keys" is true' do
14961613
let :options do
14971614
{ format_markdown: true, show_foreign_keys: true }

spec/lib/annotate/parser_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,17 @@ module Annotate # rubocop:disable Metrics/ModuleLength
260260
end
261261
end
262262

263+
%w[-c --show-check-constraints].each do |option|
264+
describe option do
265+
let(:env_key) { 'show_check_constraints' }
266+
let(:set_value) { 'yes' }
267+
it 'sets the ENV variable' do
268+
expect(ENV).to receive(:[]=).with(env_key, set_value)
269+
Parser.parse([option])
270+
end
271+
end
272+
end
273+
263274
%w[-k --show-foreign-keys].each do |option|
264275
describe option do
265276
let(:env_key) { 'show_foreign_keys' }

0 commit comments

Comments
 (0)