Archive for the ‘tutorial’ Category

Pretty-ifying URLs

Saturday, April 18th, 2009

In today’s modern world we are not limited (cough, FORTRAN) to storing information in just a couple of bits. Some say that having pretty URLs is good SEO. I say it’s just common sense. I want the URL to tell a story, not give me some number that is only important to the system it’s coming from.

This is where to_param comes in.

By default this is defined like this:

def to_param
  id
end

Pretty high-tech stuff there. I won’t explain it to you. Give it a moment to sink it. et-cetera. This method is called on an object when it is passed to a url helper such as link_to or redirect_to. It is important to note that you should NEVER call @object.id as that will give the id of the object, not the to_param version of that name.

You can override this in your model in order to give you pretty urls! The way you do this is:

def to_param
  "#{id}-#{name.parameterize}"
end

Where id is the id of the model and name is the text you want into the URL. We call parameterize on this text in order to make it URL friendly.

The reason why we put in the id in the URL is two-fold. Firstly, (/forums/1-best-forum-ever) is so that when it gets passed to our controller as params[:id] or similar, we’re able to pass it to a finder:

Forum.find(params[:id])
# will be passed in as...
Forum.find("1-best-forum-ever")

Now you may go “Hey… wait a second! I don’t have an ID in my database that says ’1-best-forum-ever’!” and you’d be right, you don’t. But you do have an ID of 1 for a record somewhere. Rails will call to_i on this value and convert it simply down to 1 and you’ll be able to treat the pretty URL as if it were a real ID.

Secondly, if two objects in your database have the same name they won’t clash for the parameterized versions, since the id will always be unique.

But I don’t want my ID in my URL

Hey, that’s cool too! Just a little tougher… Before you save your records you’ll want to write out the permalink as a new field, so define a field called permalink in your table if you want to take this route. Then you do a before_save and define to_param like this:

before_create :set_permalink

def set_permalink
  self.permalink = name.parameterize
end

def to_param
  permalink
end

Now when you call the finder you’re going to need to find_by_permalink! instead of just find:

Forum.find_by_permalink!(params[:id])

We have to use the bang version of find_by_permalink because this will raise an ActiveRecord::RecordNotFound exception if the record is not found, just like find. (Thanks to Yarsolav Markin for mentioning this)

So there you have it. to_param overriding for pretty URLs.

Testing Gems with multiruby

Thursday, April 2nd, 2009

A couple of days ago I was requested politely and with acknowledgement of my humanity, by Dr Nic to write a post about testing gems on 1.9 and because he’s my boss I’m obliged to do the things he tells me to do. He told me to read his post about Future-proofing Your Ruby Code and in particular the instructions about installing multiruby.

How to install multiruby

You can install multiruby, different versions of ruby, rubygems and useful gems using these commands:

sudo gem install ZenTest
multiruby_setup mri:svn:tag:v1_8_6_114
multiruby_setup mri:svn:tag:v1_8_7_153
multiruby_setup mri:svn:tag:v1_9_1_0

multiruby_setup update:rubygems
multiruby -S gem install --no-ri --no-rdoc --development test-unit

This will:

  1. Install ZenTest (or the latest version)
  2. Install Ruby 1.8.6, build 114
  3. Install Ruby 1.8.7, build 153
  4. Install Ruby 1.9.1, build 0
  5. Setup Rubygems for all three versions and;
  6. install test-unit for all three versions!

You may also want to multiruby -S gem install –no-ri –no-rdoc –development rails mocha rspec.

multiruby is awesome because it sticks this all in your home directory in a folder called .multiruby, so don’t worry about it clashing with your existing version(s) of ruby. Different versions go in different directories inside .multiruby/versions.

numero!

Today we’re going to be testing a gem that I recently made use of on this site. For those of you that visited the site yesterday you would’ve seen something like this:

  112.117.116.115.32.34.104.101.108.108.111.32.119.111.114.108.100.34

This is numero! (exclamation mark compulsory). I don’t know where the idea came from. According to the MSN logs I keep, at 8:03am Friday morning I went and made breakfast and then at 8:13am I had the idea. But it’s awesome. The “language” uses the ASCII representation of all your favourite characters and separates them using a dot. This is numero! script. You run it using the numero! gem. This is how you get it:

sudo gem install radar-numero --source http://gems.github.com

Test it out by running it on your favourite ruby script!

numero favourite.rb

This will generate a file called favourite.numero and then evaluate the newly generated numero! code. Now you can throw away your ugly ruby file and bathe in the glory that is numero by then running:

numero favourite.numero

So it appears we’ve gone off on a tangent here! Fear not! For I want you to test my gem on your new multiruby setup:

git clone git://github.com/radar/numero
cd numero
git checkout -b broken origin/broken

Now this sensible naming schema will probably dissolve the mystery surrounding what’s going to happen next. You’re going to be testing my gem, and it’s going to break.

Testing numero!

When I wrote this particular branch of numero!, I used “a”[0] to get the ASCII representation (97) of the letter a. Generally, I would’ve used use a variable rather than “a”, but this is just an example. The problem is that this doesn’t work the same way in Ruby 1.8.* and Ruby 1.9. If we run multiruby -S test/test_numero.rb we see that it passes on both versions of Ruby 1.8, but fails on Ruby 1.9!

Ruby 1.8.*

Loaded suite test/test_numero
Started
.
Finished in 0.001186 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Ruby 1.9 Loaded suite test/test_numero Started F

Finished in 0.025228 seconds.

1) Failure: testnumerocompiles(TestNumero) [test/test_numero.rb:9]: <”[\"p.u.t.s. .\\".h.e.l.l.o. .w.o.r.l.d.\\"\"]“> expected but was <”112.117.116.115.32.34.104.101.108.108.111.32.119.111.114.108.100.34″>.

1 tests, 1 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications

Not good! This is something I wouldn't have found if I didn't test the functionality on multiple Rubies. Using multiruby is essential to ensure that your code works for everybody. We don't want to end up like Internet Explorer.

The solution, of course, is to use str.unpack("C").first instead. You have two options: 1) change the code yourself in lib/numero.rb to use the afore-mentioned example or 2) git checkout master. Now we run the tests again and now Ruby 1.9 passes:

Ruby 1.8.7

Finished in 0.001109 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Ruby 1.9.1

Finished in 0.001353 seconds.

1 tests, 1 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications

Lovely!

I18n: An Overview

Wednesday, October 29th, 2008

Welcome to yet another overview, this time it’s on the new I18n features in Rails 2.2 which you can install by typing gem install rails -s http://gems.rubyonrails.org -v 2.2.0.

Any mention of the t method in this guide are also spots where you can use translate too, they are just aliased methods (t is aliased to translate), it’s just much easier (for me) to type t than it is to type translate. Please excuse my laziness.

I’ve begun adding in the translation calls for rboard in my personal branch on GitHub and today I would like to show you how I’ve done it.

Firstly I have added these two lines to my config/environment.rb:

config/environment.rb

I18n.loadpath = Dir.glob("#{RAILSROOT}/locales/*.rb")
I18n.default_locale = "en-AU"

This tells rails to load the translation files (aka locale files) from the locales directory in the root of my rails app, and they are in a ruby format. Alternatively you could load yaml files. this also tells it to set the default locale as “en-AU”, which will load locales/en-AU.rb by default.

My (incomplete) translation file looks like this: locales/en-AU.rb

{
  :'en-AU' => {
   :forumheading => "Forum",
   :topicsheading => "Topics",
   :postsheading => "Posts",
   :lastpostheading => "Last Post",
   :noforums => "There are no forums.", 
   :administratorshouldcreateforum => "Maybe an administrator should create one.",
   :youshouldcreateforum => "Maybe you should create a forum.",
   :forumstatistics => "Forum Statistics",
   :postspertopic => "Posts per topic",
   :recentusers => "Users on in the last 15 minutes",
   :registeredusers => "Registered Users",
   :home => "Home",
   :editprofile => "Edit Profile",
   :memberlist => "Member List",
   :search => "Search",
   :newmessage => "new message",
   :logout => "Logout",
   :timenow => "The time is now",
   :viewingforum => "Viewing forum",
   :newtopic => "New Topic",
   :moderationheading => "Moderation",
   :topicheading => "Topic",
   :repliesheading => "Replies",
   :viewsheading => "Views",
   :authorheading => "Author",
   :ago => "ago",
   :by => "by"
  }
}

When I have a string I want translated in my app I will simply call stuff like t(:author_heading) and Rails will look up the correct translation for it, which in this case is just “Author”.

Now if I had another translation file, say locales/es.rb and I had Spanish users on rboard they could select a locale from their profile page and that would store it as a string on their user record. To translate this, we can use a before_filter on the application controller:

app/controllers/application.rb

class ApplicationController < ActionController::Base
  beforefilter :setlocale
  def setlocale
    I18n.locale = currentuser.locale if logged_in?
  end
end

This will set the locale to whatever the user has set, providing that they are logged in.

Interpolation

If you wish to insert a value into a translation you can use interpolation. To do this you can specify the t method call like this:

in a i18n-friendly file somewhere

<%= t(:welcome, :user => current_user.login) %>

And then in your locales file specify this:

locales/en-AU.rb

:welcome => "welcome {{user}}!"

And the output of the translation will now be “welcome Ryan!” or whatever the user login was.

Counting

If you have a translation such as :xnewmessages in your translation file and you want the output of this translation to be correctly pluralized you can pass the count option to this:

In any t method supporting files

<%= t(:x_new_messages, :count => current_user.messages.size) %>

The x_ prefix to our translation is not important, it’s just there to show us that this translation may return different results depending on the count that is passed to it.

Then in your translation file you can do:

locales/en-AU.rb

:xnewmessages => {:zero => 'No new messages', :one => 'One new message', :other => '{{count}} new messages'}

And depending on the value of count it will return one of those three outcomes.

Forcing a Locale

If you want to force a locale on a single translation you can do this by specifying the :locale to the t method call like so:

<%= t(:english, :locale => "en-AU") %>

And this will always show the en-AU translation of the english key in the en-AU.rb locale file.

Alternative Translations

If one of your translations does not match like:

<%= t(:norsk) %>

You can have I18n fall back to any number of other translations:

<%= t(:norsk, :default => [:norwegian, :up_north, :northwards, "norway"]) %>

I18n will attempt to get a default translation from the options specified and will select the first one. If all translations failed then the string version, “norway” will be outputted.

Retrieving Multiple Translations

To get multiple translations back at the same time you can specify an array as the first argument to the t method.

<%= t(:forums, :topics) %>

Assuming you have correct translations for forums and topics you will get the translated versions returned in an array. Assuming you don’t have the correct translations for forums OR topics you will get back a string version of whatever translation is missing, possibly wrapped in a <span class=’translation_missing’></span>.

Further translation files can be found at Sven Fuch’s Github Repository

Single Table Inheritance

Sunday, September 28th, 2008

This tutorial was written using Ruby 1.8.6, Rails 2.1.

A lot of the time I see people asking how they can do something like access levels for their Rails applications and this usually boils down to some STI (single table inheritance) love. You’ll need to have restful_authentication installed.

What is Single Table Inhertiance?

Single Table Inheritance is where you have multiple models that inherit from a single table, hence the name. It’s great for situations like this where we want multiple user access levels, but we don’t want to create multiple tables for the different kinds of users of our application.

Okay.. so how do I use it?

Firstly you start off with one model that inherits from ActiveRecord::Base, and I would advise running script/generate authenticated person session to get this:

app/models/person.rb

class Person < ActiveRecord::Base

end

And then you subclass some other classes from Person app/models/student.rb
class Student < Person

end

app/models/teacher.rb
class Teacher < Person

end

app/models/admin.rb
class Admin < Person

end

The great thing about splitting these into individual files is because they’re more easily managed and closely follows the convention of one model per file.

Now you have a person, student and teacher model and you should have a brand new migration for your people table in db/migrate so go on and open that up and you’ll see something like this:

class CreatePeople < ActiveRecord::Migration
  def self.up
    createtable "people", :force => true do |t|
      t.column :login,                     :string, :limit => 40
      t.column :name,                      :string, :limit => 100, :default => '', :null => true
      t.column :email,                     :string, :limit => 100
      t.column :cryptedpassword,          :string, :limit => 40
      t.column :salt,                      :string, :limit => 40
      t.column :createdat,                :datetime
      t.column :updatedat,                :datetime
      t.column :remembertoken,            :string, :limit => 40
      t.column :remembertokenexpiresat, :datetime

end
add_index :people, :login, :unique => true

end

def self.down droptable "people" end end

To enable STI for this table, just add:
t.column :type, :string
into the create
table block, just after t.column :remembertokenexpires_at, :datetime. This type column will be set to whatever class the record is, so if we create a new Student object, this type column will be set to “Student”.

Controllers

To create new objects for these subclasses I would recommend building a people controller and using the form partial from that to base your form off from the other controllers, such as StudentsController and TeachersController. These controllers should NOT subclass from PeopleController, they should be their own independent controllers because Teachers, People and Students are all individual resource types.

Associated Models

If you have associated models (e.g. Person hasmany :enrolments), any subclass of Person will inherit this relationship. For the relationship, this will be referenced via personid still in the enrolments table.

And Now For Something Completely Useless

Tuesday, September 23rd, 2008
class Model < ActiveRecord::Base
  has_many (Dir.entries("#{RAILS_ROOT}/app/models")-['.','..',"#{self.to_s.downcase}.rb"]).delete_if{|a|
File.directory?("#{RAILS_ROOT/app/models/#{a}")}.rand.gsub(".rb","").pluralize.to_sym
end

A Sense of Belonging

Friday, August 29th, 2008

Numerous times I’ve needed and I’ve seen other people have needed the need to check whether an object belongs to the currently logged in user. I’ve worked out that something like this works:

class User < ActiveRecord::Base
  hasmany :posts
  def owns?(object)
    object.user == self
  end
end
This works when you have a currently logged in user and call it by using currentuser.owns?(@post). Now what if you wanted to do it the other way around? Well it’s really as simple as this:
class Post < ActiveRecord::Base
  belongsto :user
  def belongsto?(otheruser)
    user == otheruser
  end
end
Now you can reference that through @post.belongsto?(otheruser).

If you wanted to use either of these in the controller, it would be like this:

class PostsController < ApplicationController
  def edit
    @post = Post.find(params[:id])
    check_ownership
  end

def update @post = Post.find(params[:id]) if currentuser.owns?(@post) # or @post.belongsto?(currentuser) # carry on... if @post.updateattributes(params[:post]) flash[:success] = "Post updated!" redirectto topicpath(@topic) else flash[:error] = "Post could not be updated." render :action => "edit" end else flash[:error] = "You do not own that post." end end

private def checkownership if !currentuser.owns?(@post) # or @post.belongsto?(currentuser) flash[:error] = "You do not own that post!" redirectbackordefault topicpath(@topic) end end end

Now here we’ve called checkownership in the edit action which will stop the template from being rendered by calling redirectbackordefault. We can’t call (as I found out thanks to BlueSea) checkownership in the same way in the update action because the code will still be executed. So we must call the methods we defined in the model, either currentuser.owns?(@post) or @post.belongsto?(current_user).

Stop Being So Self-Centered!

Tuesday, July 29th, 2008

I’ve seen so many examples of people being self-centered in their code, always calling their methods on self when they don’t need to!

self is a handy reference to the current object (or class) depending on how the method is defined. In a class that inherits from ActiveRecord for example, calling self before defining a method will define that method on that class, rather than the objects of that class. Once inside of the method, you are free to go about as you will; Ruby will know what you mean when you call other methods inside that method.

A prime example would be this self-centered piece of code:

class Forum < ActiveRecord::Base
  acts_as_tree
  
  def to_s
    self.title
  end
  
  def self.find_all_without_parent
    self.find_all_by_parent_id(nil)
  end
  
  def root
    self.parent.nil? ? self : self.parent
  end
  
end

These are a few methods with both good and bad examples of using self.

We'll start with #tos. This method is defined by typing def tos, which tells ruby we want to define a method called "to_s" on all objects of the Forum class. Inside the method however, we have a bad-case of self-centering, by calling self where it's not needed. Ruby already knows that we're operating with an object derived from the forum class, and calling self will only return the same object! So why do we do it here? We can remove the superfluous self call, and the method will still work.

Our next method is self.findallwithout_parent. The self. prefix to the method tells Ruby that we want to define this method on the Forum class itself, rather than a derived object from the class. Inside the method again we have a bad case of self-centering! Ruby already knows we're operating with the Forum class, and calling self will only return the Forum class once more. Again we can remove the superfluous self call and the method will still work.

In both of these examples, you could've called self on self and then the method multiple times: self.self.title or even self.self.self.self.self.title, but that's just going a bit overboard!

In the last method is two incorrect usages of self, and one correct use of self. The method is #root. The method defines a one-lined if statement by calling a method (self.parent.nil?) which will return either true or false. The next character is a space followed by a ?, which indicates to ruby anything after this is what we want executed when self.parent.nil? returns true. Here we just call self, and this is the good use of self. The #root method is attempting to find the root element for the tree heirachy of the forums, if the forum object we're calling root does not have a parent, we want it to return itself as it is the highest level of the forum structure. The next character in the code is a colon (:), which tells Ruby what we want to do if self.parent.nil? returns false. Here we've called self.parent, which again is a superflous call to the self object. We only need to call parent because Ruby already knows which context we are referring to! The final superfluous self call is the self.parent.nil? from where our one-lined if statement originated.

I hope you've enjoyed this little tidbit, and please stop being so self-centered.

UPDATE: RSL has posted an easier way than parent.nil? ? self : parent as the first comment. The code is now simply:

def root
  parent || self
end

UPDATE #2: The root method was still broken as I realised this morning as I tampered with the tests. The method is supposed to get the highest forum in a string of forums. This works perfectly for 2-level-deep forums, but not for 3-level-deep. The third-level element would've returned the second-level element rather than the first. The revised code is now:

  def root
    parent.nil? ? self : (parent.root == parent ? parent : parent.root)
  end

Rescue Correctly!

Monday, July 28th, 2008

Too many times I’ve seen people being over-zealous in rescuing their exceptions. They try doing something like this:

def show
  @forum = Forum.find(params[:id])
  rescue Exception
   flash[:notice] = 'The forum you were looking for does not exist'
end

Which will work when it can’t find a forum, and also when you have a typo. Exception the ancestor class of all other exceptions, and so every exception will trigger this rescue. Try making the code look like this:

def show
  @forum = Forum.find(params[:id)
  rescue Exception
    flash[:notice] = 'The forum you were looking for does not exist'
end

and you’ll wonder why a forum is telling you it doesn’t exist when it obviously does! The easiest way to fix this is to rescue correctly. By rescuing correctly, you prevent hours of potential headaches and your code becomes clearer to what it’s doing. In this example, when a forum object can’t be found it will raise the ActiveRecord::RecordNotFound exception, so this is what you should rescue.

def show
  @forum = Forum.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    flash[:notice] = 'The forum you were looking for does not exist'
end

Another situation is when you’re using #save!. This “destructive” version of #save will raise an ActiveRecord::RecordInvalid or ActiveRecord::RecordNotSaved, depending on the kind of validations you have on your model. I use it here in this example:

def create
  @topic = @forum.topics.build(params[:topic])
  @post = @topic.posts.build(params[:post])
  @topic.save!
  flash[:notice] = 'Topic has been successfully created'
  rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid
    flash[:notice] = 'Topic could not be created'
  ensure
    redirect_to forum_topics_path(@forum)
end

I’ve actually specified two arguments to the rescue method here, the first is ActiveRecord::RecordNotSaved, and the next is ActiveRecord::RecordInvalid. The rescue method uses the splat operator (*), so it can take as many arguments as you can throw at it. I’ve also used another method here ensure. No matter how many exceptions get thrown, the code after the ensure statement will always be ran.

The final thing I would like to cover is rescuing two exceptions, but doing two different things.

def create
  @topic = @forum.topics.build(params[:topic])
  @post = @topic.posts.build(params[:post])
  @topic.save!
  flash[:notice] = 'Topic has been successfully created'
  rescue ActiveRecord::RecordNotSaved
    flash[:notice] = 'Topic could not be created, the record could not be saved'
  rescue ActiveRecord::RecordInvalid
    flash[:notice] = 'Topic could not be created, the record is invalid'
  ensure
    redirect_to forum_topics_path(@forum)
end