s

Posts Tagged with "tips"

Rails Tips

Jul 13, 2008  -  Comments

While there are no shortages of posts listing tips for Rails, or lesser-known Ruby methods, I decided to write this post because these are shortcuts or helpers that I didn't know about until fairly recently. I'm going to try and update this post as I discover new railties and rubyisms along the way, so check back every now and then.


Symbol#to_proc

This is really a Ruby method, but it makes running a collect or map on ActiveRecord objects much easier. Whereas before, you would do this:

@members = Member.find(:all).map { |member| member.name }

Now, you can simplify it to this:

@members = Member.find(:all).map(&:name)

Model Calculations

This will let you do simple calculations on any ActiveRecord model (as long as the field you're calculating is numeric).

Student.average(:grade) # finds the average grade for all students (returns a float)
Student.maximum(:grade) # finds the highest grade
Student.minimum(:grade) # finds the lowest grade
Student.sum(:grade) # the sum of all grades

Custom ActiveRecord Associations

Let's say you have a model called BlogPost and one called Comment. A blog post has many comments, but you also have a boolean field on comments called approved that signifies if it's been decided that the comment is not spam (like most comments are). This is what your BlogPost model might look like:

class BlogPost < ActiveRecord::Base
  has_many :comments
end

You can add custom associations for approved_comments and unapproved_comments to your model to make fetching these comments a lot easier.

class BlogPost < ActiveRecord::Base
  has_many :comments
  has_many :approved_comments, :class_name => 'Comment', :conditions => 'approved=1'
  has_many :unapproved_comments, :class_name => 'Comment', :conditions => 'approved=0'
end

All you have to do is specify the class name of the associated model and the conditions of the find. This makes it possible to easily access these associations from within your views.

You can now show the number of approved comments for each post.

<%= @blog_post.title %> - <%= @blog_post.approved_comments.count %> Comments

You can also easily list out the approved comments.

<%- @blog_post.approved_comments.each do |comment| -%>
  <%= comment.author %> said:
  <%= comment.content %>
<%- end -%>

UPDATE: Shawn reminded me this week about named_scope, which was added in Rails 2.1. It makes custom associations a bit easier and more full-featured. You can get the same approved and non-approved comments with named_scope:

class Comment < ActiveRecord::Base
  belongs_to :blog_post
  named_scope :approved, :conditions => ["approved = ?", true]
  named_scope :unapproved, :conditions => ["approved = ?", false]
end

Notice that now the named_scope declarations go in the Comment model instead of the BlogPost model like the custom associations. You get the approved comments for a post with this line:

<%= blog_post.comments.approved %>

You can also combine named_scope calls when selecting records. Suppose our Comment model now has a named_scope for both approved and unapproved comments, as well as comments made by anonymous authors.

class Comment < ActiveRecord::Base
  belongs_to :post
  named_scope :approved, :conditions => ["approved = ?", true]
  named_scope :unapproved, :conditions => ["approved = ?", false]
  named_scope :anonymous, :conditions => ["name = ?", 'Anonymous']
end

You can use this to find all of the approved comments for a post where the author is 'Anonymous'.

<%= blog_post.comments.approved.anonymous %>

To add even more functionality to named_scope, you can add conditions to the call on the fly.

<%= post.comments.approved.all(:conditions => ["created_at > ?", 2.weeks.ago]) %>

This will pass the additional date condition to the find, making it possible to refine your searches even more.


Render Partial with Collection/Object

This is how I've always done my partials.

The view (index.html.erb):

<%- @posts.each do |post| -%>
  <%= render :partial => 'post', :locals => {:post => post} %>
<%- end -%>

The partial (_post.html.erb):

<%= post.title %> written by <%= post.author %>

I can simplify the call to the partial down to only one line of code (the partial itself will stay the same).

<div id="all_posts">
  <%= render :partial => 'post', :collection => @posts %>
</div>

By specifying the collection option, the view will call the partial post for each item in the @posts array.

But, let's say I wanted to call the partial for only one post. The old way:

<div id="single_post">
  <%= render :partial => 'post', :locals => {:post => @post} %>
</div>

Not terrible, but we can make it easier with the object option.

<div id="single_post">
  <%= render :partial => 'post', :object => @post %>
</div>

This will call the partial, and pass it only the object that we specify. Shorter and cleaner.


RJS with link_to_function

If you have several things on your page that need to be updated, and you want to do it with a javascript call instead of an AJAX call to the server, you can do it by passing a block to the link_to_function method.

Suppose this is my view:

<div id="list">
  <p id="list_title"><%= @list.title %></p>
  <div id="list_items">
    <%= render :partial => 'item', :collection => @list.items %>
  </div>
  <%= link_to_function 'Change it up' do |page|
    page.replace_html 'list_items', 'New Item Title'
    page.hide 'list_items'
  end %>
</div>

This is a poor example, but hopefully you can see what I'm trying to illustrate here.


Simplified FIND in Rails 2.1

With the release of Rails 2.1, they've added some much-needed find methods for easily getting all, first and last records for a model.

@all_posts = Post.all
@first_post = Post.first
@last_post = Post.last

And, of course, you can pass conditions and order options to the new find methods.

@all_posts = Post.all(:order => :updated_at)
@first_post = Post.first(:conditions => ["title LIKE ?", '%Rails%'])
@last_post = Post.last(:order => 'created_at DESC')

Tagged: railsrubytipstutorial