Routes Demystified

AUG

02

2008

3 comments

It took me a while to discover the full potential of Rails' routes. I slowly learned more and more neat stuff that made me like them more and more. They are extremely powerful and are very useful for URL re-writing. Following is all of my route knowledge. If you notice something that I didn't cover, please leave it in the comments.

A few basic routes:

            # this will route domain.com/ to the index action of your main controller
            map.connect '', :controller => 'main'
            
            # this will create a custom URL for your about-us page
            map.connect 'custom-url/about-us', :controller => 'about_us', :action => 'index'
            

Pay attention to the order of your routes in your routes.rb file. They are evaluated in order from top to bottom. Routes at the top will be used before routes at the bottom if they are too similar.

Named Routes

Instead of saying map.connect, let's get crazy and name our routes. I'll use the same routes as above and add a few.

            # you can now call this route with home_path or home_url
            map.home '', :controller => 'main'
            
            # call this route with about_us_path or about_us_url
            map.about_us 'custom-url/about-us', :controller => 'about_us', :action => 'index'
            
            # symbols in the routes define options you pass when calling the route
            # call product_details_path(:id => @product) to access this route
            map.product_details 'products/detail-view/:id', :controller => 'products', :action => 'show'
            # all-purpose route for the products controller : products_path(:action => 'search') or products_path(:action => 'buy', :id => product)
            map.products 'products/:action/:id', :controller => 'products'
            

Route Blocks Using with_options

If we have several routes for the same controller, it makes sense to use with_options to simplify the definitions.

            map.with_options :controller => 'news' do |m|
              m.news 'news-releases/list', :action => 'index' # news_path
              m.news_details 'news-releases/details/:id', :action => 'show' # news_details_path(:id => news_release)
              m.news_author 'news-releases/by-author/:name', :action => 'author' # news_author_path(:name => 'Bill')
            end
            

Better URLs with Routes

Routes are great for creating search engine friendly URLs. Supposed you'd like to add the title of your blog post to the URL.

            map.blog_details 'blog/details/:id/:title', :controller => 'blog', :action => 'show', :title => nil
            

Now you can call the blog_details path and give it the :id and :title as options for the URL. Also notice that I set :title => nil at the end of the route. This marks that option as optional for the route call.

            # this call will generate the following URL: http://domain.com/blog/details/1/My-First-Blog-Post
            # I'm using a helper method in this call entitled 'strip_chars', it is below
            blog_details_path(:id => post, :title => strip_chars(post.title)) %>
            
            # you can also omit the title when invoking the route because it is optional.
            # since your 'details' method will be using the :id parameter to find the record
            # we don't really need the title, it's just for search engines
            blog_details_path(:id => post) %>
            
            # this helper method takes a string, replaces all spaces with dashes, then strips out all non-letter, non-number, non-dashes
            # it's good for generating URL-friendly titles
            def strip_chars(string='')
              return '' if string.blank?
              string.gsub(' ','-').gsub(/[^a-z0-9\-]+/i, '')
            end
            

Caching and Pagination

If we cache our site, and certain actions show a paginated list, then we need to add the :page attribute to the route to ensure that the cache is recorded properly. After all, to our server /blog/list looks the exact same as /blog/list?page=2 when retrieving cached pages.

            map.blog 'blog/list/:page',
                     :controller => 'blog',
                     :action => 'list',
                     :requirements => {:page => /\d+/},
                     :page => nil
            # blog_path
            # blog_path(:page => 1)
            

Notice that we added something new, the :requirements option. For :page, it's a regular expression telling us the supplied value must be a number. I've also set :page => nil so that we don't always have to specify it when invoking the route.

One thing to watch out for when calling a paged route is to be careful when you're on another paged section of your site. Say I have the route above for blogs and a similar route for news releases. If I'm on the page /news/list/5 and I call blog_path it will take the :page parameter from the current URL and you'll end up on page 5 of your blog post list. Confused? Just make sure when you are calling a paged route to specify it as blog_path(:page => 1) to explicitly go to /blog/list/1 or call blog_path(:page => nil) to go to /blog/list. Both will work.

Route Conditions

Similar to RESTful routes, you can specify the method of the request with the :requirements option. While this is useful, it makes much more sense to just use map.resources to get real RESTful routes.

            map.connect 'blog-post/:id', :controller => 'blog', :action => 'show', :requirements => { :method => :get }
            map.connect 'blog-post/:id', :controller => 'blog', :action => 'update', :requirements => { :method => :post }
            
            # a GET method to /blog-post/1 will route to the 'show' action
            # a POST method to /blog-post/1 will route to the 'update' action
            

I think that's enough for normal routes. Hopefully soon I can write a post on RESTful routes, which are a whole different beast.

Tagged: rails, routes

Working with the flash hash

AUG

17

2008

1 comment

The flash hash is what Rails uses to display messages (both notices and errors). Since it's a Hash, you can assign any key/value pair that you want, but I tend to stick with flash[:notice] for a success message, and flash[:error] for an error message.

In my opinion, using the flash hash is a little confusing. I haven't come across a book or tutorial that fully explains how to control it properly. So, here are the options for using the flash hash in Rails.

flash[:key] and flash.now[:key]

The following method shows how to best use the flash hash:

            def create
              @user = User.new(params[:user])
              respond_to do |format|
                if @user.save
                  flash[:notice] = 'User was successfully created.'
                  format.html { redirect_to admin_users_path }
                else
                  flash.now[:error] = 'The user could not be created'
                  format.html { render :action => 'new' }
                end
              end
            rescue Exception => ex
              logger.warn('ERROR: ' + ex.message)
              flash.now[:error] = 'There was an error creating the user.'
              render :action => 'new'
            end
            

Notice that I use the flash hash in two different ways: flash[:key] and flash.now[:key]. The way I use it depends on when I want it displayed. The flash[:key] usage should only be used before redirection, because it makes the object available for the current action and the next action. The flash.now[:key] usage should be used when you only want the flash object to be available to the current action.

Here's an example why you shouldn't use flash[:key] without redirection. Let's say this is your controller:

            class MainController < ApplicationController
              def index
                flash[:notice] = 'Welcome to the site!'
              end
              
              def profile
              end
            end
            

When you visit the index page, you'll see the message 'Welcome to the site!.' If you then click a link from the index page that takes you to the profile page, you'll still see the message 'Welcome to the site!' because the flash[:key] is available to the current action and the next action.

Displaying the flash

Here is a really helpful method to display the contents of the flash hash that I modified from one of Ryan Bates' awesome Railscasts:

            <%- flash.each do |key, msg| -%>
            	<div id="<%= key %>">
            		<p style="float:right;"><%= link_to_function 'X', "Effect.Fade('#{key}')" %></p>
            		<p><%= msg %></p>
            		<div class="clear"></div>
            	</div>
            <%- end -%>
            

This method will loop through each key in your flash hash and create a div with the name of the key, then put the contents inside with a link to close the message div.

I put this method in a partial called _notice_div.html.erb and include it at the top of my application layout. Here are the styles I use for notices and errors:

            #notice { background-color: #A4E7A0; border: 1px solid #26722D; }
            #error { background-color: #F0A8A8; border: 1px solid #900; }
            #notice, #error { width: 90%; margin: 0 auto 10px auto; padding: 5px; }
            #notice p, #error p { margin-left: 20px; padding: 0; font-size: .75em; color: #000; }
            #notice a, #error a { text-decoration: none; padding: 0 3px; }
            #notice a { border: 1px solid #26722D; color: #26722D; }
            #error a { border: 1px solid #900; color: #900; }
            #notice a:hover, #error a:hover { color: #333; border: 1px solid #333; }
            

This is a notice div.

This is an error div.

Tagged: css, rails, flash

Code Golf: Saving Time

AUG

28

2008

1 comment

Here is my submission for the Saving Time challenge. Code size: 319 bytes (not the worst, but close).

            h,m=gets.split(':').map{|i|i.to_i}
            h>11?h-=12:''
            m=(m-m%5)/5
            a=[]
            11.times{|x|a<<[' ']*17}
            b={}
            c=[[0,8],[1,12],[3,15],[5,16],[7,15],[9,12],[10,8],[9,4],[7,1],[5,0],[3,1],[1,4]]
            0.upto(11){|x|b[x]=c[x]}
            b.map{|x,n|a[n[0]][n[1]]='o'}
            a[b[h][0]][b[h][1]]='h'
            a[b[m][0]][b[m][1]]=h==m ?'x':'m'
            a.map{|x|puts x.join.rstrip}
            

Tagged: code golf, ruby

Code Golf: Grid Computing

AUG

31

2008

0 comments

Here is my submission for the Grid Computing challenge. Code size: 135 bytes.

            a=[]
            10.times{|x|a<<gets.split}
            y=[0]*20
            10.times{|i|a.each{|x|y[i]+=x[i].to_i}}
            a.each{|x|x.each{|i|y[a.index(x)+10]+=i.to_i}}
            p y.max
            

Tagged: code golf, ruby


© 2008 Travis Roberts. All rights reserved.