fixtures.rb 32.7 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1 2
require 'erb'
require 'yaml'
3
require 'csv'
4
require 'zlib'
J
Joshua Peek 已提交
5
require 'active_support/dependencies'
6
require 'active_support/core_ext/array/wrap'
7
require 'active_support/core_ext/object/blank'
8
require 'active_support/core_ext/logger'
D
Initial  
David Heinemeier Hansson 已提交
9

10 11 12 13 14 15
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
16
  end
17 18
end

19 20 21 22 23 24
if defined? ActiveRecord
  class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
  end
else
  class FixtureClassNotFound < StandardError #:nodoc:
  end
25 26
end

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

437
class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
438
  MAX_ID = 2 ** 30 - 1
439 440
  DEFAULT_FILTER_RE = /\.ya?ml$/

441
  @@all_cached_fixtures = {}
442 443 444

  def self.reset_cache(connection = nil)
    connection ||= ActiveRecord::Base.connection
445 446
    @@all_cached_fixtures[connection.object_id] = {}
  end
447

448 449 450 451
  def self.cache_for_connection(connection)
    @@all_cached_fixtures[connection.object_id] ||= {}
    @@all_cached_fixtures[connection.object_id]
  end
452

453 454 455 456 457 458 459 460 461 462 463 464 465
  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

466 467
  def self.cache_fixtures(connection, fixtures_map)
    cache_for_connection(connection).update(fixtures_map)
468
  end
469 470

  def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
471
    object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
472
    if load_instances
473
      ActiveRecord::Base.silence do
474
        fixtures.each do |name, fixture|
475 476 477 478
          begin
            object.instance_variable_set "@#{name}", fixture.find
          rescue FixtureClassNotFound
            nil
479
          end
480
        end
481
      end
D
Initial  
David Heinemeier Hansson 已提交
482 483
    end
  end
484

485
  def self.instantiate_all_loaded_fixtures(object, load_instances = true)
486 487
    all_loaded_fixtures.each do |table_name, fixtures|
      Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
488
    end
489
  end
490

491
  cattr_accessor :all_loaded_fixtures
492
  self.all_loaded_fixtures = {}
493

494
  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
495
    table_names = [table_names].flatten.map { |n| n.to_s }
496
    table_names.each { |n| class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/') }
497 498
    connection  = block_given? ? yield : ActiveRecord::Base.connection

499 500
    table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }

501 502
    unless table_names_to_fetch.empty?
      ActiveRecord::Base.silence do
503 504
        connection.disable_referential_integrity do
          fixtures_map = {}
505

506
          fixtures = table_names_to_fetch.map do |table_name|
507
            fixtures_map[table_name] = Fixtures.new(connection, table_name.tr('/', '_'), class_names[table_name.tr('/', '_').to_sym], File.join(fixtures_directory, table_name))
508
          end
509

510
          all_loaded_fixtures.update(fixtures_map)
511

512
          connection.transaction(:requires_new => true) do
513 514
            fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
            fixtures.each { |fixture| fixture.insert_fixtures }
515

516 517 518
            # Cap primary key sequences to max(pk).
            if connection.respond_to?(:reset_pk_sequence!)
              table_names.each do |table_name|
519
                connection.reset_pk_sequence!(table_name.tr('/', '_'))
520
              end
521
            end
522
          end
523

524
          cache_fixtures(connection, fixtures_map)
525
        end
526
      end
527
    end
528
    cached_fixtures(connection, table_names)
529 530
  end

531 532
  # Returns a consistent, platform-independent identifier for +label+.
  # Identifiers are positive integers less than 2^32.
533
  def self.identify(label)
534
    Zlib.crc32(label.to_s) % MAX_ID
535 536
  end

537
  attr_reader :table_name, :name
538

539
  def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
D
Initial  
David Heinemeier Hansson 已提交
540
    @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
541
    @name = table_name # preserve fixture base name
542
    @class_name = class_name ||
543
                  (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
544
    @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
545 546
    @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 已提交
547
    read_fixture_files
548 549 550
  end

  def delete_existing_fixtures
551
    @connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
552 553 554
  end

  def insert_fixtures
555 556 557 558
    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
559 560 561 562 563
    if is_a?(Hash)
      delete('DEFAULTS')
    else
      delete(assoc('DEFAULTS'))
    end
564 565 566 567 568 569 570 571 572

    # 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

573 574 575 576 577 578
      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
579 580 581 582 583 584 585
        end

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

586
        # generate a primary key if necessary
587 588 589
        if has_primary_key_column? && !row.include?(primary_key_name)
          row[primary_key_name] = Fixtures.identify(label)
        end
590

591 592 593 594 595 596 597 598 599
        # 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|
600 601
          case association.macro
          when :belongs_to
602 603 604 605
            # 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)
606 607 608 609 610 611 612 613 614 615
              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

616 617 618 619 620 621 622 623 624
              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(
625
                  { association.primary_key_name => row[primary_key_name],
626 627
                    association.association_foreign_key => Fixtures.identify(target) },
                  nil, @connection)
628 629 630 631 632 633
              end
            end
          end
        end
      end

634
      @connection.insert_fixture(fixture, @table_name)
635
    end
636 637 638 639 640 641

    # 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 已提交
642 643
  end

644
  private
645 646 647 648 649
    class HabtmFixtures < ::Fixtures #:nodoc:
      def read_fixture_files; end
    end

    def model_class
650 651 652 653 654 655 656 657 658 659
      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
660 661 662 663 664 665
    end

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

666 667 668
    def has_primary_key_column?
      @has_primary_key_column ||= model_class && primary_key_name &&
        model_class.columns.find { |c| c.name == primary_key_name }
669
    end
670

671 672 673 674 675 676
    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

677 678 679 680
    def inheritance_column_name
      @inheritance_column_name ||= model_class && model_class.inheritance_column
    end

681 682 683 684
    def column_names
      @column_names ||= @connection.columns(@table_name).collect(&:name)
    end

D
Initial  
David Heinemeier Hansson 已提交
685
    def read_fixture_files
686
      if File.file?(yaml_file_path)
687
        read_yaml_fixture_files
688
      elsif File.file?(csv_file_path)
689
        read_csv_fixture_files
D
Initial  
David Heinemeier Hansson 已提交
690 691 692
      end
    end

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

716
            self[name] = Fixture.new(data, model_class, @connection)
717 718 719
          end
        end
      end
720 721
    end

722
    def read_csv_fixture_files
723
      reader = CSV.parse(erb_render(IO.read(csv_file_path)))
724 725 726 727 728
      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 }
729
        self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
730 731 732 733 734
      end
    end

    def yaml_file_path
      "#{@fixture_path}.yml"
D
Initial  
David Heinemeier Hansson 已提交
735
    end
736 737 738 739

    def csv_file_path
      @fixture_path + ".csv"
    end
740

D
Initial  
David Heinemeier Hansson 已提交
741 742 743 744
    def yaml_fixtures_key(path)
      File.basename(@fixture_path).split(".").first
    end

745 746 747 748 749 750
    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 已提交
751 752 753 754 755 756 757
    def erb_render(fixture_content)
      ERB.new(fixture_content).result
    end
end

class Fixture #:nodoc:
  include Enumerable
758 759

  class FixtureError < StandardError #:nodoc:
760
  end
761 762

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

765
  attr_reader :model_class
766

767 768
  def initialize(fixture, model_class, connection = ActiveRecord::Base.connection)
    @connection = connection
769
    @fixture = fixture
770 771 772 773 774
    @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 已提交
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
  end

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

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

  def to_hash
    @fixture
  end

  def key_list
790
    columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) }
791
    columns.join(", ")
D
Initial  
David Heinemeier Hansson 已提交
792 793 794
  end

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

D
Initial  
David Heinemeier Hansson 已提交
802
  def find
803 804
    if model_class
      model_class.find(self[model_class.primary_key])
805
    else
806
      raise FixtureClassNotFound, "No class attached to find."
807
    end
D
Initial  
David Heinemeier Hansson 已提交
808 809 810
  end
end

811 812
module ActiveRecord
  module TestFixtures
813
    extend ActiveSupport::Concern
814 815 816 817 818 819 820 821 822 823 824 825 826

    included 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 = []
827 828
      self.use_transactional_fixtures = true
      self.use_instantiated_fixtures = false
829
      self.pre_loaded_fixtures = false
830

831
      self.fixture_class_names = {}
832 833 834 835 836 837
    end

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

839 840
      def fixtures(*table_names)
        if table_names.first == :all
841 842
          table_names = Dir["#{fixture_path}/**/*.{yml,csv}"]
          table_names.map! { |f| f[(fixture_path.size + 1)..-5] }
843 844
        else
          table_names = table_names.flatten.map { |n| n.to_s }
845 846
        end

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

857 858 859 860
        # 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")}")
861
        end
862
      end
863

864 865 866 867 868
      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)
869
        end
870
      end
871

872
      def setup_fixture_accessors(table_names = nil)
873 874
        table_names = Array.wrap(table_names || fixture_table_names)
        table_names.each do |table_name|
875
          table_name = table_name.to_s.tr('./', '_')
876

877 878
          define_method(table_name) do |*fixtures|
            force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
879

880
            @fixture_cache[table_name] ||= {}
881

882 883
            instances = fixtures.map do |fixture|
              @fixture_cache[table_name].delete(fixture) if force_reload
884

885 886 887 888
              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}'"
889
              end
890
            end
891

892 893
            instances.size == 1 ? instances.first : instances
          end
894
          private table_name
895
        end
896
      end
897

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

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

909
    def run_in_transaction?
910 911 912
      use_transactional_fixtures &&
        !self.class.uses_transaction?(method_name)
    end
913

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

917 918 919
      if pre_loaded_fixtures && !use_transactional_fixtures
        raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
      end
920

921
      @fixture_cache = {}
922
      @@already_loaded_fixtures ||= {}
923 924

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

942 943 944 945 946 947
      # 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?
948

949
      unless run_in_transaction?
950 951
        Fixtures.reset_cache
      end
952

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

961 962 963 964 965 966 967 968 969
    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 }
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 978 979 980 981 982 983 984 985 986 987 988
      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?)
989
          end
990
        end
991
      end
992

993 994 995
      def load_instances?
        use_instantiated_fixtures != :no_instances
      end
996
  end
997
end