Nitin Satish Salunke
Nitin Satish Salunke

Reputation: 518

Cannot load Ruby Model under a namespace

I have a namespaced Post controller as below

class Admin::Blog::PostsController < Admin::BaseController
end

and a namespaced model as follows.

class Blog::Post < ActiveRecord::Base
end

But when I try to access the model inside the index action of the post controller as below

def index
    @posts = Blog::Post.where(:foo_id => params[:id]).paginate(:page => params[:page], :per_page => 20)
  end

I get the following error

LoadError at /admin/blog/posts

Expected/app/models/blog/post.rb to define Post

But when I move the model to Admin::Blog::Post namespace from Blog::Post is works.

I'm bit confused with this and not able to get what is going on with this. Is it required that Controller and Model should be present in the same namespace ?

Following is the snippet from routes.rb

namespace :admin do
    namespace :blog do
        resources :posts
        resources :categories
    end
end

Blog module snippet

module Blog
  def self.table_name_prefix
    'blog_'
  end
end

Preloading controllers and models

config.autoload_paths += Dir["#{Rails.root}/app/models/**/**"]
    config.autoload_paths += Dir["#{Rails.root}/app/controllers/**/**"]
    config.autoload_paths += Dir["#{config.root}/app/helpers/**/**"]
    config.autoload_paths += Dir["#{config.root}/app/tags/**/**"]
    config.autoload_paths += %W[ #{Rails.root}/app/extensions #{Rails.root}/app/modules #{Rails.root}/app/drops #{Rails.root}/app/filters  #{Rails.root}/app/mailers ]

Upvotes: 0

Views: 1120

Answers (1)

Vamsi Krishna
Vamsi Krishna

Reputation: 3792

This is probably caused by rails' autoloader. When doing this :

module Foo
  class Bar
  end
end

And then trying to use Foo::Bar, the autoloader first tries to locate app/models/foo/bar.rb. The file is loaded, and module Foo is defined here (albeit as a module containing solely Bar) so the autoloader never attempts to load app/models/foo.rb.

This should only happen in development mode, as in production mode all of your files are require'd on startup.

There are two workarounds AFAIK :

Require the module

using require_dependency :

require_dependency 'foo'
module Foo
  class Bar
  end
end

This is IMHO the right solution, as it does not break the constant lookup, but it is also a bit annoying as you have to add the require statement on top of each namespaced file.

Create Custom Active record Base

This solution doesn't rely on autoloading. Set the models to inherit from the following, instead of from ActiveRecord::Base directly:

class CustomActiveRecordBase < ActiveRecord::Base
  self.abstract_class = true

  # If no table name prefix has been defined, include the namespace/module as
  # table name prefix, e.g., Blog:: -> blog_
  def self.table_name
    # If a table_name_prefix has been defined, follow default behaviour
    return super if full_table_name_prefix.present?

    # Find the prefix, e.g., Blog::Post -> 'blog', User -> ''
    prefix = model_name.name.deconstantize.underscore

    # If no prefix, follow default behaviour
    return super unless prefix.present?

    # Otherwise add the prefix with an underscore
    "#{prefix}_#{super}"
  end
end

Then there is no need to define self.table_name_prefix in blog.rb.

This could all be done by monkey-patching ActiveRecord::Base, but this interferes with other classes, such as ActiveRecord::SchemaMigration, which doesn't have a table prefix.

Note :

This bug seems to have been resolved in rails 4. I used the second workaround a lot while on rails 3, but I've tried to reproduce the bug in rails 4 and it does not show up anymore. I think they modified the way the autoloader works... For more info, see the rails guides on autoloading and reloading constants

Upvotes: 2

Related Questions