conditionally include concern methods for an ApplicationController class

Summary

A Rails controller attempted to conditionally include concerns based on @shop.nation_id, but the logic failed because the instance variable is not available at class‑load time. This produced NoMethodError and made dynamic behavior impossible. The correct solution is to delegate behavior at runtime, not by dynamically including modules.

Root Cause

  • Concerns are included at class definition time, long before any request instance exists.
  • @shop is initialized in a before_action, which runs only during a request.
  • Conditional module inclusion requires class‑level context, but the condition depends on request‑level data.
  • This mismatch leads to nil instance variables and undefined method errors.

Why This Happens in Real Systems

  • Rails controllers are singletons at the class level, but actions run on per-request instances.
  • Developers often try to mix runtime data with compile-time behavior, which Rails cannot support.
  • Concerns are designed for static composition, not dynamic switching.

Real-World Impact

  • Hard-to-debug errors such as undefined method 'nation' for nil.
  • Bloated controllers when developers fall back to large conditional blocks.
  • Unmaintainable code when concerns are force‑included everywhere.
  • Inconsistent behavior across environments if autoloading masks the issue.

Example or Code (if necessary and relevant)

A clean pattern is runtime delegation, not dynamic inclusion:

class ApplicationController < ActionController::Base
  before_action :set_shop
  before_action :set_nation_strategy

  private

  def set_shop
    @shop = Shop.find(params[:shop_id])
  end

  def set_nation_strategy
    @nation_strategy =
      case @shop.nation_id
      when 1 then UkMethods.new(@shop)
      when 2 then UsMethods.new(@shop)
      else DefaultMethods.new(@shop)
      end
  end
end

Then in any controller action:

def tax_codes
  render json: @nation_strategy.tax_codes
end

Each strategy class (formerly a concern) simply implements the required methods.

How Senior Engineers Fix It

  • Use the Strategy Pattern: encapsulate nation‑specific behavior in plain Ruby objects.
  • Avoid dynamic module inclusion: treat concerns as static composition only.
  • Inject dependencies at runtime: pass @shop into strategy objects.
  • Keep controllers thin: delegate logic to service objects or strategies.
  • Ensure testability: each strategy can be unit‑tested independently.

Why Juniors Miss It

  • They assume concerns behave like mixins at runtime, not compile time.
  • They try to reuse concerns for dynamic behavior, which they were not designed for.
  • They misunderstand the lifecycle of Rails controllers, especially when instance variables become available.
  • They focus on reducing duplication but overlook architectural boundaries.

Would you like a version of this pattern tailored to your exact concern modules (e.g., UkMethods, UsMethods)?

Leave a Comment