Skip to content

Instantly share code, notes, and snippets.

@mjwillson
Created November 20, 2008 23:55
Show Gist options
  • Save mjwillson/27283 to your computer and use it in GitHub Desktop.
Save mjwillson/27283 to your computer and use it in GitHub Desktop.
Some general-purpose fixture-loading code which supports references and respects dependencies. Quite short as it hooks into yaml.rb to do most of the work
# This is a mixin for Test::Unit::TestCase, for providing tests with a simple facility to load fixtures from
# YAML files. It aims to do as much as possible in a generic way with the YAML library rather than couple
# tightly to a database library.
#
# Fixtures can use arbitrary YAML to represent ruby objects, using !ruby/object:Some::ClassName where needed.
# you will typically need to implement yaml_initialize on the relevant class, in order for this to
# work in the way you desire (we do so here for Sequel::Model; others including ActiveRecord shouldn't be hard)
#
# Data within one fixture can refer to other fixtures by file and name, using the syntax: !fixture file/name.
# this is achieved by adding a special YAML domain type.
#
# By performing the 'parse' and 'transform' phases of the YAML parser separately, we can do so in a way that
# ensures that ruby objects are instantiated in a correct depth-first order respecting dependencies.
#
# Circular dependencies it won't cope with, though. Addressing these in a suitably general way may be hard; we
# suggest you "join up the dots" yourself post-loading for now, although it may be possible to add some kind
# of generic support for calling a "yaml_reinitialize_after_circular_dependencies_have_been_resolved" method.
#
# As an example: artists.yml
#
# non_phixion: !ruby/object:Artist
# genre: !fixture genres/rap
# similar_to: !fixture artists/arsonists
# rating: 7
#
# arsonists: !ruby/object:Artist
# genre: !fixture genres/rap
# rating: 6
require 'yaml'
module YAMLFixtures
@dir = File.dirname(__FILE__)+'/fixtures'
@fixture_files = {}
@loaded = {}
YAML.add_domain_type('yaml.org,2002', 'fixture') do |type, val|
file, name = val.split('/',2)
load(file, name)
end
def self.fixture_file_yaml_node(file)
data = File.read(File.join(@dir, "#{file}.yml"))
node = YAML.parse(data)
@fixture_files[file] ||= node
end
def self.load(file, name)
@loaded[file] ||= {}
result = @loaded[file][name] and return result
raise "Cyclic dependency" if result == false
puts "* Loading fixture #{file}/#{name}"
@loaded[file][name] = false
# we get the YAML::Syck::Node for only this fixture within the parse tree, and we invoke the transform phase on just this one node at a time,
# This ensures that objects are loaded in the right order when there are fixture references between them.
node = fixture_file_yaml_node(file)[name]
# YAML won't auto-load classes, so we give it a helping hand:
$1.constantize if node.type_id =~ /^tag:ruby.yaml.org,2002:object:(.*)$/
@loaded[file][name] = node.transform
end
def self.load_all(file)
names = fixture_file_yaml_node.value.keys.map {|key| key.value}
result = {}
names.each {|name| result[name] = load(file, name)}
result
end
def self.purge_fixtures
@loaded = {}
end
def load(file, name)
YAMLFixtures.load(file, name)
end
def load_all(file)
YAMLFixtures.load_all(file)
end
def run(*args, &block)
begin
super
ensure
YAMLFixtures.purge_fixtures
end
end
end
class Sequel::Model
def yaml_initialize(type, value_hash)
initialize(value_hash)
save
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment