Database driven root level routing, like /some_page_name

I have a "page" model, and I want to be able access those pages with a URL like: http://foo.com/my_page_name

Just wondering if there is a best practice for this. I have avoided it this far because I didnt want these routes conflicting with the routes of the rest of the app.

I see 2 ways off the top of my head.

1. Use a low priority route that will match any single parameter URL. This route will call the page controller and show the page. Since its low priority, it only triggers if no other routes match. This seems like it might make 404's a bit messy to handle since I'm basically creating a catch all that shouldn't always work.

2. Ask my Page model for a regex to match the id parameter with. This would be a cleaner solution in the routes themselves, but I don't like the idea of hitting the database to check routing.

Any comments or ideas?

Could you accept the slightly-less-beautiful:

http://foo.com/pages/my_page_name?

If so, you could override to_param on your Page class and have it return the page name. If you do that then the Pages controller can use Page.find_by_name to show the page.

AndyV wrote:

Could you accept the slightly-less-beautiful:

http://foo.com/pages/my_page_name?

If so, you could override to_param on your Page class and have it return the page name. If you do that then the Pages controller can use Page.find_by_name to show the page.

On May 13, 5:01 pm, Alex Wayne <rails-mailing-l...@andreas-s.net>

I have that now. The problem is that my client keeps wanting shorter at the root level URLs for print in ads and what not. Easier to type and remember.

In that case... you might suggest

http://foo.com/xabc/my_page_name http://foo.com/xbcd/my_page_name http://foo.com/xcde/my_page_name

where /x.../ can be routed based on starting with x and the rest maps to a specific campaign. You get a routing cop-out, but you can also log the request URLs to provide tracking for response to very specific sources. Keep the string short, but you can still get as detailed as making every advertising source a unique string.

Doesn't answer your question (not much of a routes guru yet), but maybe there's an opportunity to do something even more beneficial than planned...

-- gw

Sorry, I'd guessed that might be the case.

One other possibility... sometimes it's easy to overlook that even things like the routes.rb file are just specialized ruby scripts. As such you can still stick code in there and dynamically create the routes. Something like this totally untested, off-the-top-of-my-head block:

Page.find(:all).each do |routed_page|   map.connect "/ #{page.slug}", :controller=>'pages', :action=>'show', :id=>page.id end

Interested to know how/if this works but don't have time to test it.

HTH, AndyV

AndyV wrote:

Page.find(:all).each do |routed_page|   map.connect "/ #{page.slug}", :controller=>'pages', :action=>'show', :id=>page.id end

Interesting approach!

Although, I am pretty sure that routes.rb only gets evaluated when rails boots up, so new pages that are added would not have a route.

I did manage to come with a solution after some digging that does seem to work though. I added this to the bottom of my routes file:

  map.page ':id', :controller => 'pages', :action => 'show'

Since its at the bottom it gets referenced last so it doesn't interfere with other named routes. And the pages/show action will raise a ActiveRecord::RecordNotFound exception if the page isn't in the database, which rails rescues and displays a 404. So I guess it works great.

Thanks for the ideas all.

I do something a lot like this for my app. After the normal rails default route, I catch everything else with my show_page route. My page controllers show action checks the db for a page with the appropriate path and displays it - or renders my custom "not found" message with a status code of 404.

routes.rb

  # Install the default routes as the lowest priority.   map.connect ':controller/:action/:id'   map.connect ':controller/:action/:id.:format'

  # Finally let CMS figure out what to do with fall through pages   map.show_page '*url_parts', :controller => "pages", :action => "show"

pages_controller.rb

  def show     @page = Page.find_by_url_parts(params[:url_parts])

    respond_to do |format|       if @page         store_location         response.headers['Last-Modified'] = @page.updated_at.to_s(:rfc822)         format.html # show the page         format.xml { render :xml => @page }       else         format.html {           title = "Page Not Found"           body = '<p>Please use the search box above</p>'           render_404( [""], title, body)         }         format.xml { head :status => :not_found }       end     end   end

and in my application.rb controller, the render_404 method:

  def render_404(base, title, body)     @page = Page.find_by_url_parts( base )     @page.body = body     @page.title = title     render :template => "pages/show", :status => 404   end

render_404 grabbing a page (the home page) is so that the styling and navigation stuff gets built and sent to the user.