Ticket #1911: more_flexible_fixture_architecture_without_breakpoint.patch
| File more_flexible_fixture_architecture_without_breakpoint.patch, 21.1 kB (added by duane.johnson@gmail.com, 3 years ago) |
|---|
-
test/fixtures_test.rb
old new 68 68 end 69 69 end 70 70 71 def test_fixtures_not_found 72 assert_raise(Fixture::FixtureError) { 73 Fixtures.new(nil, File.join(File.dirname(__FILE__), 'fixtures'), FixtureGroup.new("bad_extension")) 74 } 75 end 76 71 77 def test_deprecated_yaml_extension 72 78 assert_raise(Fixture::FormatError) { 73 Fixtures.new(nil, 'bad_extension', File.join(File.dirname(__FILE__), 'fixtures'))79 Fixtures.new(nil, File.join(File.dirname(__FILE__), 'fixtures', 'bad_fixtures'), FixtureGroup.new("deprecated")) 74 80 } 75 81 end 76 82 … … 101 107 end 102 108 103 109 def test_empty_yaml_fixture 104 assert_not_nil Fixtures.new( Account.connection, "accounts", File.dirname(__FILE__) + "/fixtures/naked/yml/accounts")110 assert_not_nil Fixtures.new( Account.connection, File.dirname(__FILE__) + "/fixtures/naked/yml/", FixtureGroup.new("accounts")) 105 111 end 106 112 107 113 def test_empty_yaml_fixture_with_a_comment_in_it 108 assert_not_nil Fixtures.new( Account.connection, "companies", File.dirname(__FILE__) + "/fixtures/naked/yml/companies")114 assert_not_nil Fixtures.new( Account.connection, File.dirname(__FILE__) + "/fixtures/naked/yml/", FixtureGroup.new("companies")) 109 115 end 110 116 111 117 def test_dirty_dirty_yaml_file 112 118 assert_raises(Fixture::FormatError) do 113 Fixtures.new( Account.connection, "courses", File.dirname(__FILE__) + "/fixtures/naked/yml/courses")119 Fixtures.new( Account.connection, File.dirname(__FILE__) + "/fixtures/naked/yml/", FixtureGroup.new("courses")) 114 120 end 115 121 end 116 122 117 123 def test_empty_csv_fixtures 118 assert_not_nil Fixtures.new( Account.connection, "accounts", File.dirname(__FILE__) + "/fixtures/naked/csv/accounts")124 assert_not_nil Fixtures.new( Account.connection, File.dirname(__FILE__) + "/fixtures/naked/csv/", FixtureGroup.new("accounts")) 119 125 end 120 126 end 121 127 … … 212 218 213 219 end 214 220 221 class MultipleFixturesForTheSameTableTest < Test::Unit::TestCase 222 self.use_instantiated_fixtures = true 223 fixture :topics_set_one, :table_name => "topics", :file_name => "topics" 224 fixture :topics_set_two, :table_name => "topics", :file_name => "topics2" 225 226 def test_loaded_fixtures 227 assert_equal @topics_set_one.size, 2 228 assert_equal @topics_set_two.size, 2 229 union = Topic.find(:all) 230 assert_equal union.size, 4 231 end 232 end 215 233 234 class MixSingularAndPluralFixturesTest < Test::Unit::TestCase 235 self.use_instantiated_fixtures = true 236 fixtures :topics, :developers, :accounts 237 fixture :additional_topics, :table_name => "topics", :file_name => "topics2" 238 239 def test_loaded_fixtures 240 assert_equal @topics.size, 2 241 assert_equal @additional_topics.size, 2 242 union = Topic.find(:all) 243 assert_equal union.size, 4 244 end 245 end 216 246 217 218 219 247 class AlternateFileFixturesTest < Test::Unit::TestCase 248 self.use_instantiated_fixtures = true 249 fixture :topics, :file_name => "topics2" 250 251 def test_alternate_fixture_file_was_loaded 252 assert_equal @topics.size, 2 253 assert_nil @first 254 assert_nil @second 255 assert_equal @third.id, 3 256 assert_equal @fourth.id, 4 257 topics = Topic.find(:all) 258 assert_equal topics.size, 2 259 end 260 end -
test/fixtures/topics2.yml
old new 1 third: 2 id: 3 3 title: The Third Topic 4 author_name: Duane 5 author_email_address: david@loudthinking.com 6 written_on: 2003-07-16t15:28:00.00+01:00 7 last_read: 2004-04-15 8 bonus_time: 2005-01-30t15:28:00.00+01:00 9 content: Have a nice day 10 approved: 0 11 replies_count: 0 12 13 fourth: 14 id: 4 15 title: The Fourth topic 16 author_name: John 17 written_on: 2003-07-15t15:28:00.00+01:00 18 content: Have a nice day 19 approved: 1 20 replies_count: 2 21 parent_id: 1 -
lib/active_record/fixtures.rb
old new 2 2 require 'yaml' 3 3 require 'csv' 4 4 5 # A FixtureGroup is a set of fixtures identified by a name. Normally, this is the name of the 6 # corresponding table in the database. For example, when you declare the use of fixtures in a 7 # TestUnit class, like so: 8 # fixtures :users 9 # you are creating a FixtureGroup whose name is 'users', and whose defaults are set such that the 10 # +class_name+, +file_name+ and +table_name+ are guessed from the FixtureGroup's name. 11 class FixtureGroup 12 attr_accessor :table_name, :class_name, :file_name 13 attr_reader :group_name 14 15 def initialize(group_name, optional_names = {}) 16 self.group_name = group_name 17 self.table_name = optional_names[:table_name] || @group_name.to_s 18 self.file_name = optional_names[:file_name] || @table_name.to_s.gsub('.','_') 19 self.class_name = optional_names[:class_name] || Inflector.classify(@table_name.to_s.gsub('.','_')) 20 end 21 22 def group_name=(name) 23 @group_name = name.to_sym 24 end 25 26 def class_file_name 27 Inflector.underscore(@class_name) 28 end 29 30 # Instantiate an array of FixtureGroup objects from an array of strings (table_names) 31 def self.array_from_names(names) 32 names.collect { |n| FixtureGroup.new(n) } 33 end 34 35 def hash 36 @group_name.hash 37 end 38 39 def eql?(other) 40 @group_name.eql? other.group_name 41 end 42 end 43 5 44 # Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours: 6 45 # 7 46 # 1. YAML fixtures … … 194 233 class Fixtures < Hash 195 234 DEFAULT_FILTER_RE = /\.ya?ml$/ 196 235 197 def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)198 old_logger_level = ActiveRecord::Base.logger.level199 ActiveRecord::Base.logger.level = Logger::ERROR200 201 object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures202 if load_instances203 fixtures.each do |name, fixture|204 if model = fixture.find205 object.instance_variable_set "@#{name}", model206 end207 end208 end209 210 ActiveRecord::Base.logger.level = old_logger_level211 end212 213 def self.instantiate_all_loaded_fixtures(object, load_instances=true)214 all_loaded_fixtures.each do |table_name, fixtures|215 Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)216 end217 end218 219 236 cattr_accessor :all_loaded_fixtures 220 237 self.all_loaded_fixtures = {} 221 238 222 def self.create_fixtures(fixtures_directory, *table_names) 223 connection = block_given? ? yield : ActiveRecord::Base.connection 224 old_logger_level = ActiveRecord::Base.logger.level 239 attr_accessor :connection, :fixtures_directory, :file_filter 240 attr_accessor :fixture_group 225 241 226 begin 227 ActiveRecord::Base.logger.level = Logger::ERROR 228 229 fixtures_map = {} 230 fixtures = table_names.flatten.map do |table_name| 231 fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s)) 232 end 233 all_loaded_fixtures.merge! fixtures_map 234 235 connection.transaction do 236 fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } 237 fixtures.each { |fixture| fixture.insert_fixtures } 238 end 239 240 reset_sequences(connection, table_names) if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) 241 242 return fixtures.size > 1 ? fixtures : fixtures.first 243 ensure 244 ActiveRecord::Base.logger.level = old_logger_level 245 end 246 end 247 248 # Work around for PostgreSQL to have new fixtures created from id 1 and running. 249 def self.reset_sequences(connection, table_names) 250 table_names.flatten.each do |table| 251 table_class = Inflector.classify(table.to_s) 252 if Object.const_defined?(table_class) 253 pk = eval("#{table_class}::primary_key") 254 if pk == 'id' 255 connection.execute( 256 "SELECT setval('#{table.to_s}_id_seq', (SELECT MAX(id) FROM #{table.to_s}), true)", 257 'Setting Sequence' 258 ) 259 end 260 end 261 end 262 end 263 264 attr_reader :table_name 265 266 def initialize(connection, table_name, fixture_path, file_filter = DEFAULT_FILTER_RE) 267 @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter 268 @class_name = Inflector.classify(@table_name) 269 242 def initialize(connection, fixtures_directory, fixture_group, file_filter = DEFAULT_FILTER_RE) 243 @connection, @fixtures_directory = connection, fixtures_directory 244 @fixture_group = fixture_group 245 @file_filter = file_filter 270 246 read_fixture_files 271 247 end 272 248 273 249 def delete_existing_fixtures 274 @connection.delete "DELETE FROM #{@ table_name}", 'Fixture Delete'250 @connection.delete "DELETE FROM #{@fixture_group.table_name}", 'Fixture Delete' 275 251 end 276 252 277 253 def insert_fixtures 278 254 values.each do |fixture| 279 @connection.execute "INSERT INTO #{@ table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'255 @connection.execute "INSERT INTO #{@fixture_group.table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' 280 256 end 281 257 end 282 258 283 259 private 284 260 def read_fixture_files 285 261 if File.file?(yaml_file_path) 286 # YAML fixtures 287 begin 288 yaml = YAML::load(erb_render(IO.read(yaml_file_path))) 289 yaml.each { |name, data| self[name] = Fixture.new(data, @class_name) } if yaml 290 rescue Exception=>boom 291 raise Fixture::FormatError, "a YAML error occured 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 #{boom.class}: #{boom}" 292 end 262 read_yaml_fixture_files 293 263 elsif File.file?(csv_file_path) 294 # CSV fixtures 295 reader = CSV::Reader.create(erb_render(IO.read(csv_file_path))) 296 header = reader.shift 297 i = 0 298 reader.each do |row| 299 data = {} 300 row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip } 301 self["#{Inflector::underscore(@class_name)}_#{i+=1}"]= Fixture.new(data, @class_name) 302 end 264 read_csv_fixture_files 303 265 elsif File.file?(deprecated_yaml_file_path) 304 266 raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}" 267 elsif File.directory?(File.join(@fixtures_directory, @fixture_group.file_name)) 268 read_standard_fixture_files 305 269 else 306 # Standard fixtures 307 Dir.entries(@fixture_path).each do |file| 308 path = File.join(@fixture_path, file) 309 if File.file?(path) and file !~ @file_filter 310 self[file] = Fixture.new(path, @class_name) 311 end 270 raise Fixture::FixtureError, "Couldn't find a yaml, csv or standard file to load at #{@fixtures_directory}." 271 end 272 end 273 274 def read_yaml_fixture_files 275 # YAML fixtures 276 begin 277 yaml = YAML::load(erb_render(IO.read(yaml_file_path))) 278 yaml.each { |name, data| self[name] = Fixture.new(data, @fixture_group.class_name) } if yaml 279 rescue Exception=>boom 280 raise Fixture::FormatError, "a YAML error occured 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 #{boom.class}: #{boom}" 281 end 282 end 283 284 def read_csv_fixture_files 285 # CSV fixtures 286 reader = CSV::Reader.create(erb_render(IO.read(csv_file_path))) 287 header = reader.shift 288 i = 0 289 reader.each do |row| 290 data = {} 291 row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip } 292 self["#{fixture_group.class_file_name}_#{i+=1}"]= Fixture.new(data, @fixture_group.class_name) 293 end 294 end 295 296 def read_standard_fixture_files 297 # Standard fixtures 298 path = File.join(@fixtures_directory, @fixture_group.file_name) 299 Dir.entries(path).each do |file| 300 path = File.join(@fixtures_directory, file) 301 if File.file?(path) and file !~ @file_filter 302 self[file] = Fixture.new(path, @fixture_group.class_name) 312 303 end 313 304 end 314 305 end 315 306 316 307 def yaml_file_path 317 "#{@fixture_path}.yml"308 fixture_path_with_extension ".yml" 318 309 end 319 310 320 311 def deprecated_yaml_file_path 321 "#{@fixture_path}.yaml"312 fixture_path_with_extension ".yaml" 322 313 end 323 314 324 315 def csv_file_path 325 @fixture_path +".csv"316 fixture_path_with_extension ".csv" 326 317 end 327 318 328 def yaml_fixtures_key(path)329 File. basename(@fixture_path).split(".").first330 end 319 def fixture_path_with_extension(ext) 320 File.join(@fixtures_directory, @fixture_group.file_name + ext) 321 end 331 322 332 323 def erb_render(fixture_content) 333 324 ERB.new(fixture_content).result 334 325 end 326 327 #def yaml_fixtures_key(path) 328 # File.basename(@fixture_path).split(".").first 329 #end 330 331 public 332 class << self 333 def instantiate_fixtures(object, fixture_group_name, fixtures, load_instances=true) 334 old_logger_level = ActiveRecord::Base.logger.level 335 ActiveRecord::Base.logger.level = Logger::ERROR 336 337 # table_name.to_s.gsub('.','_') replaced by 'fixture_group_name' 338 object.instance_variable_set "@#{fixture_group_name}", fixtures 339 if load_instances 340 fixtures.each do |name, fixture| 341 if model = fixture.find 342 object.instance_variable_set "@#{name}", model 343 end 344 end 345 end 346 347 ActiveRecord::Base.logger.level = old_logger_level 348 end 349 350 def instantiate_all_loaded_fixtures(object, load_instances=true) 351 all_loaded_fixtures.each do |fixture_group_name, fixtures| 352 Fixtures.instantiate_fixtures(object, fixture_group_name, fixtures, load_instances) 353 end 354 end 355 356 357 def create_fixtures(fixtures_directory, *fixture_groups) 358 connection = block_given? ? yield : ActiveRecord::Base.connection 359 old_logger_level = ActiveRecord::Base.logger.level 360 fixture_groups.flatten! 361 362 # Backwards compatibility: Allow an array of table names to be passed in, but just use them 363 # to create an array of FixtureGroup objects 364 if not fixture_groups.empty? and fixture_groups.first.is_a?(String) 365 fixture_groups = FixtureGroup.array_from_names(fixture_groups) 366 end 367 368 begin 369 ActiveRecord::Base.logger.level = Logger::ERROR 370 371 fixtures_map = {} 372 fixtures = fixture_groups.map do |group| 373 fixtures_map[group.group_name] = Fixtures.new(connection, fixtures_directory, group) 374 end 375 # Make sure all refs to all_loaded_fixtures use group_name as hash index, not table_name 376 all_loaded_fixtures.merge! fixtures_map 377 378 connection.transaction do 379 fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } 380 fixtures.each { |fixture| fixture.insert_fixtures } 381 end 382 383 reset_sequences(connection, fixture_groups) if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) 384 385 return fixtures.size > 1 ? fixtures : fixtures.first 386 ensure 387 ActiveRecord::Base.logger.level = old_logger_level 388 end 389 end 390 391 # Work around for PostgreSQL to have new fixtures created from id 1 and running. 392 def reset_sequences(connection, fixture_groups) 393 fixture_groups.flatten.each do |group| 394 if Object.const_defined?(group.class_name) 395 pk = eval("#{group.class_name}::primary_key") 396 if pk == 'id' 397 connection.execute( 398 "SELECT setval('#{group.table_name}_id_seq', (SELECT MAX(id) FROM #{group.table_name}), true)", 399 'Setting Sequence' 400 ) 401 end 402 end 403 end 404 end 405 end 335 406 end 336 407 337 408 class Fixture #:nodoc: … … 399 470 class TestCase #:nodoc: 400 471 include ClassInheritableAttributes 401 472 402 cattr_accessor :fixture _path403 class_inheritable_accessor :fixture_ table_names473 cattr_accessor :fixtures_directory 474 class_inheritable_accessor :fixture_groups 404 475 class_inheritable_accessor :use_transactional_fixtures 405 476 class_inheritable_accessor :use_instantiated_fixtures # true, false, or :no_instances 406 477 class_inheritable_accessor :pre_loaded_fixtures 407 478 408 self.fixture_ table_names = []479 self.fixture_groups = [] 409 480 self.use_transactional_fixtures = false 410 481 self.use_instantiated_fixtures = true 411 482 self.pre_loaded_fixtures = false 412 483 413 484 @@already_loaded_fixtures = {} 414 485 415 def self.fixtures(*table_names) 416 table_names = table_names.flatten 417 self.fixture_table_names |= table_names 418 require_fixture_classes(table_names) 419 setup_fixture_accessors(table_names) 486 # Backwards compatibility 487 def self.fixture_path=(path); self.fixtures_directory = path; end 488 def self.fixture_path; self.fixtures_directory; end 489 # It would be more appropriate to call this 'fixture_group_names', but it's for back compat. 490 def fixture_table_names; fixture_groups.collect { |g| g.group_name }; end 491 492 def self.fixture(group_name, options = {}) 493 self.fixture_groups |= [FixtureGroup.new(group_name, options)] 494 require_fixture_classes 495 setup_fixture_accessors 420 496 end 421 497 422 def self.require_fixture_classes(table_names=nil) 423 (table_names || fixture_table_names).each do |table_name| 498 def self.fixtures(*group_names) 499 self.fixture_groups |= FixtureGroup.array_from_names(group_names.flatten) 500 require_fixture_classes 501 setup_fixture_accessors 502 end 503 504 def self.require_fixture_classes(override_fixture_groups=nil) 505 (override_fixture_groups || fixture_groups).each do |group| 424 506 begin 425 require Inflector.singularize(table_name.to_s)507 require group.class_file_name 426 508 rescue LoadError 427 509 # Let's hope the developer has included it himself 428 510 end 429 511 end 430 512 end 431 513 432 def self.setup_fixture_accessors( table_names=nil)433 ( table_names || fixture_table_names).each do |table_name|434 table_name = table_name.to_s.tr('.','_')435 define_method( table_name) do |fixture, *optionals|514 def self.setup_fixture_accessors(override_fixture_groups=nil) 515 (override_fixture_groups || fixture_groups).each do |group| 516 # table_name = table_name.to_s.tr('.','_') 517 define_method(group.group_name) do |fixture, *optionals| 436 518 force_reload = optionals.shift 437 @fixture_cache[ table_name] ||= Hash.new438 @fixture_cache[ table_name][fixture] = nil if force_reload439 @fixture_cache[ table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find519 @fixture_cache[group.group_name] ||= Hash.new 520 @fixture_cache[group.group_name][fixture] = nil if force_reload 521 @fixture_cache[group.group_name][fixture] ||= @loaded_fixtures[group.group_name][fixture.to_s].find 440 522 end 441 523 end 442 524 end … … 519 601 private 520 602 def load_fixtures 521 603 @loaded_fixtures = {} 522 fixtures = Fixtures.create_fixtures(fixture _path, fixture_table_names)604 fixtures = Fixtures.create_fixtures(fixtures_directory, fixture_groups) 523 605 unless fixtures.nil? 524 606 if fixtures.instance_of?(Fixtures) 525 @loaded_fixtures[fixtures. table_name] = fixtures607 @loaded_fixtures[fixtures.fixture_group.group_name] = fixtures 526 608 else 527 fixtures.each { |f| @loaded_fixtures[f. table_name] = f }609 fixtures.each { |f| @loaded_fixtures[f.fixture_group.group_name] = f } 528 610 end 529 611 end 530 612 end … … 536 618 if pre_loaded_fixtures 537 619 raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty? 538 620 unless @@required_fixture_classes 539 self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys 621 groups = Fixtures.all_loaded_fixtures.values.collect { |f| f.group_name } 622 self.class.require_fixture_classes groups 540 623 @@required_fixture_classes = true 541 624 end 542 625 Fixtures.instantiate_all_loaded_fixtures(self, load_instances?) 543 626 else 544 627 raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil? 545 @loaded_fixtures.each do | table_name, fixtures|546 Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)628 @loaded_fixtures.each do |fixture_group_name, fixtures| 629 Fixtures.instantiate_fixtures(self, fixture_group_name, fixtures, load_instances?) 547 630 end 548 631 end 549 632 end