Implicit routing for nested controllers with the same namespace
Given Rails 4.1, two controllers CRM::CompaniesController
and CRM::ContactPeople
, two models CRM::Company
and, CRM::ContactPerson
and the following routes:
namespace :crm do
resources :companies do
resources :contact_people
end
end
The generated helper urls contain the CRM namespace only once, which is exactly what I want:
crm_company_contact_people GET /crm/companies/:company_id/contact_people(.:format)
new_crm_company_contact_person GET /crm/companies/:company_id/contact_people/new(.:format)
# ...
However, using the Array approach for URL helpers
= form_for([@crm_company, @crm_contact_person]) do |f|
trying to create a url with each namespace:
undefined method `crm_company_crm_contact_people_path' for #<#<Class...
I would like to have the "crm" in my paths only once at the beginning (unless it breaks the general Rails approach), and it would be ugly to add the url to each form explicitly. Is there something I can do (perhaps in routes, model, or the first argument of form_for) so that Rails knows how to build the correct path? Or is there a more Rails way of creating such a structure so that Rails automatically knows how to create paths?
You need to include the namespace in your call to form_for
:
= form_for([:crm, @crm_company, @crm_contact_person]) do |f|
Also, since you are using named models, you need to define the following class method for them ( from rails / rails issue # 10705 ) to ignore the namespace:
def self.use_relative_model_naming?
true
end
I tried several approaches to find the least annoying approach for me. I start by changing the CRM namespace to use the relative model name:
module CRM
def self.use_relative_model_naming?
true
end
end
However, this removes the namespace from every entity, including the company. So the following doesn't work:
= url_for @crm_company
# undefined method `company_path'
To avoid this, I tried to ignore only certain models by changing the model name (note that this has more side effects than URL generation):
class CRM::ContactPerson < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new(self, CRM)
end
end
Now the following urls work fine:
= url_for @crm_company = url_for [@crm_company, @crm_contact_person]
But if CRM :: ContactPerson is shallowly nested and appears as a child of CRM :: Company AND as a child of CRM itself, the problem at the top appears:
= url_for @crm_contact_person
# undefined method `contact_person_path'
In this case, we could split the nested and unnamed part into CRM :: Companies :: ContactPerson and CRM :: ContactPerson and use the relative model name for the CRM :: Companies module. However, this appears to be a lot of overhead and may even be worse than using explicit URL helpers.
So at the moment I can't seem to find a comprehensive solution, but at least for nested resources works fine.