diff options
Diffstat (limited to 'activerecord/lib/active_record/fixtures.rb')
-rwxr-xr-x | activerecord/lib/active_record/fixtures.rb | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb new file mode 100755 index 0000000000..f17768e1f2 --- /dev/null +++ b/activerecord/lib/active_record/fixtures.rb @@ -0,0 +1,208 @@ +require 'erb' +require 'yaml' +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: +# +# david: +# id: 1 +# name: David Heinemeier Hansson +# birthday: 1979-10-15 +# profession: Systems development +# +# steve: +# id: 2 +# name: Steve Ross Kellock +# birthday: 1974-09-27 +# profession: guy with keyboard +# +# So this YAML file includes two fixtures. T +# +# 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>. +# +# 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: +# +# def test_find +# assert_equal @developers["david"]["name"], @developers["david"].find.name +# end +# +# Comparing that the data we have on the name is also what the object returns when we ask for it. +# +# == Automatic fixture setup and instance variable availability +# +# Fixtures can also be automatically instantiated in instance variables relating to their names using the following style: +# +# class FixturesTest < Test::Unit::TestCase +# fixtures :developers # you can add more with comma separation +# +# 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 +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| + object.instance_variable_set "@#{table_names[idx]}", fixtures + fixtures.each { |name, fixture| object.instance_variable_set "@#{name}", fixture.find } + end + end + + def self.create_fixtures(fixtures_directory, *table_names) + connection = block_given? ? yield : ActiveRecord::Base.connection + old_logger_level = ActiveRecord::Base.logger.level + + begin + ActiveRecord::Base.logger.level = Logger::ERROR + fixtures = connection.transaction do + table_names.flatten.map do |table_name| + Fixtures.new(connection, table_name.to_s, File.join(fixtures_directory, table_name.to_s)) + end + end + return fixtures.size > 1 ? fixtures : fixtures.first + ensure + ActiveRecord::Base.logger.level = old_logger_level + end + end + + def initialize(connection, table_name, fixture_path, file_filter = /^\.|CVS|\.yml/) + @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter + @class_name = Inflector.classify(@table_name) + + read_fixture_files + delete_existing_fixtures + insert_fixtures + end + + private + def read_fixture_files + if File.exists?(yaml_file_path) + YAML::load(erb_render(IO.read(yaml_file_path))).each do |name, data| + self[name] = Fixture.new(data, @class_name) + end + else + Dir.entries(@fixture_path).each do |file| + self[file] = Fixture.new(File.join(@fixture_path, file), @class_name) unless file =~ @file_filter + end + end + end + + def delete_existing_fixtures + @connection.delete "DELETE FROM #{@table_name}" + end + + def insert_fixtures + values.each do |fixture| + @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES(#{fixture.value_list})" + end + end + + def yaml_file_path + @fixture_path + ".yml" + end + + def yaml_fixtures_key(path) + File.basename(@fixture_path).split(".").first + end + + def erb_render(fixture_content) + ERB.new(fixture_content).result + end +end + +class Fixture #:nodoc: + include Enumerable + class FixtureError < StandardError; end + class FormatError < FixtureError; end + + def initialize(fixture, class_name) + @fixture = fixture.is_a?(Hash) ? fixture : read_fixture_file(fixture) + @class_name = class_name + end + + def each + @fixture.each { |item| yield item } + 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| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ") + end + + def find + Object.const_get(@class_name).find(self["id"]) + end + + private + def read_fixture_file(fixture_file_path) + IO.readlines(fixture_file_path).inject({}) do |fixture, line| + # Mercifully skip empty lines. + next if line.empty? + + # Use the same regular expression for attributes as Active Record. + unless md = /^\s*([a-zA-Z][-_\w]*)\s*=>\s*(.+)\s*$/.match(line) + raise FormatError, "#{path}: fixture format error at '#{line}'. Expecting 'key => value'." + end + key, value = md.captures + + # Disallow duplicate keys to catch typos. + raise FormatError, "#{path}: duplicate '#{key}' in fixture." if fixture[key] + fixture[key] = value.strip + fixture + end + end +end + +class Test::Unit::TestCase #:nodoc: + include ClassInheritableAttributes + + cattr_accessor :fixture_path + cattr_accessor :fixture_table_names + + def self.fixtures(*table_names) + write_inheritable_attribute("fixture_table_names", table_names) + end + + def setup + instantiate_fixtures(*fixture_table_names) if fixture_table_names + end + + def self.method_added(method_symbol) + if method_symbol == :setup && !method_defined?(:setup_without_fixtures) + alias_method :setup_without_fixtures, :setup + define_method(:setup) do + instantiate_fixtures(*fixture_table_names) if fixture_table_names + setup_without_fixtures + end + end + end + + private + def instantiate_fixtures(*table_names) + Fixtures.instantiate_fixtures(self, fixture_path, *table_names) + end + + def fixture_table_names + self.class.read_inheritable_attribute("fixture_table_names") + end +end
\ No newline at end of file |