Skip to content

Commit fc925d9

Browse files
committed
Rename cop. Add default settings. Add documentation.
1 parent 91e70af commit fc925d9

File tree

8 files changed

+252
-79
lines changed

8 files changed

+252
-79
lines changed

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ Metrics/BlockLength:
1616
Naming/FileName:
1717
Exclude:
1818
- 'lib/rubocop-sequel.rb'
19+
20+
RSpec/ExampleLength:
21+
Enabled: false

config/default.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ Sequel/ConcurrentIndex:
1111
VersionAdded: 0.0.1
1212
VersionChanged: 0.3.3
1313

14+
Sequel/EnforceNotValidConstraint:
15+
Description: 'Ensures that new constraints are created with the `not_valid: true` option.'
16+
Reference: https://www.rubydoc.info/gems/rubocop-sequel/RuboCop/Cop/Sequel/EnforceNotValidConstraint
17+
Enabled: false
18+
VersionAdded: '0.3.4'
19+
VersionChanged: '0.3.4'
20+
1421
Sequel/JSONColumn:
1522
Description: >-
1623
Advocates the use of the `jsonb` column type over `json` or `hstore` for performance

lib/rubocop-sequel.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
require 'rubocop/cop/sequel/migration_name'
1313
require 'rubocop/cop/sequel/save_changes'
1414
require 'rubocop/cop/sequel/partial_constraint'
15-
require 'rubocop/cop/sequel/not_valid_constraint'
15+
require 'rubocop/cop/sequel/enforce_not_valid_constraint'
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Sequel
6+
# Ensures that new constraints are created with the `not_valid: true` option.
7+
#
8+
# This cop checks database migration files for `add_foreign_key` and
9+
# `add_constraint` operations to ensure that new constraints are created
10+
# with the `not_valid: true` option, which is necessary for deferred validation.
11+
#
12+
# This cop can be configured to exclude or include specific tables through
13+
# the `ExcludeTables` and `IncludeTables` options.
14+
#
15+
# @example
16+
# # bad
17+
# add_foreign_key :users, :orders, column: :user_id
18+
#
19+
# # good
20+
# add_foreign_key :users, :orders, column: :user_id, not_valid: true
21+
#
22+
# # bad
23+
# alter_table :users do
24+
# add_constraint(:user_email_check) { "email IS NOT NULL" }
25+
# end
26+
#
27+
# # good
28+
# alter_table :users do
29+
# add_constraint(:user_email_check, not_valid: true) { "email IS NOT NULL" }
30+
# end
31+
#
32+
# @example Configuration
33+
# Sequel/EnforceNotValidConstraint:
34+
# Enabled: true
35+
# Include:
36+
# - "db/migrations"
37+
# ExcludeTables:
38+
# - users
39+
# IncludeTables:
40+
# - comments
41+
class EnforceNotValidConstraint < Base
42+
MSG = "Add the 'not_valid: true' option when creating new constraints to ensure " \
43+
'they are not validated immediately.'
44+
45+
# Matcher for alter_table blocks
46+
def_node_matcher :alter_table_block?, <<-MATCHER
47+
(block
48+
(send _ :alter_table ({str sym} $_))
49+
_
50+
(begin
51+
$...
52+
)
53+
)
54+
MATCHER
55+
56+
# Matcher for constraint operations like add_foreign_key and add_constraint
57+
def_node_matcher :constraint_operation?, <<-MATCHER
58+
(send _ {:add_foreign_key :add_constraint} $...)
59+
MATCHER
60+
61+
# Check constraint operations inside the alter_table block
62+
#
63+
# @param node [RuboCop::AST::Node] the AST node being checked
64+
def on_block(node)
65+
alter_table_block?(node) do |table_name, block_nodes|
66+
next if skip_table?(table_name)
67+
68+
block_nodes.each { |block_node| validate_node(block_node) }
69+
end
70+
end
71+
72+
private
73+
74+
# Validate constraint operations
75+
#
76+
# @param node [RuboCop::AST::Node] the AST node being checked
77+
def validate_node(node)
78+
constraint_operation?(node) do |args|
79+
next if not_valid_option_present?(args)
80+
81+
add_offense(node, message: MSG)
82+
end
83+
end
84+
85+
# Check if the not_valid: true option is present
86+
#
87+
# @param args [Array<RuboCop::AST::Node>] the operation arguments
88+
# @return [Boolean] whether the not_valid: true option is present
89+
def not_valid_option_present?(args)
90+
args.any? do |arg|
91+
arg.hash_type? && arg.pairs.any? do |pair|
92+
key = pair.key
93+
value = pair.value
94+
key.sym_type? && key.children.first == :not_valid && value.true_type?
95+
end
96+
end
97+
end
98+
99+
# Skip the table if it is excluded or not included
100+
#
101+
# @param table_name [String] the name of the table
102+
# @return [Boolean] whether the table should be skipped
103+
def skip_table?(table_name)
104+
return true if excluded_table?(table_name.to_s)
105+
106+
!included_table?(table_name.to_s)
107+
end
108+
109+
def excluded_table?(table_name)
110+
return false unless cop_config.key?('ExcludeTables')
111+
112+
cop_config['ExcludeTables'].include?(table_name)
113+
end
114+
115+
def included_table?(table_name)
116+
return true unless cop_config.key?('IncludeTables')
117+
118+
cop_config['IncludeTables'].include?(table_name)
119+
end
120+
end
121+
end
122+
end
123+
end

lib/rubocop/cop/sequel/not_valid_constraint.rb

Lines changed: 0 additions & 33 deletions
This file was deleted.

spec/project_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
expect(description.include?("\n")).to be(false)
1616
end
1717

18-
it 'stars from a verb' do # rubocop:disable RSpec/ExampleLength
18+
it 'stars from a verb' do
1919
description = config.dig(cop_name, 'Description')
2020
start_with_subject = description.match(/\AThis cop (?<verb>.+?) .*/)
2121
suggestion = start_with_subject[:verb]&.capitalize if start_with_subject
@@ -97,7 +97,7 @@
9797
end
9898
end
9999

100-
it 'sorts cop names alphabetically' do # rubocop:disable RSpec/ExampleLength
100+
it 'sorts cop names alphabetically' do
101101
previous_key = ''
102102
config_default = YAML.load_file('config/default.yml')
103103

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Sequel::EnforceNotValidConstraint do
4+
subject(:cop) { described_class.new(config) }
5+
6+
let(:cop_config) { { 'Enabled' => true } }
7+
let(:config) { RuboCop::Config.new('Sequel/EnforceNotValidConstraint' => cop_config) }
8+
9+
context 'when add_constraint used' do
10+
it 'registers an offense when used without `not_valid: true` option' do
11+
offenses = inspect_source(<<~RUBY)
12+
alter_table :foo do
13+
add_constraint :not_valid, Sequel.lit("jsonb_typeof(column) = 'object'")
14+
add_constraint({name: "not_valid"}, Sequel.lit("jsonb_typeof(column) = 'object'"))
15+
end
16+
RUBY
17+
18+
expect(offenses.size).to eq(2)
19+
end
20+
21+
it 'does not register an offense when using `not_valid: true` option' do
22+
offenses = inspect_source(<<~RUBY)
23+
alter_table :foo do
24+
add_constraint({name: "name", not_valid: true }, Sequel.lit("jsonb_typeof(column) = 'object'"))
25+
end
26+
RUBY
27+
28+
expect(offenses.size).to eq(0)
29+
end
30+
end
31+
32+
context 'when add_foreign_key used' do
33+
it 'registers an offense when used without `not_valid: true` option' do
34+
offenses = inspect_source(<<~RUBY)
35+
alter_table :foo do
36+
add_foreign_key(:not_valid, :table)
37+
add_foreign_key(:not_valid, :table, name: "not_valid")
38+
end
39+
RUBY
40+
41+
expect(offenses.size).to eq(2)
42+
end
43+
44+
it 'does not register an offense when using `not_valid: true` option' do
45+
offenses = inspect_source(<<~RUBY)
46+
alter_table :foo do
47+
add_foreign_key(:not_valid, :table, name: "not_valid", not_valid: true)
48+
end
49+
RUBY
50+
51+
expect(offenses.size).to eq(0)
52+
end
53+
end
54+
55+
context 'with ExcludeTables option' do
56+
let(:cop_config) do
57+
{
58+
'Enabled' => true,
59+
'ExcludeTables' => ['excluded_table']
60+
}
61+
end
62+
63+
it 'does not register an offense when using `not_valid: true` option inside excluded table migration' do
64+
offenses = inspect_source(<<~RUBY)
65+
alter_table :excluded_table do
66+
add_foreign_key(:not_valid, :table)
67+
add_foreign_key(:not_valid, :table, name: "not_valid")
68+
end
69+
RUBY
70+
71+
expect(offenses.size).to eq(0)
72+
end
73+
74+
it 'register an offense when using `not_valid: true` option for not excluded table' do
75+
offenses = inspect_source(<<~RUBY)
76+
alter_table :not_excluded_table do
77+
add_foreign_key(:not_valid, :table)
78+
add_foreign_key(:not_valid, :table, name: "not_valid")
79+
end
80+
RUBY
81+
82+
expect(offenses.size).to eq(2)
83+
end
84+
end
85+
86+
context 'with IncludeTables option' do
87+
let(:cop_config) do
88+
{
89+
'Enabled' => true,
90+
'IncludeTables' => ['included_table']
91+
}
92+
end
93+
94+
it 'does not register an offense when using `not_valid: true` option inside excluded table migration' do
95+
offenses = inspect_source(<<~RUBY)
96+
alter_table :included_table do
97+
add_foreign_key(:not_valid, :table)
98+
add_foreign_key(:not_valid, :table, name: "not_valid")
99+
end
100+
RUBY
101+
102+
expect(offenses.size).to eq(2)
103+
end
104+
105+
it 'register an offense when using `not_valid: true` option for not excluded table' do
106+
offenses = inspect_source(<<~RUBY)
107+
alter_table :not_included_table do
108+
add_foreign_key(:not_valid, :table)
109+
add_foreign_key(:not_valid, :table, name: "not_valid")
110+
end
111+
RUBY
112+
113+
expect(offenses.size).to eq(0)
114+
end
115+
end
116+
end

spec/rubocop/cop/sequel/not_valid_constraint_spec.rb

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)