In Rails, is it possible to namespace models in modules and still get correct behavior from url_for?
For instance, here, url_for works as expected:
# app/models/user.rb
class User < ActiveRecord::Base
end
# config/routes.rb
resources :users
# app/views/users/index.html.haml
= url_for(#user) # /users/1
Whereas after putting the User model into a module, url_for complains about an undefined method m_user_path:
# app/models/m/user.rb
module M
class User < ActiveRecord::Base
end
end
# config/routes.rb
resources :users
# app/views/users/index.html.haml
= url_for(#user) # undefined method 'm_users_path'
Is it possible to have url_for ignore the module in M::User and return user_path for url_for(#user) instead of m_user_path?
UPDATE
So, after almost 5 years, here's the solution, thanks to esad. This has been tested in Rails 4.2.
# app/models/m/user.rb
module M
class User < ActiveRecord::Base
end
end
# app/models/m.rb
module M
def self.use_relative_model_naming?
true
end
def self.table_name_prefix
'm_'
end
end
# config/routes.rb
resources :users
# app/views/users/index.html.haml
= url_for(#user) # /users/1
Note: when generating model, view and controller with bin/rails g scaffold m/user, the views and the controller will be namespaced, too. You need to move app/views/m/users to app/views/users and app/controllers/m/users_controller.rb to app/controllers/users_controller.rb; you also need to remove references to the module M everywhere except in the model M::User.
Finally, the goal here was to namespace models but not views and controllers. With esads solution, the module M (containing User) is explicitly told to not appear in routes. Thus, effectifely, the M is stripped of and only User remains.
The user model can now reside in app/views/models/m/user.rb, the users controller lives in app/views/controllers/users_controller.rb and the views can be found in app/views/users.
Just define use_relative_model_naming? in the containing module to avoid prefixing the generated route names:
module M
def self.use_relative_model_naming?
true
end
end
Use
namespace "blah" do
resources :thing
end
Then routes will be named appropiately.
rake routes
To view all routes
Specify the module on the route
resources :users, :module => "m"
or use scope to do it
scope :module => "m" do
resources :users
end
In my case I overridden the url_for method on my application_helper.rb file to add the :network param on all routes from my namespace :mkp.
module ApplicationHelper
def url_for(options = {})
if options.is_a?(Hash) && options.has_key?(:controller)
if options[:network].nil? && options[:controller].match(/^mkp\//).present?
options[:network] = #network.downcase
end
end
super(options)
end
end
Related
I'm trying to create the below route but I am running into a "routing error" of "uninitialized constant Foo::Bar::Biz"
/foo/bar/biz/<biz_id>/custom
My routes file is as follows:
namespace(:foo) do
namespace(:bar) do
resources(:biz, only: []) do
get('/custom' => 'foo/bar/biz/custom#index') # I have also tried just custom#index
end
end
end
When I run rake routes I see the route and the controller.
/foo/bar/biz/:biz_id/custom(.:format) foo/bar/biz/custom#index
My controllers file structure is this:
controllers/foo/bar/biz/custom_controller.rb
I don't currently have a controller for biz, but I have tested with one present.
My custom_controller is as follows:
module Foo
module Bar
module Biz
class CustomController < FooController
def index
// do something
end
end
end
end
end
I suspect my routes are setup correctly and my error is in my controller or module setup. Is there something I am missing?
Change your path
'foo/bar/biz/custom#index'
to path starting with root (/)
namespace(:foo) do
namespace(:bar) do
resources(:biz, only: []) do
get('/custom' => '/foo/bar/biz/custom#index')
end
end
end
I have an email_template model that has a nested resource moves to handle moving an email_template from one folder to another.
However, I want to namespace these actions in a :templates namespace because I have several other resources that are template items as well.
Since I'm namespacing, I don't want to see templates/email_templates/:id in the URL, I'd prefer to see templates/emails/:id.
In order to accomplish that I have the following:
# routes.rb
namespace :templates do
resources :emails do
scope module: :emails do
resources :moves, only: [:new, :create]
end
end
end
Everything works fine when I do CRUD actions on the emails, since they are just using the :id parameter. However, when I use the nested moves, the parent ID for the emails keeps coming across as :email_id and not :email_template_id. I'm sure this is the expected behavior from Rails, but I'm trying to figure out how the parent ID is determined. Does it come from the singular of the resource name in the routes, or is it being built from the model somehow?
I guess it's ok to use templates/emails/:email_id/moves/new, but in a perfect world I'd prefer templates/emails/:email_template_id/moves/new just so developers are clear that it's an email_template resource, not a email.
# app/controllers/templates/emails_controller.rb
module Templates
class EmailsController < ApplicationController
def show
#email_template = EmailTemplate.find(params[:id])
end
end
end
# app/controllers/templates/emails/moves_controller.rb
module Templates
module Emails
class MovesController < ApplicationController
def new
# Would prefer to reference via :email_template_id parameter
#email_template = EmailTemplate.find(params[:email_id])
end
def create
#email_template = EmailTemplate.find(params[:email_id])
# Not using strong_params here to demo code
if #email_template.update_attribute(:email_tempate_folder_id, params[:email_template][:email_template_folder_id])
redirect_to some_path
else
# errors...
end
end
end
end
end
You could customize the parameter as:
resources :emails, param: :email_template_id do
...
end
I want to create a method that, when called from a controller, will add a nested resource route with a given name that routes to a specific controller. For instance, this...
class Api::V1::FooController < ApplicationController
has_users_route
end
...should be equivalent to...
namespace :api do
namespace :v1 do
resources :foo do
resources :users, controller: 'api_security'
end
end
end
...which would allow them to browse to /api/v1/foo/:foo_id/users and would send requests to the ApiSecurityController. Or would it go to Api::V1::ApiSecurityController? It frankly doesn't matter since they're all in the same namespace. I want to do it this way because I want to avoid having dozens of lines of this:
resources :foo do
resources :users, controller: 'api_security'
end
resources :bar do
resources :users, controller: 'api_security'
end
Using a method is easier to setup and maintain.
I'm fine as far as knowing what to do once the request gets to the controller, but it's the automatic creation of routes that I'm a little unsure of. What's the best way of handling this? The closest I've been able to find is a lot of discussion about engines but that doesn't feel appropriate because this isn't separate functionality that I want to add to my app, it's just dynamic routes that add on to existing resources.
Advice is appreciated!
I ended up building on the blog post suggested by #juanpastas, http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4, and tailoring it to my needs. Calling a method from the controllers ended up being a bad way to handle it. I wrote about the whole thing in my blog at http://blog.subvertallmedia.com/2014/10/08/dynamically-adding-nested-resource-routes-in-rails/ but the TL;DR:
# First draft, "just-make-it-work" code
# app/controllers/concerns/user_authorization.rb
module UserAuthorization
extend ActiveSupport::Concern
module ClassMethods
def register_new_resource(controller_name)
AppName::Application.routes.draw do
puts "Adding #{controller_name}"
namespace :api do
namespace :v1 do
resources controller_name.to_sym do
resources :users, controller: 'user_security', param: :given_id
end
end
end
end
end
end
end
# application_controller.rb
include UserAuthorization
# in routes.rb
['resource1', 'resource2', 'resource3'].each { |resource| ApplicationController.register_new_resource(resource) }
# app/controllers/api/v1/user_security_controller.rb
class Api::V1::UserSecurityController < ApplicationController
before_action :authenticate_user!
before_action :target_id
def index
end
def show
end
private
attr_reader :root_resource
def target_id
# to get around `params[:mystery_resource_id_name]`
#target_id ||= get_target_id
end
def get_target_id
#root_resource = request.fullpath.split('/')[3].singularize
params["#{root_resource}_id".to_sym]
end
def target_model
#target_model ||= root_resource.capitalize.constantize
end
def given_id
params[:given_id]
end
end
SingularI have this problem. I defined in route file my route:
namespace :admin do
root to: "home#index"
resources :define_user
end
I created users controller:
class DefineUsersController < ApplicationController
def create
...
end
def destroy
...
end
end
I created in views new folder 'define_users' with file 'show.html.haml'. I call it using link_to:
=link_to 'User', admin_define_user_path
And I get above error. I would like to stay with singular name. Thank for all answers.
You are trying to access show route without id of DefineUser object
= link_to 'User', admin_define_user_path(define_user)
Where define_user is an object of DefineUser class or id of this object
First of all, if you want to link_to some define_user, you have to provide object or id. Something like this: admin_define_user_path(#define_user).
To display all possible routes type rake routes in console.
Also resources should be in plural form.
Moreover you have to provide namespace in controller.
P.S.
As I see, it is a very bad idea to work with model called DefineUser. It's not a rails way. Just use User. DefineUser is a good name for method, but not model.
So, the best idea to handle your code:
routes.rb
namespace :admin do
root to: "home#index"
resources :users
end
users_controller.rb
class Admin::UsersController < ApplicationController
def index
...
end
end
view
= link_to 'Users', admin_users_path # for index
= link_to 'User', admin_user_path(#user) # for one user
Good idea to separate controllers by namespace. For example, you can have next structure:
application_controller.rb
admin #folder
L base_controller.rb
L users_controller.rb
L ..._controller.rb
So your base_controller should be inherited from application_controller
class Admin::BaseController < ApplicationController
layout 'admin_layout' # Different layout for all admin pages
Other controller in admin namespace will be inherited from base_controller
class Admin::UsersController < Admin::BaseController
def create
...
end
I'm trying to create a dynamic interface. Where my model classes exist and my controllers are dynamically created when launching the application.
Everything happens in my routes file where the resources are created!
ActionController::Routing::Routes.draw do |map|
map.namespace :admin do |admin|
TestAdmin.models.each do |m|
admin.resources m.to_s.tableize.to_sym
end
end
end
And then there is my BeAdmin class, this does the following:
module TestAdmin
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def beadmin(options = {})
namespace_name = "Admin"
class_name = "#{self.to_s.pluralize.capitalize}Controller"
klass = namespace_name.constantize.const_set(class_name, Class.new(ApplicationController))
klass.module_eval do
def index
render :text => "test"
end
end
end
end
def self.models
all_models = []
Dir.chdir(File.join(Rails.root, "app/models")) do
Dir["**/*.rb"].each do |m|
class_name = m.sub(/\.rb$/,"").camelize
klass = class_name.split("::").inject(Object){ |klass,part| klass.const_get(part) }
all_models << "#{class_name}" if klass < ActiveRecord::Base && !klass.abstract_class?
end
end
all_models
end
end
And when you now browse to the /admin/users (from the User model) then you get to see "test". so it works great!
But then I just do a simple refresh of the browser and The controller that is called becomes UsersController#index instead of Admin::UsersController#index... He loses it's namespace for some reason...
Maybe another important aspect here is that I added all this as a plugin and user Rails Engines so I can make a plug-able interface...
But so far no luck because my routes seem to get lost somewhere!
Thanks in advance for your help!
Jelle