strapyourself.in and flouri.sh

Preloading fixtures

May 31st, 2007

Ever wonder why it takes so gosh darn long to run your tests?

Do you hate it when forgetting to load a valuable fixture breaks your test?

In my experience, long test times are due to two reasons: fixture loading and fixture instantiation

If you don't use instantiated fixtures such as @users_one, and instead use users(:one), then you can safely turn off instantiated fixtures, saving you all the time of finding the data. But what about fixture load time? If you use transactional fixtures, the loading only takes place once per test file, which could be 100-150 times in a given project. Without transactional fixtures, they are loaded before every test, clearly a waste of time!

My solution was to develop a plugin which proloads and preinstantiates the fixtures only once (well, 3 times was the best I could get). It takes advantage of transactions to rollback the state of the database between each individual test (don't try this on MySQL with bad storage engines like MyIASM, kids). It usually takes a few seconds, of course, but cut a down "rake test" for a large project of ours from 20 minutes to 90 seconds. Plus, you don't have to remember which fixtures you need for every single test file anymore! Try adding the globalization plugin, for example, and you'll realize you need 2 fixtures on every single page.

In my opinion, there's no reason not to preload all your fixtures once before running tests, but that's up to you to decide.

Plugin Usage

  1. Install the plugin
  2. Add the following to test/test_helper.rb
    PreloadFixtures.preload!
    PreloadFixtures.instantiate!(Test::Unit::TestCase)
    
  3. The "fixtures" function in a test is now overloaded to do nothing. All fixtures in the directory will be loaded once and then the tests will be run.

Download Plugin

From our svn respository

./script/plugin install https://wush.net/svn/public/preload_fixtures

Originally posted on ELC

See ELC's other Rails Plugins

Using and testing multiple databases in rails part 2

May 30th, 2007

I previously published a plugin on this blog that allows you to easily use, test, and migrate multiple databases in rails.

Since then, I've found numerous problems and areas that could be improved about this process. Here they are:

  • All databases are automatically cloned through rake tasks that are dependent on standard "rake test" commands.
  • Transactions are properly set up across ALL open connections during testing (if transactional fixtures are enabled)
  • This plugin is now in use in a large-scale production environment!
  • Compatibility with my preload fixtures plugin to make fixture loading super fast and transactional (coming soon to this blog)

Examples of how to use it:

To point a model at a different database:

class OtherDbBase < ActiveRecord::Base
  use_db :prefix => "otherdb"
  # will look for otherdb_development, otherdb_test, etc. in database.yml
end

To have a migration run on a different database

class AddOtherDbStuff < ActiveRecord::Migration
  def self.database_model
    OtherDbBase
  end
end

Sample config/use_db.yml file:

db1:
  prefix: db1_
db2:
  prefix: db2_ 

To run tests across multiple databases (requires file config/use_db.yml):

rake test

To have fixtures load into a different database:

# Do nothing!  Just name the fixture the same name as the model is represents (in our case it would be other_db_bases.yml), and the fixture loader will use that class's database connection to insert fixtures.

# I realize this might limit some people, especially those with habtm fixutres on other DBs (since there's no model).  I'd love your input on where we should specify which DB connection a fixture should use to load itself.

Want to download this plugin? Download from ELC's SVN repository OR:

./script/plugin install https://wush.net/svn/public/use_db

Originally posted on ELC

See ELC's other Rails Plugins

Why associated models don't save

May 21st, 2007

Acts_as_taggable conflicting with has_one

In our dashboard app, we attempted to use the following in a controller:

def create
  @employee = Employee.new(params[:employee])
  @employee.address = Address.new(params[:address])
  @employee.save
end

class Employee < ActiveRecord::Base
  has_one :address, :as => :addressable
  acts_as_taggable
end

Result: tags in params[:employee][:tag_list] weren't being saved correctly. I then changed the order in the model:

class Employee < ActiveRecord::Base
  acts_as_taggable
  has_one :address, :as => :addressable
end

The tag_list gets saved! What gives?

Failed validation weirdness

The culprit in this case was validation. There are two after_save filters involved: acts_as_taggable and has_one. Acts_as_taggable always returns true, continuing the the filter chain, regardless of the success of tagging/tag updating (I this that's a bug actually). Not the case for has_one, however, where the rails system checks that the associated model saves before continuing the filter chain. In our test, we were not passing in enough params[:address] to make the Address.new save correctly.

class Address < ActiveRecord::Base
  belongs_to :addressable, :polymorphic => true
  
  validates_presence_of :street, :city, :state, :postal, :country

This situation was our own fault, but nonetheless not trivial to debug. Rails did not help us understand what went wrong, just that no SQL to save the tags was ever getting run.

class Employee < ActiveRecord::Base
  has_one :address, :as => :addressable
  validates_associated :address
  acts_as_taggable
end

Moral of the story: always use validates_associated for has_one and has_many associations because they will not stop the main model from being saved if they fail validation!

Originally posted on ELC

original design by gorotron ported by railsgrunt powered by mephisto