rubocop-mismatched-foreign-key-type 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d7806c9473411325f857346dec50a0bfbd26446c245a01cca4afe9e0f365232f
4
+ data.tar.gz: 01cd58f6564b755edfac4b256dfe5efd8e1535345f660e9a2dba98a4cfb56990
5
+ SHA512:
6
+ metadata.gz: d29bf6f4ccec1216fec2f3d7525b689746ee18ce040c761d9a2e3157dc1de65b037e4deee698c86f87cdb5f38584b07a38ac4deedd8bd2f54ccb8e0dcaadd8a6
7
+ data.tar.gz: 3a601d99511f6071975f6548ceafc48f002af2403f139d1816fb4a09781212b962a91b405e512ba29d81ff934d3443cd0f5c58419f2363223d196136f9fb5b93
@@ -0,0 +1,7 @@
1
+ require:
2
+ - rubocop-mismatched-foreign-key-type
3
+
4
+ Rails/MismatchedForeignKeyType:
5
+ Enabled: true
6
+ Include:
7
+ - 'db/schema.rb'
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/inflector'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Rails
8
+ # This cop checks for foreign key type mismatches in db/schema.rb.
9
+ # It detects when a table uses `t.integer :xxx_id` but the referenced
10
+ # table's primary key is `bigint` (Rails 5.1+ default).
11
+ #
12
+ # @example
13
+ # # bad
14
+ # create_table "users", force: :cascade do |t|
15
+ # ...
16
+ # end
17
+ #
18
+ # create_table "posts", force: :cascade do |t|
19
+ # t.integer "user_id"
20
+ # end
21
+ #
22
+ # # good
23
+ # create_table "users", force: :cascade do |t|
24
+ # ...
25
+ # end
26
+ #
27
+ # create_table "posts", force: :cascade do |t|
28
+ # t.bigint "user_id"
29
+ # end
30
+ class MismatchedForeignKeyType < RuboCop::Cop::Base
31
+ MSG = 'Use bigint for foreign keys that reference bigint primary keys.'
32
+
33
+ def initialize(*args)
34
+ super
35
+ @table_pk_types = {}
36
+ end
37
+
38
+ # Called before all on_xxx callbacks are executed.
39
+ def on_new_investigation
40
+ super
41
+
42
+ processed_source.ast.each_node(:send) do |node|
43
+ next unless node.method?(:create_table)
44
+
45
+ table_name = extract_table_name(node)
46
+ next unless table_name
47
+
48
+ @table_pk_types[table_name] = extract_pk_type(node)
49
+ end
50
+ end
51
+
52
+ def on_send(node)
53
+ return unless node.method?(:create_table)
54
+
55
+ block = node.block_node
56
+ check_integer_foreign_keys(block) if block
57
+ end
58
+
59
+ private
60
+
61
+ def extract_table_name(node)
62
+ table_name_node = node.arguments.first
63
+ return unless table_name_node&.str_type?
64
+
65
+ table_name_node.value
66
+ end
67
+
68
+ def extract_pk_type(node)
69
+ node.arguments.each do |arg|
70
+ next unless arg.hash_type?
71
+
72
+ pk_type = extract_pk_type_from_hash(arg)
73
+ return pk_type if pk_type
74
+ end
75
+
76
+ # Default to bigint (Rails 5.1+)
77
+ :bigint
78
+ end
79
+
80
+ def extract_pk_type_from_hash(hash_node)
81
+ hash_node.each_pair do |key, value|
82
+ next unless key.sym_type? && key.value == :id
83
+ next unless value.sym_type?
84
+
85
+ case value.value
86
+ when :integer then return :integer
87
+ when :bigint then return :bigint
88
+ end
89
+ end
90
+ nil
91
+ end
92
+
93
+ def check_integer_foreign_keys(block_node)
94
+ return unless block_node.body
95
+
96
+ block_node.body.each_node(:send) do |send_node|
97
+ next unless send_node.method_name == :integer
98
+
99
+ check_integer_foreign_key(send_node)
100
+ end
101
+ end
102
+
103
+ def check_integer_foreign_key(send_node)
104
+ fk_name = send_node.arguments.first&.value
105
+ return unless fk_name&.end_with?('_id')
106
+
107
+ referenced_table = extract_referenced_table(fk_name)
108
+ referenced_pk_type = @table_pk_types[referenced_table]
109
+ return unless referenced_pk_type == :bigint
110
+
111
+ add_offense(send_node, message: MSG)
112
+ end
113
+
114
+ def extract_referenced_table(fk_name)
115
+ base = fk_name.delete_suffix('_id')
116
+ base.pluralize
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module MismatchedForeignKeyType
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rubocop/cop/rails/mismatched_foreign_key_type'
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe RuboCop::Cop::Rails::MismatchedForeignKeyType, :config do
6
+ let(:config) do
7
+ RuboCop::Config.new(
8
+ 'Rails/MismatchedForeignKeyType' => {
9
+ 'Enabled' => true
10
+ }
11
+ )
12
+ end
13
+
14
+ describe 'MismatchedForeignKeyType' do
15
+ context 'when an incorrect foreign key type is used' do
16
+ it 'registers an offense for implicit bigint primary key' do
17
+ expect_offense(<<~RUBY)
18
+ create_table "users", force: :cascade do |t|
19
+ t.string "name"
20
+ end
21
+
22
+ create_table "posts", force: :cascade do |t|
23
+ t.integer "user_id"
24
+ ^^^^^^^^^^^^^^^^^^^ Use bigint for foreign keys that reference bigint primary keys.
25
+ end
26
+ RUBY
27
+ end
28
+
29
+ it 'registers an offense for explicit bigint primary key' do
30
+ expect_offense(<<~RUBY)
31
+ create_table "users", id: :bigint, force: :cascade do |t|
32
+ t.string "name"
33
+ end
34
+
35
+ create_table "posts", force: :cascade do |t|
36
+ t.integer "user_id"
37
+ ^^^^^^^^^^^^^^^^^^^ Use bigint for foreign keys that reference bigint primary keys.
38
+ end
39
+ RUBY
40
+ end
41
+
42
+ it 'registers offenses for multiple bigint primary keys' do
43
+ expect_offense(<<~RUBY)
44
+ create_table "users", force: :cascade do |t|
45
+ t.string "name"
46
+ end
47
+
48
+ create_table "categories", force: :cascade do |t|
49
+ t.string "name"
50
+ end
51
+
52
+ create_table "posts", force: :cascade do |t|
53
+ t.integer "user_id"
54
+ ^^^^^^^^^^^^^^^^^^^ Use bigint for foreign keys that reference bigint primary keys.
55
+ t.integer "category_id"
56
+ ^^^^^^^^^^^^^^^^^^^^^^^ Use bigint for foreign keys that reference bigint primary keys.
57
+ end
58
+ RUBY
59
+ end
60
+
61
+ it 'registers an offense for foreign keys referencing tables with underscores' do
62
+ expect_offense(<<~RUBY)
63
+ create_table "user_profiles", force: :cascade do |t|
64
+ t.string "name"
65
+ end
66
+
67
+ create_table "posts", force: :cascade do |t|
68
+ t.integer "user_profile_id"
69
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use bigint for foreign keys that reference bigint primary keys.
70
+ end
71
+ RUBY
72
+ end
73
+
74
+ it 'registers offenses for various pluralization patterns' , :aggregate_failures do
75
+ expect_offense(<<~RUBY)
76
+ create_table "categories", force: :cascade do |t|
77
+ t.string "name"
78
+ end
79
+
80
+ create_table "posts", force: :cascade do |t|
81
+ t.integer "category_id"
82
+ ^^^^^^^^^^^^^^^^^^^^^^^ Use bigint for foreign keys that reference bigint primary keys.
83
+ end
84
+ RUBY
85
+
86
+ expect_offense(<<~RUBY)
87
+ create_table "addresses", force: :cascade do |t|
88
+ t.string "street"
89
+ end
90
+
91
+ create_table "users", force: :cascade do |t|
92
+ t.integer "address_id"
93
+ ^^^^^^^^^^^^^^^^^^^^^^ Use bigint for foreign keys that reference bigint primary keys.
94
+ end
95
+ RUBY
96
+
97
+ expect_offense(<<~RUBY)
98
+ create_table "people", force: :cascade do |t|
99
+ t.string "name"
100
+ end
101
+
102
+ create_table "posts", force: :cascade do |t|
103
+ t.integer "person_id"
104
+ ^^^^^^^^^^^^^^^^^^^^^ Use bigint for foreign keys that reference bigint primary keys.
105
+ end
106
+ RUBY
107
+ end
108
+
109
+ it 'registers offense even if referenced table is defined laster' do
110
+ expect_offense(<<~RUBY)
111
+ create_table "posts", force: :cascade do |t|
112
+ t.integer "user_id"
113
+ ^^^^^^^^^^^^^^^^^^^ Use bigint for foreign keys that reference bigint primary keys.
114
+ end
115
+
116
+ create_table "users", force: :cascade do |t|
117
+ t.string "name"
118
+ end
119
+ RUBY
120
+ end
121
+ end
122
+
123
+ context 'when the correct foreign key type is used' do
124
+ it 'does not register an offense for bigint foreign key referencing bigint primary key' do
125
+ expect_no_offenses(<<~RUBY)
126
+ create_table "users", force: :cascade do |t|
127
+ t.string "name"
128
+ end
129
+
130
+ create_table "posts", force: :cascade do |t|
131
+ t.bigint "user_id"
132
+ end
133
+ RUBY
134
+ end
135
+
136
+ it 'does not register an offense for integer foreign key referencing integer primary key' do
137
+ expect_no_offenses(<<~RUBY)
138
+ create_table "users", id: :integer, force: :cascade do |t|
139
+ t.string "name"
140
+ end
141
+
142
+ create_table "posts", force: :cascade do |t|
143
+ t.integer "user_id"
144
+ end
145
+ RUBY
146
+ end
147
+ end
148
+
149
+ context 'when not recognized as a foreign key' do
150
+ it 'does not register an offense if there is no foreign key column' do
151
+ expect_no_offenses(<<~RUBY)
152
+ create_table "users", force: :cascade do |t|
153
+ t.string "name"
154
+ end
155
+
156
+ create_table "posts", force: :cascade do |t|
157
+ t.string "title"
158
+ t.text "content"
159
+ end
160
+ RUBY
161
+ end
162
+
163
+ it 'does not register an offense if the referenced table does not exist' do
164
+ expect_no_offenses(<<~RUBY)
165
+ create_table "posts", force: :cascade do |t|
166
+ t.integer "user_id"
167
+ end
168
+ RUBY
169
+ end
170
+
171
+ it 'does not register an offense for columns not ending with _id' do
172
+ expect_no_offenses(<<~RUBY)
173
+ create_table "users", force: :cascade do |t|
174
+ t.string "name"
175
+ end
176
+
177
+ create_table "posts", force: :cascade do |t|
178
+ t.integer "user_reference"
179
+ end
180
+ RUBY
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+ require 'rubocop/rspec/support'
5
+ require "rubocop-mismatched-foreign-key-type"
6
+
7
+ RSpec.configure do |config|
8
+ config.disable_monkey_patching!
9
+ config.expect_with :rspec do |c|
10
+ c.syntax = :expect
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubocop-mismatched-foreign-key-type
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - yohei.hokari
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-12-25 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rubocop
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '1.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '1.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '6.0'
40
+ description: This cop identifies integer foreign keys that should be biginteger, based
41
+ on the primary key of the referenced table.
42
+ email:
43
+ - yohei.hokari@optim.co.jp
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - config/default.yml
49
+ - lib/rubocop-mismatched-foreign-key-type.rb
50
+ - lib/rubocop/cop/rails/mismatched_foreign_key_type.rb
51
+ - lib/rubocop/mismatched_foreign_key_type/version.rb
52
+ - spec/rubocop/cop/rails/mismatched_foreign_key_type_spec.rb
53
+ - spec/spec_helper.rb
54
+ homepage: https://gitlab.tokyo.optim.co.jp/yohei.hokari/rubocop-mismatched-foreign-key-type
55
+ licenses:
56
+ - MIT
57
+ metadata:
58
+ homepage_uri: https://gitlab.tokyo.optim.co.jp/yohei.hokari/rubocop-mismatched-foreign-key-type
59
+ source_code_uri: https://gitlab.tokyo.optim.co.jp/yohei.hokari/rubocop-mismatched-foreign-key-type
60
+ changelog_uri: https://gitlab.tokyo.optim.co.jp/yohei.hokari/rubocop-mismatched-foreign-key-type/blob/main/CHANGELOG.md
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.0.0
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.2
76
+ specification_version: 4
77
+ summary: A RuboCop extension to check for mismatched foreign key types in schema.rb.
78
+ test_files: []