Posts Tagged with "routes"

Using Wildcard Domains with Rails

Sep 13, 2011  -  Comments

Working on a recent project, I needed to allow users to set custom domains to point to their account pages on my server. They would set their domain when they registered, then set an A Record with their DNS to point to my server. The only problem was, I didn't know how to get Rails to do this.

The domain is already saved in the database, but I needed to tell the router to look for any incoming requests to a domain that doesn't match my primary domain (in this example, we'll say my primary domain is example.com). Thanks to Rails 3, I could just add a routing constraint (which can take a class as argument), like so

  MyApp::Application.routes.draw do
    # domain routes (THIS IS THE IMPORTANT PART)
    constraints(Domain) do
      match '/index.html' => 'user#show', :as => :user_domain
      resources :tasks
      resources :projects
      # any other routes that are domain-specific
    # this needs to be below the "domain" section
    root :to => 'site#index'

Notice that the root path is below the domain routes. This is important because it will get triggered before the domain routes otherwise (because routes are first come, first served). With these routes in place, it's as easy as finding the user by domain when we get a request.

We added the Domain class as a constraints argument, now we need to add that file. You can add the following domain.rb to your lib folder and make sure it's getting loaded on application boot.

  class Domain
    def self.matches?(request)
      request.domain.present? && request.domain != 'example.com'

It's as easy as that. We just check to make sure the domain is present and that it doesn't match our primary domain. This constraint will match any domain other than our primary domain.

As for server configuration, you have to tell your server to accept all incoming requests, regardless of domain. Below is a sample Apache virtualhost to accomplish this. Notice that we don't specify a ServerName or ServerAlias, we want it to match everything.

  <VirtualHost *:80>
    DocumentRoot /path/to/my_app/public

    <Directory /path/to/my_app/public>
      Options FollowSymLinks
      AllowOverride None
      Order allow,deny
      Allow from all

IMPORTANT: You need to ensure that this virtualhost entry is loaded last. If you have any other virtualhosts that specify a ServerName, then they need to be loaded before this one. We're using this entry as a catch-all to route to our rails app. If you have all of your virtualhosts in one file, just put this entry last. If each entry is in its own file, just make sure this file is loaded last (you might add a zzz_ to the start of the filename to make sure).

Tagged: routesrails3

RESTful Routes Demystified

Sep 14, 2008  -  Comments

RESTful application design is a rails-standard way to structure your CRUD actions. It simplifies your controllers into 7 actions: index, show, new, create, edit, update, and destroy. Here are some basics for working with RESTful routes.

The simple way to get the default routes:

ActionController::Routing::Routes.draw do |map|
  map.resources :products

Route Options

Several options can be passed to the route to customize it. You can use the path_prefix option to customize the appearance of your URLs. The name_prefix option is used to change the way the routes are called. The controller option is used to specify a custom controller.

# start all of your URLs with /admin
# '/admin/projects/id/edit/index.html'
map.resources :products, :path_prefix => '/admin/index.html'

# start all of your routes with 'mng_'
# mng_products_path or mng_product_path(:id)
map.resources :products, :name_prefix => 'mng_'

# if you want your URLs to say '/products/id/index.html'
# but your controller is called 'store_products'
map.resources :products, :controller => 'store_products'

Adding Routes for Custom Actions

The resources method will generate the routes for the default REST actions, but what if you want to add your own actions to your controller? There are two options for adding custom routes: the member option is for routes that require an id to be passed, and the collection option is for actions that don't require an id. With each option, you must pass a hash with the action name and the HTTP method.

# return_policy_products_path => '/products/return_policy/index.html'
# submit_question_products_path => '/products/submit_question/index.html'
map.resources :products, :collection => {:return_policy => :get, :submit_question => :post}

# warranty_product_path(:id) => '/products/id/warranty/index.html'
map.resources :products, :member => {:warranty => :get}

Nested Routes

You can nest routes for objects that are related. Say the products have customer reviews with a has_many relationship. You can nest the resources call within the products route definition.

# product_reviews_path(:product_id) => '/products/product_id/reviews/index.html'
# product_review_path(:product_id, :id) => '/products/product_id/reviews/id/index.html'
# edit_product_review_path(:product_id, :id) => '/products/product_id/reviews/id/edit/index.html'
map.resources :products do |products|
  products.resources :reviews, :controller => 'customer_reviews'

Those are the basics, but there are a LOT more possibilities. If you're interested, there is a really great guide to all things routes.

Tagged: railsroutestutorial

Routes Demystified

Aug 02, 2008  -  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')

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, '')

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: railsroutestutorial