Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Rails/DateAndTimeColumnName cop #335

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/new_add_new_date_and_time_column_name_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#335](https://github.com/rubocop/rubocop-rails/pull/335): Add new `Rails/DateAndTimeColumnName` cop. ([@fatkodima][])
11 changes: 11 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,17 @@ Rails/Date:
- flexible
AllowToTime: true

Rails/DateAndTimeColumnName:
Description: >-
Enforces date/time columns naming conventions.
Specifically, `date` column should end with `_on` suffix,
`datetime` and `timestamp` - with `_at` suffix,
`time` - with `_time` suffix.
Enabled: pending
VersionAdded: '<<next>>'
Include:
- db/**/*.rb

Rails/DefaultScope:
Description: 'Avoid use of `default_scope`.'
Enabled: false
Expand Down
61 changes: 61 additions & 0 deletions lib/rubocop/cop/rails/date_and_time_column_name.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Rails
# Enforces date/time columns naming conventions.
# Specifically, `date` column should end with `_on` suffix,
# `datetime` and `timestamp` - with `_at` suffix,
# `time` - with `_time` suffix.
#
# @example
# # bad
# add_column :orders, :created_on, :datetime
# t.column :visited_at, :date
# t.time :start_at
#
# # good
# add_column :orders, :created_at, :datetime
# t.column :visited_on, :date
# t.time :start_time
#
class DateAndTimeColumnName < Base
prepend MigrationsHelper

MSG = 'Name `%<type>s` columns with `%<suffix>s` suffixes.'
TYPE_TO_SUFFIX = { datetime: '_at', timestamp: '_at', date: '_on', time: '_time' }.freeze

def on_send(node)
return unless in_migration?(node)

column_name, type = column_name_and_type(node)
return unless column_name

suffix = TYPE_TO_SUFFIX[type]
return if !suffix || column_name.value.to_s.end_with?(suffix)

message = format(MSG, type: type, suffix: suffix)
add_offense(column_name, message: message)
end

private

def column_name_and_type(node)
case node.method_name
when :add_column
_table_name, column_name, type, = *node.arguments
type = type.value
when :column
column_name, type, = *node.arguments
type = type.value
when *TYPE_TO_SUFFIX.keys
column_name = node.first_argument
type = node.method_name
end

[column_name, type]
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rails_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
require_relative 'rails/create_table_with_timestamps'
require_relative 'rails/dangerous_column_names'
require_relative 'rails/date'
require_relative 'rails/date_and_time_column_name'
require_relative 'rails/default_scope'
require_relative 'rails/delegate'
require_relative 'rails/delegate_allow_blank'
Expand Down
157 changes: 157 additions & 0 deletions spec/rubocop/cop/rails/date_and_time_column_name_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Rails::DateAndTimeColumnName, :config do
described_class::TYPE_TO_SUFFIX.each do |type, suffix|
context 'when `add_column` method' do
it "registers an offense for `#{type}` column without `#{suffix}` suffix" do
source = <<~RUBY
class AddColumnNameToTableName < ActiveRecord::Migration[6.0]
def change
add_column :table_name, :column_name_without_suffix, :#{type}
end
end
RUBY

offenses = inspect_source(source)
expect(offenses.count).to eq(1)
end

it "does not register an offense for `#{type}` column with `#{suffix}` suffix" do
expect_no_offenses(<<~RUBY)
class AddColumnNameToTableName < ActiveRecord::Migration[6.0]
def change
add_column :table_name, :column_name_#{suffix}, :#{type}
end
end
RUBY
end
end

context 'when `column` method' do
it "registers an offense for `#{type}` column without `#{suffix}` suffix" do
source = <<~RUBY
class AddColumnNameToTableName < ActiveRecord::Migration[6.0]
def change
create_table :table_name do |t|
t.column :column_name_without_suffix, :#{type}
end
end
end
RUBY

offenses = inspect_source(source)
expect(offenses.count).to eq(1)
end

it "does not register an offense for `#{type}` column with `#{suffix}` suffix" do
expect_no_offenses(<<~RUBY)
class AddColumnNameToTableName < ActiveRecord::Migration[6.0]
def change
create_table :table_name do |t|
t.column :column_name_#{suffix}, :#{type}
end
end
end
RUBY
end

it 'does not register an offense when non date column is used' do
expect_no_offenses(<<~RUBY)
class AddColumnNameToTableName < ActiveRecord::Migration[6.0]
def change
create_table :table_name do |t|
t.column :column_name, :integer
end
end
end
RUBY
end
end

context 'when `t.<type>` method' do
it "registers an offense for `#{type}` column without `#{suffix}` suffix" do
source = <<~RUBY
class AddColumnNameToTableName < ActiveRecord::Migration[6.0]
def change
create_table :table_name do |t|
t.#{type} :column_name_without_suffix
end
end
end
RUBY

offenses = inspect_source(source)
expect(offenses.count).to eq(1)
end

it "does not register an offense for `#{type}` column with `#{suffix}` suffix" do
expect_no_offenses(<<~RUBY)
class AddColumnNameToTableName < ActiveRecord::Migration[6.0]
def change
create_table :table_name do |t|
t.#{type} :column_name_#{suffix}
end
end
end
RUBY
end

it 'does not register an offense when non date column is used' do
expect_no_offenses(<<~RUBY)
class AddColumnNameToTableName < ActiveRecord::Migration[6.0]
def change
create_table :table_name do |t|
t.integer :column_name
end
end
end
RUBY
end
end
end

it 'does not register an offense when `add_column` with non date column is used' do
expect_no_offenses(<<~RUBY)
class AddColumnNameToTableName < ActiveRecord::Migration[6.0]
def change
add_column :table_name, :column_name, :integer
end
end
RUBY
end

it 'does not register an offense when not inside migration' do
expect_no_offenses(<<~RUBY)
class Foo
add_column :table_name, :column_name, :datetime
end
RUBY
end

context 'MigratedSchemaVersion is used' do
let(:config) do
RuboCop::Config.new('AllCops' => { 'MigratedSchemaVersion' => '20211007000001' })
end

it 'registers an offense for newer migrations' do
expect_offense(<<~RUBY, 'db/migrate/20211007000002_add_created_on_to_orders.rb')
class AddCreatedOnToOrders < ActiveRecord::Migration[6.0]
def change
add_column :orders, :created_on, :datetime
^^^^^^^^^^^ Name `datetime` columns with `_at` suffixes.
end
end
RUBY
end

it 'does not register an offense for older migrations' do
expect_no_offenses(<<~RUBY, 'db/migrate/20211007000001_add_column_to_table_name.rb')
class AddCreatedOnToOrders < ActiveRecord::Migration[6.0]
def change
add_column :orders, :created_on, :datetime
end
end
RUBY
end
end
end
Loading