Rails tips and tricks, part 1

I picked up a few tips and tricks from the Rails Edge conference. Some of them I’ve seen before, but the discussions around them really helped me to see their value. I realize there are those who already know everything (ahem, Chris), but I’ll post all that I can remember, even if I think it’s fairly common.

Using with_scope

with_scope allows you to bind a block of code operating on an active record model to a particular subset of that model’s collection. Make sense? From words, it’s hard to see how this benefits you; but an example might clear things up a bit.

I’m often working in systems that involve users, and I always find myself passing the user_id around, which clutters and duplicates my code. For instance, here’s a case where I want to find a users ‘books’ and create a new ‘book’ for this uers. The old way:

1
2
3
4
5
6
7
user_id = current_user.id

# Getting the books for the current user
@user_books = Book.find(:all, :conditions => ["user_id = ?", user_id])

# Creating a new book for the current user
@book = Book.create(:title => params[:title], :user_id => user_id)

Wouldn’t it be nice to get that user_id stuff out of there? Let’s try the same thing using with_scope:

1
2
3
4
5
6
7
8
9
10
Book.with_scope(:find => {:conditions => "user_id = #{current_user.id}"},
                :create => {:user_id => current_user.id}) do

  # Find all user's books
  @user_books = Book.find(:all)

  # Create a new book without specifying user_id
  @book = Book.create(:title => params[:title])

end

In that example, I didn’t have to clutter up my core operations with excessive parameters, because I was working within scope of the current user. Very handy. And not only does this prevent redundant and messy code, but it can sort of allow you to take extra precaution:

1
2
3
4
5
6
# Prevent orders from being modified
Order.with_scope(:find => {:conditions => {:readonly => true}}) do
  create_invoice(order)
  bill(order)
  adjust_stock(order)
end

This assists in the prevention of the order from accidentally being changed. So… see where it fits in with your code, and try it out!

Using with_options

Another handy method is with_options. I’ve been using this in my route definitions, as I usually map more than one action per controller. However, you can use this in more than your routes.rb file. Let’s say you wanted to set the null option to false for all fields in a create_table migration…

1
2
3
4
5
6
7
8
9
def self.up
  create_table :comments do |table|
    table.with_options(:null => false) do |t|
      t.column :name, :string
      t.column :body, :text
      t.column :ip,   :string
    end
  end
end

It either adds a hash as the last parameter, or adds values into an existing hash parameter. Nifty.

Using &: (Symbol#to_proc)

I remember Chris mentioning the use of &: in his updated permalink method—so I’ve seen this before, but had forgotten about it. However, after seeing it again, it really does look pretty cool. It’s somewhat self explanatory, but here are a few examples to drive it home:

1
2
3
4
5
6
7
8
9
10
11
# Without using to_proc
%w{RYAN HEATH}.map {|word| word.downcase}
# => ["ryan","heath"]
users.map {|person| person.name}
# => ["Ryan","Amie","Heath"]

# With using to_proc
%w{RYAN HEATH}.map(&:downcase)
# => ["ryan","heath"]
users.map(&:name)
# => ["Ryan","Amie","Heath"]

Lookup Constants (“compile-time” caching)

This is a handy way to alleviate your application from executing the same query multiple times. For instance, lets say you have a states table that contains a short_name (state abbreviation) and a long_name (full state name). This may be something used in a registration form, or a purchasing form, or an address form, or whatever. I think it’s safe to assume this is a rather static table, so instead of getting this information from the database each time, we can cache the query results into a constant when the class definition is executed.

1
2
3
4
5
6
class State < ActiveRecord::Base
  STATE_LOOKUP = self.find(:all, :order => "long_name").map do |s|
    [s.long_name, s.short_name]
  end
  # ...
end

You can access your constant by calling State::STATE_LOOKUP.

1
2
State::STATE_LOOKUP
# => [["Alabama","AL"],["Alaska","AK"],["Arizona","AZ"] ... ["Wyoming","WY"]]

And then in a random form, you can use the constant to fill a drop down, rather than querying this information each time the form is loaded.

1
<%= form.select("state", State::STATE_LOOKUP) %>

Ok, I think that’s enough for now. I have a few more, but I’m going to split them up into several posts so it’s not too overwhelming all at once. I should have at least two more posts on the topic, but maybe three. Hopefully you’ve found something useful already, but if not, maybe you will in the next one.

Comments

01

Luke on Tue Feb 20 at 01:23PM

Thats the great thing about Rails: I’m still a beginner, but I can still appreciate what your saying.

Just in case, I think I’ll bookmark this and come back later. Nice post.

02

Chris on Tue Feb 20 at 03:27PM

It’s constructs like with_scope that really prove how powerful and dynamic Ruby is. Is that readonly option a built-in feature? I’ve never seen that one before.

03

Ryan on Tue Feb 20 at 06:36PM

Ooops. No, the readonly is not a built-in feature (to my knowledge, anyway). I intended it to be a field in the database. I guess I should fix that.

Have something to say?
Please rewrite the image text Are You Human? Hint: Are You Human? Formatting Tips

or

© 2009 Ryan Heath | Site Management A Ruby on Rails production.

Get in Touch