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 ]
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
Related
I'd like to make a function current_order_week that would be available globally throughout my app and could be called similarly to something like current_user. I don't want to have to include it in a specific model / controller, I just want it available everywhere.
I've modified my /lib folder to include a lib_extensions.rb file and added to that file:
class Object
def current_order_week
end
end
I've modified my application.rb to include:
config.autoload_paths << Rails.root.join('lib')
config.eager_load_paths << Rails.root.join('lib')
But when I attempt to call current_order_week from the console or from a test, I still see:
NameError: undefined local variable or method 'current_order_week'
for main:Object
What else do I need to do?
You should add this function in the application_helper.rb file. All controllers extend from ApplicationController and ApplicationController includes the ApplicationHelper.
module ApplicationHelper
def current_order_week
end
end
This will be available to use in views and controllers
Monkey-patching core classes like Object is usually not a good idea, this may interfere with some gems etc. and in general can lead to painful debugging in the future.
If you absolutely want to do this - autoloading will not pick up Object from lib, because it is already defined. Create an initializer in config/initializers, then it will be loaded, but it will not reload on code changes.
But better way is including such code in ApplicationHelper, ApplicationRecord and ApplicationController
autoload_paths and eager_load_paths don't include modules, they only require files in which modules are defined. To use current_order_week you need to specify the name of the module:
module Foo
def current_order_week
.
.
.
end
end
Foo.current_order_week()
In order to use current_order_week without prepending the name of the module to it, you need to include Foo inside your controllers and models:
class ApplicationController < ActionController::Base
include Foo
def some_action
current_order_week()
end
end
class ApplicationRecord < ActiveRecord::Base
include Foo
end
I'm new in Ruby and RoR and I'd like to create an admin section for a demo app.
From a little research I've done I've found two different options for creating an admin section. Example:
# config/routes.rb
namespace :admin do
resources :users
end
# app/controllers/admin_controller.rb
class AdminController < ApplicationController
before_filter :authorized?
...
end
Which of the two options is the most proper way to define controllers for the admin section, or they are both equally same?
# app/controllers/admin/users_controller.rb
# I think this is what rails generates if I run the "rails g controller admin::users" command
class Admin::UsersController < AdminController
...
end
# or instead
module Admin
class UsersController < AdminController
....
end
end
Both approaches yield to the same result, which is an UsersController which inherits from AdminController and is found in the Admin module (namespace).
Admin::MyClass is just a shortcut for module Admin ... class MyClass, but...
I would however prefer the explicit nested code (with module Admin on its own line), because it does make a different if the Admin-module has never been defined before. This probably won't happen to you when hacking with standard rails, but can happen when you write ruby code outside of rails.
See these examples:
class I::Am::AClass
end
i = I::Am::AClass.new
puts i.inspect
will lead to
i.rb:1:in `<main>': uninitialized constant I (NameError)
if you never declared the I and nested Am modules before in your code.
Whereas
module I
module Am
class AClass
end
end
end
i = I::Am::AClass.new
puts i.inspect
will work:
#<I::Am::AClass:0x00000001d79898>
because the modules are created along the path to AClass (at least this is how I think about it).
If you ever run in that problem and want to save whitespaces (because you will usually indent stuff in a module definition), there are some idioms to use. The one that solves the problem in the most obvious way (again, to me) is the following:
# Predefine modules
module I ; module Am ; end ; end
# Just a shortcut for:
# module I
# module Am
# end
# end
class I::Am::AClass
end
i = I::Am::AClass.new
puts i.inspect
#<I::Am::AClass:0x000000024194a0>
Just found that the nature of your question (it is not about an Admin-Interface, more about Module-Syntax) is also nicely discussed here Ruby (and Rails) nested module syntax . And I would love to see a ruby-bug report/feature-request on this :)
You can also use the administration framework for Ruby on Rails applications like
ActiveAdmin https://github.com/activeadmin/activeadmin
OR
Railsadmin
https://github.com/sferik/rails_admin
I'm trying to use nested module/class definitions consistently in a Rails app, rather than the compact (::) syntax. However, it doesn't always load the module file itself, which contains the table_name_prefix.
Using Rails 4.1.8 on Ruby 2.1.1...
rails new my_app
...
rails g scaffold User
rails g scaffold Blog::Post
This creates app/models/blog.rb:
module Blog
def self.table_name_prefix
'blog_'
end
end
There seem to be many ways of accidentally preventing Rails from auto-loading blog.rb. The simplest example is via the helpers.
Change app/helpers/blog/posts_helper.rb from:
module Blog::PostsHelper
end
to:
module Blog
module PostsHelper
end
end
Launch the server, visit /users and then visit /blog/posts:
SQLite3::SQLException: no such table: posts: SELECT "posts".* FROM "posts"
Similar problems can occur elsewhere, such as in the model tests. It's not limited to the helpers.
What's the best way of resolving this? Explicitly loading blog.rb and any other namespace modules?
One solution, which doesn't rely on autoloading, is to 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.
These can be set as defaults for future models through appropriate files in lib/templates/active_record/model/model.rb and module.rb, based on the default active record templates.
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.
I have created a simple railtie, adding a bunch of stuff to ActiveRecord:
0 module Searchable
1 class Railtie < Rails::Railtie
2 initializer 'searchable.model_additions' do
3 ActiveSupport.on_load :active_record do
4 extend ModelAdditions
5 end
6 end
7 end
8 end
I require this file (in /lib) by adding the following line to config/environment.rb before the application is called:
require 'searchable'
This works great with my application and there are no major problems.
I have however encountered a problem with rake db:seed.
In my seeds.rb file, I read data in from a csv and populate the database. The problem I am having is that the additions I made to ActiveRecord don't get loaded, and seeds fails with a method_missing error. I am not calling these methods, but I assume that since seeds.rb loads the models, it tries to call some of the methods and that's why it fails.
Can anyone tell me a better place to put the require so that it will be included every time ActiveRecord is loaded (not just when the full application is loaded)? I would prefer to keep the code outside of my models, as it is code shared between most of my models and I want to keep them clean and DRY.
Putting the extend there just adds it to ActiveRecord::Base.
When a model class is referenced, via Rails 3.1 autoloading/constant lookup, it will load the class. At that point, it is pure Ruby (nothing magic) as to what happens, basically. So I think you have at least a few options. The "bad" option that kind of does what you want it to hook into dependency loading. Maybe something like:
module ActiveSupport
module Dependencies
alias_method(:load_missing_constant_renamed_my_app_name_here, :load_missing_constant)
undef_method(:load_missing_constant)
def load_missing_constant(from_mod, const_name)
# your include here if const_name = 'ModelName'
# perhaps you could list the app/models directory, put that in an Array, and do some_array.include?(const_name)
load_missing_constant_renamed_my_app_name_here(from_mod, const_name)
end
end
end
Another way to do it would be to use a Railtie like you were doing and add a class method to ActiveRecord::Base that then includes stuff, like:
module MyModule
class Railtie < Rails::Railtie
initializer "my_name.active_record" do
ActiveSupport.on_load(:active_record) do
# ActiveRecord::Base gets new behavior
include ::MyModule::Something # where you add behavior. consider using an ActiveSupport::Concern
end
end
end
end
If using an ActiveSupport::Concern:
module MyModule
module Something
extend ActiveSupport::Concern
included do
# this area is basically for anything other than class and instance methods
# add class_attribute's, etc.
end
module ClassMethods
# class method definitions go here
def include_some_goodness_in_the_model
# include or extend a module
end
end
# instance method definitions go here
end
end
Then in each model:
class MyModel < ActiveRecord::Base
include_some_goodness_in_the_model
#...
end
However, that isn't much better than just doing an include in each model, which is what I'd recommend.
I wrote an upsert method for one of my models. I would like all my models to have this upsert method. It seemed to me that the logical solution was to define a model that inherits from ActiveRecord::Base and then have all my other models inherit from that. But if I do that, Rails complains that the new model I created doesn't have a table to go with it, which is true, but I don't care.
Since the way I tried is apparently not the right way to do it, what's the right way to do it?
You can extend ActiveRecord with a module. you only do it in one place and it will be accessible for all models that inherits from ActiveRecord.
module YourModule
def self.included(recipient)
recipient.extend(ModelClassMethods)
recipient.class_eval do
include ModelInstanceMethods
end
end # #included directives
# Class Methods
module ModelClassMethods
# A method accessible on model classes
def whatever
end
end
# Instance Methods
module ModelInstanceMethods
#A method accessible on model instances
def another_one
end
end
end
#This is where your module is being included into ActiveRecord
if Object.const_defined?("ActiveRecord")
ActiveRecord::Base.send(:include, YourModule)
end
There are two ways to do this.
1) To have a parent model, but not need to create a table for it (i.e. an abstract class) you should set
class YourAbstractClass < ActiveRecord::Base
self.abstract_class = true
# rest of class code
end
2) Put the method in a module, that you include from all your models that need it (as in #Mark's answer)
You can move that method to a module and include that module in all the models that require that method.
Like I have this Utils module in lib folder of my app
module Utils
...
def to_name(ref)
ref.gsub('_', ' ').split.collect { |w| w.capitalize }.join(' ')
end
...
end
Then in my model, I say
class MyModel < AR::Base
include Utils
...
end
Probably, if you are using Rails 3, you should load the files in the lib folder by configuring your application.rb
config.autoload_paths += %W(#{config.root}/lib)