fixtures.rb 33.7 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1 2
require 'erb'
require 'yaml'
3
require 'csv'
J
Joshua Peek 已提交
4
require 'active_support/dependencies'
5
require 'active_support/test_case'
D
Initial  
David Heinemeier Hansson 已提交
6

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

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

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

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

449
  @@all_cached_fixtures = {}
450 451 452

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

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

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

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

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

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

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

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

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

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

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

517
          all_loaded_fixtures.update(fixtures_map)
518

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

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

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

538 539 540 541 542 543 544
  # 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

545
  attr_reader :table_name, :name
546

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

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

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

    # 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

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

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

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

599 600 601 602 603 604 605 606 607
        # 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|
608 609
          case association.macro
          when :belongs_to
610 611 612 613
            # 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)
614 615 616 617 618 619 620 621 622 623
              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

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

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

    # 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 已提交
649 650
  end

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

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

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

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

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

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

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

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

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

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

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

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

    def csv_file_path
      @fixture_path + ".csv"
    end
747

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

752 753 754 755 756 757
    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 已提交
758 759 760 761 762 763 764
    def erb_render(fixture_content)
      ERB.new(fixture_content).result
    end
end

class Fixture #:nodoc:
  include Enumerable
765 766

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

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

772
  attr_reader :model_class
773

774
  def initialize(fixture, model_class)
775
    @fixture = fixture
776 777 778 779 780
    @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 已提交
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
  end

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

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

  def to_hash
    @fixture
  end

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

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

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

817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
module ActiveRecord
  module TestFixtures
    def self.included(base)
      base.class_eval do
        setup :setup_fixtures
        teardown :teardown_fixtures

        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

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

        self.fixture_class_names = {}
      end
838

839 840 841 842 843 844 845
      base.extend ClassMethods
    end

    module ClassMethods
      def set_fixture_class(class_names = {})
        self.fixture_class_names = self.fixture_class_names.merge(class_names)
      end
846

847 848 849 850 851 852
      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 }
853 854
        end

855 856 857 858 859 860 861 862 863
        self.fixture_table_names |= table_names
        require_fixture_classes(table_names)
        setup_fixture_accessors(table_names)
      end

      def try_to_load_dependency(file_name)
        require_dependency file_name
      rescue LoadError => e
        # Let's hope the developer has included it himself
864

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

872 873 874 875 876
      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
          try_to_load_dependency(file_name)
877
        end
878
      end
879

880 881 882 883
      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('.', '_')
884

885 886
          define_method(table_name) do |*fixtures|
            force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
887

888
            @fixture_cache[table_name] ||= {}
889

890 891
            instances = fixtures.map do |fixture|
              @fixture_cache[table_name].delete(fixture) if force_reload
892

893 894 895 896
              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}'"
897
              end
898
            end
899

900 901
            instances.size == 1 ? instances.first : instances
          end
902
        end
903
      end
904

905 906 907
      def uses_transaction(*methods)
        @uses_transaction = [] unless defined?(@uses_transaction)
        @uses_transaction.concat methods.map(&:to_s)
908 909
      end

910 911 912
      def uses_transaction?(method)
        @uses_transaction = [] unless defined?(@uses_transaction)
        @uses_transaction.include?(method.to_s)
913
      end
914
    end
915

916
    def run_in_transaction?
917 918 919
      use_transactional_fixtures &&
        !self.class.uses_transaction?(method_name)
    end
920

921 922
    def setup_fixtures
      return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
923

924 925 926
      if pre_loaded_fixtures && !use_transactional_fixtures
        raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
      end
927

928
      @fixture_cache = {}
929
      @@already_loaded_fixtures ||= {}
930 931

      # Load fixtures once and begin transaction.
932
      if run_in_transaction?
933 934
        if @@already_loaded_fixtures[self.class]
          @loaded_fixtures = @@already_loaded_fixtures[self.class]
935
        else
936
          load_fixtures
937
          @@already_loaded_fixtures[self.class] = @loaded_fixtures
938
        end
939 940
        ActiveRecord::Base.connection.increment_open_transactions
        ActiveRecord::Base.connection.begin_db_transaction
941
        ActiveRecord::Base.connection.transactional_fixtures = true
942 943 944 945 946
      # Load fixtures for every test.
      else
        Fixtures.reset_cache
        @@already_loaded_fixtures[self.class] = nil
        load_fixtures
947 948
      end

949 950 951 952 953 954
      # Instantiate fixtures for every test if requested.
      instantiate_fixtures if use_instantiated_fixtures
    end

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

956
      unless run_in_transaction?
957 958
        Fixtures.reset_cache
      end
959

960
      # Rollback changes if a transaction is active.
961
      if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0
962 963
        ActiveRecord::Base.connection.rollback_db_transaction
        ActiveRecord::Base.connection.decrement_open_transactions
964
        ActiveRecord::Base.connection.transactional_fixtures = false
D
Initial  
David Heinemeier Hansson 已提交
965
      end
966 967
      ActiveRecord::Base.clear_active_connections!
    end
968

969 970 971 972 973 974 975 976 977
    private
      def load_fixtures
        @loaded_fixtures = {}
        fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
        unless fixtures.nil?
          if fixtures.instance_of?(Fixtures)
            @loaded_fixtures[fixtures.name] = fixtures
          else
            fixtures.each { |f| @loaded_fixtures[f.name] = f }
978
          end
979
        end
980
      end
981

982 983
      # for pre_loaded_fixtures, only require the classes once. huge speed improvement
      @@required_fixture_classes = false
984

985 986 987 988 989 990 991 992 993 994 995 996
      def instantiate_fixtures
        if pre_loaded_fixtures
          raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
          unless @@required_fixture_classes
            self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
            @@required_fixture_classes = true
          end
          Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
        else
          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?)
997
          end
998
        end
999
      end
1000

1001 1002 1003
      def load_instances?
        use_instantiated_fixtures != :no_instances
      end
1004
  end
1005
end