Saturday, May 4, 2013

Why did Heroku stop precompiling my assets?

I come into work today to this error:

ActionView::Template::Error ('twitter/bootstrap/bootstrap.less' wasn't found.

Odd...It works in development. What could have happened?

The search

I ran the application locally with the development environment and got the same error. Great, at least it's reproducible. Let's hit the Googles...

Hmm, some mentions in twitter-bootstrap-rails and some scattered StackOverflow questions, but nothing that works. Then I noticed Heroku was no longer precompiling the assets on deploy.

The discovery

The Heroku docs have this glorious quote:

The most common cause of failures in assets:precompile is an app that relies on having its environment present to boot. Your app's config vars are not present in the environment during slug compilation, so you should take steps to handle the nil case for config vars (and add-on resources) in your initializers.

A recent change added some configuration variables in application.rb that referenced some environment variables.

The fix

Based on some more documentation and community help, I moved the configuration into an initializer that is loaded after the slug compilation when it can access the environment variables.

Deploy. Working. #winning.

Wednesday, April 17, 2013

Using ActiveSupport::TimeWithZone without Rails

My fast spec test suite does not load Rails (otherwise it's not so fast, right?), but I want to use the niceness of ActiveSupport::TimeWithZone to create a DateRange class like so:

class DateRange
  class DateRangeError < StandardError; end

  def initialize(range)
    @range = range
    raise_if_not_utc
  end

  def start_date
    range.first.beginning_of_day
  end

  def end_date
    range.last.end_of_day
  end

  private

  attr_reader :range

  def raise_if_not_utc
    if start_date.zone != 'UTC' || end_date.zone != 'UTC'
      raise DateRangeError.new('The date ranges must be in UTC.')
    end
  end
end

The corresponding tests didn't like Time.zone - they thought it was nil. Which it is.

The Fix

The tests need the proper ActiveSupport module loaded, and the time zone needs to be set.

require 'active_support/core_ext'
Time.zone = 'UTC'

Now the time zone is set and the tests will work as expected.

let(:range) { daterange.new(10.days.ago..time.zone.now) }

describe '#start_date' do
  it 'gets the beginning of the day for the earliest date in the range' do
    expect(range.start_date.to_i).to eq(Time.zone.parse('1999-12-31 00:00:00').to_i)
  end

  it 'is in UTC' do
    expect(range.start_date.zone).to eq('UTC')
  end
end

Thursday, April 11, 2013

RSpec won't clear the database between test runs

A quick one that can save a major headache. It seems that RSpec won't clear the database of a record that is defined outside of a let statement, a begin block, or an it block.

describe '#some_method' do
  record = FactoryGirl.create(:my_record)

  it 'does something' do
    # ...
  end
end

In this case, record will not get cleared out between test runs. It's noticeable when checking that a query only returns certain records because it could include an extra one that's unexpected.

The solution is to either declare the record in a let statement, a begin block, or an it block.

Headache gone.