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.
@shopis initialized in abefore_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
@shopinto 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)?