Search code examples
ruby-on-railsrubyinheritanceseparation-of-concernsactivesupport-concern

Dynamic concerns with inheritance not loading twice, but only once


We are loading code dynamically with concerns, based on some environment variables, which works pretty nice.

Something like this:

# User class
class User
  include DynamicConcern
end
module DynamicConcern
  extend ActiveSupport::Concern

  included do
    if "Custom::#{ENV["CUSTOMER_NAME"].camelize}::#{self.name}Concern".safe_constantize
      include "Custom::#{ENV["CUSTOMER_NAME"].camelize}::#{self.name}Concern".constantize 
    end
  end
end
# custom code
module Custom::Custom123::UserConcern
  extend ActiveSupport::Concern
  
  included do
    ...
  end
end

We are using this since years and it worked absolutely fine in models. Some days ago we tried to use the same approach with Controllers, but realized that this approach doesn' t work fine with inheritance, where the parent class inherits the concern as well as the inherited class:

class ApplicationController < ActionController::Base
  # this gets loaded and includes the right dynamic module
  include DynamicConcern 
end

class ShopController < ApplicationController
  # this is NOT getting loaded again and skipped, 
  # since it has been loaded already in the parent controller
  include DynamicConcern 
end

Is there a way to tell rails that it should include/evaluade the concern a second time, since the second time it would have another class name which would include another module?

I'm not looking for other solutions, since a lot of our code is based on this approach and I think it's possible to solve this without rewriting everything.

Thanks!


Solution

  • Actually it's a feature of Rails that the same module doesn't get loaded multiple times.

    We started to use the normal ruby module inclution hooks and it worked fine!

    module CustomConcern
    
      def self.included(base)
        custom_class_lookup_paths = [
          "#{HOSTNAME.camelize}::Models::#{base.name}PrependConcern",
          "#{HOSTNAME.camelize}::Controllers::#{base.name}PrependConcern"
        ].map{|class_string| class_string.safe_constantize }.compact
    
        custom_class_lookup_paths.each do |class_string|
          base.send :include, class_string
        end
      end