fixtures.rb 21.9 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1 2
require 'erb'
require 'yaml'
3
require 'csv'
D
Initial  
David Heinemeier Hansson 已提交
4

5 6 7 8 9
module YAML #:nodoc:
  class Omap #:nodoc:
    def keys;   map { |k, v| k } end
    def values; map { |k, v| v } end
  end
10 11
end

12
# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours:
D
Initial  
David Heinemeier Hansson 已提交
13
#
14 15 16
#   1.  YAML fixtures
#   2.  CSV fixtures
#   3.  Single-file fixtures
D
Initial  
David Heinemeier Hansson 已提交
17
#
18
# = YAML fixtures
D
Initial  
David Heinemeier Hansson 已提交
19
#
20 21
# 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+.
D
Initial  
David Heinemeier Hansson 已提交
22
#
D
David Heinemeier Hansson 已提交
23
# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
24 25 26
# 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:
D
Initial  
David Heinemeier Hansson 已提交
27
#
28 29 30 31
#   rubyonrails:
#     id: 1
#     name: Ruby on Rails
#     url: http://www.rubyonrails.org
D
Initial  
David Heinemeier Hansson 已提交
32
#
33 34 35 36 37
#   google:
#     id: 2
#     name: Google
#     url: http://www.google.com
#
38
# This YAML fixture file includes two fixtures.  Each YAML fixture (ie. record) is given a name and is followed by an
39
# indented list of key/value pairs in the "key: value" format.  Records are separated by a blank line for your viewing
40
# pleasure.
41
#
42 43 44 45 46 47 48 49 50 51 52 53 54 55
# 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
#
56 57
# = CSV fixtures
#
D
David Heinemeier Hansson 已提交
58
# Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
D
David Heinemeier Hansson 已提交
59
# in a single file, but instead end with the .csv file extension (Rails example: "<your-rails-app>/test/fixtures/web_sites.csv")
60
#
61
# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
62 63 64 65 66 67 68 69 70 71 72
# 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
73
# fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
74 75 76
# 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".
#
77
# Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
78
# have existing data somewhere already.
79 80
#
# = Single-file fixtures
81 82
#
# This type of fixtures was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
83
# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
# 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
D
David Heinemeier Hansson 已提交
106
# fixtures, but first let's take a look at a sample unit test found:
107 108 109 110 111 112
#
#   require 'web_site'
#
#   class WebSiteTest < Test::Unit::TestCase
#     def test_web_site_count
#       assert_equal 2, WebSite.count
113
#     end
114
#   end
115 116
#
# As it stands, unless we pre-load the web_site table in our database with two records, this test will fail.  Here's the
117 118 119 120 121 122
# easiest way to add fixtures to the database:
#
#   ...
#   class WebSiteTest < Test::Unit::TestCase
#     fixtures :web_sites # add more by separating the symbols with commas
#   ...
123
#
124
# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here tho), we trigger
125
# the testing environment to automatically load the appropriate fixtures into the database before each test.  
D
David Heinemeier Hansson 已提交
126
# To ensure consistent data, the environment deletes the fixtures before running the load.
127 128 129 130 131 132 133 134 135
#
# 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
D
Initial  
David Heinemeier Hansson 已提交
136
#   def test_find
137
#     assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
D
Initial  
David Heinemeier Hansson 已提交
138 139
#   end
#
140 141
# 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
D
David Heinemeier Hansson 已提交
142
# from a CSV fixture file, would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
143
# fixtures available as instance variables @web_site_1 and @web_site_2.
D
Initial  
David Heinemeier Hansson 已提交
144
#
145 146 147 148 149 150
# 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:
151
#       self.use_instantiated_fixtures = :no_instances 
152
#
153 154 155 156 157 158 159 160 161 162
# 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
#
163
# = Dynamic fixtures with ERb
D
Initial  
David Heinemeier Hansson 已提交
164
#
165 166
# 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:
D
Initial  
David Heinemeier Hansson 已提交
167
#
168 169 170 171 172
# <% for i in 1..1000 %>
# fix_<%= i %>:
#   id: <%= i %>
#   name: guy_<%= 1 %>
# <% end %>
D
Initial  
David Heinemeier Hansson 已提交
173
#
D
David Heinemeier Hansson 已提交
174
# This will create 1000 very simple YAML fixtures.
175 176
#
# Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>.
177
# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
178 179
# 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.
180 181 182
#
# = Transactional fixtures
#
183
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case. 
184 185 186 187 188
# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
#
#   class FooTest < Test::Unit::TestCase
#     self.use_transactional_fixtures = true
#     self.use_instantiated_fixtures = false
189
#   
190
#     fixtures :foos
191
#   
192
#     def test_godzilla
193
#       assert !Foo.find(:all).empty?
194
#       Foo.destroy_all
195
#       assert Foo.find(:all).empty?
196
#     end
197
#   
198
#     def test_godzilla_aftermath
199
#       assert !Foo.find(:all).empty?
200 201
#     end
#   end
202 203
#   
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures, 
204 205
# 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.
#
206
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide 
207 208
# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
#
209 210 211 212 213
# 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. 
214
#      Use InnoDB, MaxDB, or NDB instead.
215
class Fixtures < YAML::Omap
216 217
  DEFAULT_FILTER_RE = /\.ya?ml$/

218
  def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)
219
    object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
220
    if load_instances
221
      ActiveRecord::Base.silence do
222 223 224 225
        fixtures.each do |name, fixture|
          if model = fixture.find
            object.instance_variable_set "@#{name}", model
          end
226
        end
227
      end
D
Initial  
David Heinemeier Hansson 已提交
228 229
    end
  end
230

231
  def self.instantiate_all_loaded_fixtures(object, load_instances=true)
232 233
    all_loaded_fixtures.each do |table_name, fixtures|
      Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
234
    end
235
  end
236
  
237
  cattr_accessor :all_loaded_fixtures
238
  self.all_loaded_fixtures = {}
239

D
Initial  
David Heinemeier Hansson 已提交
240
  def self.create_fixtures(fixtures_directory, *table_names)
241
    table_names = table_names.flatten.map { |n| n.to_s }
D
Initial  
David Heinemeier Hansson 已提交
242 243
    connection = block_given? ? yield : ActiveRecord::Base.connection

244
    ActiveRecord::Base.silence do
245
      fixtures_map = {}
246
      fixtures = table_names.map do |table_name|
247 248 249
        fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s))
      end               
      all_loaded_fixtures.merge! fixtures_map  
250

251
      connection.transaction do
252 253
        fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
        fixtures.each { |fixture| fixture.insert_fixtures }
254

255
        # Cap primary key sequences to max(pk).
256
        if connection.respond_to?(:reset_pk_sequence!)
257
          table_names.each do |table_name|
258 259
            connection.reset_pk_sequence!(table_name)
          end
260 261
        end
      end
262

D
Initial  
David Heinemeier Hansson 已提交
263
      return fixtures.size > 1 ? fixtures : fixtures.first
264 265 266
    end
  end

267

268 269
  attr_reader :table_name

270
  def initialize(connection, table_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
D
Initial  
David Heinemeier Hansson 已提交
271 272
    @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter

273
    @class_name = ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize
274
    @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
D
Initial  
David Heinemeier Hansson 已提交
275
    read_fixture_files
276 277 278
  end

  def delete_existing_fixtures
279
    @connection.delete "DELETE FROM #{@table_name}", 'Fixture Delete'
280 281 282 283
  end

  def insert_fixtures
    values.each do |fixture|
284
      @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
285
    end
D
Initial  
David Heinemeier Hansson 已提交
286 287 288
  end

  private
289

D
Initial  
David Heinemeier Hansson 已提交
290
    def read_fixture_files
291
      if File.file?(yaml_file_path)
292
        # YAML fixtures
293
        begin
294
          yaml_string = ""
295
          Dir["#{@fixture_path}/**/*.yml"].select {|f| test(?f,f) }.each do |subfixture_path|
296 297 298 299 300
            yaml_string << IO.read(subfixture_path)
          end
          yaml_string << IO.read(yaml_file_path)

          if yaml = YAML::load(erb_render(yaml_string))
301
            yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
302 303 304 305
            yaml.each do |name, data|
              self[name] = Fixture.new(data, @class_name)
            end
          end
306
        rescue Exception=>boom
307
          raise Fixture::FormatError, "a YAML error occured 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  #{boom.class}: #{boom}"
D
Initial  
David Heinemeier Hansson 已提交
308
        end
309
      elsif File.file?(csv_file_path)
310 311 312 313 314 315 316 317 318
        # 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
319 320
      elsif File.file?(deprecated_yaml_file_path)
        raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}"
D
Initial  
David Heinemeier Hansson 已提交
321
      else
322
        # Standard fixtures
323 324 325 326 327
        Dir.entries(@fixture_path).each do |file|
          path = File.join(@fixture_path, file)
          if File.file?(path) and file !~ @file_filter
            self[file] = Fixture.new(path, @class_name)
          end
D
Initial  
David Heinemeier Hansson 已提交
328 329 330 331 332
        end
      end
    end

    def yaml_file_path
333 334 335 336 337
      "#{@fixture_path}.yml"
    end

    def deprecated_yaml_file_path
      "#{@fixture_path}.yaml"
D
Initial  
David Heinemeier Hansson 已提交
338
    end
339 340 341 342

    def csv_file_path
      @fixture_path + ".csv"
    end
343

D
Initial  
David Heinemeier Hansson 已提交
344 345 346 347 348 349 350 351 352 353 354
    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
355 356 357 358
  class FixtureError < StandardError#:nodoc:
  end
  class FormatError < FixtureError#:nodoc:
  end
D
Initial  
David Heinemeier Hansson 已提交
359 360

  def initialize(fixture, class_name)
361 362 363 364 365 366 367 368 369
    case fixture
      when Hash, YAML::Omap
        @fixture = fixture
      when String
        @fixture = read_fixture_file(fixture)
      else
        raise ArgumentError, "Bad fixture argument #{fixture.inspect}"
    end

D
Initial  
David Heinemeier Hansson 已提交
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
    @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
386 387
    columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
    columns.join(", ")
D
Initial  
David Heinemeier Hansson 已提交
388 389 390 391 392
  end

  def value_list
    @fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ")
  end
393

D
Initial  
David Heinemeier Hansson 已提交
394
  def find
395 396 397 398
    if Object.const_defined?(@class_name)
      klass = Object.const_get(@class_name)
      klass.find(self[klass.primary_key])
    end
D
Initial  
David Heinemeier Hansson 已提交
399
  end
400

D
Initial  
David Heinemeier Hansson 已提交
401 402 403 404
  private
    def read_fixture_file(fixture_file_path)
      IO.readlines(fixture_file_path).inject({}) do |fixture, line|
        # Mercifully skip empty lines.
405
        next if line =~ /^\s*$/
D
Initial  
David Heinemeier Hansson 已提交
406 407 408

        # Use the same regular expression for attributes as Active Record.
        unless md = /^\s*([a-zA-Z][-_\w]*)\s*=>\s*(.+)\s*$/.match(line)
409
          raise FormatError, "#{fixture_file_path}: fixture format error at '#{line}'.  Expecting 'key => value'."
D
Initial  
David Heinemeier Hansson 已提交
410 411 412 413
        end
        key, value = md.captures

        # Disallow duplicate keys to catch typos.
414
        raise FormatError, "#{fixture_file_path}: duplicate '#{key}' in fixture." if fixture[key]
D
Initial  
David Heinemeier Hansson 已提交
415 416 417 418 419 420
        fixture[key] = value.strip
        fixture
      end
    end
end

421 422
module Test #:nodoc:
  module Unit #:nodoc:
423 424
    class TestCase #:nodoc:
      cattr_accessor :fixture_path
425 426
      class_inheritable_accessor :fixture_table_names
      class_inheritable_accessor :use_transactional_fixtures
427 428
      class_inheritable_accessor :use_instantiated_fixtures   # true, false, or :no_instances
      class_inheritable_accessor :pre_loaded_fixtures
429 430 431 432

      self.fixture_table_names = []
      self.use_transactional_fixtures = false
      self.use_instantiated_fixtures = true
433
      self.pre_loaded_fixtures = false
D
Initial  
David Heinemeier Hansson 已提交
434

435
      @@already_loaded_fixtures = {}
436

437 438 439 440 441 442
      def self.fixtures(*table_names)
        table_names = table_names.flatten.map { |n| n.to_s }
        self.fixture_table_names |= table_names
        require_fixture_classes(table_names)
        setup_fixture_accessors(table_names)
      end
443

444 445
      def self.require_fixture_classes(table_names=nil)
        (table_names || fixture_table_names).each do |table_name| 
446 447
          file_name = table_name.to_s
          file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
448
          begin
449
            require file_name
450 451
          rescue LoadError
            # Let's hope the developer has included it himself
452 453
          end
        end
454
      end
455

456 457 458 459 460 461 462
      def self.setup_fixture_accessors(table_names=nil)
        (table_names || fixture_table_names).each do |table_name|
          table_name = table_name.to_s.tr('.','_')
          define_method(table_name) do |fixture, *optionals|
            force_reload = optionals.shift
            @fixture_cache[table_name] ||= Hash.new
            @fixture_cache[table_name][fixture] = nil if force_reload
463 464 465 466 467
            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
468 469
          end
        end
470
      end
471

472 473 474 475
      def self.uses_transaction(*methods)
        @uses_transaction ||= []
        @uses_transaction.concat methods.map { |m| m.to_s }
      end
476

477 478
      def self.uses_transaction?(method)
        @uses_transaction && @uses_transaction.include?(method.to_s)
479 480 481
      end

      def use_transactional_fixtures?
482 483
        use_transactional_fixtures &&
          !self.class.uses_transaction?(method_name)
484 485
      end

486
      def setup_with_fixtures
487
        if pre_loaded_fixtures && !use_transactional_fixtures
488
          raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures' 
489 490
        end

491 492
        @fixture_cache = Hash.new

493
        # Load fixtures once and begin transaction.
494
        if use_transactional_fixtures?
495 496 497 498 499 500
          if @@already_loaded_fixtures[self.class]
            @loaded_fixtures = @@already_loaded_fixtures[self.class]
          else
            load_fixtures
            @@already_loaded_fixtures[self.class] = @loaded_fixtures
          end
501 502 503 504 505
          ActiveRecord::Base.lock_mutex
          ActiveRecord::Base.connection.begin_db_transaction

        # Load fixtures for every test.
        else
506 507
          @@already_loaded_fixtures[self.class] = nil
          load_fixtures
508 509 510 511 512 513 514 515 516 517
        end

        # Instantiate fixtures for every test if requested.
        instantiate_fixtures if use_instantiated_fixtures
      end

      alias_method :setup, :setup_with_fixtures

      def teardown_with_fixtures
        # Rollback changes.
518
        if use_transactional_fixtures?
519 520 521
          ActiveRecord::Base.connection.rollback_db_transaction
          ActiveRecord::Base.unlock_mutex
        end
522
        ActiveRecord::Base.clear_connection_cache!
D
Initial  
David Heinemeier Hansson 已提交
523 524
      end

525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
      alias_method :teardown, :teardown_with_fixtures

      def self.method_added(method)
        case method.to_s
        when 'setup'
          unless method_defined?(:setup_without_fixtures)
            alias_method :setup_without_fixtures, :setup
            define_method(:setup) do
              setup_with_fixtures
              setup_without_fixtures
            end
          end
        when 'teardown'
          unless method_defined?(:teardown_without_fixtures)
            alias_method :teardown_without_fixtures, :teardown
            define_method(:teardown) do
              teardown_without_fixtures
              teardown_with_fixtures
            end
544 545 546
          end
        end
      end
547

548
      private
549 550 551 552 553 554
        def load_fixtures
          @loaded_fixtures = {}
          fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names)
          unless fixtures.nil?
            if fixtures.instance_of?(Fixtures)
              @loaded_fixtures[fixtures.table_name] = fixtures
555
            else
556 557
              fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
            end
558
          end
559
        end
560

561 562
        # for pre_loaded_fixtures, only require the classes once. huge speed improvement
        @@required_fixture_classes = false
563

564
        def instantiate_fixtures
565 566 567
          if pre_loaded_fixtures
            raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
            unless @@required_fixture_classes
568
              self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
569 570 571
              @@required_fixture_classes = true
            end
            Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
572
          else
573 574 575 576
            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
577
          end
578
        end
579

580 581 582
        def load_instances?
          use_instantiated_fixtures != :no_instances
        end
D
Initial  
David Heinemeier Hansson 已提交
583
    end
584

585
  end
586
end