-
-
Notifications
You must be signed in to change notification settings - Fork 268
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add cop Rails/AcceptsNestedAttributesForUpdateOnly
- Loading branch information
1 parent
024b6a4
commit bd69597
Showing
7 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
lib/rubocop/cop/rails/accepts_nested_attributes_for_update_only.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Rails | ||
# Looks for `accepts_nested_attributes_for` attributes writers that don't | ||
# specify an `:update_only` option. | ||
# | ||
# @example | ||
# # bad | ||
# class Member < ActiveRecord::Base | ||
# has_one :avatar | ||
# accepts_nested_attributes_for :avatar | ||
# end | ||
# | ||
# # good | ||
# class Member < ActiveRecord::Base | ||
# has_one :avatar | ||
# accepts_nested_attributes_for :avatar, update_only: true | ||
# end | ||
class AcceptsNestedAttributesForUpdateOnly < Base | ||
MSG = 'Specify a `:update_only` option.' | ||
RESTRICT_ON_SEND = %i[accepts_nested_attributes_for].freeze | ||
|
||
def_node_search :active_resource_class?, <<~PATTERN | ||
(const (const {nil? cbase} :ActiveResource) :Base) | ||
PATTERN | ||
|
||
def_node_matcher :accepts_nested_attributes_for_without_options?, <<~PATTERN | ||
(send _ {:accepts_nested_attributes_for} _) | ||
PATTERN | ||
|
||
def_node_matcher :accepts_nested_attributes_for_with_options?, <<~PATTERN | ||
(send _ {:accepts_nested_attributes_for} ... (hash $...)) | ||
PATTERN | ||
|
||
def_node_matcher :update_only_option?, <<~PATTERN | ||
(pair (sym :update_only) {!nil (nil)}) | ||
PATTERN | ||
|
||
def_node_matcher :with_options_block, <<~PATTERN | ||
(block | ||
(send nil? :with_options | ||
(hash $...)) | ||
(args) ...) | ||
PATTERN | ||
|
||
def_node_matcher :accepts_nested_attributes_for_extension_block?, <<~PATTERN | ||
(block | ||
(send nil? :accepts_nested_attributes_for _) | ||
(args) ...) | ||
PATTERN | ||
|
||
def on_send(node) | ||
return if active_resource?(node.parent) | ||
return if !accepts_nested_attributes_for_without_options?(node) && \ | ||
valid_options?(accepts_nested_attributes_for_with_options?(node)) | ||
return if valid_options_in_with_options_block?(node) | ||
|
||
add_offense(node.loc.selector) | ||
end | ||
|
||
private | ||
|
||
def valid_options_in_with_options_block?(node) | ||
return true unless node.parent | ||
|
||
n = node.parent.begin_type? | ||
n ||= accepts_nested_attributes_for_extension_block?(node.parent) ? node.parent.parent : node.parent | ||
|
||
contain_valid_options_in_with_options_block?(n) | ||
end | ||
|
||
def contain_valid_options_in_with_options_block?(node) | ||
if (options = with_options_block(node)) | ||
return true if valid_options?(options) | ||
|
||
return false unless node.parent | ||
|
||
return true if contain_valid_options_in_with_options_block?(node.parent.parent) | ||
end | ||
|
||
false | ||
end | ||
|
||
def valid_options?(options) | ||
return false if options.nil? | ||
|
||
options = extract_option_if_kwsplat(options) | ||
|
||
return true unless options | ||
return true if options.any? do |o| | ||
update_only_option?(o) | ||
end | ||
|
||
false | ||
end | ||
|
||
def extract_option_if_kwsplat(options) | ||
if options.first.kwsplat_type? && options.first.children.first.hash_type? | ||
return options.first.children.first.pairs | ||
end | ||
|
||
options | ||
end | ||
|
||
def active_resource?(node) | ||
return false if node.nil? | ||
|
||
active_resource_class?(node) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
spec/rubocop/cop/rails/accepts_nested_attributes_for_update_only_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Rails::AcceptsNestedAttributesForUpdateOnly, :config do | ||
context 'accepts_nested_attributes_for' do | ||
it 'registers an offense when not specifying any options' do | ||
expect_offense(<<~RUBY) | ||
class Member < ApplicationRecord | ||
has_one :avatar | ||
accepts_nested_attributes_for :avatar | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Specify a `:update_only` option. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when missing an explicit `:update_only` flag' do | ||
expect_offense(<<~RUBY) | ||
class Member < ApplicationRecord | ||
has_one :avatar | ||
accepts_nested_attributes_for :avatar, reject_if: :all_blank | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Specify a `:update_only` option. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when specifying `:update_only` flag' do | ||
expect_no_offenses(<<~RUBY) | ||
class Member < ApplicationRecord | ||
has_one :avatar | ||
accepts_nested_attributes_for :avatar, update_only: true | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when specifying `:update_only` flag with double splat' do | ||
expect_no_offenses(<<~RUBY) | ||
class Member < ApplicationRecord | ||
has_one :avatar | ||
accepts_nested_attributes_for :avatar, **{update_only: true} | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when a variable passed with double splat' do | ||
expect_offense(<<~RUBY) | ||
class Member < ApplicationRecord | ||
has_one :avatar | ||
accepts_nested_attributes_for :avatar, **bar | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Specify a `:update_only` option. | ||
end | ||
RUBY | ||
end | ||
|
||
context 'with_options update_only: true' do | ||
it 'does not register an offense' do | ||
expect_no_offenses(<<~RUBY) | ||
class Member < ApplicationRecord | ||
has_one :avatar | ||
with_options update_only: true do | ||
accepts_nested_attributes_for :avatar | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense for using `reject_if` option' do | ||
expect_no_offenses(<<~RUBY) | ||
class Member < ApplicationRecord | ||
has_one :avatar | ||
with_options update_only: true do | ||
accepts_nested_attributes_for :avatar, reject_if: :all_blank | ||
end | ||
end | ||
RUBY | ||
end | ||
end | ||
end | ||
|
||
context 'when an Active Record model does not have any associations' do | ||
it 'does not register an offense' do | ||
expect_no_offenses(<<~RUBY) | ||
class Member < ApplicationRecord | ||
end | ||
RUBY | ||
end | ||
end | ||
end |