fixtures.rb 33.8 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1 2
require 'erb'
require 'yaml'
3
require 'csv'
4
require 'active_support/test_case'
D
Initial  
David Heinemeier Hansson 已提交
5

6 7 8 9 10 11
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
12
  end
13 14
end

15 16 17 18 19 20
if defined? ActiveRecord
  class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
  end
else
  class FixtureClassNotFound < StandardError #:nodoc:
  end
21 22
end

23
# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavors:
D
Initial  
David Heinemeier Hansson 已提交
24
#
25 26 27
#   1.  YAML fixtures
#   2.  CSV fixtures
#   3.  Single-file fixtures
D
Initial  
David Heinemeier Hansson 已提交
28
#
29
# = YAML fixtures
D
Initial  
David Heinemeier Hansson 已提交
30
#
31
# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
32
# in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
D
Initial  
David Heinemeier Hansson 已提交
33
#
D
David Heinemeier Hansson 已提交
34
# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
35
# by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
P
Pratik Naik 已提交
36 37
# 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:
D
Initial  
David Heinemeier Hansson 已提交
38
#
39 40 41 42
#   rubyonrails:
#     id: 1
#     name: Ruby on Rails
#     url: http://www.rubyonrails.org
D
Initial  
David Heinemeier Hansson 已提交
43
#
44 45 46 47 48
#   google:
#     id: 2
#     name: Google
#     url: http://www.google.com
#
49
# This YAML fixture file includes two fixtures.  Each YAML fixture (ie. record) is given a name and is followed by an
50
# indented list of key/value pairs in the "key: value" format.  Records are separated by a blank line for your viewing
51
# pleasure.
52
#
53 54 55 56 57 58 59 60 61 62 63 64 65 66
# 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
#
67 68
# = CSV fixtures
#
D
David Heinemeier Hansson 已提交
69
# Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
P
Pratik Naik 已提交
70 71
# 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>).
72
#
73
# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
74 75 76 77 78 79 80 81 82 83 84
# 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
85
# fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
86 87 88
# 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".
#
89
# Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
90
# have existing data somewhere already.
91 92
#
# = Single-file fixtures
93
#
94
# This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
95
# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
96
# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
P
Pratik Naik 已提交
97 98
# 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).
99 100
#
# Each text file placed in this directory represents a "record".  Usually these types of fixtures are named without
P
Pratik Naik 已提交
101
# extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension.  Here's what the
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
# 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
118
# fixtures, but first let's take a look at a sample unit test:
119 120 121
#
#   require 'web_site'
#
122
#   class WebSiteTest < ActiveSupport::TestCase
123 124
#     def test_web_site_count
#       assert_equal 2, WebSite.count
125
#     end
126
#   end
127 128
#
# As it stands, unless we pre-load the web_site table in our database with two records, this test will fail.  Here's the
129 130 131
# easiest way to add fixtures to the database:
#
#   ...
132
#   class WebSiteTest < ActiveSupport::TestCase
133 134
#     fixtures :web_sites # add more by separating the symbols with commas
#   ...
135
#
136
# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here though), we trigger
137
# the testing environment to automatically load the appropriate fixtures into the database before each test.
D
David Heinemeier Hansson 已提交
138
# To ensure consistent data, the environment deletes the fixtures before running the load.
139 140 141
#
# 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
P
Pratik Naik 已提交
142
# <tt>@web_sites</tt>.  This is where the "fixture name" comes into play.
143
#
P
Pratik Naik 已提交
144 145
# On top of that, each record is automatically "found" (using <tt>Model.find(id)</tt>) and placed in the instance variable of its name.
# So for the YAML fixtures, we'd get <tt>@rubyonrails</tt> and <tt>@google</tt>, which could be interrogated using regular Active Record semantics:
146 147
#
#   # test if the object created from the fixture data has the same attributes as the data itself
D
Initial  
David Heinemeier Hansson 已提交
148
#   def test_find
149
#     assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
D
Initial  
David Heinemeier Hansson 已提交
150 151
#   end
#
P
Pratik Naik 已提交
152 153 154 155
# As seen above, the data hash created from the YAML fixtures would have <tt>@web_sites["rubyonrails"]["url"]</tt> return
# "http://www.rubyonrails.org" and <tt>@web_sites["google"]["name"]</tt> would return "Google". The same fixtures, but loaded
# from a CSV fixture file, would be accessible via <tt>@web_sites["web_site_1"]["name"] == "Ruby on Rails"</tt> and have the individual
# fixtures available as instance variables <tt>@web_site_1</tt> and <tt>@web_site_2</tt>.
D
Initial  
David Heinemeier Hansson 已提交
156
#
157 158 159 160 161 162
# 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:
163
#       self.use_instantiated_fixtures = :no_instances
164
#
165 166 167 168 169 170 171 172 173 174
# 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
#
175
# = Dynamic fixtures with ERb
D
Initial  
David Heinemeier Hansson 已提交
176
#
177 178
# 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 已提交
179
#
P
Pratik Naik 已提交
180 181 182 183 184
#   <% for i in 1..1000 %>
#   fix_<%= i %>:
#     id: <%= i %>
#     name: guy_<%= 1 %>
#   <% end %>
D
Initial  
David Heinemeier Hansson 已提交
185
#
D
David Heinemeier Hansson 已提交
186
# This will create 1000 very simple YAML fixtures.
187
#
P
Pratik Naik 已提交
188
# Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
189
# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
190 191
# 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.
192 193 194
#
# = Transactional fixtures
#
195
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
196 197
# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
#
198
#   class FooTest < ActiveSupport::TestCase
199 200
#     self.use_transactional_fixtures = true
#     self.use_instantiated_fixtures = false
201
#
202
#     fixtures :foos
203
#
204
#     def test_godzilla
205
#       assert !Foo.find(:all).empty?
206
#       Foo.destroy_all
207
#       assert Foo.find(:all).empty?
208
#     end
209
#
210
#     def test_godzilla_aftermath
211
#       assert !Foo.find(:all).empty?
212 213
#     end
#   end
214
#
215
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
216 217
# 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.
#
218
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
219 220
# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
#
221 222 223
# 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
224
#      the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
225
#   2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
226
#      Use InnoDB, MaxDB, or NDB instead.
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
#
# = Advanced YAML Fixtures
#
# YAML fixtures that don't specify an ID get some extra features:
#
# * Stable, autogenerated ID's
# * 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
#
# == Stable, autogenerated ID's
#
# 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
#
P
Pratik Naik 已提交
261
# Active Record looks at the fixture's model class, discovers the correct
262 263 264 265 266 267 268 269 270
# 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
P
Pratik Naik 已提交
271
# mention difficult to read. Since Active Record can figure out the ID of
272
# any fixture from its label, you can specify FK's by label instead of ID.
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
#
# === 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
# use labels instead of ID's:
#
#   ### in pirates.yml
#
#   reginald:
#     name: Reginald the Pirate
#     monkey: george
#
#   ### in monkeys.yml
#
#   george:
#     name: George the Monkey
#     pirate: reginald
#
P
Pratik Naik 已提交
308
# Pow! All is made clear. Active Record reflects on the fixture's model class,
309 310
# finds all the +belongs_to+ associations, and allows you to specify
# a target *label* for the *association* (monkey: george) rather than
P
Pratik Naik 已提交
311
# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
312
#
313 314 315
# ==== Polymorphic belongs_to
#
# Supporting polymorphic relationships is a little bit more complicated, since
P
Pratik Naik 已提交
316
# Active Record needs to know what type your association is pointing at. Something
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
# 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)
#
P
Pratik Naik 已提交
336
# Just provide the polymorphic target type and Active Record will take care of the rest.
337
#
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
# === 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
P
Pratik Naik 已提交
399
# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
400 401 402 403 404
# the fixture's model class and discovers the +has_and_belongs_to_many+
# associations.
#
# == Autofilled timestamp columns
#
P
Pratik Naik 已提交
405 406 407
# 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>.
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
#
# 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
P
Pratik Naik 已提交
430
# your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
431 432 433 434 435 436 437 438 439 440 441 442 443 444
#
#   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.

445
class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
446 447
  DEFAULT_FILTER_RE = /\.ya?ml$/

448
  @@all_cached_fixtures = {}
449 450 451

  def self.reset_cache(connection = nil)
    connection ||= ActiveRecord::Base.connection
452 453
    @@all_cached_fixtures[connection.object_id] = {}
  end
454

455 456 457 458
  def self.cache_for_connection(connection)
    @@all_cached_fixtures[connection.object_id] ||= {}
    @@all_cached_fixtures[connection.object_id]
  end
459

460 461 462 463 464 465 466 467 468 469 470 471 472
  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

473 474
  def self.cache_fixtures(connection, fixtures_map)
    cache_for_connection(connection).update(fixtures_map)
475
  end
476 477

  def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
478
    object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
479
    if load_instances
480
      ActiveRecord::Base.silence do
481
        fixtures.each do |name, fixture|
482 483 484 485
          begin
            object.instance_variable_set "@#{name}", fixture.find
          rescue FixtureClassNotFound
            nil
486
          end
487
        end
488
      end
D
Initial  
David Heinemeier Hansson 已提交
489 490
    end
  end
491

492
  def self.instantiate_all_loaded_fixtures(object, load_instances = true)
493 494
    all_loaded_fixtures.each do |table_name, fixtures|
      Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
495
    end
496
  end
497

498
  cattr_accessor :all_loaded_fixtures
499
  self.all_loaded_fixtures = {}
500

501
  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
502
    table_names = [table_names].flatten.map { |n| n.to_s }
503 504
    connection  = block_given? ? yield : ActiveRecord::Base.connection

505 506
    table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }

507 508
    unless table_names_to_fetch.empty?
      ActiveRecord::Base.silence do
509 510
        connection.disable_referential_integrity do
          fixtures_map = {}
511

512 513 514
          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
515

516
          all_loaded_fixtures.update(fixtures_map)
517

518
          connection.transaction(connection.open_transactions.zero?) do
519 520
            fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
            fixtures.each { |fixture| fixture.insert_fixtures }
521

522 523 524 525 526
            # 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
527
            end
528
          end
529

530
          cache_fixtures(connection, fixtures_map)
531
        end
532
      end
533
    end
534
    cached_fixtures(connection, table_names)
535 536
  end

537 538 539 540 541 542 543
  # Returns a consistent identifier for +label+. This will always
  # be a positive integer, and will always be the same for a given
  # label, assuming the same OS, platform, and version of Ruby.
  def self.identify(label)
    label.to_s.hash.abs
  end

544
  attr_reader :table_name, :name
545

546
  def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
D
Initial  
David Heinemeier Hansson 已提交
547
    @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
548
    @name = table_name # preserve fixture base name
549
    @class_name = class_name ||
550
                  (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
551
    @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
552 553
    @table_name = class_name.table_name if class_name.respond_to?(:table_name)
    @connection = class_name.connection if class_name.respond_to?(:connection)
D
Initial  
David Heinemeier Hansson 已提交
554
    read_fixture_files
555 556 557
  end

  def delete_existing_fixtures
558
    @connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
559 560 561
  end

  def insert_fixtures
562 563 564 565
    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
566 567 568 569 570
    if is_a?(Hash)
      delete('DEFAULTS')
    else
      delete(assoc('DEFAULTS'))
    end
571 572 573 574 575 576 577 578 579

    # 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)
    end

    each do |label, fixture|
      row = fixture.to_hash

580 581 582 583 584 585
      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
586 587 588 589 590 591 592
        end

        # interpolate the fixture label
        row.each do |key, value|
          row[key] = label if value == "$LABEL"
        end

593
        # generate a primary key if necessary
594 595 596
        if has_primary_key_column? && !row.include?(primary_key_name)
          row[primary_key_name] = Fixtures.identify(label)
        end
597

598 599 600 601 602 603 604 605 606
        # 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|
607 608
          case association.macro
          when :belongs_to
609 610 611 612
            # 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)
613 614 615 616 617 618 619 620 621 622
              if association.options[:polymorphic]
                if value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
                  target_type = $1
                  target_type_name = (association.options[:foreign_type] || "#{association.name}_type").to_s

                  # support polymorphic belongs_to as "label (Type)"
                  row[target_type_name] = target_type
                end
              end

623 624 625 626 627 628 629 630 631
              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(
632
                  { association.primary_key_name => row[primary_key_name],
633 634 635 636 637 638 639
                    association.association_foreign_key => Fixtures.identify(target) }, nil)
              end
            end
          end
        end
      end

640
      @connection.insert_fixture(fixture, @table_name)
641
    end
642 643 644 645 646 647

    # insert any HABTM join tables we discovered
    habtm_fixtures.values.each do |fixture|
      fixture.delete_existing_fixtures
      fixture.insert_fixtures
    end
D
Initial  
David Heinemeier Hansson 已提交
648 649
  end

650
  private
651 652 653 654 655
    class HabtmFixtures < ::Fixtures #:nodoc:
      def read_fixture_files; end
    end

    def model_class
656 657 658 659 660 661 662 663 664 665
      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
666 667 668 669 670 671
    end

    def primary_key_name
      @primary_key_name ||= model_class && model_class.primary_key
    end

672 673 674
    def has_primary_key_column?
      @has_primary_key_column ||= model_class && primary_key_name &&
        model_class.columns.find { |c| c.name == primary_key_name }
675
    end
676

677 678 679 680 681 682
    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

683 684 685 686
    def inheritance_column_name
      @inheritance_column_name ||= model_class && model_class.inheritance_column
    end

687 688 689 690
    def column_names
      @column_names ||= @connection.columns(@table_name).collect(&:name)
    end

D
Initial  
David Heinemeier Hansson 已提交
691
    def read_fixture_files
692
      if File.file?(yaml_file_path)
693
        read_yaml_fixture_files
694
      elsif File.file?(csv_file_path)
695
        read_csv_fixture_files
D
Initial  
David Heinemeier Hansson 已提交
696 697 698
      end
    end

699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
    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|
716 717
          raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
	  fixture.each do |name, data|
718 719 720 721
            unless data
              raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
            end

722
            self[name] = Fixture.new(data, model_class)
723 724 725
          end
        end
      end
726 727
    end

728
    def read_csv_fixture_files
729
      reader = CSV.parse(erb_render(IO.read(csv_file_path)))
730 731 732 733 734
      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 }
735
        self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
736 737 738 739 740
      end
    end

    def yaml_file_path
      "#{@fixture_path}.yml"
D
Initial  
David Heinemeier Hansson 已提交
741
    end
742 743 744 745

    def csv_file_path
      @fixture_path + ".csv"
    end
746

D
Initial  
David Heinemeier Hansson 已提交
747 748 749 750
    def yaml_fixtures_key(path)
      File.basename(@fixture_path).split(".").first
    end

751 752 753 754 755 756
    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\nThe exact error was:\n  #{error.class}: #{error}"
    end

D
Initial  
David Heinemeier Hansson 已提交
757 758 759 760 761 762 763
    def erb_render(fixture_content)
      ERB.new(fixture_content).result
    end
end

class Fixture #:nodoc:
  include Enumerable
764 765

  class FixtureError < StandardError #:nodoc:
766
  end
767 768

  class FormatError < FixtureError #:nodoc:
769
  end
D
Initial  
David Heinemeier Hansson 已提交
770

771
  attr_reader :model_class
772

773
  def initialize(fixture, model_class)
774
    @fixture = fixture
775 776 777 778 779
    @model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
  end

  def class_name
    @model_class.name if @model_class
D
Initial  
David Heinemeier Hansson 已提交
780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
  end

  def each
    @fixture.each { |item| yield item }
  end

  def [](key)
    @fixture[key]
  end

  def to_hash
    @fixture
  end

  def key_list
795 796
    columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
    columns.join(", ")
D
Initial  
David Heinemeier Hansson 已提交
797 798 799
  end

  def value_list
800
    list = @fixture.inject([]) do |fixtures, (key, value)|
801
      col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
802
      fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
803 804
    end
    list * ', '
D
Initial  
David Heinemeier Hansson 已提交
805
  end
806

D
Initial  
David Heinemeier Hansson 已提交
807
  def find
808 809
    if model_class
      model_class.find(self[model_class.primary_key])
810
    else
811
      raise FixtureClassNotFound, "No class attached to find."
812
    end
D
Initial  
David Heinemeier Hansson 已提交
813 814 815
  end
end

816 817
module Test #:nodoc:
  module Unit #:nodoc:
818
    class TestCase #:nodoc:
819 820 821
      setup :setup_fixtures
      teardown :teardown_fixtures

822 823 824 825 826 827
      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
828 829 830 831

      self.fixture_table_names = []
      self.use_transactional_fixtures = false
      self.use_instantiated_fixtures = true
832
      self.pre_loaded_fixtures = false
833

834
      @@already_loaded_fixtures = {}
835
      self.fixture_class_names = {}
836

837 838 839
      class << self
        def set_fixture_class(class_names = {})
          self.fixture_class_names = self.fixture_class_names.merge(class_names)
840 841
        end

842 843 844 845 846 847 848
        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 }
          end
849

850 851 852 853 854
          self.fixture_table_names |= table_names
          require_fixture_classes(table_names)
          setup_fixture_accessors(table_names)
        end

855 856 857 858
        def try_to_load_dependency(file_name)
          require_dependency file_name
        rescue LoadError => e
          # Let's hope the developer has included it himself
859

860 861
          # Let's warn in case this is a subdependency, otherwise
          # subdependency error messages are totally cryptic
862 863 864
          if ActiveRecord::Base.logger
            ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
          end
865
        end
866

867 868 869 870
        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
871
            try_to_load_dependency(file_name)
872 873
          end
        end
874

875 876 877 878
        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('.', '_')
879

880 881
            define_method(table_name) do |*fixtures|
              force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
882

883
              @fixture_cache[table_name] ||= {}
884

885 886
              instances = fixtures.map do |fixture|
                @fixture_cache[table_name].delete(fixture) if force_reload
887

888 889 890 891 892
                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
893 894
              end

895 896
              instances.size == 1 ? instances.first : instances
            end
897 898 899
          end
        end

900 901 902 903
        def uses_transaction(*methods)
          @uses_transaction = [] unless defined?(@uses_transaction)
          @uses_transaction.concat methods.map(&:to_s)
        end
904

905 906 907 908
        def uses_transaction?(method)
          @uses_transaction = [] unless defined?(@uses_transaction)
          @uses_transaction.include?(method.to_s)
        end
909 910 911
      end

      def use_transactional_fixtures?
912 913
        use_transactional_fixtures &&
          !self.class.uses_transaction?(method_name)
914 915
      end

916 917
      def setup_fixtures
        return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
918

919
        if pre_loaded_fixtures && !use_transactional_fixtures
920
          raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
921 922
        end

923
        @fixture_cache = {}
924

925
        # Load fixtures once and begin transaction.
926
        if use_transactional_fixtures?
927 928 929 930 931 932
          if @@already_loaded_fixtures[self.class]
            @loaded_fixtures = @@already_loaded_fixtures[self.class]
          else
            load_fixtures
            @@already_loaded_fixtures[self.class] = @loaded_fixtures
          end
933
          ActiveRecord::Base.connection.increment_open_transactions
934 935 936
          ActiveRecord::Base.connection.begin_db_transaction
        # Load fixtures for every test.
        else
937
          Fixtures.reset_cache
938 939
          @@already_loaded_fixtures[self.class] = nil
          load_fixtures
940 941 942 943 944 945
        end

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

946 947
      def teardown_fixtures
        return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
948

949 950
        unless use_transactional_fixtures?
          Fixtures.reset_cache
951
        end
952

953
        # Rollback changes if a transaction is active.
954
        if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
955
          ActiveRecord::Base.connection.rollback_db_transaction
956
          ActiveRecord::Base.connection.decrement_open_transactions
957
        end
958
        ActiveRecord::Base.clear_active_connections!
D
Initial  
David Heinemeier Hansson 已提交
959
      end
960

961
      private
962 963
        def load_fixtures
          @loaded_fixtures = {}
964
          fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
965 966
          unless fixtures.nil?
            if fixtures.instance_of?(Fixtures)
967
              @loaded_fixtures[fixtures.name] = fixtures
968
            else
969
              fixtures.each { |f| @loaded_fixtures[f.name] = f }
970
            end
971
          end
972
        end
973

974 975
        # for pre_loaded_fixtures, only require the classes once. huge speed improvement
        @@required_fixture_classes = false
976

977
        def instantiate_fixtures
978 979 980
          if pre_loaded_fixtures
            raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
            unless @@required_fixture_classes
981
              self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
982 983 984
              @@required_fixture_classes = true
            end
            Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
985
          else
986 987 988 989
            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
990
          end
991
        end
992

993 994 995
        def load_instances?
          use_instantiated_fixtures != :no_instances
        end
D
Initial  
David Heinemeier Hansson 已提交
996
    end
997
  end
998
end