The following article is part of a series, for more article in this series see Creating a URL Shortener in Rails 3.
In part 1 of this series, we discussed what a URL shortener was and brainstormed a bit about what the data needs for such an application would be. In this article, we'll explore our intended implementation and what it means as the Rails router is concerned.
To get us started, we'll bootstrap our new project by generating a new Rails project, creating the database and scaffolding the Short resource (though we won't be using resource routing, see below).
Generate a new Rails project using rails new mytinyurl.
$ rails new mytinyurl create create README.rdoc create Rakefile create config.ru create .gitignore create Gemfile create app create app/assets/images/rails.png …
Change to your new directory and run rake db:create, this creates the database so we can run the migration after our scaffold.
$ cd mytinyurl/ $ rake db:create
Generate the scaffold. This is the point where we really need to decide what data we need. I've decided that I only need the long URL, the short URL will be generated from the ID (it's just a base 36 encoding of the ID automatically given to each ActiveRecord object), so it only has one field. This is done with the command rails generate scaffold short long:string.
$ rails generate scaffold short long:string invoke active_record create db/migrate/20130130065444_create_shorts.rb create app/models/short.rb invoke test_unit …
Take a look at db/migrations/*_create_shorts.rb. Not much in there, just the invisible ID field (which we'll be using quite a bit), our long string and the timestamps. We don't even need the timestamps so you could even remove them if you wanted.
class CreateShorts < ActiveRecord::Migration
create_table :shorts do |t|
OK, that looks good, run the migration with rake db:migrate.
$ rake db:migrate == CreateShorts: migrating =================================================== -- create_table(:shorts) -> 0.0006s == CreateShorts: migrated (0.0007s) ==========================================
We're all set up to start now. And, believe it or not, you're almost done. Well, maybe that's not hard to believe, this web application can only do two things: accept URLs and redirect people (three if you count showing the form).
Just 3 Routes
Even though we'll be using the scaffolding generator to get us started, we're not going to be using a RESTful resource. This application will be much simpler, consisting of just three routes.
- GET /:id(/:desc) - This is the route that loads the long URL from the database and redirects you to it. This is what happens when someone clicks on a shortened link.
- GET / - This is the root URL, it's what happens when someone opens up the web application. It displays a form to create a new short URL.
- POST / - This is what the form submits to. It creates the entry in the database and displays information about the newly created URL.
So to set these up, open up config/routes.rb. Here, besides all the default comments (which I delete, if I want to see them again I can look at them on Github or generate a new dummy Rails project), there is also a single resources line. Delete that too. Yes, delete it, we don't need it, we want to start with this empty routes.rb. This is not normally how you do things, but you'll see why we do it in a moment.
Mytinyurl::Application.routes.draw do end
Now, add these three lines. We'll discuss what they do and why they need the :as arguments in a moment.
Mytinyurl::Application.routes.draw do get '/:id(/:desc)' => 'shorts#show', :as => 'short' get '/' => 'shorts#new', :as => 'new_short' post '/' => 'shorts#create', :as => 'shorts' end
These are the three routes discussed above. Why did we abandon the RESTful resource? In a nutshell, it just can't be done without making longer URL. If we want the shortest URLs possible, the /:id resource has to do something. Since an ID is just a base 36 string (that's a string that consists for numbers and the characters a-z), any other string after the initial slash in the URL is a possible ID. It would be difficult to prevent names and IDs from clashing. So rip out all the routes and use only the root route for our tiny two-function app.
The first route matches /:id(/:desc). That second part in parentheses means it's an optional parameter. It's there to describe the URL and completely optional. This goes to the Shorts controller, using the show action, which is what sends the redirect to the long URL. But what about the :as parameter? Remember that the Rails router is a two way street. Not only does it match URLs to controller/action pairs, but it's also responsible for generating URLs from helper methods. In a blog you might link to something with a URL of post_url(@post), this is the Rails router at work in reverse. And, in order to play nice with the code already generated by the scaffold generator, we'll tell Rails to make helper methods compatible with the RESTful resource helpers everyone is familiar with. In this case, since we're referring to a singular Short using the show action, we'll call this one short by using :as => 'short'. This will automatically generate helper methods like short_url(@short) and short_path(@short) that we'll use later.
The next route is get '/' => 'shorts#new', :as => 'new_short'. When you get the root, display the form and hook this up to the new_short_url and new_short_path helper methods.
The final route is a POST route, post '/' => 'shorts#create', :as => 'shorts'. POST HTTP queries are used to create new resources, which is what we're doing here. When using RESTful resources, you POST to the restful resource, so if you have a blog with resources :post, you would POST to /post to create a new post. Here we're POSTing to the root URL, but the route is still named shorts. This is a bit confusing, but it lets us use the RESTful form helper already generated by the scaffold generator.
From time to time, I like to poke around with the console. At this point in the project, all we can really do is create some things in the database and try out our routing. So let's first get a dump of all the routes Rails knows about just to doublecheck our work. You can do this by running rake routes.
$ rake routes short GET /:id(/:desc)(.:format) shorts#show new_short GET / shorts#new shorts POST / shorts#create
This outputs a table of route names, routes and the controller#action they go to. Everything looks good here, now fire up the console with rails console (or rails c if you don't like to type). Go ahead and create a new short using ActiveRecord.
:001 > s = Short.new(:long => 'http://ruby.about.com/') => #
:002 > s.save (0.0ms) begin transaction …
Now time to try to generating some routes. An important one is url_for(s). To get to these helpers, use the app object.
:004 > app.url_for(s) => "http://www.example.com/1"
Excellent, now try the others.
:005 > app.short_url(s) => "http://www.example.com/1" :006 > app.new_short_url => "http://www.example.com/" :007 > app.shorts_url => "http://www.example.com/"
Not much there yet, but it's working. In part 3, we'll gut a lot of what the scaffold generated made and hook all this together.