--- /dev/null
+require 'erb'
+require 'yaml'
+require 'csv'
+require 'active_support/test_case'
+
+if RUBY_VERSION < '1.9'
+ module YAML #:nodoc:
+ class Omap #:nodoc:
+ def keys; map { |k, v| k } end
+ def values; map { |k, v| v } end
+ end
+ end
+end
+
+if defined? ActiveRecord
+ class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
+ end
+else
+ class FixtureClassNotFound < StandardError #:nodoc:
+ end
+end
+
+# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavors:
+#
+# 1. YAML fixtures
+# 2. CSV fixtures
+# 3. Single-file fixtures
+#
+# = YAML fixtures
+#
+# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
+# in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
+#
+# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
+# by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
+# put your files in <tt><your-rails-app>/test/fixtures/</tt>). The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
+# <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a YAML fixture file looks like this:
+#
+# rubyonrails:
+# id: 1
+# name: Ruby on Rails
+# url: http://www.rubyonrails.org
+#
+# google:
+# id: 2
+# name: Google
+# url: http://www.google.com
+#
+# This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an
+# indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
+# pleasure.
+#
+# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
+# for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
+# This is commonly needed for tree structures. Example:
+#
+# --- !omap
+# - parent:
+# id: 1
+# parent_id: NULL
+# title: Parent
+# - child:
+# id: 2
+# parent_id: 1
+# title: Child
+#
+# = CSV fixtures
+#
+# Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
+# in a single file, but instead end with the <tt>.csv</tt> file extension
+# (Rails example: <tt><your-rails-app>/test/fixtures/web_sites.csv</tt>).
+#
+# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
+# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
+# of the actual data (1 per line). Here's an example:
+#
+# id, name, url
+# 1, Ruby On Rails, http://www.rubyonrails.org
+# 2, Google, http://www.google.com
+#
+# Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
+# need to use a double quote character, you must escape it with another double quote.
+#
+# Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
+# fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
+# number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
+# "web_site_2".
+#
+# Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
+# have existing data somewhere already.
+#
+# = Single-file fixtures
+#
+# This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
+# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
+# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
+# put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> --
+# like <tt><your-rails-app>/test/fixtures/web_sites/</tt> for the WebSite model).
+#
+# Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
+# extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension. Here's what the
+# above example might look like:
+#
+# web_sites/google
+# web_sites/yahoo.txt
+# web_sites/ruby-on-rails
+#
+# The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
+# of "name => value". Here's an example of the ruby-on-rails fixture above:
+#
+# id => 1
+# name => Ruby on Rails
+# url => http://www.rubyonrails.org
+#
+# = Using Fixtures
+#
+# Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
+# fixtures, but first let's take a look at a sample unit test:
+#
+# require 'web_site'
+#
+# class WebSiteTest < ActiveSupport::TestCase
+# def test_web_site_count
+# assert_equal 2, WebSite.count
+# end
+# end
+#
+# As it stands, unless we pre-load the web_site table in our database with two records, this test will fail. Here's the
+# easiest way to add fixtures to the database:
+#
+# ...
+# class WebSiteTest < ActiveSupport::TestCase
+# fixtures :web_sites # add more by separating the symbols with commas
+# ...
+#
+# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here though), we trigger
+# the testing environment to automatically load the appropriate fixtures into the database before each test.
+# To ensure consistent data, the environment deletes the fixtures before running the load.
+#
+# In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
+# of the test case. It is named after the symbol... so, in our example, there would be a hash available called
+# <tt>@web_sites</tt>. This is where the "fixture name" comes into play.
+#
+# On top of that, each record is automatically "found" (using <tt>Model.find(id)</tt>) and placed in the instance variable of its name.
+# So for the YAML fixtures, we'd get <tt>@rubyonrails</tt> and <tt>@google</tt>, which could be interrogated using regular Active Record semantics:
+#
+# # test if the object created from the fixture data has the same attributes as the data itself
+# def test_find
+# assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
+# end
+#
+# As seen above, the data hash created from the YAML fixtures would have <tt>@web_sites["rubyonrails"]["url"]</tt> return
+# "http://www.rubyonrails.org" and <tt>@web_sites["google"]["name"]</tt> would return "Google". The same fixtures, but loaded
+# from a CSV fixture file, would be accessible via <tt>@web_sites["web_site_1"]["name"] == "Ruby on Rails"</tt> and have the individual
+# fixtures available as instance variables <tt>@web_site_1</tt> and <tt>@web_site_2</tt>.
+#
+# If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.
+#
+# - to completely disable instantiated fixtures:
+# self.use_instantiated_fixtures = false
+#
+# - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance:
+# self.use_instantiated_fixtures = :no_instances
+#
+# Even if auto-instantiated fixtures are disabled, you can still access them
+# by name via special dynamic methods. Each method has the same name as the
+# model, and accepts the name of the fixture to instantiate:
+#
+# fixtures :web_sites
+#
+# def test_find
+# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
+# end
+#
+# = Dynamic fixtures with ERb
+#
+# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
+# mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
+#
+# <% for i in 1..1000 %>
+# fix_<%= i %>:
+# id: <%= i %>
+# name: guy_<%= 1 %>
+# <% end %>
+#
+# This will create 1000 very simple YAML fixtures.
+#
+# Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
+# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
+# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
+# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
+#
+# = Transactional fixtures
+#
+# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
+# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
+#
+# class FooTest < ActiveSupport::TestCase
+# self.use_transactional_fixtures = true
+# self.use_instantiated_fixtures = false
+#
+# fixtures :foos
+#
+# def test_godzilla
+# assert !Foo.find(:all).empty?
+# Foo.destroy_all
+# assert Foo.find(:all).empty?
+# end
+#
+# def test_godzilla_aftermath
+# assert !Foo.find(:all).empty?
+# end
+# end
+#
+# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
+# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
+#
+# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
+# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
+#
+# When *not* to use transactional fixtures:
+# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
+# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
+# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
+# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
+# Use InnoDB, MaxDB, or NDB instead.
+#
+# = Advanced YAML Fixtures
+#
+# YAML fixtures that don't specify an ID get some extra features:
+#
+# * Stable, autogenerated ID's
+# * Label references for associations (belongs_to, has_one, has_many)
+# * HABTM associations as inline lists
+# * Autofilled timestamp columns
+# * Fixture label interpolation
+# * Support for YAML defaults
+#
+# == Stable, autogenerated ID's
+#
+# Here, have a monkey fixture:
+#
+# george:
+# id: 1
+# name: George the Monkey
+#
+# reginald:
+# id: 2
+# name: Reginald the Pirate
+#
+# Each of these fixtures has two unique identifiers: one for the database
+# and one for the humans. Why don't we generate the primary key instead?
+# Hashing each fixture's label yields a consistent ID:
+#
+# george: # generated id: 503576764
+# name: George the Monkey
+#
+# reginald: # generated id: 324201669
+# name: Reginald the Pirate
+#
+# Active Record looks at the fixture's model class, discovers the correct
+# primary key, and generates it right before inserting the fixture
+# into the database.
+#
+# The generated ID for a given label is constant, so we can discover
+# any fixture's ID without loading anything, as long as we know the label.
+#
+# == Label references for associations (belongs_to, has_one, has_many)
+#
+# Specifying foreign keys in fixtures can be very fragile, not to
+# mention difficult to read. Since Active Record can figure out the ID of
+# any fixture from its label, you can specify FK's by label instead of ID.
+#
+# === belongs_to
+#
+# Let's break out some more monkeys and pirates.
+#
+# ### in pirates.yml
+#
+# reginald:
+# id: 1
+# name: Reginald the Pirate
+# monkey_id: 1
+#
+# ### in monkeys.yml
+#
+# george:
+# id: 1
+# name: George the Monkey
+# pirate_id: 1
+#
+# Add a few more monkeys and pirates and break this into multiple files,
+# and it gets pretty hard to keep track of what's going on. Let's
+# use labels instead of ID's:
+#
+# ### in pirates.yml
+#
+# reginald:
+# name: Reginald the Pirate
+# monkey: george
+#
+# ### in monkeys.yml
+#
+# george:
+# name: George the Monkey
+# pirate: reginald
+#
+# Pow! All is made clear. Active Record reflects on the fixture's model class,
+# finds all the +belongs_to+ associations, and allows you to specify
+# a target *label* for the *association* (monkey: george) rather than
+# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
+#
+# ==== Polymorphic belongs_to
+#
+# Supporting polymorphic relationships is a little bit more complicated, since
+# Active Record needs to know what type your association is pointing at. Something
+# like this should look familiar:
+#
+# ### in fruit.rb
+#
+# belongs_to :eater, :polymorphic => true
+#
+# ### in fruits.yml
+#
+# apple:
+# id: 1
+# name: apple
+# eater_id: 1
+# eater_type: Monkey
+#
+# Can we do better? You bet!
+#
+# apple:
+# eater: george (Monkey)
+#
+# Just provide the polymorphic target type and Active Record will take care of the rest.
+#
+# === has_and_belongs_to_many
+#
+# Time to give our monkey some fruit.
+#
+# ### in monkeys.yml
+#
+# george:
+# id: 1
+# name: George the Monkey
+# pirate_id: 1
+#
+# ### in fruits.yml
+#
+# apple:
+# id: 1
+# name: apple
+#
+# orange:
+# id: 2
+# name: orange
+#
+# grape:
+# id: 3
+# name: grape
+#
+# ### in fruits_monkeys.yml
+#
+# apple_george:
+# fruit_id: 1
+# monkey_id: 1
+#
+# orange_george:
+# fruit_id: 2
+# monkey_id: 1
+#
+# grape_george:
+# fruit_id: 3
+# monkey_id: 1
+#
+# Let's make the HABTM fixture go away.
+#
+# ### in monkeys.yml
+#
+# george:
+# name: George the Monkey
+# pirate: reginald
+# fruits: apple, orange, grape
+#
+# ### in fruits.yml
+#
+# apple:
+# name: apple
+#
+# orange:
+# name: orange
+#
+# grape:
+# name: grape
+#
+# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
+# on George's fixture, but we could've just as easily specified a list
+# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
+# the fixture's model class and discovers the +has_and_belongs_to_many+
+# associations.
+#
+# == Autofilled timestamp columns
+#
+# If your table/model specifies any of Active Record's
+# standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
+# they will automatically be set to <tt>Time.now</tt>.
+#
+# If you've set specific values, they'll be left alone.
+#
+# == Fixture label interpolation
+#
+# The label of the current fixture is always available as a column value:
+#
+# geeksomnia:
+# name: Geeksomnia's Account
+# subdomain: $LABEL
+#
+# Also, sometimes (like when porting older join table fixtures) you'll need
+# to be able to get ahold of the identifier for a given label. ERB
+# to the rescue:
+#
+# george_reginald:
+# monkey_id: <%= Fixtures.identify(:reginald) %>
+# pirate_id: <%= Fixtures.identify(:george) %>
+#
+# == Support for YAML defaults
+#
+# You probably already know how to use YAML to set and reuse defaults in
+# your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
+#
+# DEFAULTS: &DEFAULTS
+# created_on: <%= 3.weeks.ago.to_s(:db) %>
+#
+# first:
+# name: Smurf
+# <<: *DEFAULTS
+#
+# second:
+# name: Fraggle
+# <<: *DEFAULTS
+#
+# Any fixture labeled "DEFAULTS" is safely ignored.
+
+class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
+ DEFAULT_FILTER_RE = /\.ya?ml$/
+
+ @@all_cached_fixtures = {}
+
+ def self.reset_cache(connection = nil)
+ connection ||= ActiveRecord::Base.connection
+ @@all_cached_fixtures[connection.object_id] = {}
+ end
+
+ def self.cache_for_connection(connection)
+ @@all_cached_fixtures[connection.object_id] ||= {}
+ @@all_cached_fixtures[connection.object_id]
+ end
+
+ def self.fixture_is_cached?(connection, table_name)
+ cache_for_connection(connection)[table_name]
+ end
+
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
+ if keys_to_fetch
+ fixtures = cache_for_connection(connection).values_at(*keys_to_fetch)
+ else
+ fixtures = cache_for_connection(connection).values
+ end
+ fixtures.size > 1 ? fixtures : fixtures.first
+ end
+
+ def self.cache_fixtures(connection, fixtures_map)
+ cache_for_connection(connection).update(fixtures_map)
+ end
+
+ def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
+ object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
+ if load_instances
+ ActiveRecord::Base.silence do
+ fixtures.each do |name, fixture|
+ begin
+ object.instance_variable_set "@#{name}", fixture.find
+ rescue FixtureClassNotFound
+ nil
+ end
+ end
+ end
+ end
+ end
+
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
+ all_loaded_fixtures.each do |table_name, fixtures|
+ Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ end
+ end
+
+ cattr_accessor :all_loaded_fixtures
+ self.all_loaded_fixtures = {}
+
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
+ table_names = [table_names].flatten.map { |n| n.to_s }
+ connection = block_given? ? yield : ActiveRecord::Base.connection
+
+ table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
+
+ unless table_names_to_fetch.empty?
+ ActiveRecord::Base.silence do
+ connection.disable_referential_integrity do
+ fixtures_map = {}
+
+ fixtures = table_names_to_fetch.map do |table_name|
+ fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
+ end
+
+ all_loaded_fixtures.update(fixtures_map)
+
+ connection.transaction(connection.open_transactions.zero?) do
+ fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
+ fixtures.each { |fixture| fixture.insert_fixtures }
+
+ # Cap primary key sequences to max(pk).
+ if connection.respond_to?(:reset_pk_sequence!)
+ table_names.each do |table_name|
+ connection.reset_pk_sequence!(table_name)
+ end
+ end
+ end
+
+ cache_fixtures(connection, fixtures_map)
+ end
+ end
+ end
+ cached_fixtures(connection, table_names)
+ end
+
+ # Returns a consistent identifier for +label+. This will always
+ # be a positive integer, and will always be the same for a given
+ # label, assuming the same OS, platform, and version of Ruby.
+ def self.identify(label)
+ label.to_s.hash.abs
+ end
+
+ attr_reader :table_name, :name
+
+ def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
+ @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
+ @name = table_name # preserve fixture base name
+ @class_name = class_name ||
+ (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
+ @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
+ @table_name = class_name.table_name if class_name.respond_to?(:table_name)
+ @connection = class_name.connection if class_name.respond_to?(:connection)
+ read_fixture_files
+ end
+
+ def delete_existing_fixtures
+ @connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
+ end
+
+ def insert_fixtures
+ now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
+ now = now.to_s(:db)
+
+ # allow a standard key to be used for doing defaults in YAML
+ if is_a?(Hash)
+ delete('DEFAULTS')
+ else
+ delete(assoc('DEFAULTS'))
+ end
+
+ # track any join tables we need to insert later
+ habtm_fixtures = Hash.new do |h, habtm|
+ h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil)
+ end
+
+ each do |label, fixture|
+ row = fixture.to_hash
+
+ if model_class && model_class < ActiveRecord::Base
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
+ if model_class.record_timestamps
+ timestamp_column_names.each do |name|
+ row[name] = now unless row.key?(name)
+ end
+ end
+
+ # interpolate the fixture label
+ row.each do |key, value|
+ row[key] = label if value == "$LABEL"
+ end
+
+ # generate a primary key if necessary
+ if has_primary_key_column? && !row.include?(primary_key_name)
+ row[primary_key_name] = Fixtures.identify(label)
+ end
+
+ # If STI is used, find the correct subclass for association reflection
+ reflection_class =
+ if row.include?(inheritance_column_name)
+ row[inheritance_column_name].constantize rescue model_class
+ else
+ model_class
+ end
+
+ reflection_class.reflect_on_all_associations.each do |association|
+ case association.macro
+ when :belongs_to
+ # Do not replace association name with association foreign key if they are named the same
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
+ if association.options[:polymorphic]
+ if value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ target_type = $1
+ target_type_name = (association.options[:foreign_type] || "#{association.name}_type").to_s
+
+ # support polymorphic belongs_to as "label (Type)"
+ row[target_type_name] = target_type
+ end
+ end
+
+ row[fk_name] = Fixtures.identify(value)
+ end
+ when :has_and_belongs_to_many
+ if (targets = row.delete(association.name.to_s))
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
+ join_fixtures = habtm_fixtures[association]
+
+ targets.each do |target|
+ join_fixtures["#{label}_#{target}"] = Fixture.new(
+ { association.primary_key_name => row[primary_key_name],
+ association.association_foreign_key => Fixtures.identify(target) }, nil)
+ end
+ end
+ end
+ end
+ end
+
+ @connection.insert_fixture(fixture, @table_name)
+ end
+
+ # insert any HABTM join tables we discovered
+ habtm_fixtures.values.each do |fixture|
+ fixture.delete_existing_fixtures
+ fixture.insert_fixtures
+ end
+ end
+
+ private
+ class HabtmFixtures < ::Fixtures #:nodoc:
+ def read_fixture_files; end
+ end
+
+ def model_class
+ unless defined?(@model_class)
+ @model_class =
+ if @class_name.nil? || @class_name.is_a?(Class)
+ @class_name
+ else
+ @class_name.constantize rescue nil
+ end
+ end
+
+ @model_class
+ end
+
+ def primary_key_name
+ @primary_key_name ||= model_class && model_class.primary_key
+ end
+
+ def has_primary_key_column?
+ @has_primary_key_column ||= model_class && primary_key_name &&
+ model_class.columns.find { |c| c.name == primary_key_name }
+ end
+
+ def timestamp_column_names
+ @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name|
+ column_names.include?(name)
+ end
+ end
+
+ def inheritance_column_name
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
+ end
+
+ def column_names
+ @column_names ||= @connection.columns(@table_name).collect(&:name)
+ end
+
+ def read_fixture_files
+ if File.file?(yaml_file_path)
+ read_yaml_fixture_files
+ elsif File.file?(csv_file_path)
+ read_csv_fixture_files
+ end
+ end
+
+ def read_yaml_fixture_files
+ yaml_string = ""
+ Dir["#{@fixture_path}/**/*.yml"].select { |f| test(?f, f) }.each do |subfixture_path|
+ yaml_string << IO.read(subfixture_path)
+ end
+ yaml_string << IO.read(yaml_file_path)
+
+ if yaml = parse_yaml_string(yaml_string)
+ # If the file is an ordered map, extract its children.
+ yaml_value =
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
+ yaml.value
+ else
+ [yaml]
+ end
+
+ yaml_value.each do |fixture|
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
+ fixture.each do |name, data|
+ unless data
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ end
+
+ self[name] = Fixture.new(data, model_class)
+ end
+ end
+ end
+ end
+
+ def read_csv_fixture_files
+ reader = CSV.parse(erb_render(IO.read(csv_file_path)))
+ header = reader.shift
+ i = 0
+ reader.each do |row|
+ data = {}
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
+ self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
+ end
+ end
+
+ def yaml_file_path
+ "#{@fixture_path}.yml"
+ end
+
+ def csv_file_path
+ @fixture_path + ".csv"
+ end
+
+ def yaml_fixtures_key(path)
+ File.basename(@fixture_path).split(".").first
+ end
+
+ def parse_yaml_string(fixture_content)
+ YAML::load(erb_render(fixture_content))
+ rescue => error
+ raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
+ end
+
+ def erb_render(fixture_content)
+ ERB.new(fixture_content).result
+ end
+end
+
+class Fixture #:nodoc:
+ include Enumerable
+
+ class FixtureError < StandardError #:nodoc:
+ end
+
+ class FormatError < FixtureError #:nodoc:
+ end
+
+ attr_reader :model_class
+
+ def initialize(fixture, model_class)
+ @fixture = fixture
+ @model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
+ end
+
+ def class_name
+ @model_class.name if @model_class
+ end
+
+ def each
+ @fixture.each { |item| yield item }
+ end
+
+ def [](key)
+ @fixture[key]
+ end
+
+ def to_hash
+ @fixture
+ end
+
+ def key_list
+ columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
+ columns.join(", ")
+ end
+
+ def value_list
+ list = @fixture.inject([]) do |fixtures, (key, value)|
+ col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
+ fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
+ end
+ list * ', '
+ end
+
+ def find
+ if model_class
+ model_class.find(self[model_class.primary_key])
+ else
+ raise FixtureClassNotFound, "No class attached to find."
+ end
+ end
+end
+
+module Test #:nodoc:
+ module Unit #:nodoc:
+ class TestCase #:nodoc:
+ setup :setup_fixtures
+ teardown :teardown_fixtures
+
+ superclass_delegating_accessor :fixture_path
+ superclass_delegating_accessor :fixture_table_names
+ superclass_delegating_accessor :fixture_class_names
+ superclass_delegating_accessor :use_transactional_fixtures
+ superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
+ superclass_delegating_accessor :pre_loaded_fixtures
+
+ self.fixture_table_names = []
+ self.use_transactional_fixtures = false
+ self.use_instantiated_fixtures = true
+ self.pre_loaded_fixtures = false
+
+ @@already_loaded_fixtures = {}
+ self.fixture_class_names = {}
+
+ class << self
+ def set_fixture_class(class_names = {})
+ self.fixture_class_names = self.fixture_class_names.merge(class_names)
+ end
+
+ def fixtures(*table_names)
+ if table_names.first == :all
+ table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
+ table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
+ else
+ table_names = table_names.flatten.map { |n| n.to_s }
+ end
+
+ self.fixture_table_names |= table_names
+ require_fixture_classes(table_names)
+ setup_fixture_accessors(table_names)
+ end
+
+ def try_to_load_dependency(file_name)
+ require_dependency file_name
+ rescue LoadError => e
+ # Let's hope the developer has included it himself
+
+ # Let's warn in case this is a subdependency, otherwise
+ # subdependency error messages are totally cryptic
+ if ActiveRecord::Base.logger
+ ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
+ end
+ end
+
+ def require_fixture_classes(table_names = nil)
+ (table_names || fixture_table_names).each do |table_name|
+ file_name = table_name.to_s
+ file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
+ try_to_load_dependency(file_name)
+ end
+ end
+
+ def setup_fixture_accessors(table_names = nil)
+ table_names = [table_names] if table_names && !table_names.respond_to?(:each)
+ (table_names || fixture_table_names).each do |table_name|
+ table_name = table_name.to_s.tr('.', '_')
+
+ define_method(table_name) do |*fixtures|
+ force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
+
+ @fixture_cache[table_name] ||= {}
+
+ instances = fixtures.map do |fixture|
+ @fixture_cache[table_name].delete(fixture) if force_reload
+
+ if @loaded_fixtures[table_name][fixture.to_s]
+ @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
+ else
+ raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
+ end
+ end
+
+ instances.size == 1 ? instances.first : instances
+ end
+ end
+ end
+
+ def uses_transaction(*methods)
+ @uses_transaction = [] unless defined?(@uses_transaction)
+ @uses_transaction.concat methods.map(&:to_s)
+ end
+
+ def uses_transaction?(method)
+ @uses_transaction = [] unless defined?(@uses_transaction)
+ @uses_transaction.include?(method.to_s)
+ end
+ end
+
+ def use_transactional_fixtures?
+ use_transactional_fixtures &&
+ !self.class.uses_transaction?(method_name)
+ end
+
+ def setup_fixtures
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
+
+ if pre_loaded_fixtures && !use_transactional_fixtures
+ raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
+ end
+
+ @fixture_cache = {}
+
+ # Load fixtures once and begin transaction.
+ if use_transactional_fixtures?
+ if @@already_loaded_fixtures[self.class]
+ @loaded_fixtures = @@already_loaded_fixtures[self.class]
+ else
+ load_fixtures
+ @@already_loaded_fixtures[self.class] = @loaded_fixtures
+ end
+ ActiveRecord::Base.connection.increment_open_transactions
+ ActiveRecord::Base.connection.begin_db_transaction
+ # Load fixtures for every test.
+ else
+ Fixtures.reset_cache
+ @@already_loaded_fixtures[self.class] = nil
+ load_fixtures
+ end
+
+ # Instantiate fixtures for every test if requested.
+ instantiate_fixtures if use_instantiated_fixtures
+ end
+
+ def teardown_fixtures
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
+
+ unless use_transactional_fixtures?
+ Fixtures.reset_cache
+ end
+
+ # Rollback changes if a transaction is active.
+ if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
+ ActiveRecord::Base.connection.rollback_db_transaction
+ ActiveRecord::Base.connection.decrement_open_transactions
+ end
+ ActiveRecord::Base.clear_active_connections!
+ end
+
+ private
+ def load_fixtures
+ @loaded_fixtures = {}
+ fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
+ unless fixtures.nil?
+ if fixtures.instance_of?(Fixtures)
+ @loaded_fixtures[fixtures.name] = fixtures
+ else
+ fixtures.each { |f| @loaded_fixtures[f.name] = f }
+ end
+ end
+ end
+
+ # for pre_loaded_fixtures, only require the classes once. huge speed improvement
+ @@required_fixture_classes = false
+
+ def instantiate_fixtures
+ if pre_loaded_fixtures
+ raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
+ unless @@required_fixture_classes
+ self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
+ @@required_fixture_classes = true
+ end
+ Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
+ else
+ raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
+ @loaded_fixtures.each do |table_name, fixtures|
+ Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
+ end
+ end
+ end
+
+ def load_instances?
+ use_instantiated_fixtures != :no_instances
+ end
+ end
+ end
+end