views:

228

answers:

3

What I'm trying to do:

I'm building a system where there are different types of post. Setting aside the models, this question is about the routes and the controller

Basically /posts/new should go to an index page of sorts, while /posts/new/anything should look up the type anything and then build a form for creating a new one.

How I'm trying to do it:

Feel free to ignore this section as I could be completely on the wrong track.

In the routes config:

map.connect '/posts/new', :controller => 'posts', :action => 'new_index'
map.resources :posts, :path_names => { :new => 'new/:type' }

In the controller:

class PostsController
  # implicit: def new_index ; end

  def new
    @post = class_for_type(params[:type]).new
  end
end

The view has code which looks at the type of @post to determine which set of views to use. Turns out this gets me 90% of the way there: /posts/new/quip does actually send me to the correct page to create a quip, and so forth. /posts/new does send me to an index page.

Problem is twofold.

  1. I still want to have convenience methods like this:

    <%= link_to 'New Post', new_post_path %>
    

    But this is now invalid as new_post_path requires the :type parameter.

  2. I would like to do it using one route if possible.

A: 

I think you should take a look at rails' single table inheritance : http://juixe.com/techknow/index.php/2006/06/03/rails-single-table-inheritance/

That'd make it a lot easier to manage your posts types as you'd have many models inheriting the global one but being based on the same sql base.

For your problem itself, why not just redefine the new_post_path helper method ? Something like :

def new_post_path
    { :controller => 'posts', :action => 'new', :type => params[:type] }
end

The type is now automatically given based on the params array.

Damien MATHIEU
(1) I am already using STI. Like I said, the model is irrelevant for the purposes of this question. (2) The link to new_post_path is used in the layout (it's on every page) so the params[:type] may not even exist. Hence when you're on such a page, I want to display an index page. It's true that I could redefine the helper to just return '/posts/new' though I suppose.
Trejkaz
I should add though, the main problem with my existing two-line solution is that it was hackish. If I add 3 more lines of hack to work around it then it becomes 4 lines of hack which could be 1 line of route if I knew how to do it like I want.
Trejkaz
A: 

Peter Wagenet's solution gave me the missing piece of the puzzle (:type => nil) which allows me to do it in one line:

map.resources :posts, :path_names => { :new => 'new/:type' },
              :requirements => { :type => nil }

Of course I still had to go into the controller and make a fix so it renders new_index.html.erb from the :new action.

(Well, I guess it's not one line anymore.)

new_post_path                     # => '/posts/new/'
new_post_path(:type => 'quip')    # => '/posts/new/quip'
new_post_path('quip')             # => '/posts/new/quip'
Trejkaz
+2  A: 

If you can live with sharing the actions then you can set up the following:

# Routes
map.new_person '/people/new/:type', :controller => :people, :action => :new, :type => nil
map.resources :people, :except => [:new]

# Controller
class PeopleController < ApplicationController

  def new
    unless params[:type]
      render :action => :new_index and return
    end

    @post = class_for_type(params[:type]).new
  end

end

This allows you to keep a single route in the default format as well as the ability to specify types:

new_person_path                      # => /people/new/
new_person_path(:type => 'anything') # => /people/new/anything
new_person_path(:employee)           # => /people/new/employee
Peter Wagenet