5 little-known Rails methods

Posted by Eric
on Wednesday, April 23

While the next release of Rails appears to be coming up, there’s still plenty of small, useful features from previous releases that aren’t widely used.

A few of my favorites:
  1. query_attribute
  2. polymorphic_path
  3. debug
  4. rake -T `query` ( Not Rails specific, but still handy! )
  5. extract_options!

1. ActiveRecord’s query_attribute

Query methods are available for each of a record’s attributes, providing for a cleaner way to check for the presence of an attribute.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# == Schema Information
# Schema version: 17
#
# Table name: users
#
#  id                    :integer(11)     not null, primary key 
#  first_name            :string(255)     
#  last_name             :string(255) 

# Original
class User < ActiveRecord::Base
  def named?
    !first_name.blank? && !last_name.blank?
  end
end

# Refactored to use query_attribute
class User < ActiveRecord::Base
  def named?
     first_name? && last_name?
  end
end

2. Indifferent links with polymorphic paths.

Rails has polymorphic edit/new/formatted path routing available out of the box. Providing an array will namespace the path with those array parameters. Available Methods: (edit|new|formatted|)polymorphic_path(record_or_hash_or_array)

1
2
3
4
5
6
7
8
9
10

# Before:
<% if @record.is_a?(User) %>
<%= user_path(@record) %>
<% elsif @record.is_a?(Friend)
<%= friend_path(@record) %>
 ... etc.

# After:
<%= polymorphic_path(@record) %>
Quite a few options are supported, and other Rails methods take advantage of polymorphic routing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Paths can be namespaced:
#=> admin/users/5/edit
edit_polymorphic_path([:admin, @record])

# Polymorphic urls are also internally used by helpers:
# redirects to store_path(@store)
redirect_to @store
  
# builds a form with an action to 'new_admin_stores_path'
#=> <form action="admin/stores/new" ... />
form_for([:admin, Store.new])

# <a href="/stores/5">A store in Minneapolis, MN</a>
link_to @store.name, @store

3. debug

Especially useful when starting out a project, this is quick way to understand what objects are being used in the view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


<%= debug @user %>

# Yields this in the view:
# - !ruby/object:User 
#  attributes: 
#    salt: 7be4287a1b27426fa6e5b6d733c707dd66425e82
#    updated_at: 2008-04-22 19:01:23
#    crypted_password: abb611def895dac923ba8ea59a78451f77473d5e
#    id: "1"
#    first_name: Eric
#    last_name: Chapweske
#    created_at: 2008-04-06 01:45:33
#  attributes_cache: {}

4. rake -T task

This is a handy Rake feature, and not limited to Rails. Can’t remember the exact syntax for a particular rake task? Trim the results generated with `rake -T` with an optional search parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

$rake -T

rake annotate_models                 # Add schema information (as comments)...
rake audit:purge                     # Removes Audit records older than 2 m...
rake db:abort_if_pending_migrations  # Raises an error if there are pending...
... etc.
rake tmp:sockets:clear               # Clears all files in tmp/sockets

// Searching by the task's name

$rake -T db:migrate:r

rake db:migrate:redo   # Rollbacks the database one migration and re migrat...
rake db:migrate:reset  # Resets your database using your migrations for the...

5. extract_options!

While not needed very often, Rails comes bundled with a method to extract the options from methods that utilize the splat operator. This method removes the last object from an array if it’s a Hash, otherwise an empty hash is returned.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


class Story < ActiveRecord::Base

  # Example: 
  # Story.published_and_tagged_with('deep', 'thoughts', :order => 'created_at desc') 
  # The generated options for this method look like this: 
  #=> { :include => :tags, :order => 'created_at desc' }
  def self.published_and_tagged_with(*tag_names)
    options = tag_names.extract_options!
    options[:include] ||= :tags
    
    ...
  end
end

References

I wasn’t able to find any write ups on the above methods, so reading the source code may be the best path if you’re curious about their exact implementations.

Cleaner code with Conversions

Posted by Eric
on Friday, February 29

Rails comes with built in support for easily customizing the to_s method. It’s something that’s pretty well documented in the source, but rarely used in practice, which is unfortunate, because the common alternative approach is a bit messy:

1
2

Date.today.strftime('%B %e, %Y') #=> "2008-02-29"

A small, but useful improvement:

1
2

Date.today.to_s(:long) #=> "February 29, 2008"

The underlying implementation is straightforward, with some classes (Date, Time, DateTime, and Range) allowing you to add custom formats. The advantage is all the formatting logic is kept in one place, and available via a unified interface, rather than defined randomly throughout the application.

Write a custom conversion format

Rolling a custom conversion is as easy as adding an entry to the classes’s format hash:

config/initializers/conversions.rb
1
2
3
4
5

# Formats the time using strftime.
# Example: Time.now.to_s(:event) #=> "03:23PM" 

Time::Conversions::DATE_FORMATS.update(:event => '%I:%M%p')

In addition to strings, lambdas are also supported. If the value is a callable object instead of a string, the result of that call will be returned:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Formats the date using a lambda.
# Example: Date.today.to_s(:event) #=> "February 29th"

date_formats = { :event => lambda { |date| date.strftime("%B #{date.day.ordinalize}") } }
Date::Conversions::DATE_FORMATS.update(date_formats)

# Examples: 
# (1.day.ago..5.days.from_now).to_s(:event) #=> "February 28th to March 5th"
# (1.hour.ago..3.hours.from_now).to_s(:event) #=> "3:10PM to 7:10PM"
                                          
range_formats = { :event => lambda do |start, stop| 
                              [ start.to_s(:event), stop.to_s(:event) ].join(" to ") 
                            end }
Range::Conversions::DATE_FORMATS.update(range_formats)

A common situation is where both the Date and Time objects should share the same formats. Keeping the format definitions in the same initializer makes this easy:

1
2
3
4
5
6
7
8

  date_formats = { :event => lambda { |date| date.strftime("%B #{date.day.ordinalize}") },
                   :story => ... }

  
  Date::Conversions::DATE_FORMATS.update(date_formats)
  Time::Conversions::DATE_FORMATS.update(date_formats)
  # DateTime uses Time's DATE_FORMATS, so there's nothing to update for it.

A note on naming

Coming up with an easy to remember, expressive name for formats is a bit challenging. The default Rails formats take the approach of trying to describe the result in their names (:short, :long, :long_ordinal). In larger projects, it’s difficult to remember what each format does and where it should be used.

A naming system that’s working a bit better are formats named after their intended use case (:event, :blog, :hours, :event_hours, etc.)

API References