23 AugHuman readable URLs with Friendly_id in rails 3

Tuesday, 23 August 2011 — 21:44

If you follow rails standards, most of your routes for showing, editing, updating or deleting records from your tables, will depend on the record_id. For example, if you have users in your application, you can get the list of them by yourdomain.com/users. And you get a specific user with yourdomain.com/users/3, where 3 is the user.id value.

There are some cases, where it’s interesting to have human readable urls. Let’s think we want to have places than can be shared between friends. What would you prefer for sharing? a url like: yourdomain.com/places/times_square_new_york, or a url like yourdomain.com/places/1 ?

Just this is what i pretended with my project, and here i will explain how easy can it be to accomplish. Slugify is basically a term that defines the process of getting a human readable word from a string. In the previous case, we are getting the slug from place definition (Times Square, New York).

There are different alternatives available to accomplish this. You can get a list of the most popular ones here. I have to admit that i was looking at different alternatives, and i finally chose friendly_id because of its potential and popularity. I tested it and it really works fine to me.

Let’s see the steps:

1. Add the gem to your Gemfile and run bundle

+# Slug. https://github.com/norman/friendly_id
+gem "friendly_id", "~> 4.0.0.beta8"
> bundle

2. Get our model ready. Let’s think the model is Places.

class Place < ActiveRecord::Base
  extend FriendlyId 
  
  friendly_id :location, :use => :slugged

end

3. Create a migration file for adding a new slug column to our model. You can use the special syntax for doing it automatically

rails generate migration add_slug_to_places slug:string

You will get a migration file that adds that column automatically. But i finally edited it a little bit. Make it look like this:

class AddSlugToPlaces < ActiveRecord::Migration
  def self.up
    add_column :places, :slug, :string, :unique => true, :null => false
    Place.all.map(&:save)
    add_index :places, :slug, :unique => true
  end

  def self.down
    remove_index :places, :slug
    remove_column :places, :slug
  end
end

I’m adding the new slug column to places table, as well as creating some constraints at database level. Then i’m re-saving all the existing Places i have in the database. With this, i’m making sure all the places are slugified. Before we save a new record, the slug is computed, so after adding the slug column to the table, i want to have it filled with the slug. If you don’t have any record yet on the table, you can comment that line out. I just had a couple of them, so for easiness i preferred to go that way. If you have more, i recommend to use a rake task for that.

This step is essential to accomplish the last action. We are creating a new index over the unique value of the slug. We can’t have two identical slugs in our application. So we firstly compute them, looking at location column. The gem will make sure they are not repeated.

4. Now you can ran migrations

rake db:migrate

5. Lunch your server and get ready for the magic. You are done!

rails s

Note aside. I like to have some before_filter actions on edit and destroy actions of model_controller. Those are really sensitive actions that you need to be really cautious. I like to make sure the user that pretends to remove or edit that particular record is the owner of that record. I’m doing that with something like in the controller

before_filter :validate_owner, :only => [:destroy, :edit]

def validate_owner
    unless current_user.places.exists?(params[:id])
      redirect_to(places_path)
    end
  end

Ok, it’s likely that you get redirected to places_path because of friendly_id gem. I reported that to Norman, and you can find all the info here

EDIT: As a temporal fix, you can replace the exists? with:

def validate_owner
    unless current_user.places.where("slug = :id OR id = :id", {:id => params[:id]}).limit(1).select(1)
      redirect_to(places_path)
    end
  end

10 JulInstall jQuery, jQuery UI in Rails 3

Sunday, 10 July 2011 — 10:50

As you might know, rails 3.1 will come with jQuery as the default javascript library.Also, RJS has been extracted out. That means that:

rails new new_app

will generate an application with jQuery.

But those are not the only adoptions. Rails 3.1 will also ship with some other dependencies in the box: CoffeScript and Sass

While we’re waiting for the rails 3.1 final release (looks like there might be a rc5 before that), i’m gonna play with those new technologies, because they both look really promising. If rails team is betting for them, they will likely worth it, i guess :).

However, the show must go on, and for the time being Rails 3.0.x is all we have (you can play with rails 3.1 release candidates, but not recommended for production environments). jQuery doesn’t come installed on Rails 3.0.×. But that’s not complicated at all, thanks to the jquery-rails gem.

In your Gemfile, add this line:

gem "jquery-rails"

Then, run bundle install. To invoke the generator, run:

rails generate jquery:install #--ui to enable jQuery UI

and you’re done!.