diff --git a/changelog/new_add_strict_loading_associations_cop.md b/changelog/new_add_strict_loading_associations_cop.md new file mode 100644 index 0000000000..5dc02a3b0b --- /dev/null +++ b/changelog/new_add_strict_loading_associations_cop.md @@ -0,0 +1 @@ +- [#876](https://github.com/rubocop/rubocop-rails/pull/876): Add new Rails/StrictLoadingAssociations cop. ([@drenmi][]) diff --git a/config/default.yml b/config/default.yml index b4c8d28d44..b793a4d3ce 100644 --- a/config/default.yml +++ b/config/default.yml @@ -977,6 +977,11 @@ Rails/SquishedSQLHeredocs: # to be preserved in order to work, thus autocorrection is not safe. SafeAutoCorrect: false +Rails/StrictLoadingAssociations: + Description: 'Declare a `strict_loading` option on `has_many` associations.' + Enabled: pending + VersionAdded: '2.18' + Rails/StripHeredoc: Description: 'Enforces the use of squiggly heredoc over `strip_heredoc`.' StyleGuide: 'https://rails.rubystyle.guide/#prefer-squiggly-heredoc' diff --git a/lib/rubocop/cop/rails/strict_loading_associations.rb b/lib/rubocop/cop/rails/strict_loading_associations.rb new file mode 100644 index 0000000000..d2f1ee7cfa --- /dev/null +++ b/lib/rubocop/cop/rails/strict_loading_associations.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Rails + # Declare a `strict_loading` option on `has_many` associations. + # + # @example + # + # # bad + # has_many :posts + # + # # good + # has_many :posts, strict_loading: true + # + # # good + # has_many :posts, strict_loading: false + # + class StrictLoadingAssociations < Base + extend TargetRailsVersion + + minimum_target_rails_version 6.1 + + MSG = 'Declare a `strict_loading` option on `has_many` associations.' + + RESTRICT_ON_SEND = %i[has_many].freeze + + # @!method has_many(node) + def_node_matcher :has_many, <<~PATTERN + (send nil? :has_many _ $hash ?) + PATTERN + + def on_send(node) + has_many(node) do |options| + add_offense(node) unless strict_loading_declared?(options.first) + end + end + + private + + def strict_loading_declared?(options) + return false if options.nil? + + options.each_key.any? { |k| k.value.to_s == "strict_loading" } + end + end + end + end +end diff --git a/lib/rubocop/cop/rails_cops.rb b/lib/rubocop/cop/rails_cops.rb index 24908f0516..5364126cbe 100644 --- a/lib/rubocop/cop/rails_cops.rb +++ b/lib/rubocop/cop/rails_cops.rb @@ -112,6 +112,7 @@ require_relative 'rails/short_i18n' require_relative 'rails/skips_model_validations' require_relative 'rails/squished_sql_heredocs' +require_relative 'rails/strict_loading_associations' require_relative 'rails/strip_heredoc' require_relative 'rails/table_name_assignment' require_relative 'rails/time_zone' diff --git a/spec/rubocop/cop/rails/strict_loading_associations_spec.rb b/spec/rubocop/cop/rails/strict_loading_associations_spec.rb new file mode 100644 index 0000000000..0145ca963d --- /dev/null +++ b/spec/rubocop/cop/rails/strict_loading_associations_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Rails::StrictLoadingAssociations, :config do + context "when using Rails 6.1 or newer", :rails61 do + it 'registers an offense when not declaring `strict_loading`' do + expect_offense(<<~RUBY) + class Author < ApplicationRecord + has_many :posts + ^^^^^^^^^^^^^^^ Declare a `strict_loading` option on `has_many` associations. + end + RUBY + end + + it 'does not register an offense when declaring `strict_loading`' do + expect_no_offenses(<<~RUBY) + class Author < ApplicationRecord + has_many :posts, strict_loading: true + end + RUBY + end + end + + context "when using Rails 6.0 or older", :rails60 do + it 'does not register an offense when not declaring `strict_loading`' do + expect_no_offenses(<<~RUBY) + class Author < ApplicationRecord + has_many :posts + end + RUBY + end + end +end