Skip to content

Commit 57fe014

Browse files
committed
Add support for annotating check constraints
1 parent 68f5689 commit 57fe014

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,
@@ -217,7 +225,7 @@ def mock_column(name, type, options = {})
217225
end
218226

219227
let :klass do
220-
mock_class(:users, primary_key, columns, indexes, foreign_keys)
228+
mock_class(:users, primary_key, columns, indexes, foreign_keys, check_constraints)
221229
end
222230

223231
let :indexes do
@@ -228,6 +236,10 @@ def mock_column(name, type, options = {})
228236
[]
229237
end
230238

239+
let :check_constraints do
240+
[]
241+
end
242+
231243
context 'when option is not present' do
232244
let :options do
233245
{}
@@ -752,6 +764,73 @@ def mock_column(name, type, options = {})
752764
end
753765
end
754766

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

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