isolate_namespace is that one feature which Rails boasts about to use while creating gems, but doesn’t work when one wants to extend models, controllers and views which are provided by that gem. We have used it while developing one of our gems and found that its really hard to extend models, controllers and views. For instance, if you copy views from gem to main application in order to customize them, and try to use routes defined in the main application, you will be slapped with an error saying those routes are not defined! It takes some time to understand how it works under the hood, and all it boils down to is: isolate_namespace is nothing but a bunch of hacks. This blog post will do a code walk-through and will try to explain how it works:

The isolate_namespace method

This method can be found in railties-engine file. Its reads as:

def isolate_namespace ( mod ) engine_name ( generate_railtie_name ( mod )) self . routes . default_scope = { module : ActiveSupport :: Inflector . underscore ( mod . name ) } self . isolated = true unless mod . respond_to? ( :railtie_namespace ) name , railtie = engine_name , self mod . singleton_class . instance_eval do define_method ( :railtie_namespace ) { railtie } unless mod . respond_to? ( :table_name_prefix ) define_method ( :table_name_prefix ) { " #{ name } _" } end # removed code for :use_relative_model_naming? # removed code for :railtie_helpers_paths unless mod . respond_to? ( :railtie_routes_url_helpers ) define_method ( :railtie_routes_url_helpers ) { railtie . routes . url_helpers } end end end end

mod is module, which is has to be isolated. In case of blorgh gem, its Blorgh itself. Hence forth, we will use blorgh gem as an example given in rails guides

routes.default_scope : It defines default scope for routes. This scope will be used while generating routes for Rails engine. It says module to be used is blorgh , and all the controllers will be searched under gem. This can be easily understood. Just put a binding.pry inside routes.rb of gem, and you can see this: [ 4 ] pry ( #<Mapper>)> self.instance_variable_get(:@scope) => { :path_names => { :new => "new" , :edit => "edit" }, :module => "blorgh" , :constraints => {}, :defaults => {}, :options => {}} It says default module that should be used is blorgh . All the controllers will be prepended by blorgh/ If module doesn’t respond to railtie_namespace {generally modules dont!}, it goes ahead and adds bunch of methods to module {i.e Blorgh for example}, and not to engine. Thats why its a hack! There is nothing done on engine! Everything is added to Blorgh module. So, what it adds exactly? table_name_prefix : This can be easily guessed. It will be used by active record. Now searching through activerecord source, we find this: def full_table_name_prefix #:nodoc: ( parents . detect { | p | p . respond_to? ( :table_name_prefix ) } || self ) . table_name_prefix end It looks tricky, but this is how it works. It searches through all the parents of the AR class, and checks whether any of them responds to table_name_prefix and returns it. {Default value is empty string}. Well, parents are not the parents of the class, but the hierarchy of the modules. activesupport defines this method: # Returns all the parents of this module according to its name, # ordered from nested outwards. The receiver is not contained # within the result. # # module M # module N # end # end # X = M::N # # M.parents # => [Object] # M::N.parents # => [M, Object] # X.parents # => [M, Object] def parents parents = [] if parent_name parts = parent_name . split ( '::' ) until parts . empty? parents << ActiveSupport :: Inflector . constantize ( parts * '::' ) parts . pop end end parents << Object unless parents . include? Object parents end Now, here comes the funny part: Create a folder called blorgh in your main application under app/models folder, and create a model called Test module Blorgh class Test < ActiveRecord :: Base end end Now, fire up console, and execute the following: [ 1 ] pry ( main ) > Blorgh :: Test . table_name => "blorgh_tests" See, it automatically prepends tests table name with blorgh_ . One will be wondering how did that happen? Well it happened because of the module called Blorgh . So anything one puts under Blorgh module will get special treatment. Do it with any other namespace, {i.e module}, it will just return tests . The only way you can get rid of this behavior is to specify the table name explicity on model using self.table_name = "tests" . If someone in some gem magically says to isolate a namespace which you are using in your application, hell breaks loose! You will be wondering why your application code is behaving strangely. You can find other hacks by searching through the Rails code. We will cover another hack here: railtie_routes_url_helpers : This method is used to define route helpers which can be accessible to generate paths. Digging through the code, you can find it in actionpack. module AbstractController module Railties module RoutesHelpers def self . with ( routes ) Module . new do define_method ( :inherited ) do | klass | super ( klass ) namespace = klass . parents . detect { | m | m . respond_to? ( :railtie_routes_url_helpers ) } if namespace klass . send ( :include , namespace . railtie_routes_url_helpers ) else klass . send ( :include , routes . url_helpers ) end end end end end end end This inherited method will be called in the context of your controllers. Again if your controller is under Blorgh module, it magically includes only the routes defined by gem, otherwise it includes the application route helpers. Thats why even though you copy views from gem to your main app, they still cannot access helpers defined by main application. Generally we all know how to fix this: Call the url helpers by prepending with either main_app or gem mount point, i.e blorgh here. This way all the route helpers will be available in all the views. = link_to "Surveys" , blorgh . question_groups_path = link_to "Login" , main_app . new_user_session_path Other issues include extending models and controllers. Rails guides gives two options here. One to use class_eval , and other to use concerns introduced in Rails 4. Both are kind of hacky. Hope there is a better solution.

If you have any questions or feedback, feel free to drop us a mail at team@codemancers.com