Class: RuboCop::Cop::Rails::InverseOf
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::Rails::InverseOf
- Defined in:
- lib/rubocop/cop/rails/inverse_of.rb
Overview
Looks for has_(one|many) and belongs_to associations where Active Record can’t automatically determine the inverse association because of a scope or the options used. Using the blog with order scope example below, traversing the a Blog’s association in both directions with ‘blog.posts.first.blog` would cause the `blog` to be loaded from the database twice.
‘:inverse_of` must be manually specified for Active Record to use the associated object in memory, or set to `false` to opt-out. Note that setting `nil` does not stop Active Record from trying to determine the inverse automatically, and is not considered a valid value for this.
Constant Summary collapse
- SPECIFY_MSG =
'Specify an `:inverse_of` option.'- NIL_MSG =
'You specified `inverse_of: nil`, you probably meant to use `inverse_of: false`.'- RESTRICT_ON_SEND =
%i[has_many has_one belongs_to].freeze
Instance Method Summary collapse
- #dynamic_options?(options) ⇒ Boolean
-
#on_send(node) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity.
- #options_contain_inverse_of?(options) ⇒ Boolean
- #options_ignoring_inverse_of?(options) ⇒ Boolean
- #options_requiring_inverse_of?(options) ⇒ Boolean
- #same_context_in_with_options?(arg, recv) ⇒ Boolean
-
#scope?(arguments) ⇒ Boolean
rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity.
- #with_options_arguments(recv, node) ⇒ Object
Instance Method Details
#dynamic_options?(options) ⇒ Boolean
222 223 224 |
# File 'lib/rubocop/cop/rails/inverse_of.rb', line 222 def () .any? { |option| option&.kwsplat_type? } end |
#on_send(node) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/rubocop/cop/rails/inverse_of.rb', line 182 def on_send(node) recv, arguments = association_recv_arguments(node) return unless arguments = (recv, node) = arguments.concat().flat_map do |arg| (arg) end return if () return unless scope?(arguments) || () return if () return if () && .none? { |opt| inverse_of_nil_option?(opt) } add_offense(node.loc.selector, message: ()) end |
#options_contain_inverse_of?(options) ⇒ Boolean
226 227 228 |
# File 'lib/rubocop/cop/rails/inverse_of.rb', line 226 def () .any? { |opt| inverse_of_option?(opt) } end |
#options_ignoring_inverse_of?(options) ⇒ Boolean
216 217 218 219 220 |
# File 'lib/rubocop/cop/rails/inverse_of.rb', line 216 def () .any? do |opt| through_option?(opt) || polymorphic_option?(opt) end end |
#options_requiring_inverse_of?(options) ⇒ Boolean
206 207 208 209 210 211 212 213 214 |
# File 'lib/rubocop/cop/rails/inverse_of.rb', line 206 def () required = .any? do |opt| conditions_option?(opt) || foreign_key_option?(opt) end return required if target_rails_version >= 5.2 required || .any? { |opt| as_option?(opt) } end |
#same_context_in_with_options?(arg, recv) ⇒ Boolean
237 238 239 240 241 |
# File 'lib/rubocop/cop/rails/inverse_of.rb', line 237 def (arg, recv) return true if arg.nil? && recv.nil? arg && recv && arg.children[0] == recv.children[0] end |
#scope?(arguments) ⇒ Boolean
rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
202 203 204 |
# File 'lib/rubocop/cop/rails/inverse_of.rb', line 202 def scope?(arguments) !ignore_scopes? && arguments.any?(&:block_type?) end |
#with_options_arguments(recv, node) ⇒ Object
230 231 232 233 234 235 |
# File 'lib/rubocop/cop/rails/inverse_of.rb', line 230 def (recv, node) blocks = node.each_ancestor(:block).select do |block| block.send_node.command?(:with_options) && (block.first_argument, recv) end blocks.flat_map { |n| n.send_node.arguments } end |