18

Let's say my app has two models, Foo and Bar.

Foo optionally belongs_to Bar.

Right now I can look at a single Foo, or search for a particular Foo, and the FoosController handles all that. My URLS are like: foos/1 and foos/new

Sometimes I want to look at a Bar. The BarsController handles that, and I get to it like: bars/1 or bars/1/edit.

If I'm looking at a Bar I might want to browse all the Foos that are part of that Bar. So, I'd like to use bars/1/foos/ to look at those Foos.

This is pretty straightforward with nested resources, and it looks like this:

resources :foo
resources :bar do
  resources :foo
end

However, Foos that are part of a Bar are kind of special, set apart from regular Foos. So, for instance, if I load foos/1 or bars/1/foos/1, I would be looking at the same Foo, but I am focused on different information in each case.

So I've been thinking about having a BarFoos Controller to handle Foos when they're in the context of a Bar. However, if I nest BarFoos under Bar, then my helpers are going to be like bar_bar_foos_path and new_bar_bar_foo_path. That seems redundant.

So, now I'm thinking about namespaces, which is something I've never looked into before. I see in the rails guide that I could define:

namespace "bar" do
  resources :foos
end

If I do that I can make a second FoosController under app/bar/, and that FoosController can handle Foos inside of a Bar with nice helpers like bar_foo_path(:id) instead of bar_bar_foo_path(:id).

But if I do that, what happens to my BarsController? How do requests get routed to BarsController if instead of resources :bars I have namespace "bar"?

And, lastly, is there anything special I need to do inside my secondary FoosController to make sure there's not a name conflict with the top-level FoosController? I realize the routing says "namespace", but how does the rest of the ruby code know that the app/bar/foos_controller and app/foos_controller are not the same class?

Thanks!

Andrew
  • 42,517
  • 51
  • 181
  • 281

2 Answers2

43

I think what you're trying to achieve is:

  1. Bar has many Foos
  2. View Foos belonging to Bar
  3. View all Foos regardless of parent.

You can achieve that with: routes.rb:

resources :foos
resources :bars do
  resources :foos, :controller => 'bars/foos'
end

The route helpers you end up with are:

  • bars_path
  • foos_path
  • bars_foos_path
  • etc, etc, 'rake routes' for the rest =)

In essence, you end up with:

  • app/BarsController (rails g controller bars)
  • app/FoosController (rails g controller foos)
  • app/bars/FoosController (rails g controller bars/foos)

In FoosController, you would access foos as usual with:

@foos = Foos.all

and in bars/FoosController, you would access bar's foos with:

@foos = @bar.foos

where bar can be pre-retrieved in the bars/foos controller with:

before_filter :get_client

private
def get_client
  @bar = Bar.find(params[:bar_id])
end

Hope this helps. =)

Edit: As for namespaced routes, I've personally used them when I some of my resources retrieved from a sub-path. For example, if I have an admin section of my site, then I might have the following:

routes.rb:

namespace :admin do
  resources :foos
end

and I create my controller with:

rails g controller admin/foos

This sets up my foos resource, such that I can access it at "my site url"/admin/foos, and also get helpers such as admin_foos_path.

clemensp
  • 2,525
  • 23
  • 21
  • Your description of my scenario is exactly right, and your answer is very clear, thank you! I see how your answer would work, so that's awesome! Just to follow up on one detail, would you *recommend* nesting over namespacing in this scenario? Could you offer any pros/cons you've experienced either way? – Andrew Mar 02 '11 at 00:35
  • 3
    In your case, because bar and foo are both resources, and foo belongs to bar, it makes perfect sense to use nested resources. To expand upon the namespace explanation above: if you're in a situation where you're dealing with different behaviour for different contexts, i.e. if I'm admin, I do A, otherwise I do B, and the logic is anything but the simplest or scattered all over, then I will consider namespacing and add a controller to allow me to work in that specific context. 'admin' represent a context as opposed to a resource in this case. – clemensp Mar 02 '11 at 00:54
  • Great, that makes a lot of sense! – Andrew Mar 04 '11 at 02:31
6

There are cons to this approach.

If you declare a constant, eg. CONST_NAME, in nested resource foos, rails will throw "uninitialized constant ::Foo::CONST_NAME" exception because of its scope algorithm.

To avoid such behaviour, use:

resources :foos
resources :bars do
  scope :module => "bar" do
    resources :foos #, :controller => 'bar/foos' no need to use this now because route will be searched there by default
  end
end

Now you will not get an exception while using:

Foo::CONST_NAME

or

Bar::Foo::CONST_NAME
pjmorse
  • 9,204
  • 9
  • 54
  • 124
Slawek
  • 61
  • 1
  • 1