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