fixtures.rb 23.0 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 13 14
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
end

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

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

236
  def self.instantiate_all_loaded_fixtures(object, load_instances=true)
237 238
    all_loaded_fixtures.each do |table_name, fixtures|
      Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
239
    end
240
  end
241
  
242
  cattr_accessor :all_loaded_fixtures
243
  self.all_loaded_fixtures = {}
244

245
  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
246
    table_names = [table_names].flatten.map { |n| n.to_s }
D
Initial  
David Heinemeier Hansson 已提交
247
    connection = block_given? ? yield : ActiveRecord::Base.connection
248
    ActiveRecord::Base.silence do
249
      fixtures_map = {}
250
      fixtures = table_names.map do |table_name|
251
        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))
252 253
      end               
      all_loaded_fixtures.merge! fixtures_map  
254

255
      connection.transaction do
256 257
        fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
        fixtures.each { |fixture| fixture.insert_fixtures }
258

259
        # Cap primary key sequences to max(pk).
260
        if connection.respond_to?(:reset_pk_sequence!)
261
          table_names.each do |table_name|
262 263
            connection.reset_pk_sequence!(table_name)
          end
264 265
        end
      end
266

D
Initial  
David Heinemeier Hansson 已提交
267
      return fixtures.size > 1 ? fixtures : fixtures.first
268 269 270
    end
  end

271

272 273
  attr_reader :table_name

274
  def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
D
Initial  
David Heinemeier Hansson 已提交
275
    @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
276 277
    @class_name = class_name || 
                  (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
278
    @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
D
Initial  
David Heinemeier Hansson 已提交
279
    read_fixture_files
280 281 282
  end

  def delete_existing_fixtures
283
    @connection.delete "DELETE FROM #{@table_name}", 'Fixture Delete'
284 285 286 287
  end

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

  private
293

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

          if yaml = YAML::load(erb_render(yaml_string))
305
            yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
306 307 308 309
            yaml.each do |name, data|
              self[name] = Fixture.new(data, @class_name)
            end
          end
310
        rescue Exception=>boom
311
          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 已提交
312
        end
313
      elsif File.file?(csv_file_path)
314 315 316 317 318 319 320 321 322
        # 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
323 324
      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 已提交
325
      else
326
        # Standard fixtures
327 328 329 330 331
        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 已提交
332 333 334 335 336
        end
      end
    end

    def yaml_file_path
337 338 339 340 341
      "#{@fixture_path}.yml"
    end

    def deprecated_yaml_file_path
      "#{@fixture_path}.yaml"
D
Initial  
David Heinemeier Hansson 已提交
342
    end
343 344 345 346

    def csv_file_path
      @fixture_path + ".csv"
    end
347

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

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

  def value_list
395 396 397 398 399 400 401
    klass = @class_name.constantize rescue nil

    list = @fixture.inject([]) do |fixtures, (key, value)|
      col = klass.columns_hash[key] unless klass.nil?
      fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('\\n', "\n").gsub('\\r', "\r")
    end
    list * ', '
D
Initial  
David Heinemeier Hansson 已提交
402
  end
403

D
Initial  
David Heinemeier Hansson 已提交
404
  def find
405 406
    klass = @class_name.is_a?(Class) ? @class_name : Object.const_get(@class_name) rescue nil
    if klass
407
      klass.find(self[klass.primary_key])
408 409
    else
      raise FixtureClassNotFound, "The class #{@class_name.inspect} was not found."
410
    end
D
Initial  
David Heinemeier Hansson 已提交
411
  end
412

D
Initial  
David Heinemeier Hansson 已提交
413 414 415 416
  private
    def read_fixture_file(fixture_file_path)
      IO.readlines(fixture_file_path).inject({}) do |fixture, line|
        # Mercifully skip empty lines.
417
        next if line =~ /^\s*$/
D
Initial  
David Heinemeier Hansson 已提交
418 419 420

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

        # Disallow duplicate keys to catch typos.
426
        raise FormatError, "#{fixture_file_path}: duplicate '#{key}' in fixture." if fixture[key]
D
Initial  
David Heinemeier Hansson 已提交
427 428 429 430 431 432
        fixture[key] = value.strip
        fixture
      end
    end
end

433 434
module Test #:nodoc:
  module Unit #:nodoc:
435 436
    class TestCase #:nodoc:
      cattr_accessor :fixture_path
437
      class_inheritable_accessor :fixture_table_names
438
      class_inheritable_accessor :fixture_class_names
439
      class_inheritable_accessor :use_transactional_fixtures
440 441
      class_inheritable_accessor :use_instantiated_fixtures   # true, false, or :no_instances
      class_inheritable_accessor :pre_loaded_fixtures
442 443 444 445

      self.fixture_table_names = []
      self.use_transactional_fixtures = false
      self.use_instantiated_fixtures = true
446
      self.pre_loaded_fixtures = false
447 448 449
      
      self.fixture_class_names = {}
      
450
      @@already_loaded_fixtures = {}
451 452 453 454 455 456
      self.fixture_class_names = {}
      
      def self.set_fixture_class(class_names = {})
        self.fixture_class_names = self.fixture_class_names.merge(class_names)
      end
      
457 458 459 460 461 462
      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
463

464 465
      def self.require_fixture_classes(table_names=nil)
        (table_names || fixture_table_names).each do |table_name| 
466 467
          file_name = table_name.to_s
          file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
468
          begin
469
            require file_name
470 471
          rescue LoadError
            # Let's hope the developer has included it himself
472 473
          end
        end
474
      end
475

476 477 478 479 480 481 482
      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
483 484 485 486 487
            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
488 489
          end
        end
490
      end
491

492
      def self.uses_transaction(*methods)
J
Jeremy Kemper 已提交
493 494
        @uses_transaction = [] unless defined?(@uses_transaction)
        @uses_transaction.concat methods.map(&:to_s)
495
      end
496

497
      def self.uses_transaction?(method)
J
Jeremy Kemper 已提交
498 499
        @uses_transaction = [] unless defined?(@uses_transaction)
        @uses_transaction.include?(method.to_s)
500 501 502
      end

      def use_transactional_fixtures?
503 504
        use_transactional_fixtures &&
          !self.class.uses_transaction?(method_name)
505 506
      end

507
      def setup_with_fixtures
508
        return if !defined?(ActiveRecord::Base) || ActiveRecord::Base.configurations.blank?
509
        if pre_loaded_fixtures && !use_transactional_fixtures
510
          raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures' 
511 512
        end

513 514
        @fixture_cache = Hash.new

515
        # Load fixtures once and begin transaction.
516
        if use_transactional_fixtures?
517 518 519 520 521 522
          if @@already_loaded_fixtures[self.class]
            @loaded_fixtures = @@already_loaded_fixtures[self.class]
          else
            load_fixtures
            @@already_loaded_fixtures[self.class] = @loaded_fixtures
          end
523
          ActiveRecord::Base.send :increment_open_transactions
524 525 526 527
          ActiveRecord::Base.connection.begin_db_transaction

        # Load fixtures for every test.
        else
528 529
          @@already_loaded_fixtures[self.class] = nil
          load_fixtures
530 531 532 533 534 535 536 537 538
        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
539
        return if !defined?(ActiveRecord::Base) || ActiveRecord::Base.configurations.blank?
540
        # Rollback changes.
541
        if use_transactional_fixtures?
542
          ActiveRecord::Base.connection.rollback_db_transaction
543
          ActiveRecord::Base.send :decrement_open_transactions
544
        end
R
Rick Olson 已提交
545
        ActiveRecord::Base.verify_active_connections!
D
Initial  
David Heinemeier Hansson 已提交
546 547
      end

548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
      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
567 568 569
          end
        end
      end
570

571
      private
572 573
        def load_fixtures
          @loaded_fixtures = {}
574
          fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
575 576 577
          unless fixtures.nil?
            if fixtures.instance_of?(Fixtures)
              @loaded_fixtures[fixtures.table_name] = fixtures
578
            else
579 580
              fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
            end
581
          end
582
        end
583

584 585
        # for pre_loaded_fixtures, only require the classes once. huge speed improvement
        @@required_fixture_classes = false
586

587
        def instantiate_fixtures
588 589 590
          if pre_loaded_fixtures
            raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
            unless @@required_fixture_classes
591
              self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
592 593 594
              @@required_fixture_classes = true
            end
            Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
595
          else
596 597 598 599
            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
600
          end
601
        end
602

603 604 605
        def load_instances?
          use_instantiated_fixtures != :no_instances
        end
D
Initial  
David Heinemeier Hansson 已提交
606
    end
607

608
  end
609
end