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
Related
Rails 4.2
I've created a multi-step wizard for a User model i.e. a visitor to the app registers as a User over a 4 step form. Its working in it's current setup.
However, I've had to use require statements to include several of the wizard related ruby files. As a consequence these files are not auto loaded by Rails.
I'd like to refactor the relevant files so that they follow Rails conventions and are able to be auto loaded.
Current Structure - is working
app/wizards/user.rb User wizard model - runs validations on each step etc
module Wizard
module User
STEPS = %w[step1 step2 step3 step4].freeze
# omitted class implementations, not relevant
class Base
end
class Step1 < Base
end
class Step2 < Step1
end
class Step3 < Step2
end
class Step4 < Step3
end
end
end
app/controllers/user_wizards_controller.rb
# I have to require the file above, would like to avoid
require Rails.root.join('app', 'wizards', 'user.rb')
class UserWizardsController < ApplicationController
# I have to specify the template, would like to avoid
def step1
render 'wizards/users/step1'
end
# Notice how I have to refer to module/classes above.
def wizard_user_for_step(step)
raise InvalidStep unless step.in?(::Wizard::User::STEPS)
"Wizard::User::#{step.camelize}".constantize.new(session[:user_attributes])
end
end
app/views/wizards/users/step1.html.erb
app/views/wizards/users/step2.html.erb
Attempted Solution
Based on this statement by xfn
The directories in autoload_paths are considered to be root directories, they do not reflect namespacing. For example, the classes below app/models are not under a Models namespace, any namespace has to go within that directory.
The file app/services/doctor_finder.rb for example does not follow autoloading conventions because of that, since it defines Services::DoctorFinder rather than DoctorFinder. Therefore, that file cannot be autoloaded.
I'm going for...
Models
app/wizards/user/user.rb
app/wizards/user/base.rb
app/wizards/user/step1.rb
app/wizards/user/step2.rb
app/wizards/user/step3.rb
app/wizards/user/step4.rb
However, I'm not getting very far. Any ideas?
If you want to autoload these files move it into e.g services or steps directories like:
app/services/wizards/user/step1
and rails should autoload module:
module Wizards::User
class Step1
end
end
Depend on rails version you will need to add 'services' to autoload path.
Regards views:
render 'wizards/users/step1'
isn't bad and In my opinion could be consider as good practice.(using render method allow you to pass non global variables to view)
If you want to remove this line you should put views for UserWizardsController to user_wizards/step1.html.xxx
or if you want to have view in wizards/users/step1.html.xxx
you should scope your controller in that way:
module Wizards
class UsersController < ApplicationController
end
edn
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
In Rails, how do you use a specific method from a module. For eg,
# ./app/controllers/my_controller.rb
class MyController < ApplicationController
include MyModule
def action
MyModule.a_method
end
private
def a_method
...
end
end
# ------------------------------------------------ #
# ./app/helpers/my_module.rb
module MyModule
def a_method
...
end
end
MyController includes MyModule. And in action ,I want to use MyModule.a_method (Please note I also have a private a_method in MyController and I don't want to use this.)
Things I've tried :
1) Defining the method in the module as self.
def self.a_method
end
2) Using the :: notation in controller (MyModule::a_method)
The error that I keep getting is
Undefined method:a_method for MyModule:module
For now, I've resorted to using a different name for the modules method. But I'd like to know how to namespace the function with either the Module:: or Module. notation
[UPDATE - 11/24/2014]
adding file structure in code, since Rails heavily relies on convention.
So I am not really sure what you are trying to accomplish with your module but a quick solution to get it working is below.
Move my_module.rb out of helpers and into lib/my_module.rb. The helpers directory is for methods that you use in your views. The convention is to utilize helpers that are namespaced after their respective controller or the application_helper.rb for global methods for your views. Not sure if that's what you are trying to accomplish with your module but wanted to throw that out there.
Create an initializer (you can all it whatever) in config/initializers/custom_modules.rb and add require 'my_module'
Update the a_method back to be self.a_method
You can now call MyModule.a_method in your app
Don't forget to restart your server for changes to lib/my_module.rb to take effect.
Also, a lot of people reference this post by Yehuda Katz as guidance on where to store code for your app. Thought it might be a helpful reference.
if you include MyModule into MyController, all the "instance methods" of the first will be mixed-in into the 2nd.
So if you only want to call MyModule.a_method, no need to include your module.
Then you'd want to require (or better autoload) your module before using it. To do so place it in controllers/concerns/my_module.rb, rails (4 at least) should autoload it, otherwise require its file in an intializer
# my_module.rb
module MyModule
def self.a_method
...
end
end
should work, but doing
# my_module.rb
module MyModule
extend self
def a_method
...
end
end
is more clean to me. You'd like to have a look to rails active support concern to understand the "rails way" on this topic.
In my Rails 3.2 app I have a bunch of plain old ruby objects in the /app/models/ directory. I'd like to move some of these into a separate folder, say /app/models/data_presenter/. For one of the objects,
# /app/models/data_presenter.rb
class DataPresenter
# ...
end
I've tried the following
# /app/models/data_presenter/data_presenter.rb
class DataPresenter::DataPresenter
# ...
end
however, I got the TypeError (wrong argument type Module (expected Class)) error. Any suggestions to overcome this (with or without namespaces)? Do I also need to change the corresponding models' tests names and locations?
As #BroiSatse pointed out, the problem was that I had a bunch of subclasses that were inheriting from the base class DataPresenter. For those subclasses I forgot about the namespacing, i.e.
# /app/models/data_presenter/color_data_presenter.rb
class ColorDataPresenter < DataPresenter
# ...
end
should have been
# /app/models/data_presenter/color_data_presenter.rb
class DataPresenter::ColorDataPresenter < DataPresenter::DataPresenter
# ...
end
or similarly
module DataPresenter
class ColorDataPresenter < DataPresenter
# ...
end
end
For the tests, I couldn't find a magick solution so I just wrote
# /test/unit/answers_presenter/color_data_presenter_test.rb
require 'test_helper'
class ColorDataPresenterTest < ActiveSupport:TestCase
should 'do something cool' do
presenter = DataPresenter::ColorDataPresenter.new
assert presenter.do_something_cool
end
end
I have all my controllers declared as a
class Api::V1::SomeController < Api::V1::ApiController; (...); end
where
class Api::V1::ApiController < ApplicationController; end
All my controllers are placed in /app/controllers/api/v1/*_controller.rb, ApplicationController is under app/controllers/application_controller.rb.
On development everything works fine, but I have problem with requiring and loading controllers wrapped in api versioning namespace in production.
In production environment (locally or heroku) I got: LoadError (Unable to autoload constant Api::V1::SomeController, expected /app/app/controllers/api/v1/some_controller.rb to define it):
What is the correct way to configure app/config/environments/production.rb and require versioning api on production environment.
I'm pretty sure you'll get around this issue by taking a modular approach to defining all of your namespaced classes. For example:
module Api
module V1
class SomeController < ApiController
# ...
end
end
end
And:
module Api
module V1
class ApiController < ::ApplicationController
# ...
end
end
end
Using this pattern disambiguates the namespaces for autoloading in Rails. Autoloading is a fairly complex mechanism... (And does seem to behave differently between development and production!) If you'd like to learn more of the inner workings this article is well worth the read.
UPDATE
The :: in ::ApplicationController means "with no namespace" or the "default namespace". That part is probably not needed in this case since you probably only have one ApplicationController constant defined.
The difference this approach creates is that it ensures that Rails will not "skip over" your constant definition, so to speak. The article I linked above explains it by example.