Courtesy of Twitter

Archive for the ‘tutorial’ Category

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.load_path = Dir.glob("#{RAILS_ROOT}/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' => {
   :forum_heading => "Forum",
   :topics_heading => "Topics",
   :posts_heading => "Posts",
   :last_post_heading => "Last Post",
   :no_forums => "There are no forums.", 
   :administrator_should_create_forum => "Maybe an administrator should create one.",
   :you_should_create_forum => "Maybe you should create a forum.",
   :forum_statistics => "Forum Statistics",
   :posts_per_topic => "Posts per topic",
   :recent_users => "Users on in the last 15 minutes",
   :registered_users => "Registered Users",
   :home => "Home",
   :edit_profile => "Edit Profile",
   :member_list => "Member List",
   :search => "Search",
   :new_message => "new message",
   :logout => "Logout",
   :time_now => "The time is now",
   :viewing_forum => "Viewing forum",
   :new_topic => "New Topic",
   :moderation_heading => "Moderation",
   :topic_heading => "Topic",
   :replies_heading => "Replies",
   :views_heading => "Views",
   :author_heading => "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
  before_filter :set_locale
  def set_locale
    I18n.locale = current_user.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 :x_new_messages 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

:x_new_messages => {: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
    create_table "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 :crypted_password,          :string, :limit => 40
      t.column :salt,                      :string, :limit => 40
      t.column :created_at,                :datetime
      t.column :updated_at,                :datetime
      t.column :remember_token,            :string, :limit => 40
      t.column :remember_token_expires_at, :datetime
 
    end
    add_index :people, :login, :unique => true
  end
 
  def self.down
    drop_table "people"
  end
end

To enable STI for this table, just add:

t.column :type, :string

into the create_table block, just after t.column :remember_token_expires_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 has_many :enrolments), any subclass of Person will inherit this relationship. For the relationship, this will be referenced via person_id 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
  has_many :posts
  def owns?(object)
    object.user == self
  end
end

This works when you have a currently logged in user and call it by using current_user.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
  belongs_to :user
  def belongs_to?(other_user)
    user == other_user
  end
end

Now you can reference that through @post.belongs_to?(other_user).

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 current_user.owns?(@post) # or @post.belongs_to?(current_user)
    # carry on...
      if @post.update_attributes(params[:post])
        flash[:success] = "Post updated!"
        redirect_to topic_path(@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 check_ownership
      if !current_user.owns?(@post) # or @post.belongs_to?(current_user)
        flash[:error] = "You do not own that post!"
        redirect_back_or_default topic_path(@topic)
      end
    end
end

Now here we’ve called check_ownership in the edit action which will stop the template from being rendered by calling redirect_back_or_default. We can’t call (as I found out thanks to Blue_Sea) check_ownership 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 current_user.owns?(@post) or @post.belongs_to?(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 #to_s. This method is defined by typing def to_s, 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.find_all_without_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