Skip to content

Child controller's before action is overridden by parent #43332

@samzhao2008

Description

@samzhao2008

Steps to reproduce

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "rails", github: "rails/rails", branch: "main"
end

require "action_controller/railtie"
require 'rails/test_help'
class TestApp < Rails::Application
  config.root = __dir__
  config.hosts << "www.example.com"
  secrets.secret_key_base = "secret_key_base"

  config.logger = Logger.new($stdout)
  Rails.logger  = config.logger

  routes.draw do
    get "/child_index" => "child#index"
    get "/child_show" => "child#show"
    get "/base_index" => "base#index"
    get "/base_show" => "base#show"
  end
end

class BaseController < ActionController::Base
  def index
    render plain: "BaseController#index"
  end

  def show
    render plain: "BaseController#show"
  end
end

class ChildController < BaseController
  def index
    render plain: "ChildController#index"
  end

  def show
    render plain: "ChildController#show"
  end
end

module ChildControllerDecorator
  def self.prepended(base)
    base.before_action :set_instance_var, only: :index
  end

  def set_instance_var
    @instance_var = :index
  end
end
ChildController.prepend(ChildControllerDecorator)

module BaseControllerDecorator
  def self.prepended(base)
    base.before_action :set_instance_var, only: :show
  end

  def set_instance_var
    @instance_var = :show
  end
end
BaseController.prepend(BaseControllerDecorator)

require "minitest/autorun"

class ChildControllerTest < ActionDispatch::IntegrationTest
  
  test "instance var should equal to :index when child index action is invoked" do
    get "/child_index"
    assert_equal :index, @controller.instance_variable_get(:@instance_var)
  end

  test "instance var should equal to :nil when child show action is invoked" do
    get "/child_show"
    assert_equal nil, @controller.instance_variable_get(:@instance_var)
  end

  private
    def app
      Rails.application
    end
end

Expected behavior

ChildController's before action should not be overridden by parent. In the test case, @instance_var should be set to :index, and only be set when index action is executed.

Actual behavior

ChildController's before action is overridden by parent. wrong execution condition and value.

System configuration

Rails version: main branch

Ruby version: 2.7.2

In order to figure it out, I read the source code. It seems every time the before_action or prepend_before_action is executed, there is a operation called remove_duplicates which remove the filters with the same name from itself and its descendants. It should not remove filters with the same name from its descendants, right?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions