require 'yaml'
# Fixtures are a way of organizing data that you want to test against. Each fixture file is created as a row
# in the database and created as a hash with column names as keys and data as values. All of these fixture hashes
# are kept in an overall hash where they can be accessed by their file name.
#
# Example:
#
# Directory with the fixture files
#
# developers/
# david
# luke
# jamis
#
# The file +david+ then contains:
#
# id => 1
# name => David Heinemeier Hansson
# birthday => 1979-10-15
# profession => Systems development
#
# Now when we call @developers = Fixtures.new(ActiveRecord::Base.connection, "developers", "developers/") all three
# developers will get inserted into the "developers" table through the active Active Record connection (that must be setup
# before-hand). And we can now query the fixture data through the @developers hash, so @developers["david"]["name"]
# will return "David Heinemeier Hansson" and @developers["david"]["birthday"] will return Date.new(1979, 10, 15).
#
# This can then be used for comparison in a unit test. Something like:
#
# def test_find
# assert_equal @developers["david"]["name"], Developer.find(@developers["david"]["id"]).name
# end
#
# == YAML fixtures
#
# Additionally, fixtures supports yaml files. Like fixture files, these yaml files have a pre-defined format. The document
# must be formatted like this:
#
# name: david
# data:
# id: 1
# name: David Heinemeier Hansson
# birthday: 1979-10-15
# profession: Systems development
# ---
# name: steve
# data:
# id: 2
# name: Steve Ross Kellock
# birthday: 1974-09-27
# profession: guy with keyboard
#
# In that file, there's two records. Each record must have two parts: 'name' and 'data'. The data that you add
# must be indented like you see above.
#
# Yaml fixtures file names must end with .yaml as in people.yaml or camel.yaml. The yaml fixtures are placed in the same
# directory as the normal fixtures and can happy co-exist. :)
class Fixtures
def self.create_fixtures(fixtures_directory, *table_names)
connection = block_given? ? yield : ActiveRecord::Base.connection
ActiveRecord::Base.logger.level = Logger::ERROR
fixtures = [ table_names ].flatten.collect do |table_name|
Fixtures.new(connection, table_name, "#{fixtures_directory}/#{table_name}")
end
ActiveRecord::Base.logger.level = Logger::DEBUG
return fixtures.size > 1 ? fixtures : fixtures.first
end
def initialize(connection, table_name, fixture_path, file_filter = /^\.|CVS|\.yaml/)
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
@fixtures = read_fixtures
delete_existing_fixtures
insert_fixtures
end
# Access a fixture hash by using its file name as the key
def [](key)
@fixtures[key]
end
# Get the number of fixtures kept in this container
def length
@fixtures.length
end
private
def read_fixtures
Dir.entries(@fixture_path).inject({}) do |fixtures, file|
# is this a regular fixture file?
fixtures[file] = Fixture.new(@fixture_path, file) unless file =~ @file_filter
# is this a *.yaml file?
if file =~ /\.yaml/
YamlFixture.produce( "#{@fixture_path}/#{file}" ).each { |fix| fixtures[fix.yaml_name] = fix }
end
fixtures
end
end
def delete_existing_fixtures
@connection.delete "DELETE FROM #{@table_name}"
end
def insert_fixtures
@fixtures.values.each do |fixture|
@connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES(#{fixture.value_list})"
end
end
def []=(key, value)
@fixtures[key] = value
end
end
class Fixture #:nodoc:
def initialize(fixture_path, file)
@fixture_path, @file = fixture_path, file
@fixture = read_fixture
end
def [](key)
@fixture[key]
end
def to_hash
@fixture
end
def key_list
@fixture.keys.join(", ")
end
def value_list
@fixture.values.map { |v| "'#{v}'" }.join(", ")
end
private
def read_fixture
IO.readlines("#{@fixture_path}/#{@file}").inject({}) do |fixture, line|
key, value = line.split(/ => /)
fixture[key.strip] = value.strip
fixture
end
end
end
# A YamlFixture is like a fixture, but instead of a name to use as
# a key, it uses a yaml_name.
class YamlFixture < Fixture #:nodoc:
# yaml_name is equivalent to a normal fixture's filename
attr_accessor :yaml_name
# constructor is passed the name & the actual instantiate fixture
def initialize(yaml_name, fixture)
@yaml_name, @fixture = yaml_name, fixture
end
# given a valid yaml file name, create an array of YamlFixture objects
def self.produce( yaml_file_name )
results = []
yaml_file = File.open( yaml_file_name )
YAML::load_documents( yaml_file ) do |doc|
f = YamlFixture.new( doc['name'], doc['data'] )
results << f
end
yaml_file.close
results
end
end