Froze rails gems
[depot.git] / vendor / rails / activerecord / lib / active_record / fixtures.rb
1 require 'erb'
2 require 'yaml'
3 require 'csv'
4 require 'active_support/test_case'
5
6 if RUBY_VERSION < '1.9'
7 module YAML #:nodoc:
8 class Omap #:nodoc:
9 def keys; map { |k, v| k } end
10 def values; map { |k, v| v } end
11 end
12 end
13 end
14
15 if defined? ActiveRecord
16 class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
17 end
18 else
19 class FixtureClassNotFound < StandardError #:nodoc:
20 end
21 end
22
23 # Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavors:
24 #
25 # 1. YAML fixtures
26 # 2. CSV fixtures
27 # 3. Single-file fixtures
28 #
29 # = YAML fixtures
30 #
31 # This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
32 # in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
33 #
34 # Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
35 # by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
36 # put your files in <tt><your-rails-app>/test/fixtures/</tt>). The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
37 # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a YAML fixture file looks like this:
38 #
39 # rubyonrails:
40 # id: 1
41 # name: Ruby on Rails
42 # url: http://www.rubyonrails.org
43 #
44 # google:
45 # id: 2
46 # name: Google
47 # url: http://www.google.com
48 #
49 # This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an
50 # indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
51 # pleasure.
52 #
53 # Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
54 # for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
55 # This is commonly needed for tree structures. Example:
56 #
57 # --- !omap
58 # - parent:
59 # id: 1
60 # parent_id: NULL
61 # title: Parent
62 # - child:
63 # id: 2
64 # parent_id: 1
65 # title: Child
66 #
67 # = CSV fixtures
68 #
69 # Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
70 # in a single file, but instead end with the <tt>.csv</tt> file extension
71 # (Rails example: <tt><your-rails-app>/test/fixtures/web_sites.csv</tt>).
72 #
73 # The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
74 # humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
75 # of the actual data (1 per line). Here's an example:
76 #
77 # id, name, url
78 # 1, Ruby On Rails, http://www.rubyonrails.org
79 # 2, Google, http://www.google.com
80 #
81 # Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
82 # need to use a double quote character, you must escape it with another double quote.
83 #
84 # Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
85 # fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
86 # number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
87 # "web_site_2".
88 #
89 # Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
90 # have existing data somewhere already.
91 #
92 # = Single-file fixtures
93 #
94 # This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
95 # Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
96 # appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
97 # put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> --
98 # like <tt><your-rails-app>/test/fixtures/web_sites/</tt> for the WebSite model).
99 #
100 # Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
101 # extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension. Here's what the
102 # above example might look like:
103 #
104 # web_sites/google
105 # web_sites/yahoo.txt
106 # web_sites/ruby-on-rails
107 #
108 # The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
109 # of "name => value". Here's an example of the ruby-on-rails fixture above:
110 #
111 # id => 1
112 # name => Ruby on Rails
113 # url => http://www.rubyonrails.org
114 #
115 # = Using Fixtures
116 #
117 # Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
118 # fixtures, but first let's take a look at a sample unit test:
119 #
120 # require 'web_site'
121 #
122 # class WebSiteTest < ActiveSupport::TestCase
123 # def test_web_site_count
124 # assert_equal 2, WebSite.count
125 # end
126 # end
127 #
128 # As it stands, unless we pre-load the web_site table in our database with two records, this test will fail. Here's the
129 # easiest way to add fixtures to the database:
130 #
131 # ...
132 # class WebSiteTest < ActiveSupport::TestCase
133 # fixtures :web_sites # add more by separating the symbols with commas
134 # ...
135 #
136 # By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here though), we trigger
137 # the testing environment to automatically load the appropriate fixtures into the database before each test.
138 # To ensure consistent data, the environment deletes the fixtures before running the load.
139 #
140 # In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
141 # of the test case. It is named after the symbol... so, in our example, there would be a hash available called
142 # <tt>@web_sites</tt>. This is where the "fixture name" comes into play.
143 #
144 # On top of that, each record is automatically "found" (using <tt>Model.find(id)</tt>) and placed in the instance variable of its name.
145 # So for the YAML fixtures, we'd get <tt>@rubyonrails</tt> and <tt>@google</tt>, which could be interrogated using regular Active Record semantics:
146 #
147 # # test if the object created from the fixture data has the same attributes as the data itself
148 # def test_find
149 # assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
150 # end
151 #
152 # As seen above, the data hash created from the YAML fixtures would have <tt>@web_sites["rubyonrails"]["url"]</tt> return
153 # "http://www.rubyonrails.org" and <tt>@web_sites["google"]["name"]</tt> would return "Google". The same fixtures, but loaded
154 # from a CSV fixture file, would be accessible via <tt>@web_sites["web_site_1"]["name"] == "Ruby on Rails"</tt> and have the individual
155 # fixtures available as instance variables <tt>@web_site_1</tt> and <tt>@web_site_2</tt>.
156 #
157 # If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.
158 #
159 # - to completely disable instantiated fixtures:
160 # self.use_instantiated_fixtures = false
161 #
162 # - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance:
163 # self.use_instantiated_fixtures = :no_instances
164 #
165 # Even if auto-instantiated fixtures are disabled, you can still access them
166 # by name via special dynamic methods. Each method has the same name as the
167 # model, and accepts the name of the fixture to instantiate:
168 #
169 # fixtures :web_sites
170 #
171 # def test_find
172 # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
173 # end
174 #
175 # = Dynamic fixtures with ERb
176 #
177 # 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
178 # mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
179 #
180 # <% for i in 1..1000 %>
181 # fix_<%= i %>:
182 # id: <%= i %>
183 # name: guy_<%= 1 %>
184 # <% end %>
185 #
186 # This will create 1000 very simple YAML fixtures.
187 #
188 # Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
189 # This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
190 # sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
191 # is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
192 #
193 # = Transactional fixtures
194 #
195 # TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
196 # They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
197 #
198 # class FooTest < ActiveSupport::TestCase
199 # self.use_transactional_fixtures = true
200 # self.use_instantiated_fixtures = false
201 #
202 # fixtures :foos
203 #
204 # def test_godzilla
205 # assert !Foo.find(:all).empty?
206 # Foo.destroy_all
207 # assert Foo.find(:all).empty?
208 # end
209 #
210 # def test_godzilla_aftermath
211 # assert !Foo.find(:all).empty?
212 # end
213 # end
214 #
215 # If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
216 # 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.
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 value of +use_instantiated_fixtures+)
220 #
221 # When *not* to use transactional fixtures:
222 # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
223 # particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
224 # the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
225 # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
226 # Use InnoDB, MaxDB, or NDB instead.
227 #
228 # = Advanced YAML Fixtures
229 #
230 # YAML fixtures that don't specify an ID get some extra features:
231 #
232 # * Stable, autogenerated ID's
233 # * Label references for associations (belongs_to, has_one, has_many)
234 # * HABTM associations as inline lists
235 # * Autofilled timestamp columns
236 # * Fixture label interpolation
237 # * Support for YAML defaults
238 #
239 # == Stable, autogenerated ID's
240 #
241 # Here, have a monkey fixture:
242 #
243 # george:
244 # id: 1
245 # name: George the Monkey
246 #
247 # reginald:
248 # id: 2
249 # name: Reginald the Pirate
250 #
251 # Each of these fixtures has two unique identifiers: one for the database
252 # and one for the humans. Why don't we generate the primary key instead?
253 # Hashing each fixture's label yields a consistent ID:
254 #
255 # george: # generated id: 503576764
256 # name: George the Monkey
257 #
258 # reginald: # generated id: 324201669
259 # name: Reginald the Pirate
260 #
261 # Active Record looks at the fixture's model class, discovers the correct
262 # primary key, and generates it right before inserting the fixture
263 # into the database.
264 #
265 # The generated ID for a given label is constant, so we can discover
266 # any fixture's ID without loading anything, as long as we know the label.
267 #
268 # == Label references for associations (belongs_to, has_one, has_many)
269 #
270 # Specifying foreign keys in fixtures can be very fragile, not to
271 # mention difficult to read. Since Active Record can figure out the ID of
272 # any fixture from its label, you can specify FK's by label instead of ID.
273 #
274 # === belongs_to
275 #
276 # Let's break out some more monkeys and pirates.
277 #
278 # ### in pirates.yml
279 #
280 # reginald:
281 # id: 1
282 # name: Reginald the Pirate
283 # monkey_id: 1
284 #
285 # ### in monkeys.yml
286 #
287 # george:
288 # id: 1
289 # name: George the Monkey
290 # pirate_id: 1
291 #
292 # Add a few more monkeys and pirates and break this into multiple files,
293 # and it gets pretty hard to keep track of what's going on. Let's
294 # use labels instead of ID's:
295 #
296 # ### in pirates.yml
297 #
298 # reginald:
299 # name: Reginald the Pirate
300 # monkey: george
301 #
302 # ### in monkeys.yml
303 #
304 # george:
305 # name: George the Monkey
306 # pirate: reginald
307 #
308 # Pow! All is made clear. Active Record reflects on the fixture's model class,
309 # finds all the +belongs_to+ associations, and allows you to specify
310 # a target *label* for the *association* (monkey: george) rather than
311 # a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
312 #
313 # ==== Polymorphic belongs_to
314 #
315 # Supporting polymorphic relationships is a little bit more complicated, since
316 # Active Record needs to know what type your association is pointing at. Something
317 # like this should look familiar:
318 #
319 # ### in fruit.rb
320 #
321 # belongs_to :eater, :polymorphic => true
322 #
323 # ### in fruits.yml
324 #
325 # apple:
326 # id: 1
327 # name: apple
328 # eater_id: 1
329 # eater_type: Monkey
330 #
331 # Can we do better? You bet!
332 #
333 # apple:
334 # eater: george (Monkey)
335 #
336 # Just provide the polymorphic target type and Active Record will take care of the rest.
337 #
338 # === has_and_belongs_to_many
339 #
340 # Time to give our monkey some fruit.
341 #
342 # ### in monkeys.yml
343 #
344 # george:
345 # id: 1
346 # name: George the Monkey
347 # pirate_id: 1
348 #
349 # ### in fruits.yml
350 #
351 # apple:
352 # id: 1
353 # name: apple
354 #
355 # orange:
356 # id: 2
357 # name: orange
358 #
359 # grape:
360 # id: 3
361 # name: grape
362 #
363 # ### in fruits_monkeys.yml
364 #
365 # apple_george:
366 # fruit_id: 1
367 # monkey_id: 1
368 #
369 # orange_george:
370 # fruit_id: 2
371 # monkey_id: 1
372 #
373 # grape_george:
374 # fruit_id: 3
375 # monkey_id: 1
376 #
377 # Let's make the HABTM fixture go away.
378 #
379 # ### in monkeys.yml
380 #
381 # george:
382 # name: George the Monkey
383 # pirate: reginald
384 # fruits: apple, orange, grape
385 #
386 # ### in fruits.yml
387 #
388 # apple:
389 # name: apple
390 #
391 # orange:
392 # name: orange
393 #
394 # grape:
395 # name: grape
396 #
397 # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
398 # on George's fixture, but we could've just as easily specified a list
399 # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
400 # the fixture's model class and discovers the +has_and_belongs_to_many+
401 # associations.
402 #
403 # == Autofilled timestamp columns
404 #
405 # If your table/model specifies any of Active Record's
406 # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
407 # they will automatically be set to <tt>Time.now</tt>.
408 #
409 # If you've set specific values, they'll be left alone.
410 #
411 # == Fixture label interpolation
412 #
413 # The label of the current fixture is always available as a column value:
414 #
415 # geeksomnia:
416 # name: Geeksomnia's Account
417 # subdomain: $LABEL
418 #
419 # Also, sometimes (like when porting older join table fixtures) you'll need
420 # to be able to get ahold of the identifier for a given label. ERB
421 # to the rescue:
422 #
423 # george_reginald:
424 # monkey_id: <%= Fixtures.identify(:reginald) %>
425 # pirate_id: <%= Fixtures.identify(:george) %>
426 #
427 # == Support for YAML defaults
428 #
429 # You probably already know how to use YAML to set and reuse defaults in
430 # your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
431 #
432 # DEFAULTS: &DEFAULTS
433 # created_on: <%= 3.weeks.ago.to_s(:db) %>
434 #
435 # first:
436 # name: Smurf
437 # <<: *DEFAULTS
438 #
439 # second:
440 # name: Fraggle
441 # <<: *DEFAULTS
442 #
443 # Any fixture labeled "DEFAULTS" is safely ignored.
444
445 class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
446 DEFAULT_FILTER_RE = /\.ya?ml$/
447
448 @@all_cached_fixtures = {}
449
450 def self.reset_cache(connection = nil)
451 connection ||= ActiveRecord::Base.connection
452 @@all_cached_fixtures[connection.object_id] = {}
453 end
454
455 def self.cache_for_connection(connection)
456 @@all_cached_fixtures[connection.object_id] ||= {}
457 @@all_cached_fixtures[connection.object_id]
458 end
459
460 def self.fixture_is_cached?(connection, table_name)
461 cache_for_connection(connection)[table_name]
462 end
463
464 def self.cached_fixtures(connection, keys_to_fetch = nil)
465 if keys_to_fetch
466 fixtures = cache_for_connection(connection).values_at(*keys_to_fetch)
467 else
468 fixtures = cache_for_connection(connection).values
469 end
470 fixtures.size > 1 ? fixtures : fixtures.first
471 end
472
473 def self.cache_fixtures(connection, fixtures_map)
474 cache_for_connection(connection).update(fixtures_map)
475 end
476
477 def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
478 object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
479 if load_instances
480 ActiveRecord::Base.silence do
481 fixtures.each do |name, fixture|
482 begin
483 object.instance_variable_set "@#{name}", fixture.find
484 rescue FixtureClassNotFound
485 nil
486 end
487 end
488 end
489 end
490 end
491
492 def self.instantiate_all_loaded_fixtures(object, load_instances = true)
493 all_loaded_fixtures.each do |table_name, fixtures|
494 Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
495 end
496 end
497
498 cattr_accessor :all_loaded_fixtures
499 self.all_loaded_fixtures = {}
500
501 def self.create_fixtures(fixtures_directory, table_names, class_names = {})
502 table_names = [table_names].flatten.map { |n| n.to_s }
503 connection = block_given? ? yield : ActiveRecord::Base.connection
504
505 table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
506
507 unless table_names_to_fetch.empty?
508 ActiveRecord::Base.silence do
509 connection.disable_referential_integrity do
510 fixtures_map = {}
511
512 fixtures = table_names_to_fetch.map do |table_name|
513 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))
514 end
515
516 all_loaded_fixtures.update(fixtures_map)
517
518 connection.transaction(connection.open_transactions.zero?) do
519 fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
520 fixtures.each { |fixture| fixture.insert_fixtures }
521
522 # Cap primary key sequences to max(pk).
523 if connection.respond_to?(:reset_pk_sequence!)
524 table_names.each do |table_name|
525 connection.reset_pk_sequence!(table_name)
526 end
527 end
528 end
529
530 cache_fixtures(connection, fixtures_map)
531 end
532 end
533 end
534 cached_fixtures(connection, table_names)
535 end
536
537 # Returns a consistent identifier for +label+. This will always
538 # be a positive integer, and will always be the same for a given
539 # label, assuming the same OS, platform, and version of Ruby.
540 def self.identify(label)
541 label.to_s.hash.abs
542 end
543
544 attr_reader :table_name, :name
545
546 def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
547 @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
548 @name = table_name # preserve fixture base name
549 @class_name = class_name ||
550 (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
551 @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
552 @table_name = class_name.table_name if class_name.respond_to?(:table_name)
553 @connection = class_name.connection if class_name.respond_to?(:connection)
554 read_fixture_files
555 end
556
557 def delete_existing_fixtures
558 @connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
559 end
560
561 def insert_fixtures
562 now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
563 now = now.to_s(:db)
564
565 # allow a standard key to be used for doing defaults in YAML
566 if is_a?(Hash)
567 delete('DEFAULTS')
568 else
569 delete(assoc('DEFAULTS'))
570 end
571
572 # track any join tables we need to insert later
573 habtm_fixtures = Hash.new do |h, habtm|
574 h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil)
575 end
576
577 each do |label, fixture|
578 row = fixture.to_hash
579
580 if model_class && model_class < ActiveRecord::Base
581 # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
582 if model_class.record_timestamps
583 timestamp_column_names.each do |name|
584 row[name] = now unless row.key?(name)
585 end
586 end
587
588 # interpolate the fixture label
589 row.each do |key, value|
590 row[key] = label if value == "$LABEL"
591 end
592
593 # generate a primary key if necessary
594 if has_primary_key_column? && !row.include?(primary_key_name)
595 row[primary_key_name] = Fixtures.identify(label)
596 end
597
598 # If STI is used, find the correct subclass for association reflection
599 reflection_class =
600 if row.include?(inheritance_column_name)
601 row[inheritance_column_name].constantize rescue model_class
602 else
603 model_class
604 end
605
606 reflection_class.reflect_on_all_associations.each do |association|
607 case association.macro
608 when :belongs_to
609 # Do not replace association name with association foreign key if they are named the same
610 fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
611
612 if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
613 if association.options[:polymorphic]
614 if value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
615 target_type = $1
616 target_type_name = (association.options[:foreign_type] || "#{association.name}_type").to_s
617
618 # support polymorphic belongs_to as "label (Type)"
619 row[target_type_name] = target_type
620 end
621 end
622
623 row[fk_name] = Fixtures.identify(value)
624 end
625 when :has_and_belongs_to_many
626 if (targets = row.delete(association.name.to_s))
627 targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
628 join_fixtures = habtm_fixtures[association]
629
630 targets.each do |target|
631 join_fixtures["#{label}_#{target}"] = Fixture.new(
632 { association.primary_key_name => row[primary_key_name],
633 association.association_foreign_key => Fixtures.identify(target) }, nil)
634 end
635 end
636 end
637 end
638 end
639
640 @connection.insert_fixture(fixture, @table_name)
641 end
642
643 # insert any HABTM join tables we discovered
644 habtm_fixtures.values.each do |fixture|
645 fixture.delete_existing_fixtures
646 fixture.insert_fixtures
647 end
648 end
649
650 private
651 class HabtmFixtures < ::Fixtures #:nodoc:
652 def read_fixture_files; end
653 end
654
655 def model_class
656 unless defined?(@model_class)
657 @model_class =
658 if @class_name.nil? || @class_name.is_a?(Class)
659 @class_name
660 else
661 @class_name.constantize rescue nil
662 end
663 end
664
665 @model_class
666 end
667
668 def primary_key_name
669 @primary_key_name ||= model_class && model_class.primary_key
670 end
671
672 def has_primary_key_column?
673 @has_primary_key_column ||= model_class && primary_key_name &&
674 model_class.columns.find { |c| c.name == primary_key_name }
675 end
676
677 def timestamp_column_names
678 @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name|
679 column_names.include?(name)
680 end
681 end
682
683 def inheritance_column_name
684 @inheritance_column_name ||= model_class && model_class.inheritance_column
685 end
686
687 def column_names
688 @column_names ||= @connection.columns(@table_name).collect(&:name)
689 end
690
691 def read_fixture_files
692 if File.file?(yaml_file_path)
693 read_yaml_fixture_files
694 elsif File.file?(csv_file_path)
695 read_csv_fixture_files
696 end
697 end
698
699 def read_yaml_fixture_files
700 yaml_string = ""
701 Dir["#{@fixture_path}/**/*.yml"].select { |f| test(?f, f) }.each do |subfixture_path|
702 yaml_string << IO.read(subfixture_path)
703 end
704 yaml_string << IO.read(yaml_file_path)
705
706 if yaml = parse_yaml_string(yaml_string)
707 # If the file is an ordered map, extract its children.
708 yaml_value =
709 if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
710 yaml.value
711 else
712 [yaml]
713 end
714
715 yaml_value.each do |fixture|
716 raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
717 fixture.each do |name, data|
718 unless data
719 raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
720 end
721
722 self[name] = Fixture.new(data, model_class)
723 end
724 end
725 end
726 end
727
728 def read_csv_fixture_files
729 reader = CSV.parse(erb_render(IO.read(csv_file_path)))
730 header = reader.shift
731 i = 0
732 reader.each do |row|
733 data = {}
734 row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
735 self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
736 end
737 end
738
739 def yaml_file_path
740 "#{@fixture_path}.yml"
741 end
742
743 def csv_file_path
744 @fixture_path + ".csv"
745 end
746
747 def yaml_fixtures_key(path)
748 File.basename(@fixture_path).split(".").first
749 end
750
751 def parse_yaml_string(fixture_content)
752 YAML::load(erb_render(fixture_content))
753 rescue => error
754 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}"
755 end
756
757 def erb_render(fixture_content)
758 ERB.new(fixture_content).result
759 end
760 end
761
762 class Fixture #:nodoc:
763 include Enumerable
764
765 class FixtureError < StandardError #:nodoc:
766 end
767
768 class FormatError < FixtureError #:nodoc:
769 end
770
771 attr_reader :model_class
772
773 def initialize(fixture, model_class)
774 @fixture = fixture
775 @model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
776 end
777
778 def class_name
779 @model_class.name if @model_class
780 end
781
782 def each
783 @fixture.each { |item| yield item }
784 end
785
786 def [](key)
787 @fixture[key]
788 end
789
790 def to_hash
791 @fixture
792 end
793
794 def key_list
795 columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
796 columns.join(", ")
797 end
798
799 def value_list
800 list = @fixture.inject([]) do |fixtures, (key, value)|
801 col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
802 fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
803 end
804 list * ', '
805 end
806
807 def find
808 if model_class
809 model_class.find(self[model_class.primary_key])
810 else
811 raise FixtureClassNotFound, "No class attached to find."
812 end
813 end
814 end
815
816 module Test #:nodoc:
817 module Unit #:nodoc:
818 class TestCase #:nodoc:
819 setup :setup_fixtures
820 teardown :teardown_fixtures
821
822 superclass_delegating_accessor :fixture_path
823 superclass_delegating_accessor :fixture_table_names
824 superclass_delegating_accessor :fixture_class_names
825 superclass_delegating_accessor :use_transactional_fixtures
826 superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
827 superclass_delegating_accessor :pre_loaded_fixtures
828
829 self.fixture_table_names = []
830 self.use_transactional_fixtures = false
831 self.use_instantiated_fixtures = true
832 self.pre_loaded_fixtures = false
833
834 @@already_loaded_fixtures = {}
835 self.fixture_class_names = {}
836
837 class << self
838 def set_fixture_class(class_names = {})
839 self.fixture_class_names = self.fixture_class_names.merge(class_names)
840 end
841
842 def fixtures(*table_names)
843 if table_names.first == :all
844 table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
845 table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
846 else
847 table_names = table_names.flatten.map { |n| n.to_s }
848 end
849
850 self.fixture_table_names |= table_names
851 require_fixture_classes(table_names)
852 setup_fixture_accessors(table_names)
853 end
854
855 def try_to_load_dependency(file_name)
856 require_dependency file_name
857 rescue LoadError => e
858 # Let's hope the developer has included it himself
859
860 # Let's warn in case this is a subdependency, otherwise
861 # subdependency error messages are totally cryptic
862 if ActiveRecord::Base.logger
863 ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
864 end
865 end
866
867 def require_fixture_classes(table_names = nil)
868 (table_names || fixture_table_names).each do |table_name|
869 file_name = table_name.to_s
870 file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
871 try_to_load_dependency(file_name)
872 end
873 end
874
875 def setup_fixture_accessors(table_names = nil)
876 table_names = [table_names] if table_names && !table_names.respond_to?(:each)
877 (table_names || fixture_table_names).each do |table_name|
878 table_name = table_name.to_s.tr('.', '_')
879
880 define_method(table_name) do |*fixtures|
881 force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
882
883 @fixture_cache[table_name] ||= {}
884
885 instances = fixtures.map do |fixture|
886 @fixture_cache[table_name].delete(fixture) if force_reload
887
888 if @loaded_fixtures[table_name][fixture.to_s]
889 @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
890 else
891 raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
892 end
893 end
894
895 instances.size == 1 ? instances.first : instances
896 end
897 end
898 end
899
900 def uses_transaction(*methods)
901 @uses_transaction = [] unless defined?(@uses_transaction)
902 @uses_transaction.concat methods.map(&:to_s)
903 end
904
905 def uses_transaction?(method)
906 @uses_transaction = [] unless defined?(@uses_transaction)
907 @uses_transaction.include?(method.to_s)
908 end
909 end
910
911 def use_transactional_fixtures?
912 use_transactional_fixtures &&
913 !self.class.uses_transaction?(method_name)
914 end
915
916 def setup_fixtures
917 return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
918
919 if pre_loaded_fixtures && !use_transactional_fixtures
920 raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
921 end
922
923 @fixture_cache = {}
924
925 # Load fixtures once and begin transaction.
926 if use_transactional_fixtures?
927 if @@already_loaded_fixtures[self.class]
928 @loaded_fixtures = @@already_loaded_fixtures[self.class]
929 else
930 load_fixtures
931 @@already_loaded_fixtures[self.class] = @loaded_fixtures
932 end
933 ActiveRecord::Base.connection.increment_open_transactions
934 ActiveRecord::Base.connection.begin_db_transaction
935 # Load fixtures for every test.
936 else
937 Fixtures.reset_cache
938 @@already_loaded_fixtures[self.class] = nil
939 load_fixtures
940 end
941
942 # Instantiate fixtures for every test if requested.
943 instantiate_fixtures if use_instantiated_fixtures
944 end
945
946 def teardown_fixtures
947 return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
948
949 unless use_transactional_fixtures?
950 Fixtures.reset_cache
951 end
952
953 # Rollback changes if a transaction is active.
954 if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
955 ActiveRecord::Base.connection.rollback_db_transaction
956 ActiveRecord::Base.connection.decrement_open_transactions
957 end
958 ActiveRecord::Base.clear_active_connections!
959 end
960
961 private
962 def load_fixtures
963 @loaded_fixtures = {}
964 fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
965 unless fixtures.nil?
966 if fixtures.instance_of?(Fixtures)
967 @loaded_fixtures[fixtures.name] = fixtures
968 else
969 fixtures.each { |f| @loaded_fixtures[f.name] = f }
970 end
971 end
972 end
973
974 # for pre_loaded_fixtures, only require the classes once. huge speed improvement
975 @@required_fixture_classes = false
976
977 def instantiate_fixtures
978 if pre_loaded_fixtures
979 raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
980 unless @@required_fixture_classes
981 self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
982 @@required_fixture_classes = true
983 end
984 Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
985 else
986 raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
987 @loaded_fixtures.each do |table_name, fixtures|
988 Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
989 end
990 end
991 end
992
993 def load_instances?
994 use_instantiated_fixtures != :no_instances
995 end
996 end
997 end
998 end