Quick tip: store_location with subdomains

Posted by Jon
on Thursday, May 01

Both restful_authentication and the older acts_as_authenticated have a handy method called store_location. This method stores a URL in a session variable for future reference. The obvious use case involves login. For example, if you’re browsing a product anonymously and want to write a review, you’ll need to sign in first. So if you click a link on that product page that requires you to be logged in, and this sends you through the login process, you’ll ideally want to be returned right back to where you were before you logged in. store_location enables this, along with the redirect_back_or_default(), also provided by Rick Olson’s authentication plugins.

You store a location like this:

1
2
3
4
5
6
7

  def private_action
    unless logged_in?
      store_location
      redirect_to login_path
    end
  end

After authenticating the user, you send them back to the stored location with this:

1
2
3
4
5
6

  def login
    if login_successful? # pseudocode, obviously
      redirect_back_or_default(home_path)
    end
  end

If a location is stored in session, redirect_back_or_default will send the user that location. Otherwise, it redirects to the default path.

This is pretty handy. But unfortunately, it doesn’t jump across domains, including subdomains. Tumblon lets parents set up blogs for their families, and these blogs are either identified by a subdomain (e.g. myfamily.tumblon.com) or by a top-level domain (coming soon). Tumblon also has privacy controls, so I can set a story to be viewable only by my family and friends. So if an anonymous user hits the URL of a private photo/story/video, they should be redirected to the login screen and then right back to the item they were trying to view. But out of the box, store_location can’t handle this.

Let’s look at the store_location method to see why. This method is in lib/authenticated_system.rb.

1
2
3
4

    def store_location
      session[:return_to] = request.request_uri
    end

store_location uses the request.request_uri method, which only provides the relative path (e.g. /photos/932783). So if you login at tumblon.com, store_location won’t return you to myfamily.tumblon.com/photos/932783 – it will send you to tumblon.com/photos/932783. Your app could have logic to redirect from this page to the subdomain, but an easier solution is just to create a new store_location method, like store_location_with_domain. Or you could always override the store_location method to always use request.url instead of request.request_uri if you don’t want a separate method.

1
2
3
4

    def store_location_with_domain
      session[:return_to] = request.url
    end

Put this method in application.rb, and you can now use redirect_back_or_default to hit an exact URL – complete with subdomain, top-level domain, and port.

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

Fuzzing your database for fun and profit

Posted by Luke
on Friday, January 25

Fuzz testing is throwing random data at your application and seeing what breaks. We don’t usually do that. But we often do need lots of semi-realistic data added our development database.

This helps you:

  • see how things will look when there’s more in the site.
  • nail down the indexes you’ll need (Queries that run fine with 10 rows of fixture data fall down on 10,000 rows of random data).

It’s possible to do this with fixtures and ERB but I find it tedious. Plus by using Active Record directly you can guarantee that the objects you’re inserting are valid.

First, create a new rake task in lib/tasks/fuzz.rake:

1
2
3
4
5
6
7
8
9
10
11
namespace :db do
  desc 'Insert some random posts'
  task :fuzz => :environment do
    if RAILS_ENV.downcase == "production"
      raise "You can't fuzz your production environment. Think of the children!"
    end
    
    Fuzz.execute(ENV['SIZE'].to_i)
    
  end
end

You’ll call this with rake db:fuzz SIZE=1000. You can actually put all the code in the rakefile, but it’s a little easier to manage to split it out into a separate class.

In lib/fuzz.rb, write something like this example, which finds a random user and adds a post from them to the system SIZE times. The fuzz script could do anything you want, though.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Fuzz
  ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)

  # This file location varies by OS. This is the Mac OS X location.
  # At 2.4M, you have plenty of RAM to read it all into memory!
  @@words = File.open("/usr/share/dict/words").collect do |line|
    line.strip
  end
  
  def self.execute(size)
    if size == 0 or size.nil?
      size = 100
    end

    ActiveRecord::Base.silence {
      User.transaction do
        size.times do
          user = User.find(:first, :order => "rand()")
          user.posts.create!(:body => random_words(rand(30))
        end
        puts "Created #{size} posts"
      end
    }
  end
  
  # provide a string with num random words in it.
  def self.random_words(num = 1)
    w = []
    num.times do
      w << @@words[rand(@@words.size)]
    end
    w.join(" ")
  end
  
end

Silencing the logger and using a transaction makes the code execute faster. Which can be a problem if you’re running 10,000 of these. Another thing you can do to speed things up is disable timestamps, but I’ve found that causes more trouble than it’s worth, because you often want to use those timestamps in your app!

Extra credit: While the data generated from random dictionary words is often hilarious, it’s not very realistic. Use Faker to create more realistic fake data and sometimes to randomize those non-required fields.

Remote MySQL GUI with SSH

Posted by Luke
on Tuesday, August 28

Back in my PHP/MySQL days I used to be quite the MySQL console jockey. I used it for all kinds of stuff. Then I got a new job, moved to DB2 and thankfully forgot as much as I could about MySQL. Now I’m doing Rails and working with MySQL again. But these days I use CocoaMySQL for nosing around the database on my local machines.

On remote servers, I was still using the console, but I recently found this trick which allows you to open up CocoaMySQL on a remote database using an SSH tunnel. The database doesn’t have to be configured to accept connections from outside of localhost.

Here’s how it works.

First, create an SSH tunnel.

ssh -L 8888:example.com:3306 user@example.com

Here I’m connecting the free port 8888 on my local machine to 3306 (the MySQL port) on the remote server, logging in as user.

Then configure CocoaMySQL to use the tunnel. Set the host to 127.0.0.1 and the port to 8888. The user, database, and password will be that of your remote server.

(There’s a section in the config screen to use an SSH tunnel, which I think is supposed to create the tunnel automatically, but I wasn’t able to get that to work.)

I’ve found this tip useful in my work. Hopefully you will too!

Uploads with respond_to

Posted by Luke
on Tuesday, May 08

File this one under “Stupid Rails Tricks.”

You’re probably familiar with using respond_to in your controller to generate different content representations based on the HTTP Accept: header or the URL extension:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def list
  respond_to do |wants|
    wants.html {
      @people = Person.find(:all)
    }
    wants.csv {
      people = Person.find(:all)
      csv = FasterCSV.generate do |csv|
        csv << ["user name", "first name", "last name", "email"]
        people.each do |person|
          csv << [person.user_name, person.first_name, person.last_name, person.email]
        end
      end

      send_data(csv, :filename => 'people.csv', 
                :type => 'text/csv', :disposition => 'attachment')
    }
  end
end

With RESTful routing, the first form gets the named route people_path, and the second form gets formatted_people_path(:csv).

But did you know you can use respond_to on uploads, too?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def create
  respond_to do |wants|
    
    wants.html {
      @person = Person.new(params[:player])
      @person.group = @group        
      if @person.save
        redirect_to people_path(@group)
      else
        flash[:error] = "Person wasn't saved."
        render :template => 'person/new'
      end
    }
    
    wants.csv {
      row_count = import_csv(params[:csv_file]
      flash[:message] = "Imported #{row_count} players."
      redirect_to people_path
    }
  end
end

If you POST a multipart form to formatted_people_path(:csv) you’ll get the CSV creation method.

This can be used to keep your URLs clean and you controllers a little smaller. I’m not sure if it’s very useful, but it is kind of neat.

Simplify your URLs with conditions

Posted by Luke
on Thursday, March 22

Have you ever wondered how Rails’ RESTful Routes create URL mappings that can only be accessed by certain HTTP methods?

For example, map.resources :users creates an entire set of named routes that can only be accessed with the appropriate HTTP method. GET’ing users_path maps to a different controller method than POST’ing to users_path (see the documentation and the free PeepCode cheatsheet for more details).

The answer is the undocumented :conditions parameter for ActionController::Routing::RouteSet#add_route. It’s kind of sad when the best documentation available for one of Rails’ most powerful features is from a Python re-implementation.

But once you know it’s there, it’s really easy to use :conditions to create your own HTTP-savvy routes, simplifying your URLs in the process.

Let’s say you’ve got a form that sends e-mail. You need two actions: one to show the form, and one to send the mail.

1
2
3
4
ActionController::Routing::Routes.draw do |map|
  map.show_form '/contact', :controller => 'contact', :action => 'show_form'
  map.send_mail '/send', :controller => 'contact', :action => 'send_mail'
end

But with :conditions, you can consolidate these two URLs into a single URL that responds differently to GET and POST requests.

1
2
3
4
ActionController::Routing::Routes.draw do |map|
  map.show_form '/contact', :controller => 'contact', :action => 'show_form', :conditions => {:method => :get}
  map.send_mail '/contact', :controller => 'contact', :action => 'send_mail', :conditions => {:method => :post}
end

This keeps your URLs simple, and ensures that your methods are hit with the proper HTTP verb.

P.S.: Interested in how RESTful routes work? Check out the code and take a look at the routes it generates, as produced by the Route Navigator plugin.