fixtures.rb 32.8 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
require 'erb'
2 3 4 5 6 7

begin
  require 'psych'
rescue LoadError
end

D
Initial  
David Heinemeier Hansson 已提交
8
require 'yaml'
9
require 'csv'
10
require 'zlib'
J
Joshua Peek 已提交
11
require 'active_support/dependencies'
12
require 'active_support/core_ext/array/wrap'
13
require 'active_support/core_ext/object/blank'
14
require 'active_support/core_ext/logger'
15
require 'active_support/ordered_hash'
16

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

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

447
class Fixtures
448
  MAX_ID = 2 ** 30 - 1
449 450
  DEFAULT_FILTER_RE = /\.ya?ml$/

451
  @@all_cached_fixtures = {}
452

453 454 455 456 457 458
  def self.find_table_name(table_name) # :nodoc:
    ActiveRecord::Base.pluralize_table_names ?
      table_name.to_s.singularize.camelize :
      table_name.to_s.camelize
  end

459 460
  def self.reset_cache(connection = nil)
    connection ||= ActiveRecord::Base.connection
461 462
    @@all_cached_fixtures[connection.object_id] = {}
  end
463

464 465 466 467
  def self.cache_for_connection(connection)
    @@all_cached_fixtures[connection.object_id] ||= {}
    @@all_cached_fixtures[connection.object_id]
  end
468

469 470 471 472 473 474 475 476 477 478 479 480 481
  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

482 483
  def self.cache_fixtures(connection, fixtures_map)
    cache_for_connection(connection).update(fixtures_map)
484
  end
485 486

  def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
487
    object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
488
    if load_instances
489
      ActiveRecord::Base.silence do
490
        fixtures.each do |name, fixture|
491 492 493 494
          begin
            object.instance_variable_set "@#{name}", fixture.find
          rescue FixtureClassNotFound
            nil
495
          end
496
        end
497
      end
D
Initial  
David Heinemeier Hansson 已提交
498 499
    end
  end
500

501
  def self.instantiate_all_loaded_fixtures(object, load_instances = true)
502 503
    all_loaded_fixtures.each do |table_name, fixtures|
      Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
504
    end
505
  end
506

507
  cattr_accessor :all_loaded_fixtures
508
  self.all_loaded_fixtures = {}
509

510
  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
511
    table_names = [table_names].flatten.map { |n| n.to_s }
512
    table_names.each { |n| class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/') }
513 514
    connection  = block_given? ? yield : ActiveRecord::Base.connection

515 516
    table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }

517 518
    unless table_names_to_fetch.empty?
      ActiveRecord::Base.silence do
519 520
        connection.disable_referential_integrity do
          fixtures_map = {}
521

522
          fixtures = table_names_to_fetch.map do |table_name|
523
            fixtures_map[table_name] = Fixtures.new(connection, table_name.tr('/', '_'), class_names[table_name.tr('/', '_').to_sym], File.join(fixtures_directory, table_name))
524
          end
525

526
          all_loaded_fixtures.update(fixtures_map)
527

528
          connection.transaction(:requires_new => true) do
529 530
            fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
            fixtures.each { |fixture| fixture.insert_fixtures }
531

532 533 534
            # Cap primary key sequences to max(pk).
            if connection.respond_to?(:reset_pk_sequence!)
              table_names.each do |table_name|
535
                connection.reset_pk_sequence!(table_name.tr('/', '_'))
536
              end
537
            end
538
          end
539

540
          cache_fixtures(connection, fixtures_map)
541
        end
542
      end
543
    end
544
    cached_fixtures(connection, table_names)
545 546
  end

547 548
  # Returns a consistent, platform-independent identifier for +label+.
  # Identifiers are positive integers less than 2^32.
549
  def self.identify(label)
550
    Zlib.crc32(label.to_s) % MAX_ID
551 552
  end

553
  attr_reader :table_name, :name, :fixtures
554

555
  def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
556 557 558 559 560 561 562 563 564 565
    @fixtures     = ActiveSupport::OrderedHash.new
    @connection   = connection
    @table_name   = table_name
    @fixture_path = fixture_path
    @file_filter  = file_filter
    @name         = table_name # preserve fixture base name
    @class_name   = class_name
    @table_name   = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
    @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 已提交
566
    read_fixture_files
567 568
  end

569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
  def [](x)
    fixtures[x]
  end

  def []=(k,v)
    fixtures[k] = v
  end

  def each(&block)
    fixtures.each(&block)
  end

  def size
    fixtures.size
  end

585
  def delete_existing_fixtures
586
    @connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
587 588 589
  end

  def insert_fixtures
590 591 592 593
    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
594
    fixtures.delete('DEFAULTS')
595 596 597

    # track any join tables we need to insert later
    habtm_fixtures = Hash.new do |h, habtm|
598 599 600 601
      h[habtm] = HabtmFixtures.new(
        @connection,
        habtm.options[:join_table],
        Fixtures.find_table_name(habtm.options[:join_table]), nil)
602 603
    end

604
    fixtures.each do |label, fixture|
605 606
      row = fixture.to_hash

607 608 609 610 611 612
      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
613 614 615 616 617 618 619
        end

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

620
        # generate a primary key if necessary
621 622 623
        if has_primary_key_column? && !row.include?(primary_key_name)
          row[primary_key_name] = Fixtures.identify(label)
        end
624

625 626 627 628 629 630 631 632 633
        # 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|
634 635
          case association.macro
          when :belongs_to
636 637 638 639
            # 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)
640 641 642
              if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
                # support polymorphic belongs_to as "label (Type)"
                row[association.foreign_type] = $1
643 644
              end

645 646 647 648 649 650 651 652 653
              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(
654
                  { association.foreign_key             => row[primary_key_name],
655 656
                    association.association_foreign_key => Fixtures.identify(target) },
                  nil, @connection)
657 658 659 660 661 662
              end
            end
          end
        end
      end

663
      @connection.insert_fixture(fixture, @table_name)
664
    end
665 666 667 668 669 670

    # 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 已提交
671 672
  end

673
  private
674 675 676 677 678
    class HabtmFixtures < ::Fixtures #:nodoc:
      def read_fixture_files; end
    end

    def model_class
679 680 681 682 683 684 685 686 687 688
      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
689 690 691 692 693 694
    end

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

695
    def has_primary_key_column?
696 697
      @has_primary_key_column ||= primary_key_name &&
        model_class.columns.any? { |c| c.name == primary_key_name }
698
    end
699

700
    def timestamp_column_names
701 702
      @timestamp_column_names ||=
        %w(created_at created_on updated_at updated_on) & column_names
703 704
    end

705 706 707 708
    def inheritance_column_name
      @inheritance_column_name ||= model_class && model_class.inheritance_column
    end

709
    def column_names
710
      @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
711 712
    end

D
Initial  
David Heinemeier Hansson 已提交
713
    def read_fixture_files
714
      if File.file?(yaml_file_path)
715
        read_yaml_fixture_files
716
      elsif File.file?(csv_file_path)
717
        read_csv_fixture_files
718 719
      else
        raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
D
Initial  
David Heinemeier Hansson 已提交
720 721 722
      end
    end

723
    def read_yaml_fixture_files
724 725 726
      yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
        File.file?(f)
      } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
727 728 729 730 731 732 733 734 735 736 737

      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|
738
          raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
739
          fixture.each do |name, data|
740 741 742 743
            unless data
              raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
            end

744
            fixtures[name] = Fixture.new(data, model_class, @connection)
745 746 747
          end
        end
      end
748 749
    end

750
    def read_csv_fixture_files
751
      reader = CSV.parse(erb_render(IO.read(csv_file_path)))
752 753 754 755 756
      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 }
757
        fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
758 759 760 761 762
      end
    end

    def yaml_file_path
      "#{@fixture_path}.yml"
D
Initial  
David Heinemeier Hansson 已提交
763
    end
764 765 766 767

    def csv_file_path
      @fixture_path + ".csv"
    end
768

D
Initial  
David Heinemeier Hansson 已提交
769 770 771 772
    def yaml_fixtures_key(path)
      File.basename(@fixture_path).split(".").first
    end

773 774 775 776 777 778
    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 已提交
779 780 781 782 783 784 785
    def erb_render(fixture_content)
      ERB.new(fixture_content).result
    end
end

class Fixture #:nodoc:
  include Enumerable
786 787

  class FixtureError < StandardError #:nodoc:
788
  end
789 790

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

793
  attr_reader :model_class
794

795 796
  def initialize(fixture, model_class, connection = ActiveRecord::Base.connection)
    @connection = connection
797
    @fixture = fixture
798 799 800 801 802
    @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 已提交
803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
  end

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

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

  def to_hash
    @fixture
  end

  def key_list
818
    @fixture.keys.map { |column_name| @connection.quote_column_name(column_name) }.join(', ')
D
Initial  
David Heinemeier Hansson 已提交
819 820 821
  end

  def value_list
822 823 824 825
    cols = (model_class && model_class < ActiveRecord::Base) ? model_class.columns_hash : {}
    @fixture.map do |key, value|
      @connection.quote(value, cols[key]).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
    end.join(', ')
D
Initial  
David Heinemeier Hansson 已提交
826
  end
827

D
Initial  
David Heinemeier Hansson 已提交
828
  def find
829 830
    if model_class
      model_class.find(self[model_class.primary_key])
831
    else
832
      raise FixtureClassNotFound, "No class attached to find."
833
    end
D
Initial  
David Heinemeier Hansson 已提交
834 835 836
  end
end

837 838
module ActiveRecord
  module TestFixtures
839
    extend ActiveSupport::Concern
840 841 842 843 844

    included do
      setup :setup_fixtures
      teardown :teardown_fixtures

845 846 847 848 849 850
      class_attribute :fixture_path
      class_attribute :fixture_table_names
      class_attribute :fixture_class_names
      class_attribute :use_transactional_fixtures
      class_attribute :use_instantiated_fixtures   # true, false, or :no_instances
      class_attribute :pre_loaded_fixtures
851 852

      self.fixture_table_names = []
853 854
      self.use_transactional_fixtures = true
      self.use_instantiated_fixtures = false
855
      self.pre_loaded_fixtures = false
856

857 858 859
      self.fixture_class_names = Hash.new do |h, table_name|
        h[table_name] = Fixtures.find_table_name(table_name)
      end
860 861 862 863 864 865
    end

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

867 868
      def fixtures(*table_names)
        if table_names.first == :all
869 870
          table_names = Dir["#{fixture_path}/**/*.{yml,csv}"]
          table_names.map! { |f| f[(fixture_path.size + 1)..-5] }
871 872
        else
          table_names = table_names.flatten.map { |n| n.to_s }
873 874
        end

875 876 877 878 879 880 881 882 883
        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
884

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

892 893 894 895 896
      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)
897
        end
898
      end
899

900
      def setup_fixture_accessors(table_names = nil)
901 902
        table_names = Array.wrap(table_names || fixture_table_names)
        table_names.each do |table_name|
903
          table_name = table_name.to_s.tr('./', '_')
904

905
          redefine_method(table_name) do |*fixtures|
906
            force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
907

908
            @fixture_cache[table_name] ||= {}
909

910 911
            instances = fixtures.map do |fixture|
              @fixture_cache[table_name].delete(fixture) if force_reload
912

913 914 915 916
              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}'"
917
              end
918
            end
919

920 921
            instances.size == 1 ? instances.first : instances
          end
922
          private table_name
923
        end
924
      end
925

926 927
      def uses_transaction(*methods)
        @uses_transaction = [] unless defined?(@uses_transaction)
928
        @uses_transaction.concat methods.map { |m| m.to_s }
929 930
      end

931 932 933
      def uses_transaction?(method)
        @uses_transaction = [] unless defined?(@uses_transaction)
        @uses_transaction.include?(method.to_s)
934
      end
935
    end
936

937
    def run_in_transaction?
938 939 940
      use_transactional_fixtures &&
        !self.class.uses_transaction?(method_name)
    end
941

942 943
    def setup_fixtures
      return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
944

945 946 947
      if pre_loaded_fixtures && !use_transactional_fixtures
        raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
      end
948

949
      @fixture_cache = {}
950
      @@already_loaded_fixtures ||= {}
951 952

      # Load fixtures once and begin transaction.
953
      if run_in_transaction?
954 955
        if @@already_loaded_fixtures[self.class]
          @loaded_fixtures = @@already_loaded_fixtures[self.class]
956
        else
957
          load_fixtures
958
          @@already_loaded_fixtures[self.class] = @loaded_fixtures
959
        end
960
        ActiveRecord::Base.connection.increment_open_transactions
961
        ActiveRecord::Base.connection.transaction_joinable = false
962 963 964 965 966 967
        ActiveRecord::Base.connection.begin_db_transaction
      # Load fixtures for every test.
      else
        Fixtures.reset_cache
        @@already_loaded_fixtures[self.class] = nil
        load_fixtures
968 969
      end

970 971 972 973 974 975
      # 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?
976

977
      unless run_in_transaction?
978 979
        Fixtures.reset_cache
      end
980

981
      # Rollback changes if a transaction is active.
982
      if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0
983 984
        ActiveRecord::Base.connection.rollback_db_transaction
        ActiveRecord::Base.connection.decrement_open_transactions
D
Initial  
David Heinemeier Hansson 已提交
985
      end
986 987
      ActiveRecord::Base.clear_active_connections!
    end
988

989 990 991 992 993 994 995 996 997
    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 }
998
          end
999
        end
1000
      end
1001

1002 1003
      # for pre_loaded_fixtures, only require the classes once. huge speed improvement
      @@required_fixture_classes = false
1004

1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
      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?)
1017
          end
1018
        end
1019
      end
1020

1021 1022 1023
      def load_instances?
        use_instantiated_fixtures != :no_instances
      end
1024
  end
1025
end