Edge Rails namespaced routing

Hi,

I was wondering if anyone came across a similar scenario when working
with namespaced routes with edge rails.

Consider an application with controllers customers and products that
also have other resources.

map.resources :customers do |customer|
  customer.resources :notes
  customer.resources :tags
end

map.resources :products do |product|
  customer.resources :notes
  customer.resources :tags
end

Now of course this poses a problem as it stands because the notes and
tags controllers will conflict. I know there are polymorphic routes/
controllers (http://revolutiononrails.blogspot.com/2007/05/drying-up-
polymorphic-controllers.html) however this doesnt make sense if the
controllers have little shared logic/behaviour; only a common name. I
know I can just use a different name, but with the addition of
namespaces this should no longer be a concern with a proper design.

So.. I'm thinking something like:

/app/controllers/customers/root_controller.rb
/app/controllers/customers/tags_controller.rb
/app/controllers/customers/notes_controller.rb
/app/controllers/products/root_controller.rb
/app/controllers/products/tags_controller.rb
/app/controllers/products/notes_controller.rb

with routes like:

map.namespace(:customers) do |customers|
  customers.resources :root, :has_many => [:tags, :notes]
end
map.namespace(:products) do |products|
  products.resources :root, :has_many => [:tags, :notes]
end

creating routes like:
/customers/root/#{root_id}
/customers/root/#{root_id}/tags/#{id}
/customers/root/#{root_id}/notes/#{id
/products/root/#{root_id}
/products/root/#{root_id}/tags/#{id}
/products/root/#{root_id}/notes/#{id

This will work. But we have an unnecessary /root/ in the route. I
suppose the solution would be adding a flag to the namespace function
to pass a root controller with a default name of "root_controller" so
the root of the namespace can itself be a resource. Can this be done?
What do you guys think about it as an addition to rails?

This leads me to another scenario. Namespaced models.... quite simply
as it stands you can in /app/models/ make a folder called /customer/
there add tag.rb and note.rb. With models Customer::Tag and
Customer::Note. One thing is the table_name will remain "tags" and
"notes", which in this case should be prefixed by the namespace name
like "customer_tags" and "customer_notes". Also with the ability to
have a root model like Customer in /customer/root.rb.

I'd love to hear that this can already be done as rails stands. I can
make the patches and submit them, however I don't want to divulge from
other design considerations.

Regards,

Peter

This leads me to another scenario. Namespaced models.... quite simply
as it stands you can in /app/models/ make a folder called /customer/
there add tag.rb and note.rb. With models Customer::Tag and
Customer::Note. One thing is the table_name will remain "tags" and
"notes", which in this case should be prefixed by the namespace name
like "customer_tags" and "customer_notes". Also with the ability to
have a root model like Customer in /customer/root.rb.

On a totally unrelated note, I'm completely against the idea of
namespaced models. Putting them in a separate directory is perfect,
but not naming them like Customer::Tag. Why would u need to do that ?

-Pratik

Hey,

so you're saying that you are using the same resource names (notes,
tags) but you want those routes to go to different controllers?

map.resources :customers do |customer|
   customer.resources :notes, :controller => 'customer_notes'
   customer.resources :tags, :controller => 'customer_tags'
end

map.resources :products do |product|
   customer.resources :notes, :controller => 'product_notes'
   customer.resources :tags, :controller => 'product_tags'
end

In edge-rails your url helpers will automatically be name-prefixed so:

customer_notes_path #=> /customer/3/notes, controller ==
CustomerNotesController

etc etc

HTH,
Trevor

To not have naming collisions. You can't have two classes named Tag
in the same namespace (and not using one is still in a namespace.. the
default one). Pretty standard namespace stuff.

+1 for namespaced models! :slight_smile:

So what will the table be for those namespaced models ?

I don't like namespaced models either, but its pretty easy to do. Just
add the folder to the load path and all the models in the folder will
be loaded. As for the table name, I think you end up with
"customer_tag" or maybe just "tag". Either way you could set your own
table name on the model.

The only good use I found for named spaced models is for third party
code.

Exactly. config.load_paths was my point when I said "Putting them in a
separate directory is perfect,".

Also, if you're gonna namespace your models as Customer::Tag - and put
them inside models/customer/tag.rb - you better name your model
CustomerTag and keep it as models/customer/customer_tag.rb

-Pratik

I have read over everyone's comments and I agree to a point. I'll go
over each proposed solution and add my comments.

1. Use :controller => 'customer_notes' when specifying resources to
have unique controller names
2. Use longer model names like CustomerNote instead of Customer::Note
3. Use load_paths to locate classes grouped by directory names

The first word that comes to mind is, "elegance". None of these
produce an optimal solution. They are hacks to achieve the same
functionality that namespaces could offer.

Comments ...

1. The same reason that rails core named their classes
ActiveRecord::Base, not ActiveRecordBase, or
ActonController::Caching::Actions, not ActionControllerCachingActions,
etc., is its poor style/design/foresight to use
CustomerNotesController instead of Customers::NotesController. We
should be leveraging the ruby language, not shaping ruby to fit
rails. What happens if we have to refactor? In addition, having to
configure the routes by hand to use the correct controller name is not
the rails-way.

2. A lot of the same reasons I wrote in #1. What about an enterprise-
level web application with 80 models. If we prefix them with another
name anyways, why not make a namespace out of it?

3. So for every module I write, I should have an entry in my
environment listing each of them so it can load the paths properly?
Thats rhetorical.

The bottom line is, convention over configuration is the rails motto
but it doesn't seem to be in these cases. It is short sighted to
think that each application designed should only serve the purposes
within that application. The concept of modules and components are
necessary in a proper software design. How often do people reuse
their controllers, models, etc.? My biggest question is, what is the
resistance? Why not have more specific directory structures that
organize our controllers/models without the worry of conflicting
names. I can't think of a single reason why not. It's starting to
begin with the new map.namespace functionality for edge rails'
routing.

Thanks for all the feedback everyone. Rails is a phenomenal framework
and its the communal effort that made it this way.

Regards,

Peter

1. The same reason that rails core named their classes
ActiveRecord::Base, not ActiveRecordBase, or
ActonController::Caching::Actions, not ActionControllerCachingActions,
etc., is its poor style/design/foresight to use
CustomerNotesController instead of Customers::NotesController. We
should be leveraging the ruby language, not shaping ruby to fit
rails. What happens if we have to refactor? In addition, having to
configure the routes by hand to use the correct controller name is not
the rails-way.

Reason why it's AR::Base and not ActiveRecordBase is because AR is a
module that has lot more stuff than just the Base class.

Customers::NotesControlle is poor to use instead of NotesController <
CommonController. Or you can just do something like

class NotesController < AC
  include CustomerBehavior
end

Please note that you can have any directory structure you want,
keeping sane controller names. Also, you can have any url you need as
well.

2. A lot of the same reasons I wrote in #1. What about an enterprise-
level web application with 80 models. If we prefix them with another
name anyways, why not make a namespace out of it?

My current app has like 40 models so far. 40 more would fit in just perfect.

3. So for every module I write, I should have an entry in my
environment listing each of them so it can load the paths properly?
Thats rhetorical.

No. Just the directory. root/models/customer/

Thanks,
Pratik

Peter,

When you invoke "convention over configuration" you have to bear in mind that *your* convention is a product of the problems you're trying to solve - and those problems may not match the majority's problem-space.

There's nothing in your proposal that I can see which would make any of my applications better structured - but I recognize that my own sample isn't necessarily representative of the whole.

The best way to show us how much better things would be is to take your ideas and implement them as a plugin - that way everyone can plainly see the benefits and how much the convention buys us.

Regards,
Trevor

This leads me to another scenario. Namespaced models.... quite simply
as it stands you can in /app/models/ make a folder called /customer/
there add tag.rb and note.rb. With models Customer::Tag and
Customer::Note. One thing is the table_name will remain "tags" and
"notes", which in this case should be prefixed by the namespace name
like "customer_tags" and "customer_notes". Also with the ability to
have a root model like Customer in /customer/root.rb.

I'd love to hear that this can already be done as rails stands. I can
make the patches and submit them, however I don't want to divulge from
other design considerations.

class Customer < ActiveRecord::Base; end

=> nil

Customer.table_name

=> "customers"

class Customer::Tag < ActiveRecord::Base; end

=> nil

Customer::Tag.table_name

=> "customer_tags"

It's already working in edge, did you try it out? It's smart enough
to prefix the table name if the parent is an active record model, not
a regular module. Try this layout:

app/models/customer.rb
app/models/customer/tag.rb

I appreciate everyone sharing their own perspective. It's insightful.

Trevor, I absolutely agree with you in saying that "convention over
configuration" applies to a universal solution. But how does what I'm
proposing take away from that? Namespaces should by no means be an
enforced axiom, but it should be one that exists for a particular
scenario, which in my opinion can work for most.

Rick, that sounds great! Thats exactly what I'm talking about here.
So it looks like there is already some overlap. What would the class
definitions for customer.rb and tag.rb look like in edge?

... I'm assuming:

class Customer < ActiveRecord::Base
end
# table => "customers"

class Customer::Tag < ActiveRecord::Base
end
# table => "customer_tags"

Regards,

Peter

To add to that.. Rick, that type of parent/child functionality in
models is exactly what I have envisioned but with controllers. So if
that means instead of putting everything in the /app/models/customer/
directory, there exists a parent model and then child models under the
directory of the parent class name, I'm all for that. To revise my
first posting then:

routes:

map.resources :customers, :namespace => true do |customers|
  customers.resources :tags
  customers.resources :notes
end

or

map.resources :customers, :namespace => :customers do |customers|
  customers.resources :tags
  customers.resources :notes
end

or

map.namespace :customers, :resource => :customers do |customers|
  customers.resources :tags
  customers.resources :notes
end

controllers:

/app/controllers/customers_controller.rb
/app/controllers/customers/notes_controller.rb
/app/controllers/customers/tags_controller.rb

..something to that effect.

Regards,

Peter

However,

as it stands in edge it might work that:

map.resources :customers
map.namespace(:customers) do |customers|
  customers.resources :tags
  customers.resources :notes
end

This would _almost_ work. Except that there wouldnt be a reference
(at least not apparent through this routing) that tags and notes are
children of customers. So you can have routes like /customers, /
customers/2, /customers/:action, /customers/notes, /customers/notes/
new, /customers/2/notes/4, etc.

Peter

Peter,

your original ideas don't "take-away" from anything. They just don't
speak to me and they wouldn't make my apps better organized. Note
that I'm talking strictly about the controllers issue - the
namespaced models thing... I use that all over the place.

But as far as interactions between routes.rb and controllers - I have
never felt that there should be a strict correlation between the
hierarchy of my URLs and my class structure. Your original proposal
seemed to push very strongly in that direction.

Trev

I didn't mean for my explaination enforce a strict mapping between
controllers and routes. Only to the same extent that it already is
now. This is already becoming apparent with the map.namespace route.
I'd just like to see as I said a parent/child resource relationship in
a namespace.

Peter

Hello again,

For information purposes I wanted to follow up on this discussion with
an already existing solution in edge.

map.resources :customers do |ns|
  ns.namespace(:customers) do |customers|
    customers.resources :tags
  end
end

GET /customers/
{:action=>"index", :controller=>"customers"}
POST /customers.:format/
{:action=>"create", :controller=>"customers"}
GET /customers/new/
{:action=>"new", :controller=>"customers"}
GET /customers/new.:format/
{:action=>"new", :controller=>"customers"}
GET /customers/:id/edit/
{:action=>"edit", :controller=>"customers"}
GET /customers/:id/edit.:format/
{:action=>"edit", :controller=>"customers"}
GET /customers/:id/
{:action=>"show", :controller=>"customers"}
GET /customers/:id.:format/
{:action=>"show", :controller=>"customers"}
PUT /customers/:id/
{:action=>"update", :controller=>"customers"}
PUT /customers/:id.:format/
{:action=>"update", :controller=>"customers"}
DELETE /customers/:id/
{:action=>"destroy", :controller=>"customers"}
DELETE /customers/:id.:format/
{:action=>"destroy", :controller=>"customers"}
GET /customers/:customer_id/tags/
{:action=>"index", :controller=>"customers/tags"}
GET /customers/:customer_id/tags.:format/
{:action=>"index", :controller=>"customers/tags"}
POST /customers/:customer_id/tags/
{:action=>"create", :controller=>"customers/tags"}
POST /customers/:customer_id/tags.:format/
{:action=>"create", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/new/
{:action=>"new", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/new/
{:action=>"new", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/new.:format/
{:action=>"new", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/new.:format/
{:action=>"new", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/:id/edit/
{:action=>"edit", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/:id/edit/
{:action=>"edit", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/:id/edit.:format/
{:action=>"edit", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/:id/edit.:format/
{:action=>"edit", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/:id/
{:action=>"show", :controller=>"customers/tags"}
GET /customers/:customer_id/tags/:id.:format/
{:action=>"show", :controller=>"customers/tags"}
PUT /customers/:customer_id/tags/:id/
{:action=>"update", :controller=>"customers/tags"}
PUT /customers/:customer_id/tags/:id.:format/
{:action=>"update", :controller=>"customers/tags"}
DELETE /customers/:customer_id/tags/:id/
{:action=>"destroy", :controller=>"customers/tags"}
DELETE /customers/:customer_id/tags/:id.:format/
{:action=>"destroy", :controller=>"customers/tags"}

Now your controllers can be organized in a namespaced structure:

/app/controllers/customers_controller.rb => CustomersController
/app/controllers/customers/tags_controller.rb =>
Customers::TagsController

Regards,

Peter