2007-01-22 14:43:50 +01:00
require 'erb'
require 'yaml'
require 'csv'
2009-08-04 17:16:03 +02:00
require 'zlib'
2009-02-04 21:26:08 +01:00
require 'active_support/dependencies'
2008-05-18 06:22:34 +02:00
require 'active_support/test_case'
2007-01-22 14:43:50 +01:00
2008-05-18 06:22:34 +02:00
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
2007-01-22 14:43:50 +01:00
end
end
2007-12-21 08:48:59 +01:00
if defined? ActiveRecord
class FixtureClassNotFound < ActiveRecord :: ActiveRecordError #:nodoc:
end
else
class FixtureClassNotFound < StandardError #:nodoc:
end
2007-01-22 14:43:50 +01:00
end
2009-02-28 02:23:00 +01:00
# Fixtures are a way of organizing data that you want to test against; in short, sample data.
#
# = Fixture formats
#
# Fixtures come in 3 flavors:
2007-01-22 14:43:50 +01:00
#
# 1. YAML fixtures
# 2. CSV fixtures
# 3. Single-file fixtures
#
2009-02-28 02:23:00 +01:00
# == YAML fixtures
2007-01-22 14:43:50 +01:00
#
# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
2007-12-21 08:48:59 +01:00
# in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
2007-01-22 14:43:50 +01:00
#
# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
2008-05-18 06:22:34 +02:00
# by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
2008-06-02 08:35:38 +02:00
# 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:
2007-01-22 14:43:50 +01:00
#
# 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
#
2009-02-28 02:23:00 +01:00
# == CSV fixtures
2007-01-22 14:43:50 +01:00
#
2009-02-28 02:23:00 +01:00
# Fixtures can also be kept in the Comma Separated Value (CSV) format. Akin to YAML fixtures, CSV fixtures are stored
2008-06-02 08:35:38 +02:00
# 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>).
2007-01-22 14:43:50 +01:00
#
# 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.
#
2009-02-28 02:23:00 +01:00
# == Single-file fixtures
2007-01-22 14:43:50 +01:00
#
2007-12-21 08:48:59 +01:00
# This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
2007-01-22 14:43:50 +01:00
# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
2008-05-18 06:22:34 +02:00
# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
2008-06-02 08:35:38 +02:00
# 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).
2007-01-22 14:43:50 +01:00
#
# Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
2008-06-02 08:35:38 +02:00
# extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension. Here's what the
2007-01-22 14:43:50 +01:00
# 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
#
2009-02-28 02:23:00 +01:00
# = Using fixtures in testcases
2007-01-22 14:43:50 +01:00
#
# Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
2007-12-21 08:48:59 +01:00
# fixtures, but first let's take a look at a sample unit test:
2007-01-22 14:43:50 +01:00
#
2009-02-28 02:23:00 +01:00
# require 'test_helper'
2007-01-22 14:43:50 +01:00
#
2008-05-18 06:22:34 +02:00
# class WebSiteTest < ActiveSupport::TestCase
2009-02-28 02:23:00 +01:00
# test "web_site_count" do
2007-01-22 14:43:50 +01:00
# assert_equal 2, WebSite.count
# end
# end
#
2009-02-28 02:23:00 +01:00
# By default, the <tt>test_helper module</tt> will load all of your fixtures into your test database, so this test will succeed.
# The testing environment will automatically load the all fixtures into the database before each test.
2007-01-22 14:43:50 +01:00
# To ensure consistent data, the environment deletes the fixtures before running the load.
#
2009-02-28 02:23:00 +01:00
# In addition to being available in the database, the fixture's data may also be accessed by
# using a special dynamic method, which has the same name as the model, and accepts the
# name of the fixture to instantiate:
2007-01-22 14:43:50 +01:00
#
2009-02-28 02:23:00 +01:00
# test "find" do
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
2007-01-22 14:43:50 +01:00
# end
#
2009-02-28 02:23:00 +01:00
# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the following tests:
2007-01-22 14:43:50 +01:00
#
2009-02-28 02:23:00 +01:00
# test "find_alt_method_1" do
# assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
# end
2007-01-22 14:43:50 +01:00
#
2009-02-28 02:23:00 +01:00
# test "find_alt_method_2" do
# assert_equal "Ruby on Rails", @rubyonrails.news
# end
2007-01-22 14:43:50 +01:00
#
2009-02-28 02:23:00 +01:00
# In order to use these methods to access fixtured data within your testcases, you must specify one of the
# following in your <tt>ActiveSupport::TestCase</tt>-derived class:
2007-01-22 14:43:50 +01:00
#
2009-02-28 02:23:00 +01:00
# - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
# self.use_instantiated_fixtures = true
2007-01-22 14:43:50 +01:00
#
2009-02-28 02:23:00 +01:00
# - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
# self.use_instantiated_fixtures = :no_instances
2007-01-22 14:43:50 +01:00
#
2009-02-28 02:23:00 +01:00
# Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
# traversed in the database to create the fixture hash and/or instance variables. This is expensive for
# large sets of fixtured data.
2007-01-22 14:43:50 +01:00
#
# = 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:
#
2008-05-18 06:22:34 +02:00
# <% for i in 1..1000 %>
# fix_<%= i %>:
# id: <%= i %>
# name: guy_<%= 1 %>
# <% end %>
2007-01-22 14:43:50 +01:00
#
# This will create 1000 very simple YAML fixtures.
#
2008-06-02 08:35:38 +02:00
# Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
2007-01-22 14:43:50 +01:00
# 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
#
2007-12-21 08:48:59 +01:00
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
2007-01-22 14:43:50 +01:00
#
2008-05-18 06:22:34 +02:00
# class FooTest < ActiveSupport::TestCase
2007-01-22 14:43:50 +01:00
# self.use_transactional_fixtures = true
2008-06-02 08:35:38 +02:00
#
2009-02-28 02:23:00 +01:00
# test "godzilla" do
2007-01-22 14:43:50 +01:00
# assert !Foo.find(:all).empty?
# Foo.destroy_all
# assert Foo.find(:all).empty?
# end
2008-06-02 08:35:38 +02:00
#
2009-02-28 02:23:00 +01:00
# test "godzilla aftermath" do
2007-01-22 14:43:50 +01:00
# assert !Foo.find(:all).empty?
# end
# end
2008-06-02 08:35:38 +02:00
#
2007-12-21 08:48:59 +01:00
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
2007-01-22 14:43:50 +01:00
# 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.
#
2007-12-21 08:48:59 +01:00
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
2007-01-22 14:43:50 +01:00
# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
#
2007-12-21 08:48:59 +01:00
# When *not* to use transactional fixtures:
2009-02-28 02:23:00 +01:00
#
# 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.
2007-12-21 08:48:59 +01:00
#
# = Advanced YAML Fixtures
#
# YAML fixtures that don't specify an ID get some extra features:
#
2009-02-28 02:23:00 +01:00
# * Stable, autogenerated IDs
2007-12-21 08:48:59 +01:00
# * 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
#
2009-02-28 02:23:00 +01:00
# == Stable, autogenerated IDs
2007-12-21 08:48:59 +01:00
#
# 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
#
2008-06-02 08:35:38 +02:00
# Active Record looks at the fixture's model class, discovers the correct
2007-12-21 08:48:59 +01:00
# 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
2008-06-02 08:35:38 +02:00
# mention difficult to read. Since Active Record can figure out the ID of
2007-12-21 08:48:59 +01:00
# 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
2009-02-28 02:23:00 +01:00
# use labels instead of IDs:
2007-12-21 08:48:59 +01:00
#
# ### in pirates.yml
#
# reginald:
# name: Reginald the Pirate
# monkey: george
#
# ### in monkeys.yml
#
# george:
# name: George the Monkey
# pirate: reginald
#
2008-06-02 08:35:38 +02:00
# Pow! All is made clear. Active Record reflects on the fixture's model class,
2007-12-21 08:48:59 +01:00
# finds all the +belongs_to+ associations, and allows you to specify
# a target *label* for the *association* (monkey: george) rather than
2008-06-02 08:35:38 +02:00
# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
2007-12-21 08:48:59 +01:00
#
# ==== Polymorphic belongs_to
#
# Supporting polymorphic relationships is a little bit more complicated, since
2008-06-02 08:35:38 +02:00
# Active Record needs to know what type your association is pointing at. Something
2007-12-21 08:48:59 +01:00
# 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)
#
2008-06-02 08:35:38 +02:00
# Just provide the polymorphic target type and Active Record will take care of the rest.
2007-12-21 08:48:59 +01:00
#
# === 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
2008-06-02 08:35:38 +02:00
# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
2007-12-21 08:48:59 +01:00
# the fixture's model class and discovers the +has_and_belongs_to_many+
# associations.
#
# == Autofilled timestamp columns
#
2008-06-02 08:35:38 +02:00
# 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>.
2007-12-21 08:48:59 +01:00
#
# 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
2008-05-18 06:22:34 +02:00
# your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
2007-12-21 08:48:59 +01:00
#
# 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.
2008-05-18 06:22:34 +02:00
class Fixtures < ( RUBY_VERSION < '1.9' ? YAML :: Omap : Hash )
2009-08-04 17:16:03 +02:00
MAX_ID = 2 ** 31 - 1
2007-01-22 14:43:50 +01:00
DEFAULT_FILTER_RE = / \ .ya?ml$ /
2007-12-21 08:48:59 +01:00
@@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
2008-05-18 06:22:34 +02:00
def self . cache_fixtures ( connection , fixtures_map )
cache_for_connection ( connection ) . update ( fixtures_map )
2007-12-21 08:48:59 +01:00
end
def self . instantiate_fixtures ( object , table_name , fixtures , load_instances = true )
2007-01-22 14:43:50 +01:00
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
2007-12-21 08:48:59 +01:00
def self . instantiate_all_loaded_fixtures ( object , load_instances = true )
2007-01-22 14:43:50 +01:00
all_loaded_fixtures . each do | table_name , fixtures |
Fixtures . instantiate_fixtures ( object , table_name , fixtures , load_instances )
end
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
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 }
2007-12-21 08:48:59 +01:00
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 )
2009-02-04 21:26:08 +01:00
connection . transaction ( :requires_new = > true ) do
2007-12-21 08:48:59 +01:00
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
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
2008-05-18 06:22:34 +02:00
cache_fixtures ( connection , fixtures_map )
2007-01-22 14:43:50 +01:00
end
end
end
2007-12-21 08:48:59 +01:00
cached_fixtures ( connection , table_names )
2007-01-22 14:43:50 +01:00
end
2009-08-04 17:16:03 +02:00
# Returns a consistent, platform-independent identifier for +label+.
# Identifiers are positive integers less than 2^32.
2007-12-21 08:48:59 +01:00
def self . identify ( label )
2009-08-04 17:16:03 +02:00
Zlib . crc32 ( label . to_s ) % MAX_ID
2007-12-21 08:48:59 +01:00
end
2007-01-22 14:43:50 +01:00
2008-10-27 07:47:01 +01:00
attr_reader :table_name , :name
2007-01-22 14:43:50 +01:00
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
2008-10-27 07:47:01 +01:00
@name = table_name # preserve fixture base name
2007-12-21 08:48:59 +01:00
@class_name = class_name ||
2007-01-22 14:43:50 +01:00
( ActiveRecord :: Base . pluralize_table_names ? @table_name . singularize . camelize : @table_name . camelize )
2008-10-27 07:47:01 +01:00
@table_name = " #{ ActiveRecord :: Base . table_name_prefix } #{ @table_name } #{ ActiveRecord :: Base . table_name_suffix } "
2007-02-09 09:04:31 +01:00
@table_name = class_name . table_name if class_name . respond_to? ( :table_name )
@connection = class_name . connection if class_name . respond_to? ( :connection )
2007-01-22 14:43:50 +01:00
read_fixture_files
end
def delete_existing_fixtures
2007-12-21 08:48:59 +01:00
@connection . delete " DELETE FROM #{ @connection . quote_table_name ( table_name ) } " , 'Fixture Delete'
2007-01-22 14:43:50 +01:00
end
def insert_fixtures
2007-12-21 08:48:59 +01:00
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
2008-05-18 06:22:34 +02:00
if is_a? ( Hash )
delete ( 'DEFAULTS' )
else
delete ( assoc ( 'DEFAULTS' ) )
end
2007-12-21 08:48:59 +01:00
# 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 )
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
each do | label , fixture |
row = fixture . to_hash
2007-01-22 14:43:50 +01:00
2007-12-21 08:48:59 +01:00
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
2007-02-09 09:04:31 +01:00
end
2007-03-18 17:56:12 +01:00
2007-12-21 08:48:59 +01:00
# interpolate the fixture label
row . each do | key , value |
row [ key ] = label if value == " $LABEL "
2007-03-18 17:56:12 +01:00
end
2007-12-21 08:48:59 +01:00
# 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
2007-03-18 17:56:12 +01:00
2007-12-21 08:48:59 +01:00
# support polymorphic belongs_to as "label (Type)"
row [ target_type_name ] = target_type
end
2007-03-18 17:56:12 +01:00
end
2007-12-21 08:48:59 +01:00
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 ] ,
2009-09-05 09:01:46 +02:00
association . association_foreign_key = > Fixtures . identify ( target ) } ,
nil , @connection )
2007-12-21 08:48:59 +01:00
end
2007-01-22 14:43:50 +01:00
end
end
end
2007-12-21 08:48:59 +01:00
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
2008-05-18 06:22:34 +02:00
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
2007-12-21 08:48:59 +01:00
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
2007-01-22 14:43:50 +01:00
elsif File . file? ( csv_file_path )
2007-12-21 08:48:59 +01:00
read_csv_fixture_files
2007-01-22 14:43:50 +01:00
end
end
2007-12-21 08:48:59 +01:00
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 |
2008-05-18 06:22:34 +02:00
raise Fixture :: FormatError , " Bad data for #{ @class_name } fixture named #{ fixture } " unless fixture . respond_to? ( :each )
2009-09-05 09:01:46 +02:00
fixture . each do | name , data |
2007-12-21 08:48:59 +01:00
unless data
raise Fixture :: FormatError , " Bad data for #{ @class_name } fixture named #{ name } (nil) "
end
2009-09-05 09:01:46 +02:00
self [ name ] = Fixture . new ( data , model_class , @connection )
2007-12-21 08:48:59 +01:00
end
end
end
end
def read_csv_fixture_files
2008-05-18 06:22:34 +02:00
reader = CSV . parse ( erb_render ( IO . read ( csv_file_path ) ) )
2007-12-21 08:48:59 +01:00
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 }
2009-09-05 09:01:46 +02:00
self [ " #{ @class_name . to_s . underscore } _ #{ i += 1 } " ] = Fixture . new ( data , model_class , @connection )
2007-12-21 08:48:59 +01:00
end
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
def yaml_file_path
" #{ @fixture_path } .yml "
2007-01-22 14:43:50 +01:00
end
def csv_file_path
@fixture_path + " .csv "
end
def yaml_fixtures_key ( path )
File . basename ( @fixture_path ) . split ( " . " ) . first
end
2007-12-21 08:48:59 +01:00
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 \n The exact error was: \n #{ error . class } : #{ error } "
end
2007-01-22 14:43:50 +01:00
def erb_render ( fixture_content )
ERB . new ( fixture_content ) . result
end
end
class Fixture #:nodoc:
include Enumerable
2007-12-21 08:48:59 +01:00
class FixtureError < StandardError #:nodoc:
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
class FormatError < FixtureError #:nodoc:
2007-01-22 14:43:50 +01:00
end
2008-05-18 06:22:34 +02:00
attr_reader :model_class
2007-12-21 08:48:59 +01:00
2009-09-05 09:01:46 +02:00
def initialize ( fixture , model_class , connection = ActiveRecord :: Base . connection )
@connection = connection
2008-05-18 06:22:34 +02:00
@fixture = fixture
@model_class = model_class . is_a? ( Class ) ? model_class : model_class . constantize rescue nil
end
2007-01-22 14:43:50 +01:00
2008-05-18 06:22:34 +02:00
def class_name
@model_class . name if @model_class
2007-01-22 14:43:50 +01:00
end
def each
@fixture . each { | item | yield item }
end
def [] ( key )
@fixture [ key ]
end
def to_hash
@fixture
end
def key_list
2009-09-05 09:01:46 +02:00
columns = @fixture . keys . collect { | column_name | @connection . quote_column_name ( column_name ) }
2007-01-22 14:43:50 +01:00
columns . join ( " , " )
end
def value_list
2007-02-09 09:04:31 +01:00
list = @fixture . inject ( [ ] ) do | fixtures , ( key , value ) |
2008-05-18 06:22:34 +02:00
col = model_class . columns_hash [ key ] if model_class . respond_to? ( :ancestors ) && model_class . ancestors . include? ( ActiveRecord :: Base )
2009-09-05 09:01:46 +02:00
fixtures << @connection . quote ( value , col ) . gsub ( '[^\]\\n' , " \n " ) . gsub ( '[^\]\\r' , " \r " )
2007-02-09 09:04:31 +01:00
end
list * ', '
2007-01-22 14:43:50 +01:00
end
def find
2008-05-18 06:22:34 +02:00
if model_class
model_class . find ( self [ model_class . primary_key ] )
2007-01-22 14:43:50 +01:00
else
2008-05-18 06:22:34 +02:00
raise FixtureClassNotFound , " No class attached to find. "
2007-01-22 14:43:50 +01:00
end
end
end
2009-02-04 21:26:08 +01:00
module ActiveRecord
module TestFixtures
def self . included ( base )
base . class_eval do
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
self . fixture_class_names = { }
end
2007-12-21 08:48:59 +01:00
2009-02-04 21:26:08 +01:00
base . extend ClassMethods
end
module ClassMethods
def set_fixture_class ( class_names = { } )
self . fixture_class_names = self . fixture_class_names . merge ( class_names )
end
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
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 }
2008-05-18 06:22:34 +02:00
end
2009-02-04 21:26:08 +01:00
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
2008-06-02 08:35:38 +02:00
2009-02-04 21:26:08 +01:00
# 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 " ) } " )
2008-05-18 06:22:34 +02:00
end
2009-02-04 21:26:08 +01:00
end
2008-06-02 08:35:38 +02:00
2009-02-04 21:26:08 +01:00
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 )
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
end
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
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 ( '.' , '_' )
2007-12-21 08:48:59 +01:00
2009-02-04 21:26:08 +01:00
define_method ( table_name ) do | * fixtures |
force_reload = fixtures . pop if fixtures . last == true || fixtures . last == :reload
2007-12-21 08:48:59 +01:00
2009-02-04 21:26:08 +01:00
@fixture_cache [ table_name ] || = { }
2007-12-21 08:48:59 +01:00
2009-02-04 21:26:08 +01:00
instances = fixtures . map do | fixture |
@fixture_cache [ table_name ] . delete ( fixture ) if force_reload
2007-12-21 08:48:59 +01:00
2009-02-04 21:26:08 +01:00
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 } ' "
2007-12-21 08:48:59 +01:00
end
2008-05-18 06:22:34 +02:00
end
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
instances . size == 1 ? instances . first : instances
end
2008-05-18 06:22:34 +02:00
end
2009-02-04 21:26:08 +01:00
end
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
def uses_transaction ( * methods )
@uses_transaction = [ ] unless defined? ( @uses_transaction )
@uses_transaction . concat methods . map ( & :to_s )
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
def uses_transaction? ( method )
@uses_transaction = [ ] unless defined? ( @uses_transaction )
@uses_transaction . include? ( method . to_s )
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
end
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
def run_in_transaction?
use_transactional_fixtures &&
! self . class . uses_transaction? ( method_name )
end
2007-02-09 09:04:31 +01:00
2009-02-04 21:26:08 +01:00
def setup_fixtures
return unless defined? ( ActiveRecord ) && ! ActiveRecord :: Base . configurations . blank?
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
if pre_loaded_fixtures && ! use_transactional_fixtures
raise RuntimeError , 'pre_loaded_fixtures requires use_transactional_fixtures'
end
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
@fixture_cache = { }
@@already_loaded_fixtures || = { }
# Load fixtures once and begin transaction.
if run_in_transaction?
if @@already_loaded_fixtures [ self . class ]
@loaded_fixtures = @@already_loaded_fixtures [ self . class ]
2007-01-22 14:43:50 +01:00
else
load_fixtures
2009-02-04 21:26:08 +01:00
@@already_loaded_fixtures [ self . class ] = @loaded_fixtures
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
ActiveRecord :: Base . connection . increment_open_transactions
ActiveRecord :: Base . connection . transaction_joinable = false
ActiveRecord :: Base . connection . begin_db_transaction
# Load fixtures for every test.
else
Fixtures . reset_cache
@@already_loaded_fixtures [ self . class ] = nil
load_fixtures
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
# 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?
2007-02-09 09:04:31 +01:00
2009-02-04 21:26:08 +01:00
unless run_in_transaction?
Fixtures . reset_cache
end
2007-12-21 08:48:59 +01:00
2009-02-04 21:26:08 +01:00
# Rollback changes if a transaction is active.
if run_in_transaction? && ActiveRecord :: Base . connection . open_transactions != 0
ActiveRecord :: Base . connection . rollback_db_transaction
ActiveRecord :: Base . connection . decrement_open_transactions
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
ActiveRecord :: Base . clear_active_connections!
end
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
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 }
2007-01-22 14:43:50 +01:00
end
end
2009-02-04 21:26:08 +01:00
end
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
# for pre_loaded_fixtures, only require the classes once. huge speed improvement
@@required_fixture_classes = false
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
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? )
2007-01-22 14:43:50 +01:00
end
end
2009-02-04 21:26:08 +01:00
end
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
def load_instances?
use_instantiated_fixtures != :no_instances
end
2007-01-22 14:43:50 +01:00
end
end