Rails 2: Your integration tests are lying

Integration tests call the whole rack middleware stack, but stubbornly return the last controller response, which can be completely different especially if you use warden or other middleware-tools.

See actionpack-2.3.14/lib/action_controller/integration.rb:342

To fix that and prepare for Rails 3 (which also relies on rack response) do this:

# test/test_helper.rb

if Rails::VERSION::MAJOR == 2
  # https://grosser.it/2012/10/19/rails-2-your-integration-tests-are-lying/
  # make integration tests use rack response, so we can test our middlewares
  # and not only the pure controller response

  ActionController::Base.class_eval do
    # this is usually done just-in-time by #process but we need to do it earlier
    include ActionController::Integration::ControllerCapture

    # then we hide last_instantiation from #process
    def self.last_instantiation;end
  end

  ActionController::Integration::Session.class_eval do
    def process_with_rackify(*args)
      process_without_rackify(*args)
    ensure
      # needed e.g. inside of assert_redirect_to
      capture = ActionController::Integration::ControllerCapture::ClassMethods

      # not set by original #process
      @response.redirected_to = @response.headers["Location"] if @response
      if @controller = capture.send(:class_variable_get, :@@last_instantiation)
        @request = @controller.request
        @response.template = @controller.response.template if @controller.response
        @controller.send(:set_test_assigns)
      end
    end
    alias_method_chain :process, :rackify
  end
end

Making sure you are not creating unwanted actions by including Modules

This will blow up if someone includes a module with public methods that would count as actions.

# application_controller_test.rb
class ApplicationControllerTest < ActionController::TestCase
  class CleanController < ApplicationController
  end

  test "should be clean" do
    assert_equal [], CleanController.action_methods.to_a.
      reject{|a| a =~ /^_conditional_callback_around_/}
  end
end

Airbrake error backtrace summary

We often have an error with a few thousand occurances and want to find out which code paths caused it.

Usage
Use auth-token from settings page, not your api-key.

ruby airbrake_backtraces.rb your-account your-auth-token error-id

Output

Trace 1: occurred 597 times
...funky backtraces...
Trace 2: occurred 119 times
...funky backtraces...
Trace 3: occurred 13 times
...funky backtraces...

Code

#! /usr/bin/env ruby
# lists all sources of a given error
# https://grosser.it/2012/09/08/airbrake-error-summary
#
# gem install airbrake-api
# USAGE: ruby airbrake_backtraces.rb your-account your-auth-token error-id
# https://your-account.airbrake.io/errors/ID

require "airbrake-api"

AirbrakeAPI.account = ARGV[0] || raise("need airbrake account as ARGV[0]")
AirbrakeAPI.auth_token = ARGV[1] || raise("need airbrake token as ARGV[1], go to airbrake -> settings, copy your auth token")
AirbrakeAPI.secure = true

error_id = ARGV[2] || raise("need error id")
compare_depth = (ARGV[3] || 4).to_i

notices = AirbrakeAPI.notices(error_id, :pages => 20)
backtraces = notices.select{|n| n.backtrace }.group_by do |notice|
  notice.backtrace.first[1][0..compare_depth]
end

backtraces.sort_by{|k,t| t.size }.reverse.each_with_index do |(key, traces), index|
  puts "Trace #{index + 1}: occurred #{traces.size} times"
  puts key
  puts ""
end

Airbrake search

We need to search for specific errors quiet often, this script helps us find them without having to go through the web interface.

Usage
auth_token can be found on your settings page, it is NOT the api-key.

ruby airbrake_search.rb your-account your-auth-token | grep foo

Code

#! /usr/bin/env ruby
# https://grosser.it/2012/09/08/airbrake-search
# search for errors given a name
#
# gem install airbrake-api
# USAGE: ruby airbrake_search.rb your-account your-auth-token | grep SOMETHING
# https://your-account.airbrake.io/errors/ID

require "airbrake-api"

AirbrakeAPI.account = ARGV[0] || raise("need airbrake account as ARGV[0]")
AirbrakeAPI.auth_token = ARGV[1] || raise("need airbrake token as ARGV[1], go to airbrake -> settings, copy your auth token")
AirbrakeAPI.secure = true

page = 1
while errors = AirbrakeAPI.errors(:page => page)
  errors.each do |error|
    puts "#{error.id} -- #{error.error_class} -- #{error.error_message} -- #{error.created_at}"
  end
  $stderr.puts "Page #{page} ----------\n"
  page += 1
end

Rack::Utils.escape / unescape + CGI.escape/unescape/escapeHTML vs undefined method bytesize for nil

The official solution for this problem is to use e.g. CGI.escape thing.to_str,
my unofficial solution is to automate that 🙂

Code

# https://grosser.it/2012/08/16/rackutils-escape-unescape-cgi-escapeunescapeescapehtml-vs-undefined-method-bytesize-for-nil/
AUTOMATED_TO_STR_FOR_SAFE_BUFFER = <<-RUBY
  def METHOD_with_html_safe(object)
    if object.is_a?(ActiveSupport::SafeBuffer)
      METHOD(object.to_str)
    else
      METHOD_without_html_safe(object)
    end
  end
  alias_method_chain :METHOD, :html_safe
RUBY

# can be removed if
# Rack::Utils.escape "a & & %24".html_safe
# Rack::Utils.unescape "a & & %24".html_safe
# does not raise an error, must work with strings and symbols
Rack::Utils.class_eval do
  [:escape, :unescape].each do |method|
    eval AUTOMATED_TO_STR_FOR_SAFE_BUFFER.gsub("METHOD", method.to_s)
    module_function :"#{method}_without_html_safe"
    module_function method
  end
end

# can be removed if
# CGI.escape "a & & %24".html_safe
# CGI.unescape "a & & %24".html_safe
# CGI.unescapeHTML "a & & %24".html_safe
# does not raise an error, must work with strings and symbols
# (escapeHTML always works)
CGI.class_eval do
  class << self
    [:escape, :unescape, :unescapeHTML].each do |method|
      eval AUTOMATED_TO_STR_FOR_SAFE_BUFFER.gsub("METHOD", method.to_s)
    end
  end
end

Object.send :remove_const, :AUTOMATED_TO_STR_FOR_SAFE_BUFFER