mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Added CSV format for fixtures #272 [what-a-day]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@55 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
165097ede5
commit
5e3eaff5bb
2 changed files with 145 additions and 36 deletions
|
@ -1,5 +1,7 @@
|
|||
*CVS*
|
||||
|
||||
* Added CSV format for fixtures #272 [what-a-day]. (See the new and expanded documentation on fixtures for more information)
|
||||
|
||||
* Fixed fixtures using primary key fields called something else than "id" [dave]
|
||||
|
||||
* Added proper handling of time fields that are turned into Time objects with the dummy date of 2000/1/1 [HariSeldon]
|
||||
|
|
|
@ -1,54 +1,145 @@
|
|||
require 'erb'
|
||||
require 'yaml'
|
||||
require 'csv'
|
||||
require 'active_record/support/class_inheritable_attributes'
|
||||
require 'active_record/support/inflector'
|
||||
|
||||
# Fixtures are a way of organizing data that you want to test against. You normally have one YAML file with fixture
|
||||
# definitions per model. They're just hashes of hashes with the first-level key being the name of fixture (try to keep
|
||||
# that name unique across all fixtures in the system for reasons that will follow). The value to that key is a hash
|
||||
# where the keys are column names and the values the fixture data you want to insert into it. Example for developers.yml:
|
||||
# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours:
|
||||
#
|
||||
# david:
|
||||
# 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, humanly-readable format. It ships with Ruby 1.8.1+.
|
||||
#
|
||||
# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which is place in the directory appointed
|
||||
# by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
|
||||
# put your files in <your-rails-app>/test/fixtures/). The fixture file ends with the .yml file extension (Rails example:
|
||||
# "<your-rails-app>/test/fixtures/web_sites.yml"). The format of a YAML fixture file looks like this:
|
||||
#
|
||||
# rubyonrails:
|
||||
# id: 1
|
||||
# name: David Heinemeier Hansson
|
||||
# birthday: 1979-10-15
|
||||
# profession: Systems development
|
||||
# name: Ruby on Rails
|
||||
# url: http://www.rubyonrails.org
|
||||
#
|
||||
# steve:
|
||||
# google:
|
||||
# id: 2
|
||||
# name: Steve Ross Kellock
|
||||
# birthday: 1974-09-27
|
||||
# profession: guy with keyboard
|
||||
# name: Google
|
||||
# url: http://www.google.com
|
||||
#
|
||||
# So this YAML file includes two fixtures. T
|
||||
# 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.
|
||||
#
|
||||
# Now when we call <tt>@developers = Fixtures.create_fixtures(".", "developers")</tt> both 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 <tt>@developers</tt> hash, so <tt>@developers["david"]["name"]</tt> will return
|
||||
# <tt>"David Heinemeier Hansson"</tt> and <tt>@developers["david"]["birthday"]</tt> will return <tt>Date.new(1979, 10, 15)</tt>.
|
||||
# = CSV fixtures
|
||||
#
|
||||
# In addition to getting the raw data, we can also get the Developer object by doing @developers["david"].find. This can then
|
||||
# be used for comparison in a unit test. Something like:
|
||||
# Fixtures can also be kept in the in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
|
||||
# in a single file, but, instead end with the .csv file extension (Rails example: "<your-rails-app>/test/fixtures/web_sites.csv")
|
||||
#
|
||||
# The format of this tye 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 fixtures 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>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
|
||||
# put your files in <your-rails-app>/test/fixtures/<your-model-name>/ -- like <your-rails-app>/test/fixtures/web_sites/ 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 .txt 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 lets take a look at a sample unit test found:
|
||||
#
|
||||
# require 'web_site'
|
||||
#
|
||||
# class WebSiteTest < Test::Unit::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 < Test::Unit::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 tho), we trigger
|
||||
# the testing environment to automatically load the appropriate fixtures into the database before each test, and
|
||||
# automatically delete them after each test.
|
||||
#
|
||||
# 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
|
||||
# @web_sites. This is where the "fixture name" comes into play.
|
||||
#
|
||||
# On top of that, each record is automatically "found" (using Model.find(id)) and placed in the instance variable of its name.
|
||||
# So for the YAML fixtures, we'd get @rubyonrails and @google, 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 @developers["david"]["name"], @developers["david"].find.name
|
||||
# assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
|
||||
# end
|
||||
#
|
||||
# Comparing that the data we have on the name is also what the object returns when we ask for it.
|
||||
# As seen above, the data hash created from the YAML fixtures would have @web_sites["rubyonrails"]["url"] return
|
||||
# "http://www.rubyonrails.org" and @web_sites["google"]["name"] would return "Google". The same fixtures, but loaded
|
||||
# from a CSV fixture file would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
|
||||
# fixtures available as instance variables @web_site_1 and @web_site_2.
|
||||
#
|
||||
# == Automatic fixture setup and instance variable availability
|
||||
# = Dynamic fixtures with ERb
|
||||
#
|
||||
# Fixtures can also be automatically instantiated in instance variables relating to their names using the following style:
|
||||
# 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:
|
||||
#
|
||||
# class FixturesTest < Test::Unit::TestCase
|
||||
# fixtures :developers # you can add more with comma separation
|
||||
# <% for i in 1..1000 %>
|
||||
# fix_<%= i %>:
|
||||
# id: <%= i %>
|
||||
# name: guy_<%= 1 %>
|
||||
# <% end %>
|
||||
#
|
||||
# def test_developers
|
||||
# assert_equal 3, @developers.size # the container for all the fixtures is automatically set
|
||||
# assert_kind_of Developer, @david # works like @developers["david"].find
|
||||
# assert_equal "David Heinemeier Hansson", @david.name
|
||||
# end
|
||||
# end
|
||||
# This will create 1000 YAML very simple fixtures.
|
||||
#
|
||||
# Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>.
|
||||
# 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.
|
||||
class Fixtures < Hash
|
||||
def self.instantiate_fixtures(object, fixtures_directory, *table_names)
|
||||
[ create_fixtures(fixtures_directory, *table_names) ].flatten.each_with_index do |fixtures, idx|
|
||||
|
@ -68,8 +159,8 @@ class Fixtures < Hash
|
|||
fixtures = table_names.flatten.map do |table_name|
|
||||
Fixtures.new(connection, table_name.to_s, File.join(fixtures_directory, table_name.to_s))
|
||||
end
|
||||
fixtures.reverse.each{ |fixture| fixture.delete_existing_fixtures }
|
||||
fixtures.each{ |fixture| fixture.insert_fixtures }
|
||||
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
|
||||
fixtures.each { |fixture| fixture.insert_fixtures }
|
||||
end
|
||||
return fixtures.size > 1 ? fixtures : fixtures.first
|
||||
ensure
|
||||
|
@ -77,7 +168,7 @@ class Fixtures < Hash
|
|||
end
|
||||
end
|
||||
|
||||
def initialize(connection, table_name, fixture_path, file_filter = /^\.|CVS|\.yml/)
|
||||
def initialize(connection, table_name, fixture_path, file_filter = /^\.|CVS|\.yml|\.csv/)
|
||||
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
|
||||
@class_name = Inflector.classify(@table_name)
|
||||
|
||||
|
@ -97,10 +188,22 @@ class Fixtures < Hash
|
|||
private
|
||||
def read_fixture_files
|
||||
if File.exists?(yaml_file_path)
|
||||
# YAML fixtures
|
||||
YAML::load(erb_render(IO.read(yaml_file_path))).each do |name, data|
|
||||
self[name] = Fixture.new(data, @class_name)
|
||||
end
|
||||
elsif File.exists?(csv_file_path)
|
||||
# CSV fixtures
|
||||
reader = CSV::Reader.create(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["#{Inflector::underscore(@class_name)}_#{i+=1}"]= Fixture.new(data, @class_name)
|
||||
end
|
||||
else
|
||||
# Standard fixtures
|
||||
Dir.entries(@fixture_path).each do |file|
|
||||
self[file] = Fixture.new(File.join(@fixture_path, file), @class_name) unless file =~ @file_filter
|
||||
end
|
||||
|
@ -111,6 +214,10 @@ class Fixtures < Hash
|
|||
@fixture_path + ".yml"
|
||||
end
|
||||
|
||||
def csv_file_path
|
||||
@fixture_path + ".csv"
|
||||
end
|
||||
|
||||
def yaml_fixtures_key(path)
|
||||
File.basename(@fixture_path).split(".").first
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue