Posts Tagged ‘rails’

Clickable Stack Traces on your Rails Error Page

Wednesday, April 30th, 2008

Wouldn’t it be nice if when an error happened in your application, you could not only see the stack trace, but click on a line and jump to the offending code? This is not groundbreaking stuff, I know, I had this like 10 years ago in C++ and later Java fat clients, and I’m sure other languages & IDEs had it too – but somehow in moving to writing web apps in Ruby, I lost it.

I want it back damnit!

Turns out it’s pretty easy to get back (at least it is if you use textmate) – check it out.

Custom Error Page

First, to get a custom error page for your project, add something like this to your application.rb :

def rescue_action_locally(*args)
  render :template  => "application/public_error", :layout => false
end
 
alias rescue_action_in_public render_action_locally

Note, if you’re using exception notifiable, you probably want to change the last line to something like :

alias render_404 rescue_action_locally
alias render_500 rescue_action_locally

We use markaby, so our public_error template looks something like this; it’s probably a good idea to keep this simple and not use a layout, just in case the error came from the layout :

html do
  head do
    title action_name
    stylesheet_link_tag 'error'
  end
  body do
    div.error do
      div.message do
        h1 "Whoops"
 
        p "We detected an error.  Don't worry, though, 
we've been notified and we're on it."
      end
    end
  end
end

Adding a Stack Trace w/ Links to the Error Page

So, it would be helpful to us for our error page to tell us more in our development and staging environments. We do use exception notifiable, so we don’t actually need or want it to say anything else to a real user in production. Adding this to our template, it now looks like this :

html do
  head do
    title action_name
    stylesheet_link_tag 'error'
  end
  body do
    div.error do
      div.message do
        h1 "Whoops"
 
        p "We detected an error.  Don't worry, though, 
we've been notified and we're on it."
      end
 
      if RAILS_ENV != 'production'
        div.stack_trace do
          h2 "Stack Trace"
          div { link_to_code $!.to_s.to_s.gsub("\n", "
") }
          hr
          div { link_to_code $!.backtrace.join("
") }
        end
      end
    end
  end
end

What’s that “link_to_code” method in there?

It’s a method in application_helper that replaces any path with a textmate url to open up that file on your local system and jump to the offending line. Check it out :

def link_to_code(text)
  text.gsub(/([\w\.-]*\/[\w\/\.-]+)\:(\d+)/) do |match|
    file = $1.starts_with?("/") ? $1 : File.join(RAILS_ROOT, $1)
    link_to match, "txmt://open?url=file://#{file}&line=#{$2}"
  end
end

That’s it. Suddenly, stack traces are friendly again!

Using Markaby w/ Rails 2.0.1

Thursday, December 13th, 2007

Rails 2.0.1 is out, in general, it’s pretty nice. Markaby doesn’t play too nice w/ it. Markaby requires every named route to have a .to_s on the end of it. Yuck.

After migrating my 3rd project to rails 2.0.1, I got sick of adding .to_s to each named routes, and did some digging.

Apparently, somehow a string coming from a named route is not exactly a string. Sure

  apples_path().class == String

but

  String === apples_path()

is NOT true. I couldn’t quite track down why this was but it eventually leads to a

ActionView::TemplateError (undefined method `string_path' for #) on line #10 of messages/index.mab:

actionpack-2.0.1/lib/action_controller/polymorphic_routes.rb:27:in `send!'
actionpack-2.0.1/lib/action_controller/polymorphic_routes.rb:27:in `polymorphic_url'
actionpack-2.0.1/lib/action_controller/polymorphic_routes.rb:31:in `polymorphic_path'
actionpack-2.0.1/lib/action_view/helpers/url_helper.rb:79:in `url_for'
actionpack-2.0.1/lib/action_view/helpers/url_helper.rb:144:in `link_to'
...

After an hour of trying in vain to find the source of the problem, I found a simple hack that fixes this. Should have thought of this before, but add this code to your application_helper.rb

  def string_path(string)
    string
  end

If anyone else has a better solution, please pass it over!

Daemonizing a Ruby Script in Rails

Thursday, September 20th, 2007

This took way longer than it should have, so I thought I’d jot down what I did so it might take less time next time.

1. Install the daemons gem

sudo gem install daemons

2. Presumably you have a script that looks something like this already

#!/usr/bin/env ruby
require File.dirname(__FILE__) + "/. ./config/environment"
 
SchedulerDaemon.new.run

This is a scheduler script that lives in the scripts directory of a rails project.

3. You’re going to take this code and wrap it in daemon stuff, like so

#!/usr/bin/env ruby
RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/. .')
 
require 'rubygems'
gem 'daemons'
require 'daemons'
 
Daemons.run_proc("scheduler", 
                 :log_output => true, 
                 :dir_mode => :normal, 
                 :dir => "#{RAILS_ROOT}/log") do
  require File.join(RAILS_ROOT, "config/environment")
  SchedulerDaemon.new.run
end

The really hard part for me was debugging it. Which meant figuring out how to get logging going. With this code, you can just tail “log/scheduler.output” and see the contents of any puts in the code. Once I started doing that, everything else was easy.

RAILS_ROOT has to be set first, because daemons changes the current working directory. Also, I use the log dir, because it’s shared by capistrano, so I can deploy, then stop / start my scheduler and not worry about losing my first pid file – plus that’s where logs are supposed to go.

4. To make my life just a little easier, I also added some debugging statements

Daemons.run_proc("scheduler", 
                 :log_output => true, 
                 :dir_mode => :normal, 
                 :dir => "#{RAILS_ROOT}/log") do
  begin
    require File.join(RAILS_ROOT, "config/environment")
 
    puts "starting scheduler at #{Time.now} for #{RAILS_ENV}"
    SchedulerDaemon.new.run
 
  ensure
    puts "ending scheduler at #{Time.now}"
  end
end

This was actually pretty easy, and next time it will take 5 minutes to create a daemon. Nice gem.

Object Mother…in rails?

Tuesday, March 6th, 2007

That’s right, the circa 2000 pattern still makes sense today. Use an “object mother” as a test factory to conveniently create objects for your unit tests to bang against. It will often default values, or have different states in which to create objects. For example, you might have a new_user, as well as a new_superuser and new_guest method all of which return users.

Read about the original pattern

But why, you ask, not just use rails’ fixtures? Glad you asked.

  1. it’s more intuitive and maintainable to setup the data you need right next to the test
  2. it’s easier to create just exactly the objects you need when you have test factory methods

But don’t take my word for it. Let’s look at some code.

First off, what does this look like in ruby? I am creating an “ObjectMother” module, which I then just mixin to my tests. A test might then look like :

  include ObjectMother
 
  def test_delete_project
    project = new_project('foo')
    post :delete, :id => project.id
    assert_raise(ActiveRecord::RecordNotFound) { Tag.find(project.id)}
  end

The magic is in “new_project”. It’s an entirely pragmatic construct. If you pass it a string, it will set everything else to acceptable defaults, and use that string as a name. It looks something like this.

module ObjectMother
  def new_project(options)
    options = {:name => options} if options.is_a? String
    options[:url_name] = options[:name].gsub(/\W/, '') if !options.has_key?(:url_name)
    Project.create!(options)
  end
 
  ...
end

Here in project, it defaults the url_name (which must be unique) from the name you’ve given it. However, you could also create a more custom project by running this :

  new_project(:name => 'garage', :url_name => 'the_garage', :description => 'foo')

This is how it works for projects, but it’s only purpose in life is to make my life easier and reduce the amount of code one has to type or read. So each type of thing it creates works a little bit differently according to our needs
——
h2. A more complicated example

I was playing with ferret last week, and wrote a test that looked like this :

  def test_across_types
    project = new_project('rabbit holes')
    post = new_post(:subject => 'a rabbit has a big head')
    user = new_user(:display_name => 'rabbit head')
 
    @search.string = 'rabbit'
    assert_find [project, post, user]
 
    @search.string = 'rabbit head'
    assert_find [post, user]
  end

My thought process was something along the lines of :

  1. I want to test that my searcher works across types
  2. I need to create a project, post, and user that all have a term in them (in different places)
  3. I want to search for the term, and make sure i get all of them
  4. I want to search for a term that maybe 2 of them have and make sure I only get those 2

Writing the test for this part literally took 30 seconds, I didn’t have to go lookup the fixtures or add a new fixture for my new case. I also didn’t have to remember all the things that it takes to make a valid project or post or user.

I’m Using Ruby on Rails

Wednesday, January 26th, 2005

So it may look like the old site, but you are looking at brand spanking rewrite.

OneMansWalk now sits on top of RubyOnRails. And it’s nice. Very, very nice. For the first time I could actually see Ruby as something that I would actually ask a client to consider for a web application.

It still is file based which lets me use cvs as a way to work in a disconnected fashion. However, I’ve started separating content from logic, so the site is now a project on RubyForge at http://rubyforge.org/projects/blinki/ and doesn’t include any content :).

I’ve had a lot of requests to get a site like this up and running for my friends, so next up I’m going to try to install it on one of their machines and then work on packaging it for non programmers. I want to create a windows installer that installs apache, ruby, imagemagick, and this source code. We’ll see how that goes. It will be successful when my mom can install it :)

One note : if you have a link to my rss feed, you’ll need to update it, the new link is

http://www.onemanswalk.com/wiki/rss