1. Computing

Part 3: Implementing

By

Getting Down to Business

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 tutorial, we discussed what a URL shortener is and brainstormed a bit. In part 2 we generated the project, scaffolded the only resource needed and worked on the routing. In this part, we'll implement (more or less) the rest.

The Controller

Time to open up the app/controllers/shorts_controller.rb file and (you guessed it) gut it. Not of everything like we did with config/routes.rb, but of any method that isn't show, new or create, those are the only three we're going to need. And we'll be changing those (somewhat subtly in some places, so be careful) to suit our needs. So start by deleting the index, update, edit and delete methods. We'll go over the remaining three methods one by one.

This is the new show method.


# GET /shorts/1
def show
	@short = Short.find(params[:id].to_i(36))

	respond_to do |format|
		format.html { redirect_to @short.long }
	end
end

We removed the JSON format, we don't need it here. On the first line of the method we changed how we search for the Short URLs. Instead of blindly chucking the ID in there, we first decode it from base 36, so ActiveRecord just sees an integer. And finally, instead of rendering anything we'll redirect to the URL we pulled out the database.

Next, the new new method.


# GET /shorts/new
def new
	@short = Short.new

	respond_to do |format|
		format.html # new.html.erb
	end
end

Not much here. Create a new (empty) Short object and display the form. That's it.

And finally the create method.


# POST /shorts
def create
	@short = Short.new(params[:short])

	respond_to do |format|
		if @short.save
			format.html { render action: "show" } 
		else
			format.html { render action: "new" }
		end
	end
end

Again, not much here. We create a new Short object from the parameters (which come from the form) and save it to the database. Instead of redirecting to the show action (which, again, redirects you to the long URL), just render the show view instead. If there was an error, render the form view again.

And that's it for the controller, there's just not much in there.

The Model

Now, onto the model. There are a few things worth noting here. But first, the file in its entirety.


class Short < ActiveRecord::Base
  attr_accessible :long, :desc
  attr_accessor :desc

  validates :long,
		:presence => true,
		:format => {
			:with => %r{^http://},
			:message => "Only HTTP links allowed!"
		}

  def to_param
    if self.desc.nil?
      b36_id
    else
      "#{b36_id}/#{self.desc}"
    end
  end

  def b36_id
    self.id.to_i.to_s(36)
  end
end

The first line is the attr_accessible line that prevents fields from mass assignment. Mass assignment is a type of bug or vulnerability that allows a form to submit fields that shouldn't be writable. For example, if your user model as an is_admin field and you (a non-admin user) submitted the form to change your profile information but added a custom field to write to the is_admin field, making yourself an administrator. Not good. So to write to any field using a form, you must explicitly list it here.

But there are two fields listed here, the long URL and a description. I don't remember any description in the database, do you? There isn't any, it only exists in memory. It's not an attribute backed by the database. Since the database doesn't really care what the description really is, it's only displayed on the page directly after creating a new URL and then forgotten about. The next line creates this attribute, a normal Ruby attribute using attr_accessor.

The next little cluster of lines are the ActiveRecord validations. This is what's responsible for maintaining the sanity of your data. Here, we stipulate that the long parameter must be present, and that it must begin with http://, so only absolute HTTP URLs are allowed. No relative URLs, no file:// URLs, or even shell: URLs (yes, on some browsers you could open up someone's folders with a link, or possibly even run programs). If either of these validations fail, there are error messages included.

The final two methods here are where a bit of magic happens. When you generate a URL for an ActiveRecord object, generally Rails will just take the ID and throw it at the Rails router. This is not the case here, since we don't want the decimal ID in the URL, we want the base 36 ID. The to_param method is a method called by Rails that generates what's called a "slug." This slug is then passed to the Rails router and inserted into a URL. Here, we'll take the normal ID and generate the base 36 ID for our short URL. We also add in the parameterize description, if one was given. The last method there is only there for DRY purposes.

Great, now we have the model set up, let's try it out. Make the modifications and fire up your Rails console. Try creating an invalid Short to test out the validations, and generate a URL for a Short object with an ID 10 or higher, you should start seeing it count up through the digits.


:001 > s = Short.new
 => # 
:002 > s.save
   (0.0ms)  begin transaction
   (0.0ms)  rollback transaction
 => false 
:003 > s.errors
 => #, @messages={:long=>["can't be blank", "Only HTTP links allowed!"]}> 

:004 > 20.times do Short.new(:long=>'http://ruby.about.com/').save; end

:005 > s = Short.new(:long => 'http://ruby.about.com/', :desc => 'The About.com Guide to Ruby')
:006 > s.save
:007 > app.url_for(s)
 => "http://www.example.com/m/the-about-com-guide-to-ruby" 

This illustrates that everything is working fine. The validations don't let us create invalid Shorts, and the to_param method works great. The m in the last line is the ID for the last created Short. The string after that is the parameterized description, which remember is optional. Simply going to http://example.com/m will do the same thing. Great, all that's left now are the views.

The View

This is perhaps the easiest part. We had the scaffold generator cook this up for us already, all we need to do is take out a few irrelevant bits and then we're done. First up, app/views/shorts/new.html.erb. There wasn't much in there before, and this is all there is in there now. It just displays a header and renders the form partial.


<h1>Create a new short URL</h1>

<%= render 'form' %>

Next up, the form partial in app/views/shorts/_form.html.erb. Almost no changes here, we just added the description field and changed the labels to something more descriptive.


<%= form_for(@short) do |f| %>
<% if @short.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@short.errors.count, "error") %> prohibited this short from being saved:</h2>

<ul>
<% @short.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>

<div class="field">
<%= f.label "Long URL" %><br />
<%= f.text_field :long %>
</div>

<div class="field">
<%= f.label "Description (optional)" %><br />
<%= f.text_field :desc %>
</div>

<div class="actions">
<%= f.submit %>
</div>
<% end %>

And finally, the app/views/shorts/show.html.erb file. Even though we no longer have a show action that renders anything, this is the view rendered when the create action succeeds. Not much here either, just a few lines saying what the new URL is, what the old URL was and a link to shorted another URL.


<p id="notice"><%= notice %></p>

<h3>Your URL was shortened</h3>

<p>The new short URL is <%= link_to short_url(@short), short_url(@short) %>.</p>

<p>The URL leads to <%= link_to @short.long, @short.long %>.</p>

<p><%= link_to 'Shorten another URL', new_short_path %></p>

Done

That's it. You're done. While there's more that could be done, most of this project consisted of removing things put in by the scaffold generator. There were a few tricky steps with the router, by far the most time consuming part if you don't yet understand the router fully, but the rest was just housekeeping. But of course these are still blank white pages, we can do something about that in the next part though.

©2014 About.com. All rights reserved.