Courtesy of Twitter

Archive for the ‘rails’ Category

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

Updating a select box based on another

Tuesday, May 20th, 2008

In two projects now I’ve had to use code where I update a select box (machines) in regards to what’s selected in the first (customers). mark[oz] from #rubyonrails asked how to do this last week and I told him I would write a blog post the following night. 5 days later, here it is.

I use this when I’m creating a new call and the user needs to select which customer and then which machine. I have the following in my CallsController:

def new
 
@call = Call.new
 
@customers = Customer.find(:all, :order => "name ASC")
 
end

Relatively simple. We instantiate a new call object so the form knows where to go, and we define @customers so the first select box has some information.

Of course in our Customer model we have:

class Customer < ActiveRecord::Base
has_many :machines
end

And the machine model:

class Machine < ActiveRecord::Base
belongs_to :customer
end

These define the relationships used within the system.

Next is the new.html.erb page itself.

<strong>New Call</strong>
<% form_for @call do |@f| %>
<%= render :partial => "form" %>
<%= submit_tag "Create" %>
<% end %>

And a stripped-down version of the form partial:

<%= @f.label "customer_id" %>
<%= @f.select "customer_id", @customers.map { |c| [c.name, c.id] }%>
<%= observe_field "call_customer_id", :url => by_customer_machines_path, :with => "customer_id" %>
<%= @f.label "machine_id" %>
<%= @f.select "machine_id", "Please select a customer" %>

Right now that we have the form all set up, lets set up our machines controller

class MachinesController < ApplicationController
#CRUD operations go here
 
def by_customer
@machines = Machine.find_by_customer_id(params[:customer_id])
end
end

And our by_customer.html.erb:

<%= options_from_collection_for_select(@machines, "id", "name") %>

And finally the config/routes.rb:

map.resources :customers do |customer|
customer.resources :machines
end
 
map.resources :machines, :collection => { :by_customer => :post }

Now when we select a customer from the top drop down, the machines drop down should be populated too.

Rails 2.1: Migration Timestamping

Thursday, April 3rd, 2008

Rails 2.1 is right around the corner, and with it come a few new changes, and one of them is migration timestamping. Instead of adding a number before the migration, for example 001_create_forums.rb, it now adds a UTC timestamp before it, for example 20080403021817_create_forums.rb.

Now, you may think this is a good idea because it will stop the problem of conflicting version numbers on migrations when two developers are working on the same thing, which happens frequently and even there were a few cases at SeaLink where we had this problem and spent some time sorting it out. With the new timestamping it requires that two developers create migrations at precisely the same second, which is less common to happen than creating migrations with the same version number.

But then we get to the tab-completion. Many developers use the console to do their work, or to make small changes to migrations quickly and easily. Tab-completion is a lifesaver and it fills out the name for you. If you were using vim.ruby and the current version of rails, you would type :e to open the file (assuming you’re already in db/migrate) and type 001 and press tab, it would auto complete the name and you can carry on your merry way. Now think if you created two migrations, each 5 minutes apart on this new Rails 2.1 system. You would have to first type :e and then 200804030220 and then <tab> to get it to tab complete, which is a pretty large number to remember.

I myself don’t mind this new change, as I open migrations using Netbeans 99% of the time. There was a lot of heated debate on this in #rubyonrails recently, and there was little talk on the actual ticket which was accepted rather quickly; not that I’m saying that’s a bad thing. The changeset is also available.