Skip to content

Commit 705c794

Browse files
committed
Add new MigrationClassName cop
1 parent 2a76325 commit 705c794

File tree

5 files changed

+99
-0
lines changed

5 files changed

+99
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#644](https://github.com/rubocop/rubocop-rails/pull/644): Add new `Rails/MigrationClassName` cop. ([@johnny-miyake][])

config/default.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,13 @@ Rails/MatchRoute:
527527
- config/routes.rb
528528
- config/routes/**/*.rb
529529

530+
Rails/MigrationClassName:
531+
Description: 'The class name of the migration should match its file name.'
532+
Enabled: pending
533+
VersionAdded: '<<next>>'
534+
Include:
535+
- db/migrate/*.rb
536+
530537
Rails/NegateInclude:
531538
Description: 'Prefer `collection.exclude?(obj)` over `!collection.include?(obj)`.'
532539
StyleGuide: 'https://rails.rubystyle.guide#exclude'
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# This cop makes sure that each migration file defines a migration class
7+
# whose name matches the file name.
8+
# (e.g. `20220224111111_create_users.rb` should define `CreateUsers` class.)
9+
#
10+
# @example
11+
# # db/migrate/20220224111111_create_users.rb
12+
#
13+
# # bad
14+
# class SellBooks < ActiveRecord::Migration[7.0]
15+
# end
16+
#
17+
# # good
18+
# class CreateUsers < ActiveRecord::Migration[7.0]
19+
# end
20+
#
21+
class MigrationClassName < Base
22+
extend AutoCorrector
23+
24+
MSG = 'Replace with `%<corrected_class_name>s` that matches the file name.'
25+
26+
def on_class(node)
27+
snake_class_name = to_snakecase(node.identifier.source)
28+
29+
return if snake_class_name == basename_without_timestamp
30+
31+
corrected_class_name = to_camelcase(basename_without_timestamp)
32+
message = format(MSG, corrected_class_name: corrected_class_name)
33+
34+
add_offense(node.identifier, message: message) do |corrector|
35+
corrector.replace(node.identifier, corrected_class_name)
36+
end
37+
end
38+
39+
private
40+
41+
def basename_without_timestamp
42+
filepath = processed_source.file_path
43+
basename = File.basename(filepath, '.rb')
44+
basename.sub(/\A\d+_/, '')
45+
end
46+
47+
def to_camelcase(word)
48+
word.split('_').map(&:capitalize).join
49+
end
50+
51+
def to_snakecase(word)
52+
word
53+
.gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
54+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
55+
.tr('-', '_')
56+
.downcase
57+
end
58+
end
59+
end
60+
end
61+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
require_relative 'rails/link_to_blank'
6565
require_relative 'rails/mailer_name'
6666
require_relative 'rails/match_route'
67+
require_relative 'rails/migration_class_name'
6768
require_relative 'rails/negate_include'
6869
require_relative 'rails/not_null_column'
6970
require_relative 'rails/order_by_id'
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Rails::MigrationClassName, :config do
4+
let(:filename) { 'db/migrate/20220101050505_create_users.rb' }
5+
6+
context 'when the class name matches its file name' do
7+
it 'does not register an offense' do
8+
expect_no_offenses(<<~RUBY, filename)
9+
class CreateUsers < ActiveRecord::Migration[7.0]
10+
end
11+
RUBY
12+
end
13+
end
14+
15+
context 'when the class name does not match its file name' do
16+
it 'registers an offense' do
17+
expect_offense(<<~RUBY, filename)
18+
class SellBooks < ActiveRecord::Migration[7.0]
19+
^^^^^^^^^ Replace with `CreateUsers` that matches the file name.
20+
end
21+
RUBY
22+
23+
expect_correction(<<~RUBY)
24+
class CreateUsers < ActiveRecord::Migration[7.0]
25+
end
26+
RUBY
27+
end
28+
end
29+
end

0 commit comments

Comments
 (0)