class RuboCop::Cop::Rails::LexicallyScopedActionFilter

This cop checks that methods specified in the filter's `only` or `except` options are defined within the same class or module.

You can technically specify methods of superclass or methods added by mixins on the filter, but these can confuse developers. If you specify methods that are defined in other classes or modules, you should define the filter in that class or module.

If you rely on behaviour defined in the superclass actions, you must remember to invoke `super` in the subclass actions.

@example

# bad
class LoginController < ApplicationController
  before_action :require_login, only: %i[index settings logout]

  def index
  end
end

# good
class LoginController < ApplicationController
  before_action :require_login, only: %i[index settings logout]

  def index
  end

  def settings
  end

  def logout
  end
end

@example

# bad
module FooMixin
  extend ActiveSupport::Concern

  included do
    before_action proc { authenticate }, only: :foo
  end
end

# good
module FooMixin
  extend ActiveSupport::Concern

  included do
    before_action proc { authenticate }, only: :foo
  end

  def foo
    # something
  end
end

@example

class ContentController < ApplicationController
  def update
    @content.update(content_attributes)
  end
end

class ArticlesController < ContentController
  before_action :load_article, only: [:update]

  # the cop requires this method, but it relies on behaviour defined
  # in the superclass, so needs to invoke `super`
  def update
    super
  end

  private

  def load_article
    @content = Article.find(params[:article_id])
  end
end

Constants

FILTERS
MSG

Public Instance Methods

on_send(node) click to toggle source
# File lib/rubocop/cop/rails/lexically_scoped_action_filter.rb, line 115
def on_send(node)
  methods_node = only_or_except_filter_methods(node)
  return unless methods_node

  parent = node.each_ancestor(:class, :module).first
  return unless parent

  block = parent.each_child_node(:begin).first
  return unless block

  defined_methods = block.each_child_node(:def).map(&:method_name)
  methods = array_values(methods_node).reject do |method|
    defined_methods.include?(method)
  end

  message = message(methods, parent)
  add_offense(node, message: message) unless methods.empty?
end

Private Instance Methods

array_values(node) click to toggle source

@param node [RuboCop::AST::Node] @return [Array<Symbol>]

# File lib/rubocop/cop/rails/lexically_scoped_action_filter.rb, line 138
def array_values(node) # rubocop:disable Metrics/MethodLength
  case node.type
  when :str
    [node.str_content.to_sym]
  when :sym
    [node.value]
  when :array
    node.values.map do |v|
      case v.type
      when :str
        v.str_content.to_sym
      when :sym
        v.value
      end
    end.compact
  else
    []
  end
end
message(methods, parent) click to toggle source

@param methods [Array<String>] @param parent [RuboCop::AST::Node] @return [String]

# File lib/rubocop/cop/rails/lexically_scoped_action_filter.rb, line 161
def message(methods, parent)
  if methods.size == 1
    format(MSG,
           action: "`#{methods[0]}` is",
           type: parent.type)
  else
    format(MSG,
           action: "`#{methods.join('`, `')}` are",
           type: parent.type)
  end
end